iphone编程中核心图形中坐标的定位规律是什么?

时间:2011-11-15 20:05:40

标签: iphone objective-c xcode core-graphics retina-display

我需要在视网膜显示器上绘制一些形状,所以我将逻辑点转换为视网膜显示,如下所示:

self.inverseScale = (CGFloat)1.0/[UIScreen mainScreen].scale;
CGContextRef context=UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, inverseScale, inverseScale);

然后定义下一个:

CGContextSetShouldAntialias(context, NO);
CGContextSetLineWidth(context, 1.0);

好的,现在我的视网膜显示尺寸为640x960,但我对坐标感到困惑。 我想如果我需要绘制一些矩形框架,我需要做下一个:

CGContextSetFillColorWithColor(context, someBlackColor);
CGContextFillRect(context, displaySizeRect);
CGContextSetStrokeColorWithColor(context, white);
CGContextAddRect(context, CGRectMake(0, 0, 639, 959));
CGContextStrokePath(context);

但不是,我发现我需要写:

CGContextAddRect(context, CGRectMake(0, 1, 639, 959));

而不是:

CGContextAddRect(context, CGRectMake(0, 0, 639, 959));

为什么???

我的转换是错误还是什么?

4 个答案:

答案 0 :(得分:6)

底部的编辑/添加

听起来你可能会对坐标系如何映射到像素网格感到困惑。当您正在绘制CGContext时,您将进入"连续"基于浮点的平面。该连续平面被映射到显示器的像素网格上,使得整数值落在屏幕像素之间的线上。

理论上,在任何给定设备的默认比例(非视网膜1.0,视网膜2.0),如果你从0,0画出一个矩形 - > 320,480,100%不透明黑色笔划,宽度为1.0pt,您可以期待以下结果:

  • 在非视网膜上,你会在外面有一个1像素宽的矩形 50%不透明度的屏幕
  • 在视网膜上,你会有一个像素 屏幕外侧的宽矩形,100%不透明度。

这源于以下事实:零线位于显示器的边缘,显示器中第一行/多列像素与显示器中第一行/多列像素之间。在这种情况下以默认比例绘制1pt线时,它会沿着路径的中心绘制,因此其中一半将从显示屏上绘制出来,而在连续平面中,您将有一条宽度延伸的线从-0.5pt到0.5pt。在非视网膜显示器上,这将成为50%不透明度的1px线,并且在视网膜显示器上将呈现为1px线,具有100%不透明度。

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); 

这样做会将(0.5,0.5)添加到您传递给所有后续绘图调用的每个点(直到您调用CGContextRestoreGState)。如果您没有对上下文进行其他更改,那么当您从0,0 - >中绘制一条线时,应该这样做。即使使用抗锯齿功能,它也会完全按像素对齐。 (请参阅上面我的初步答案,了解为何会出现这种情况。)

使用0.5,0.5技巧可以让你开始更快,但如果你想使用像素精确的CG绘图,你真的需要了解基于浮点的图形上下文如何工作,以及它们如何相关到可能(或可能不)支持它们的位图。

关闭AA,然后轻推价值,直到他们“正确”#34;只是在以后惹麻烦。例如,有一天说UIKit会给你一个上下文,它有一个不同的翻转变换?在这种情况下,你的一个值可能向下舍入,它用于向上舍入(因为现在它在应用翻转时乘以-1.0)。对于已转换为负象限的上下文,可能会发生同样的问题。此外,你不知道(并且不能严格依赖版本到版本)当你关闭AA时,CoreGraphics会使用什么舍入规则,所以如果你最终被交给不同CTM的上下文无论什么原因,你都会得到不一致的结果。

对于记录,您可能想要关闭抗锯齿的时间是您正在绘制非直线路径并且您不希望CoreGraphics对边缘进行alpha混合。你可能确实想要这种效果,但是关闭AA会让你更难学习,理解并确定发生了什么,所以我强烈建议你继续学习,直到你完全理解这些东西,然后把你所有的东西都拿走了直线绘图完美像素对齐。 然后翻转开关以摆脱非直线路径上的AA。

关闭抗锯齿功能 NOT 会神奇地允许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);
}

创建一个新的单视图应用程序,使用此drawRect:方法添加自定义视图子类,并在.xib文件中设置默认视图以使用您的自定义类。这个if语句的两面在视网膜显示器上产生相同的结果:100x100设备像素,非alpha混合方块。第一方通过使用"对"默认比例的值。其它方面通过退出2x比例,然后将平面从对齐到设备像素的边缘平移到与设备像素的中心对齐来实现。注意笔划宽度是如何不同的(比例因子也适用于它们。)希望这会有所帮助。

OP回答说:

  

但是请注意,有一些alpha混合,一点点。这是   截图缩放为3200倍:

