自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

iOS: 如何正確的繪制1像素的線

移動(dòng)開(kāi)發(fā)
為了獲得良好的視覺(jué)效果,繪圖系統(tǒng)通常都會(huì)采用一個(gè)叫“antialiasing(反鋸齒)”的技術(shù),iOS也不例外。 顯示屏幕有很多小的顯示單元組成,可以接單的理解為一個(gè)單元就代表一個(gè)像素。如果要畫一條黑線,條線剛好落在了一列或者一行顯示顯示單元之內(nèi),將會(huì)渲染出標(biāo)準(zhǔn)的一個(gè)像素的黑線。 但如果線落在了兩個(gè)行或列的中間時(shí),那么會(huì)得到一條“失真”的線,其實(shí)是兩個(gè)像素寬的灰線。

 [[140291]]

一、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):

  1. One point does not necessarily correspond to one physical pixel. 

1 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. 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è)像素寬的灰線。

如下圖所示:

blob.png

  1. Positions defined by whole-numbered points fall at the midpoint between pixels.
  2.  For example, if you draw a one-pixel-wide vertical line from (1.0, 1.0) to (1.0, 10.0), 
  3. you get a fuzzy grey line. If you draw a two-pixel-wide line, 
  4. you get a solid black line because it fully covers two pixels (one on either side of the specified point).
  5.  As a rule, lines that are an odd number of physical pixels wide appear softer than lines with widths
  6.  measured in even numbers of physical pixels unless you adjust their position to make them cover pixels fully. 

官方解釋如上,簡(jiǎn)單翻譯一下:

  1. 規(guī)定:奇數(shù)像素寬度的線在渲染的時(shí)候?qū)?huì)表現(xiàn)為柔和的寬度擴(kuò)展到向上的整數(shù)寬度的線,
  2. 除非你手動(dòng)的調(diào)整線的位置,使線剛好落在一行或列的顯示單元內(nèi)。 

如何對(duì)齊呢?

  1. On a low-resolution display (with a scale factor of 1.0), a one-point-wide line 
  2. is one pixel wide. To avoid antialiasing when you draw a one-point-wide horizontal or vertical line, 
  3. if the line is an odd number of pixels in width, you must offset the position by 0.5 points to 
  4. either side of a whole-numbered position. If the line is an even number of points in width, 
  5. to avoid a fuzzy line, you must not do so. 
  6. On a high-resolution display (with a scale factor of 2.0), a line that is one point wide is 
  7. not antialiased at all because it occupies two full pixels (from -0.5 to +0.5). 
  8. 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. 

翻譯一下

 
 
 
  1. 在非高清屏上,一個(gè)Point對(duì)應(yīng)一個(gè)像素。為了防止“antialiasing”導(dǎo)致的奇數(shù)像素的線渲染時(shí)出現(xiàn)失真,你需要設(shè)置偏移0.5 Point。
  2. 在高清屏幕上,要繪制一個(gè)像素的線,需要設(shè)置線寬為0.5個(gè)Point,同事設(shè)置偏移為0.25 Point。
  3. 如果線寬為偶數(shù)Point的話,則不要去設(shè)置偏移,否則線條也會(huì)失真。

如下圖所示:

blob.png

看了上述一通解釋,我們了解了1像素寬的線條失真的原因,及解決辦法。

至此問(wèn)題貌似都解決了?再想想為什么在非Retina和Retina屏幕上調(diào)整位置時(shí)值不一樣,前者為0.5Point,后者為0.25Point,那么scale為3的6 Plus設(shè)備又該調(diào)整多少呢?

要回答這個(gè)問(wèn)題,我們需要理解調(diào)整多少依舊什么原則。

blob.png

