Nik*_*ita 2 iphone xcode core-graphics objective-c retina-display
我需要在视网膜显示器上绘制一些形状,所以我转换视网膜显示的逻辑点,如下所示:
self.inverseScale = (CGFloat)1.0/[UIScreen mainScreen].scale;
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, inverseScale, inverseScale);
Run Code Online (Sandbox Code Playgroud)
之后定义下一个:
CGContextSetShouldAntialias(context, NO);
CGContextSetLineWidth(context, 1.0);
Run Code Online (Sandbox Code Playgroud)
好的,现在我的视网膜显示尺寸为640x960,但我对坐标感到困惑.我想如果我需要绘制一些矩形框架,我需要做下一个:
CGContextSetFillColorWithColor(context, someBlackColor);
CGContextFillRect(context, displaySizeRect);
CGContextSetStrokeColorWithColor(context, white);
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
CGContextStrokePath(context);
Run Code Online (Sandbox Code Playgroud)
但不是,我发现我需要写:
CGContextAddRect(context, CGRectMake(0, 1, 639, 959));
Run Code Online (Sandbox Code Playgroud)
代替:
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
Run Code Online (Sandbox Code Playgroud)
为什么???
我的转换是错的还是什么?
底部的编辑/添加
听起来你可能会对坐标系如何映射到像素网格感到困惑.当您绘制CGContext时,您将绘制到一个"连续"基于浮点的平面.该连续平面被映射到显示器的像素网格上,使得整数值落在屏幕像素之间的线上.
理论上,在任何给定设备的默认比例(非视网膜1.0,视网膜2.0),如果您从0,0 - > 320,480画一个矩形,100%不透明黑色笔画宽度为1.0pt,你可以期待以下结果:
这源于零线在显示器的边缘处,在显示器中的第一行/多个像素与不在显示器中的第一行/列像素之间的事实.当你在那种情况下以默认比例绘制1pt线时,它沿着路径的中心绘制,因此它的一半将从显示器上绘制出来,而在你的连续平面中,你将有一条宽度从 - 延伸的线 - 0.5pt至0.5pt.在非视网膜显示器上,这将成为50%不透明度的1px线,并且在视网膜显示器上将呈现为具有100%不透明度的1px线.
EDITS
OP说:
我的目标是在视网膜显示器上绘制一些形状,每条线将是1像素宽度和100%不透明度.
这个目标没有任何内容需要关闭抗锯齿功能.如果您在打开抗锯齿的情况下在水平和垂直线上看到alpha混合,那么您不会在正确的位置或正确的大小上绘制线条.
在那种情况下,函数:CGContextMoveToPoint(context,X,Y); 在X轴上的2个像素之间,引擎将选择正确的一个,并且在Y轴上它将选择较高的一个.但在函数中:CGContextFillRect(context,someRect); 它会像在像素网格(1到1)上映射一样填充.
您看到这种令人困惑的行为的原因是您已关闭抗锯齿功能.能够看到并理解这里发生的事情的关键是留下抗锯齿,然后进行必要的更改,直到你得到它为止.开始这样做的最简单方法是使CGContext的变换保持不变,并更改您传递给绘制例程的值.确实,您也可以通过转换CGContext来完成这项工作,但是这会在您传递给任何CG例程的每个坐标上添加一个数学步骤,您无法在调试器中进入,所以我高度重视建议您从标准转换开始,并保留AA.在尝试弄乱上下文转换之前,您需要充分了解CG绘图的工作原理.
是否有一些Apple定义的方法来映射像素上的图形而不是像素之间的线上?
总之,"不",因为CGContexts不是普遍的位图(例如,你可能正在绘制PDF - 你无法通过询问CGContext来了解).你正在绘制的平面本质上是一个浮点平面.这就是它的工作原理.使用浮点平面完全可以实现100%像素精确的位图绘制,但为了做到这一点,您必须了解这些内容实际上是如何工作的.
您可以通过获取您给出的默认CGContext并进行此调用来更快地获得引导:
CGContextTranslateCTM(context, 0.5, 0.5);
Run Code Online (Sandbox Code Playgroud)
这将做的是将(0.5,0.5)添加到您传递给所有后续绘图调用的每个点(直到您调用CGContextRestoreGState).如果您没有对上下文进行其他更改,那么当您从0,0 - > 10,0绘制一条线时,即使使用抗锯齿,它也会完全按像素对齐.(请参阅上面我的初步答案,了解为何会出现这种情况.)
使用0.5,0.5技巧可以让你开始更快,但如果你想使用像素精确的CG绘图,你真的需要了解基于浮点的图形上下文如何工作,以及它们与位图的关系可能(或可能不)支持他们.
关闭AA然后轻推价值直到他们"正确"只是稍后要求麻烦.例如,有人说有一天UIKit会给你一个上下文,它的变换有不同的翻转?在这种情况下,你的一个值可能向下舍入,它用于向上舍入(因为现在它在应用翻转时乘以-1.0).对于已转换为负象限的上下文,可能会发生同样的问题.此外,你不知道(并且不能严格依赖版本到版本)当你关闭AA时,CoreGraphics会使用什么舍入规则,所以如果你因为某种原因最终被交给不同CTM的上下文会得到不一致的结果.
对于记录,您可能想要关闭抗锯齿的时间将是您绘制非直线路径并且您不希望CoreGraphics对边缘进行alpha混合.你可能确实想要这种效果,但是关闭AA会让学习,理解和确定发生的事情变得更加困难,所以我强烈建议你继续学习,直到你完全理解这些东西,然后把你所有的直线绘图完美地放在像素上对齐. 然后翻转开关以摆脱非直线路径上的AA.
关闭抗锯齿并不会神奇地允许CGContext作为位图进行寻址.它需要一个浮点平面并添加一个您无法看到的(代码方式),无法控制以及难以理解和预测的舍入步骤.最后,你仍然有一个浮点平面.
只是一个简单的例子来帮助澄清:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
BOOL doAtDefaultScale = YES;
if (doAtDefaultScale)
{
// Do it by using the right values for the default context
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(ctx, 0.5); // We're working in scaled pixel. 0.5pt => 1.0px
CGContextStrokeRect(ctx, CGRectMake(25.25, 25.25, 50, 50));
}
else
{
// Do it by transforming the context
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextScaleCTM(ctx, 0.5, 0.5); // Back out the default scale
CGContextTranslateCTM(ctx, 0.5, 0.5); // Offset from edges of pixels to centers of pixels
CGContextSetLineWidth(ctx, 1.0); // we're working in device pixels now, having backed out the scale.
CGContextStrokeRect(ctx, CGRectMake(50, 50, 100, 100));
}
CGContextRestoreGState(ctx);
}
Run Code Online (Sandbox Code Playgroud)
创建一个新的单视图应用程序,使用此drawRect:方法添加自定义视图子类,并在.xib文件中设置默认视图以使用您的自定义类.这个if语句的两面在视网膜显示器上产生相同的结果:100x100设备像素,非alpha混合方块.第一方通过使用默认比例的"正确"值来完成它.其它方面通过退出2x比例,然后将平面从对齐到设备像素的边缘平移到与设备像素的中心对齐来实现.注意笔划宽度是如何不同的(比例因子也适用于它们.)希望这会有所帮助.
OP回答说:
但需要注意的是,有一些alpha混合,一点点.这是3200x缩放的屏幕截图:
不完全是.相信我.还有一个原因,这是不是抗锯齿的情况下被打开.(另外,我认为你的意思是3200%变焦,而不是3200倍变焦 - 在3200倍变焦时,单个像素不适合30英寸显示器.)
在我给出的示例中,我们绘制了一个rect,因此我们不需要考虑行结尾,因为它是一条封闭的路径 - 该行是连续的.既然您正在绘制单个段,则必须考虑行结尾以避免alpha混合.这是"像素的边缘"与"像素的中心"的东西回归.默认的线帽样式是kCGLineCapButt.kCGLineCapButt意味着该行的结尾正好从您开始绘制的位置开始.如果你想让它表现得更像钢笔 - 也就是说,如果你把毡尖笔放下来,打算在右边划出10个单位的线条,那么一些墨水就会渗出到左边.你指向笔的确切点 - 你可能会考虑使用kCGLineCapSquare(或者kCGLineCapRound用于圆角端,但对于单像素级绘图,这会让你疯狂地使用alpha混合,因为它会将alpha计算为1.0 - 0.5/pi).我在Apple的Quartz 2D编程指南中叠加了这个插图上的假设像素网格,以说明行结尾与像素的关系:
但是,我离题了.这是一个例子; 请考虑以下代码:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(ctx, 0.5); //On device pixel at default scale
CGContextMoveToPoint(ctx, 2.0, 2.25); // Edge of pixel in X, Center of pixel in Y
CGContextAddLineToPoint(ctx, 7.0, 2.25); // Draw a line of 5 scaled, 10 device pixels
CGContextStrokePath(ctx); // Stroke it
CGContextRestoreGState(ctx);
}
Run Code Online (Sandbox Code Playgroud)
请注意,不是移动到2.25,2.25,而是移动到2.0,2.25.这意味着我位于X维度中像素的边缘,以及Y维度中像素的中心.因此,我不会在行的末尾进行alpha混合.事实上,在Acorn中放大到3200%,我看到以下内容:

现在,是的,在某些时候,超出关怀点(对于大多数人),如果您正在转换值或使用长时间的计算管道,则可能会遇到累积浮点错误.在非常复杂的情况下,我看到错误就像0.000001爬升一样重要,如果你在讨论像1.999999和2.000001之间的差异这样的情况,即使是这样的错误也会咬你,但这不是你在这里看到的.
如果您的目标只是绘制像素精确位图(仅由轴对齐/直线元素组成),则没有理由关闭上下文抗锯齿.在我们在这种情况下讨论的缩放级别和位图密度时,您应该很容易避免由1e-6或更小幅度的浮点错误引起的几乎所有问题.(事实上,在这种情况下,任何小于1/256th的像素都不会对8位上下文中的alpha混合产生任何影响,因为当量化为8位时,这种误差实际上会为零.)
让我借此机会推荐一本书:使用Quartz编程:Mac OS X中的2D和PDF图形这是一个很好的阅读,并且非常详细地介绍了所有这些内容.
| 归档时间: |
|
| 查看次数: |
1893 次 |
| 最近记录: |