不,真的。相信我。这是一个原因,并且在上下文中打开了 NOT 抗锯齿功能。 (另外,我认为你的意思是3200%变焦,而不是3200倍变焦 - 在3200倍变焦时,单个像素不适合30"显示器。)

在我给出的例子中,我们绘制了一个矩形,所以我们不需要考虑行结尾,因为它是一条封闭的路径 - 这条线是连续的。既然您正在绘制单个细分,那么必须考虑行结尾以避免alpha混合。这是像素的边缘" vs."像素的中心"回来的东西。默认的线帽样式是kCGLineCapButt。 kCGLineCapButt意味着该行的结尾正好从您开始绘制的位置开始。如果你想让它表现得更像钢笔 - 也就是说,如果你把毡尖笔放下来,打算在右边划出10个单位的线条,那么一些墨水就会渗出到左边。您指向笔的精确点 - 您可能会考虑使用kCGLineCapSquare(或kCGLineCapRound用于圆形末端,但对于单像素级绘图,这会让您厌倦了alpha混合,因为它会将alpha计算为1.0 - 0.5 / pi)。我在Apple的Quartz 2D Programming Guide插图中叠加了假想的像素网格,以说明行结尾与像素的关系:

Line endings with hypothetical pixel grids overlaid

但是,我离题了。这是一个例子;请考虑以下代码:

- (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);
}

请注意,不是移动到2.25,2.25,而是移动到2.0,2.25。这意味着我位于X维度中像素的边缘,以及Y维度中像素的中心。因此,我不会在行的末尾进行alpha混合。事实上,在Acorn中放大到3200%,我看到以下内容:

Non-alpha-blended 10 device pixel line at 3200%

现在,是的,在某些时候,超出关怀点(对于大多数人),如果您正在转换值或使用长时间,可能会遇到累积浮点错误计算管道。在非常复杂的情况下,我发现错误像0.000001一样显着,如果你在谈论1.999999与2.000001之间的差异,即使是这样的错误也会让你感到困惑,但这不是你和#39;看到这里。

如果您的目标只是绘制像素精确位图(仅由轴对齐/直线元素组成),则 NO 原因会关闭上下文消除锯齿。在我们在这种情况下谈论的缩放级别和位图密度时,您应该很容易避免由1e-6或更小幅度的浮点错误引起的几乎所有问题。 (实际上在这种情况下,任何小于1/256的像素都不会对8位上下文中的alpha混合产生任何影响,因为当量化为8位时,这种误差实际上会为零。)

让我借此机会推荐一本书:Programming with Quartz: 2D and PDF Graphics in Mac OS X这是一本很棒的读物,并且非常详细地介绍了所有这些内容。

答案 1 :(得分:2)

如果您正在绘制屏幕,​​则无需调整坐标系。一般来说,你可以假装你正在绘制非视网膜显示器。在幕后,图形系统将调整上下文变换以考虑2.0比例因子。如果您确实需要优化视网膜,可以在代码中添加检测,但听起来您还不需要。

来自iOS绘图指南。

  

在应用程序中进行任何自定义绘图时,大多数情况下您不需要关心底层屏幕的分辨率。原生绘图技术自动确保您在逻辑坐标空间中指定的坐标正确映射到底层屏幕上的像素。但是,有时您可能需要知道当前比例因子是什么才能正确呈现内容。对于这些情况,UIKit,Core Animation和其他系统框架提供了正确绘制图形所需的帮助。

请参阅此处Supporting High Res Screens

答案 2 :(得分:0)

我的目标是在视网膜显示屏上绘制一些形状,每条线将 1 像素宽度和 100%不透明度。在那种情况下,我的转换方法是写吗? 我还需要关闭抗锯齿,否则它不会是1像素宽。

在这种情况下的功能:

CGContextMoveToPoint(context, X, Y);

在X轴上的2个像素之间,引擎将选择正确的一个,并且在Y轴上它将选择较高的一个。像这样:

pixel map

但是在功能中:

CGContextFillRect(context, someRect);

它会像像素网格(1到1)上的地图一样填充。

是怀疑吗?

对于功能:

CGContextAddRect(context, someRect);

我还没有明白。

是否有一些Apple定义的方法来映射像素上的图形而不是像素之间的线上?

P.S。我不能依赖所有方便的方法,也不能使用抗锯齿,因为我正在编写一些我需要控制每个像素的实用工具。

答案 3 :(得分:0)

ipmcc 谢谢你举个例子! 现在一切都清楚了!

但是请注意, 某些alpha混合,一点点。这是3200x缩放的屏幕截图: enter image description here

*这是1像素宽和10像素长的线,用:

绘制
CGContextAddLineToPoint();

功能。 只有关闭抗锯齿能解决这个问题。