一剩燥、Point Vs Pixel
iOS中當(dāng)我們使用Quartz澳骤,UIKit,CoreAnimation等框架時(shí)反番,所有的坐標(biāo)系統(tǒng)采用Point來(lái)衡量沙热。系統(tǒng)在實(shí)際渲染到設(shè)置時(shí)會(huì)幫助我們處理Point到Pixel的轉(zhuǎn)換。
這樣做的好處隔離變化罢缸,即我們?cè)诓季值氖潞蟛恍枰P(guān)注當(dāng)前設(shè)備是否為Retina篙贸,直接按照一套坐標(biāo)系統(tǒng)來(lái)布局即可。
實(shí)際使用中我們需要牢記下面這一點(diǎn):
One point does not necessarily correspond to one physical pixel.
1px Point的線在非Retina屏幕則是一個(gè)像素枫疆,在Retina屏幕上則可能是2個(gè)或者3個(gè)爵川,取決于系統(tǒng)設(shè)備的DPI。
iOS系統(tǒng)中息楔,UIScreen寝贡,UIView,UIImage钞螟,CALayer類都提供相關(guān)屬性來(lái)獲取scale factor兔甘。
原生的繪制技術(shù)天然的幫我們處理了scale factor,例如在drawRect:方法中鳞滨,UIKit自動(dòng)的根據(jù)當(dāng)前運(yùn)行的設(shè)備設(shè)置了正切的scale factor。所以我們?cè)赿rawRect: 方法中繪制的任何內(nèi)容都會(huì)被自動(dòng)縮放到設(shè)備的物理屏幕上蟆淀。
基于以上信息可以看出拯啦,我們大部分情況下都不需要去關(guān)注pixel澡匪,然而存在部分情況需要考慮像素的轉(zhuǎn)化。
例如畫1個(gè)像素的分割線
看到這個(gè)問(wèn)題你的第一想法可能是褒链,直接根據(jù)當(dāng)前屏幕的縮放因子計(jì)算出1 像素線對(duì)應(yīng)的Point唁情,然后設(shè)置線寬即可。
代碼如下:
1.0f / [UIScreen mainScreen].scale
表面上看著一切正常了甫匹,但是通過(guò)實(shí)際的設(shè)備測(cè)試你會(huì)發(fā)現(xiàn)渲染出來(lái)的線寬并不是1個(gè)像素甸鸟。Why?
為了獲得良好的視覺(jué)效果,繪圖系統(tǒng)通常都會(huì)采用一個(gè)叫“antialiasing(反鋸齒)”的技術(shù)兵迅,iOS也不例外抢韭。
顯示屏幕有很多小的顯示單元組成,可以接單的理解為一個(gè)單元就代表一個(gè)像素恍箭。如果要畫一條黑線刻恭,條線剛好落在了一列或者一行顯示顯示單元之內(nèi),將會(huì)渲染出標(biāo)準(zhǔn)的一個(gè)像素的黑線扯夭。
但如果線落在了兩個(gè)行或列的中間時(shí)鳍贾,那么會(huì)得到一條“失真”的線,其實(shí)是兩個(gè)像素寬的灰線交洗。
如下圖所示:
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.
官方解釋如上骑科,簡(jiǎn)單翻譯一下:
規(guī)定:奇數(shù)像素寬度的線在渲染的時(shí)候?qū)?huì)表現(xiàn)為柔和的寬度擴(kuò)展到向上的整數(shù)寬度的線,除非你手動(dòng)的調(diào)整線的位置构拳,使線剛好落在一行或列的顯示單元內(nèi)纵散。
如何對(duì)齊呢?
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.
翻譯一下
在非高清屏上隐圾,一個(gè)Point對(duì)應(yīng)一個(gè)像素伍掀。為了防止“antialiasing”導(dǎo)致的奇數(shù)像素的線渲染時(shí)出現(xiàn)失真,你需要設(shè)置偏移0.5 Point暇藏。
在高清屏幕上蜜笤,要繪制一個(gè)像素的線,需要設(shè)置線寬為0.5個(gè)Point盐碱,同事設(shè)置偏移為0.25 Point把兔。
如果線寬為偶數(shù)Point的話,則不要去設(shè)置偏移瓮顽,否則線條也會(huì)失真县好。
如下圖所示:
看了上述一通解釋,我們了解了1像素寬的線條失真的原因暖混,及解決辦法缕贡。
至此問(wèn)題貌似都解決了?再想想為什么在非Retina和Retina屏幕上調(diào)整位置時(shí)值不一樣,前者為0.5Point晾咪,后者為0.25Point收擦,那么scale為3的6 Plus設(shè)備又該調(diào)整多少呢?
要回答這個(gè)問(wèn)題谍倦,我們需要理解調(diào)整多少依舊什么原則塞赂。
再回過(guò)頭來(lái)看看這上面的圖片,圖片中每一格子代表一個(gè)像素昼蛀,而頂部標(biāo)記的則代碼我們布局時(shí)的坐標(biāo)宴猾。
可以看到左邊的非Retina屏幕,我們要在(3,0)這個(gè)位置畫一條一個(gè)像素寬的豎線時(shí)叼旋,由于渲染的最小單位是像素仇哆,而(3,0)這個(gè)坐標(biāo)恰好位于兩個(gè)像素中間,此時(shí)系統(tǒng)會(huì)對(duì)坐標(biāo)3左右兩列的像素對(duì)填充送淆,為了不至于線顯得太寬税产,為對(duì)線的顏色淡化。那么根據(jù)上述信息我們可以得出偷崩,如果要畫出一個(gè)像素寬的線辟拷,就得把繪制的坐標(biāo)移動(dòng)到(2.5, 0)或者(3.5,0)這個(gè)位置,這樣系統(tǒng)渲染的時(shí)候剛好可以填充一列像素阐斜,也就是標(biāo)準(zhǔn)的一個(gè)像素的線衫冻。
基于上面的分析,我們可以得出“Scale為3的6 Plus”設(shè)備如果要繪制1個(gè)像素寬的線條時(shí)谒出,位置調(diào)整也應(yīng)該是0.5像素隅俘,對(duì)應(yīng)該的Point計(jì)算如下:
(1.0f / [UIScreen mainScreen].scale) / 2;
奉上一個(gè)畫一像素線的一個(gè)宏:
#define SINGLE_LINE_WIDTH (1 / [UIScreen mainScreen].scale)
#define SINGLE_LINE_ADJUST_OFFSET ((1 / [UIScreen mainScreen].scale) / 2)
使用代碼如下:
CGFloat xPos = 5; UIView *view = [[UIView alloc] initWithFrame:CGrect(x - SINGLE_LINE_ADJUST_OFFSET, 0, SINGLE_LINE_WIDTH, 100)];
二、正確的繪制Grid線條
貼上一個(gè)寫的GridView的代碼笤喳,代碼中對(duì)Grid線條的奇數(shù)像素做了偏移为居,防止出現(xiàn)線條模糊的情況。
SvGridView.h
//
// SvGridView.h
// SvSinglePixel
//
// Created by xiaoyong.cxy on 6/23/15.
// Copyright (c) 2015 smileEvday. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface SvGridView : UIView
/**
*@brief 網(wǎng)格間距杀狡,默認(rèn)30
*/
@property (nonatomic, assign) CGFloat gridSpacing;
/**
* @brief 網(wǎng)格線寬度蒙畴,默認(rèn)為1 pixel (1.0f / [UIScreen mainScreen].scale)
*/
@property (nonatomic, assign) CGFloat gridLineWidth;
/**
* @brief 網(wǎng)格顏色,默認(rèn)藍(lán)色
*/
@property (nonatomic, strong) UIColor *gridColor;
@end
SvGridView.m
//
// 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
* 僅當(dāng)要繪制的線寬為奇數(shù)像素時(shí)呜象,繪制位置需要調(diào)整
*/
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
使用方法如下:
SvGridView *gridView = [[SvGridView alloc] initWithFrame:self.view.bounds];
gridView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
gridView.alpha = 0.6;
gridView.gridColor = [UIColor greenColor];
[self.view addSubview:gridView];
參考文檔:
注:smileEvday保留本文的一切權(quán)利
轉(zhuǎn)載請(qǐng)著名原文出處
本文所有內(nèi)容僅代表個(gè)人觀點(diǎn)膳凝,如有有不對(duì)的地方,歡迎指出恭陡。