再回過(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. (1.0f / [UIScreen mainScreen].scale) / 2

奉上一個(gè)畫一像素線的一個(gè)宏:

  1. #define SINGLE_LINE_WIDTH           (1 / [UIScreen mainScreen].scale) 
  2. #define SINGLE_LINE_ADJUST_OFFSET   ((1 / [UIScreen mainScreen].scale) / 2

使用代碼如下:

  1. CGFloat xPos = 5
  2. UIView *view = [[UIView alloc] initWithFrame:CGrect(x - SINGLE_LINE_ADJUST_OFFSET, 0, SINGLE_LINE_WIDTH, 100)]; 

#p#

二、正確的繪制Grid線條

貼上一個(gè)寫的GridView的代碼,代碼中對(duì)Grid線條的奇數(shù)像素做了偏移,防止出現(xiàn)線條模糊的情況。

SvGridView.h

 
  1. // 
  2. //  SvGridView.h 
  3. //  SvSinglePixel 
  4. // 
  5. //  Created by xiaoyong.cxy on 6/23/15. 
  6. //  Copyright (c) 2015 smileEvday. All rights reserved. 
  7. // 
  8. #import @interface SvGridView : UIView 
  9. /** 
  10.  * @brief 網(wǎng)格間距,默認(rèn)30 
  11.  */ 
  12. @property (nonatomic, assign) CGFloat   gridSpacing; 
  13. /** 
  14.  * @brief 網(wǎng)格線寬度,默認(rèn)為1 pixel (1.0f / [UIScreen mainScreen].scale) 
  15.  */ 
  16. @property (nonatomic, assign) CGFloat   gridLineWidth; 
  17. /** 
  18.  * @brief 網(wǎng)格顏色,默認(rèn)藍(lán)色 
  19.  */ 
  20. @property (nonatomic, strong) UIColor   *gridColor; 
  21. @end 

SvGridView.m

  1. // 
  2. //  SvGridView.m 
  3. //  SvSinglePixel 
  4. // 
  5. //  Created by xiaoyong.cxy on 6/23/15. 
  6. //  Copyright (c) 2015 smileEvday. All rights reserved. 
  7. // 
  8. #import "SvGridView.h" 
  9. #define SINGLE_LINE_WIDTH           (1 / [UIScreen mainScreen].scale) 
  10. #define SINGLE_LINE_ADJUST_OFFSET   ((1 / [UIScreen mainScreen].scale) / 2
  11. @implementation SvGridView 
  12. @synthesize gridColor = _gridColor; 
  13. @synthesize gridSpacing = _gridSpacing; 
  14. - (instancetype)initWithFrame:(CGRect)frame 
  15.     self = [super initWithFrame:frame]; 
  16.     if (self) { 
  17.         self.backgroundColor = [UIColor clearColor]; 
  18.           
  19.         _gridColor = [UIColor blueColor]; 
  20.         _gridLineWidth = SINGLE_LINE_WIDTH; 
  21.         _gridSpacing = 30
  22.     } 
  23.       
  24.     return self; 
  25. - (void)setGridColor:(UIColor *)gridColor 
  26.     _gridColor = gridColor; 
  27.       
  28.     [self setNeedsDisplay]; 
  29. - (void)setGridSpacing:(CGFloat)gridSpacing 
  30.     _gridSpacing = gridSpacing; 
  31.       
  32.     [self setNeedsDisplay]; 
  33. - (void)setGridLineWidth:(CGFloat)gridLineWidth 
  34.     _gridLineWidth = gridLineWidth; 
  35.       
  36.     [self setNeedsDisplay]; 
  37. // Only override drawRect: if you perform custom drawing. 
  38. // An empty implementation adversely affects performance during animation. 
  39. - (void)drawRect:(CGRect)rect 
  40.     CGContextRef context = UIGraphicsGetCurrentContext(); 
  41.       
  42.     CGContextBeginPath(context); 
  43.     CGFloat lineMargin = self.gridSpacing; 
  44.       
  45.     /** 
  46.      *  https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html 
  47.      * 僅當(dāng)要繪制的線寬為奇數(shù)像素時(shí),繪制位置需要調(diào)整 
  48.      */ 
  49.     CGFloat pixelAdjustOffset = 0
  50.     if (((int)(self.gridLineWidth * [UIScreen mainScreen].scale) + 1) % 2 == 0) { 
  51.         pixelAdjustOffset = SINGLE_LINE_ADJUST_OFFSET; 
  52.     } 
  53.       
  54.     CGFloat xPos = lineMargin - pixelAdjustOffset; 
  55.     CGFloat yPos = lineMargin - pixelAdjustOffset; 
  56.     while (xPos < self.bounds.size.width) { 
  57.         CGContextMoveToPoint(context, xPos, 0); 
  58.         CGContextAddLineToPoint(context, xPos, self.bounds.size.height); 
  59.         xPos += lineMargin; 
  60.     } 
  61.       
  62.     while (yPos < self.bounds.size.height) { 
  63.         CGContextMoveToPoint(context, 0, yPos); 
  64.         CGContextAddLineToPoint(context, self.bounds.size.width, yPos); 
  65.         yPos += lineMargin; 
  66.     } 
  67.       
  68.     CGContextSetLineWidth(context, self.gridLineWidth); 
  69.     CGContextSetStrokeColorWithColor(context, self.gridColor.CGColor); 
  70.     CGContextStrokePath(context); 
  71. @end 

使用方法如下:

 
  1. SvGridView *gridView = [[SvGridView alloc] initWithFrame:self.view.bounds]; 
  2. gridView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 
  3. gridView.alpha = 0.6
  4. gridView.gridColor = [UIColor greenColor]; 
  5. [self.view addSubview:gridView]; 

三、一個(gè)問(wèn)題

好了,到這兒本文的全部知識(shí)就結(jié)束了,***我還有一個(gè)問(wèn)題。

設(shè)計(jì)師為什么一定要一個(gè)像素的線?

一個(gè)像素的線可能在非Retina設(shè)備上顯示寬度看著合適,在Retina屏幕上顯示可能會(huì)比較細(xì)。是不是一定需要一個(gè)像素的線,需要根據(jù)情況來(lái)處理。

責(zé)任編輯:倪明 來(lái)源: cnblog
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)