First, point Vs Pixel
In iOS, when we use frames such as quartz,uikit,coreanimation, all coordinate systems are measured by point. The system will help us deal with point-to-pixel conversions when actually rendering to settings.
The benefits of doing so isolate the change, i.e. we do not need to focus on whether the current device is retina after the layout, and can be laid out directly in a set of coordinate systems.
In practice we need to keep this in mind:
1 |
One point does not necessarily correspond to one physical pixel. |
A line of 1 point on a non-retina screen is a pixel, which may be 2 or 3 on the Retina screen, depending on the DPI of the system device.
On iOS systems, the Uiscreen,uiview,uiimage,calayer class provides properties to get the scale factor.
Native rendering techniques naturally help us with scale factor, for example, in the DrawRect: method, Uikit automatically sets the tangent scale factor based on the currently running device. So the drawrect: anything drawn in the method will be automatically scaled to the physical screen of the device.
Based on the above information, we can see that we do not need to pay attention to pixel in most cases, but some cases need to consider the conversion of pixels.
For example, draw a 1-pixel split Line
The first idea you'll see is that you can calculate the point of a 1-pixel line directly based on the zoom factor of the current screen, and then set the line width.
The code is as follows:
1 |
1.0f / [UIScreen mainScreen].scale |
On the surface it looks normal, but with the actual device testing you will find that the rendered line width is not 1 pixels.
Why?
For good visuals, the drawing system usually uses a technique called antialiasing (anti-aliasing), and iOS is no exception.
The display screen has a lot of small display units, which can be understood as a single unit that represents a pixel. If you want to draw a black line, the lines just fall in a column or a row of display cells, will render a standard one pixel black line.
But if the line falls in the middle of two rows or columns, you get a "distorted" line, which is actually two pixels wide gray line.
As shown in the following:
1 |
Positions defined by whole-numbered points fall at the midpoint between pixels. For example,
if
you draw a one-pixel-wide vertical line from (1.0, 1.0) to (1.0, 10.0), you get a fuzzy grey line. If you draw a two-pixel-wide line, you get a solid black line because it fully covers two pixels (one on either side of the specified point). As a rule, lines that are an odd number of physical pixels wide appear softer than lines
with
widths measured
in even numbers of physical pixels unless you adjust their position to make them cover pixels fully.
|
As an official explanation, simply translate:
1 |
规定:奇数像素宽度的线在渲染的时候将会表现为柔和的宽度扩展到向上的整数宽度的线,除非你手动的调整线的位置,使线刚好落在一行或列的显示单元内。 |
How to align it?
12 |
On a low-resolution display (
with a scale factor of 1.0), a one-point-wide line is one pixel wide. To avoid antialiasing when you draw a one-point-wide horizontal or vertical line,
if
the line is an odd number of pixels
in
width, you must offset the position by 0.5 points to either side of a whole-numbered position. If the line is an even number of points
in width, to avoid a fuzzy line, you must not
do
so.
On a high-resolution display (
with
a scale factor of 2.0), a line that is one point wide is not antialiased at all because it occupies two full pixels (from -0.5 to +0.5). To draw a line that covers only a single physical pixel, you would need to make it 0.5 points
in thickness and offset its position by 0.25 points. A comparison between the two types of screens is shown
in
Figure 1-4.
|
Translate a bit
123 |
在非高清屏上,一个Point对应一个像素。为了防止“antialiasing”导致的奇数像素的线渲染时出现失真,你需要设置偏移0.5 Point。 在高清屏幕上,要绘制一个像素的线,需要设置线宽为0.5个Point,同事设置偏移为0.25 Point。 如果线宽为偶数Point的话,则不要去设置偏移,否则线条也会失真。 |
As shown in the following:
Read the above explanation, we understand the 1 pixel wide line distortion reasons, and solutions.
So the problem seems to have been solved? Think about why the position value is different on the non-retina and retina screens, the former is 0.5Point and the latter is 0.25Point, so how much is the 6 Plus device with scale 3?
To answer this question, we need to understand the principle of how much adjustment remains.
Looking back at the picture above, each of the squares in the picture represents a pixel, and the top tag is the coordinates of the code we are laying out.
You can see the non-Retina screen on the left, we want to draw a vertical line at this position (3,0), because the smallest unit of the rendering is pixels, and (3,0) This coordinate is located in the middle of two pixels, the system will be around the coordinates 3 of the two columns of pixels to fill, in order not to appear too wide, Fades the color of the line. So, based on the above information, we can conclude that if you want to draw a line with a wide pixel width, you have to move the plotted coordinates to (2.5, 0) or (3.5,0) This position, so that the system renders exactly one column of pixels, which is the standard one-pixel line.
Based on the above analysis, we can draw a "scale of 3 6 Plus" device if you want to draw 1 pixel-wide lines, the position adjustment should also be 0.5 pixels, the point should be calculated as follows:
1 |
(1.0f / [UIScreen mainScreen].scale) / 2; |
A macro that draws a pixel line:
12 |
#define SINGLE_LINE_WIDTH (1 / [UIScreen mainScreen].scale) #define SINGLE_LINE_ADJUST_OFFSET ((1 / [UIScreen mainScreen].scale) / 2) |
Use the following code:
12 |
CGFloat xPos = 5; UIView *view = [[UIView alloc] initWithFrame:CGrect(x - SINGLE_LINE_ADJUST_OFFSET, 0, SINGLE_LINE_WIDTH, 100)]; |
Second, the correct drawing grid lines
Paste the code of the GridView, which offsets the odd pixels of the grid lines to prevent the blurring of lines.
SvGridView.h
123456789101112131415161718192021 |
//
// SvGridView.h
// SvSinglePixel
//
// Created by xiaoyong.cxy on 6/23/15.
// Copyright (c) 2015 smileEvday. All rights reserved.
//
#import @interface SvGridView : UIView
/**
* @brief 网格间距,默认30
*/
@property (nonatomic, assign) CGFloat gridSpacing;
/**
* @brief 网格线宽度,默认为1 pixel (1.0f / [UIScreen mainScreen].scale)
*/
@property (nonatomic, assign) CGFloat gridLineWidth;
/**
* @brief 网格颜色,默认蓝色
*/
@property (nonatomic, strong) UIColor *gridColor;
@end
|
svgridview.m
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 66676869707172737475767778798081 |
//
// SvGridView.m
// SvSinglePixel
//
// Created by xiaoyong.cxy on 6/23/15.
// Copyright (c) 2015 smileEvday. All rights reserved.
//
#import "SvGridView.h"
#define SINGLE_LINE_WIDTH (1 / [UIScreen mainScreen].scale)
#define SINGLE_LINE_ADJUST_OFFSET ((1 / [UIScreen mainScreen].scale) / 2)
@implementation SvGridView
@synthesize gridColor = _gridColor;
@synthesize gridSpacing = _gridSpacing;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [
super
initWithFrame:frame];
if
(self) {
self.backgroundColor = [UIColor clearColor];
_gridColor = [UIColor blueColor];
_gridLineWidth = SINGLE_LINE_WIDTH;
_gridSpacing = 30;
}
return
self;
}
- (void)setGridColor:(UIColor *)gridColor
{
_gridColor = gridColor;
[self setNeedsDisplay];
}
- (void)setGridSpacing:(CGFloat)gridSpacing
{
_gridSpacing = gridSpacing;
[self setNeedsDisplay];
}
- (void)setGridLineWidth:(CGFloat)gridLineWidth
{
_gridLineWidth = gridLineWidth;
[self setNeedsDisplay];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGFloat lineMargin = self.gridSpacing;
/**
* https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html
* 仅当要绘制的线宽为奇数像素时,绘制位置需要调整
*/
CGFloat pixelAdjustOffset = 0;
if
(((int)(self.gridLineWidth * [UIScreen mainScreen].scale) + 1) % 2 == 0) {
pixelAdjustOffset = SINGLE_LINE_ADJUST_OFFSET;
}
CGFloat xPos = lineMargin - pixelAdjustOffset;
CGFloat yPos = lineMargin - pixelAdjustOffset;
while
(xPos < self.bounds.size.width) {
CGContextMoveToPoint(context, xPos, 0);
CGContextAddLineToPoint(context, xPos, self.bounds.size.height);
xPos += lineMargin;
}
while
(yPos < self.bounds.size.height) {
CGContextMoveToPoint(context, 0, yPos);
CGContextAddLineToPoint(context, self.bounds.size.width, yPos);
yPos += lineMargin;
}
CGContextSetLineWidth(context, self.gridLineWidth);
CGContextSetStrokeColorWithColor(context, self.gridColor.CGColor);
CGContextStrokePath(context);
}
@end
|
Here's how to use it:
12345 |
SvGridView *gridView = [[SvGridView alloc] initWithFrame:self.view.bounds]; gridView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; gridView.alpha = 0.6; gridView.gridColor = [UIColor greenColor]; [self.view addSubview:gridView]; |
A city problems
All right, here we go. The whole knowledge of this article is over, and finally I have a question.
Why does the designer have to have a pixel line?
A pixel line may look appropriate on a non-retina device, and the display on the Retina screen may be thinner. is not sure to need a pixel line, it needs to be handled according to the situation.
IOS: How to draw a 1-pixel line correctly