当前位置: 首页 > 科技观察

iOS:如何正确绘制1像素的线

时间:2023-03-13 18:46:57 科技观察

1.PointVsPixel当我们在iOS中使用Quartz、UIKit、CoreAnimation等框架时,所有的坐标系都是以Point来衡量的。在实际渲染到设置时,系统将帮助我们处理点到像素的转换。这样做的好处是隔离变化,即我们布局后不需要关注当前设备是否为Retina,直接按照一套坐标系进行布局即可。在实际使用中,我们需要牢记以下几点:一个点不一定对应一个物理像素。1Point的线在非Retina屏幕上是一个像素,在Retina屏幕上可能是2或3,这取决于系统设备的DPI。在iOS系统中,UIScreen、UIView、UIImage、CALayer类都提供了获取比例因子的相关属性。原生绘图技术自然会帮助我们处理比例因子。例如在drawRect:方法中,UIKit会根据当前运行的设备自动设置切线比例因子。所以我们在drawRect:方法中绘制的任何内容都会自动缩放到设备的物理屏幕。根据以上信息,我们可以看出,大多数情况下我们不需要关注像素,但也有一些情况需要考虑像素的转换。比如你画一条1像素的分界线,看到这个问题,你最好的思路可能是直接根据当前屏幕的缩放倍数,计算出1像素分界线对应的Point,然后设置线宽。代码如下:1.0f/[UIScreenmainScreen].scale表面上看一切正常,但是通过实际设备测试,你会发现渲染出来的线宽不是1像素。为什么?为了获得良好的视觉效果,绘图系统通常会使用一种叫做“抗锯齿(anti-aliasing)”的技术,iOS也不例外。显示屏是由许多小的显示单元组成的,可以理解为一个单元代表一个像素。如果要画一条黑线,而这条线刚好落在一列或一行显示单元内,就会渲染出标准的一个像素的黑线。但是如果线落在两行或两列的中间,你会得到一条“扭曲”的线,它实际上是一条两像素宽的灰色线。如下图所示:Positionsdefinedbywhole-numberedpointsfallatthemidpointbetweenpixels.Forexample,ifyoudrawaone-pixel-wideverticallinefrom(1.0,1.0)to(1.0,10.0),yougetafuzzygreyline.Ifyoudrawatwo-pixel-wideline,yougetasolidblacklinebecauseitfullycoverstwopixels(oneoneithersideofthespecifiedpoint).Asarule,linesthatareanoddnumberofphysicalpixelswideappearsofterthanlineswithwidthsmeasuredinevennumbersofphysicalpixelsunlessyouadjusttheirpositiontomakethemcoverpixelsfully.官方解释如上,简单Translate:Rules:奇数像素宽度的线在渲染时会显示为软宽度延伸到向上整数宽度的线,除非你手动调整线的位置,使线刚好落在一个显示单元上行或列里面。如何对齐?Onalow-resolutiondisplay(withascalefactorof1.0),aone-point-widelineisonepixelwide.Toavoidantialiasingwhenyoudrawaone-point-widehorizo??ntalorverticalline,ifthelineisanoddnumberofpixelsinwidth,youmustoffsetthepositionby0.5pointstoeithersideofawhole-numberedposition.Ifthelineisanevennumberofpointsinwidth,toavoidafuzzyline,youmustnotdoso.Onahigh-resolutiondisplay(withascalefactorof2.0),alinethatisonepointwideisnotantialiasedatallbecauseitoccupiestwofullpixels(from-0.5to+0.5)。要绘制仅覆盖单个物理像素的线,您需要将其粗细设为0.5点并将其位置偏移0.25点。为了防止“抗锯齿”在渲染奇数像素的线条时造成失真,需要将offset设置为0.5Point。在高清屏幕上,要绘制一个像素的线,需要将线宽设置为0.5Point,并将偏移设置为0.25Point。如果线宽为偶数点,则不要设置偏移量,否则线会变形。如下图所示:看了上面的解释,我们明白了1像素宽的线条失真的原因和解决方法。至此问题好像解决了?然后想想为什么在non-Retina和Retina屏上调整位置的值不一样,前者是0.5Point,后者是0.25Point,那么对于scale为3的6Plus设备应该调整多少?要回答这个问题,我们需要了解调整多少或调整多少的原理。回头看上图,图中每一个格子代表一个像素点,最上面的标记就是我们代码布局的坐标。您可以在左侧看到非Retina屏幕。当我们要在(3,0)位置绘制一条宽度为一个像素的竖线时,由于渲染的最小单位是像素,所以(3,0)的坐标恰好在两个像素的中间,系统会填充坐标3左右两列的像素对,为了不让线条显得太宽,将线条的颜色变浅。那么根据上面的信息,我们可以得出结论,如果要画一条像素宽度的线,就得把画好的坐标移动到(2.5,0)或者(3.5,0)的位置,这样系统才能渲染Pixel时只填充一列,即一个像素的标准行。基于以上分析,我们可以得出结论,如果“6PluswithScale3”设备要画一条宽度为1像素的线,位置调整也应该是0.5像素,对应的Point计算如下:(1.0f/[UIScreenmainScreen].scale)/2;下面是绘制像素线的宏:#defineSINGLE_LINE_WIDTH(1/[UIScreenmainScreen].scale)#defineSINGLE_LINE_ADJUST_OFFSET((1/[UIScreenmainScreen].scale)/2)代码如下:CGFloatxPos=5;UIView*view=[[UIViewalloc]initWithFrame:CGrect(x-SINGLE_LINE_ADJUST_OFFSET,0,SINGLE_LINE_WIDTH,100)];#p#2。正确绘制Grid线,粘贴一段写好的GridView代码,代码中Grid线的奇数像素点被偏移,防止线条模糊。SvGridView.h////SvGridView.h//SvSinglePixel////Createdbyxiaoyong.cxyon6/23/15.//Copyright(c)2015smileEvday.Allrightsreserved.//#import@interfaceSvGridView:UIView/***@brief网格间距离,默认30*/@property(nonatomic,assign)CGFloatgridSpacing;/***@brief网格线宽,默认为1pixel(1.0f/[UIScreenmainScreen].scale)*/@property(nonatomic,assign)CGFloatgridLineWidth;/***@brief网格颜色,默认蓝色*/@property(nonatomic,strong)UIColor*gridColor;@endSvGridView.m////SvGridView.m//SvSinglePixel////Createdbyxiaoyong.cxyon6/23/15.//Copyright(c)2015smileEvday.Allrightsreserved.//#import"SvGridView.h"#defineSINGLE_LINE_WIDTH(1/[UIScreenmainScreen].scale)#defineSINGLE_LINE_ADJUST_OFFSET((1/[UIScreenmainScreen].scale)/2)@implementationSvGridView@synthesizegridColor=_gridColor;@synthesizegridSpacing=_gridSpacing;-(instancetype)initWithFrame:(CGRect)frame{self=[superinitWithFrame:frame];if(self){self.backgroundColor=[UIColorclearColor];_gridColor=[UIColorblueColor];_gridLineWidth=SINGLE_LINE_WIDTH;_gridSpacing=30;}returnsself;}-(void)setGridColor:(UIColor*)gridColor{_gridColor=gridColor;[selfsetNeedsDisplay];}-(void)setGridSpacing:(CGFloat)gridSpacing{_gridSpacing=gridSpacing;[selfsetNeedsDisplay];}-(void)setGridLineWidth:(CGFloat)gridLineWidth{_gridLineWidth=gridLineWidth;[selfsetNeedsDisplay];}//OnlyoverridedrawRect:ifyouperformcustomdrawing.//Anemptyimplementationadverselyaffectsperformanceduringanimation.-(void)drawRect:(CGRect)rect{CGGetContextGurentContextRefcontexts=UIGrect()context);CGFloatlineMargin=self.gridSpacing;/***https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html*只当需要绘制的线宽为奇数像素时,绘制位置需要调整*/CGFloatpixelAdjustOffset=0;if(((int)(self.gridLineWidth*[UIScreenmainScreen].scale)+1)%2==0){pixelAdjustOffset=SINGLE_LINE_ADJUST_OFFSET;}CGFloatxPos=lineMargin-pixelAdjustOffset;CGFloatyPos=lineMargin-pixelAdjustOffset;while(xPos