【问题标题】:what is the positioning regularity of coordinates in core graphics in iphone programming?iphone编程中核心图形坐标的定位规律是什么?
【发布时间】:2011-11-15 20:05:40
【问题描述】:

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

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

为什么???

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

【问题讨论】:

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


    【解决方案1】:

    底部的编辑/添加

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

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

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

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

    编辑

    OP 说:

    我的目标是在视网膜显示屏上绘制一些形状,并且每一行 将是 1 像素宽度和 100% 不透明度。

    这个目标不需要关闭抗锯齿功能。如果您在启用抗锯齿的情况下看到水平和垂直线上的 alpha 混合,那么您没有在正确的位置或以正确的尺寸绘制线条。

    在这种情况下,在函数中:CGContextMoveToPoint(context, X, Y); 在 X 轴上的 2 个像素之间,引擎将选择正确的一个,然后在 Y 轴它会选择较高的那个。但在函数中: CGContextFillRect(context, someRect);它会像地图一样填充 像素网格(1 比 1)。

    您看到这种令人困惑的行为的原因是您关闭了抗锯齿功能。能够看到和理解这里发生的事情的关键是保持抗锯齿打开,然后进行必要的更改,直到你得到它恰到好处。开始执行此操作的最简单方法是保持 CGContext 的变换与默认值保持不变,并更改您传递给绘图例程的值。确实,您也可以通过转换 CGContext 来完成其中的一些工作,但这会增加一个数学步骤,该步骤是在您传递给任何 CG 例程的每个坐标上完成的,您无法在调试器中单步执行,所以我高度建议您从标准变换开始并保留 AA。在尝试弄乱上下文变换之前,您需要全面了解 CG 绘图的工作原理。

    是否有一些 Apple 定义的方法可以将图形映射到像素而不是像素 像素之间的线?

    总而言之,“不”,因为 CGContext 不是通用的位图(例如,您可能正在绘制 PDF —— 您无法通过询问 CGContext 知道)。您正在绘制的平面本质上是一个浮点平面。这就是它的工作原理。使用浮点平面完全可以实现 100% 像素精度的位图绘制,但要做到这一点,您必须了解这些东西的实际工作原理。

    可能可以通过获取给定的默认 CGContext 并进行此调用来更快地启动:

    CGContextTranslateCTM(context, 0.5, 0.5); 
    

    这将做的是将 (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);
    }
    

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

    OP 回复:

    但请注意,有一点阿尔法混合。这是 3200 倍缩放截图:

    不,真的。相信我。这是有原因的,它在上下文中打开了抗锯齿。 (另外,我认为您的意思是 3200% 变焦,而不是 3200 倍变焦——在 3200 倍变焦时,单个像素不适合 30 英寸的显示器。)

    在我给出的示例中,我们绘制了一个矩形,所以我们不需要考虑线的结尾,因为它是一条封闭的路径——这条线是连续的。既然您正在绘制单个线段,您确实必须考虑行尾以避免 alpha 混合。这是“像素边缘”与“像素中心”的回归。默认线帽样式是 kCGLineCapButt。 kCGLineCapButt 表示线条的末端正好从您开始绘制的位置开始。如果你想让它表现得更像钢笔——也就是说,如果你把一支毡尖笔放下,打算在右边画一条线 10 个单位,一些墨水会从左边渗出。 exact 您将笔指向的点 - 您可以考虑使用 kCGLineCapSquare (或 kCGLineCapRound 用于圆形末端,但对于单像素级绘图只会让您对 alpha 混合感到疯狂,因为它会将 alpha 计算为 1.0 - 0.5/pi)。我在 Apple 的 Quartz 2D Programming Guide 的插图上覆盖了假设的像素网格,以说明行尾与像素的关系:

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

    - (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%,我看到以下内容:

    现在,是的,在某些时候,如果您正在转换值或使用较长的计算。我已经看到在非常复杂的情况下像 0.000001 一样严重的错误会出现,如果您谈论的是 1.999999 与 2.000001 之间的差异之类的情况,即使是这样的错误也会咬到您,但这不是您在这里看到的.

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

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

    【讨论】:

    • 还值得一提的是,对于简单的情况,最好不要设置 CGContextSetShouldAntialias(context, NO);并调整您绘制的内容,使其正确对齐。如果您的目标是在屏幕边缘周围绘制一个 1pt(即在非视网膜上为 1px,在视网膜上为 2px)矩形,您可以通过在矩形 0,0->320,480 周围绘制一个 2pt 描边来获得该结果,一半描边将离开屏幕,另一半应该排成一行,没有 alpha 混合。开启抗锯齿功能后,Alpha 混合会成为一个危险信号,当您没有正确排列时会向您显示。
    • 非常感谢!!!我花了大约 30 个小时来解决这个问题,不知道该笑还是该哭)))
    • 我没有看到 alpha 混合,这不是问题。它绘制 2 像素宽度线而不是 1 的问题,直到我关闭 AA。
    • 假设您使用默认比例,您的描边宽度是 1.0 还是 0.5?如果您希望在视网膜显示器上显示 1 个物理像素,使用默认变换,您需要使用 0.5 的笔画宽度进行绘制
    • 描边宽度为 0.5,它绘制 2 像素线,alpha 为 0.5。而且我确信我正在定义所有应该定义的东西。已经尝试过 Apple 示例和一些书籍示例。
    【解决方案2】:

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

    来自 iOS 绘图指南。

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

    请看这里Supporting High Res Screens

    【讨论】:

      【解决方案3】:

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

      在这种情况下,在函数中:

      CGContextMoveToPoint(context, X, Y);
      

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

      但在函数中:

      CGContextFillRect(context, someRect);
      

      它将像像素网格上的映射一样填充(1 到 1)。

      真的吗?

      对于函数:

      CGContextAddRect(context, someRect);
      

      我还没明白。

      是否有一些 Apple 定义的方法可以将图形映射到像素而不是像素之间的线?

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

      【讨论】:

        【解决方案4】:

        ipmcc 谢谢! 现在一切都清楚了!

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

        *这是宽度为 1 像素、长度为 10 像素的线条,绘制方式为:

        CGContextAddLineToPoint();
        

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

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-21
        • 1970-01-01
        • 1970-01-01
        • 2011-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多