当传递UIImage.CGImage时,CGContextDrawImage会将图像颠倒过来绘制。

187

有人知道为什么CGContextDrawImage会倒着画我的图片吗?我正在从我的应用程序中加载一张图片:

UIImage *image = [UIImage imageNamed:@"testImage.png"];

然后只需请求核心图形将其绘制到我的上下文即可:

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

它在正确的位置和尺寸上呈现,但图像是倒置的。我肯定是漏掉了什么非常明显的东西吗?

18个回答

250

替换为

CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);

使用
[image drawInRect:CGRectMake(0, 0, 145, 15)];

在你的CGcontext方法的开始/结束中间。
这将在当前图像上下文中以正确的方向绘制图像 - 我相信这与UIImage保持方向知识有关,而CGContextDrawImage方法获取底层原始图像数据而不理解方向。

20
这个解决方案不允许你在你的图像上使用CG函数,比如CGContextSetAlpha(),而第二个答案可以。 - samvermette
3
不错,尽管通常在绘制图像时不需要自定义 alpha 值(通常这些值会提前嵌入到需要 alpha 的图像中)。我的座右铭是,使用最少的代码,因为代码越多,可能出现错误的机会就越多。 - Kendall Helmstetter Gelner
2
虽然它可能适用于 Rusty,但是-[UIImage drawInRect:] 存在线程不安全的问题。对于我的特定应用程序,这就是我首选使用 CGContextDrawImage() 的原因。 - Olie
苹果在UIKit文档中指出,您应该在主线程上调用UI方法,包括绘图方法... - Kendall Helmstetter Gelner
2
只是为了澄清:Core Graphics 是建立在 OS X 上的,其中坐标系统与 iOS 相比是倒置的(在 OS X 中,y 从左下角开始)。这就是为什么 Core Graphics 绘制图像时会颠倒,而 drawInRect 则会为您处理这个问题。 - borchero
显示剩余3条评论

218
即使我已经尝试了所有我提到的方法,仍然遇到了图像问题。最终,我使用了Gimp创建了所有图像的“垂直翻转”版本。现在我不需要再使用任何变换。希望这不会在以后引起更多问题。
有人知道为什么CGContextDrawImage会倒置我的图像吗?我从我的应用程序中加载一个图像:
Quartz2d使用不同的坐标系,其中原点位于左下角。因此,当Quartz绘制100*100图像的像素x[5],y[10]时,该像素被绘制在左下角而不是左上角。从而导致“翻转”的图像。
x坐标系匹配,因此您需要翻转y坐标。
CGContextTranslateCTM(context, 0, image.size.height);

这意味着我们在x轴上将图像平移0个单位,在y轴上将其平移图像高度的距离。然而,仅仅这样做会导致图像仍然颠倒,只是被绘制在我们希望它被绘制的位置下方"image.size.height"。

Quartz2D编程指南建议使用ScaleCTM并传递负值来翻转图像。您可以使用以下代码实现-

CGContextScaleCTM(context, 1.0, -1.0);

在调用CGContextDrawImage之前将两者结合起来,然后您应该可以正确地绘制图像。

UIImage *image = [UIImage imageNamed:@"testImage.png"];    
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);       

CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage(context, imageRect, image.CGImage);

如果您的imageRect坐标与图像不匹配,就要小心了,因为可能会得到意想不到的结果。

要将坐标转换回去:

CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);

这很好,但我发现即使我尝试将其设置回0,0和1.0,1.0,其他东西仍受到翻译和缩放的影响。最终我选择了Kendall的解决方案,但我意识到它使用UIImage而不是我们在这里使用的低级CGImage。 - acheo
3
如果你正在使用drawLayer并且想将CGImageRef绘制到上下文中,这里介绍的技巧正好合适!如果有图像的矩形,那么使用CGContextTranslateCTM(context, 0, image.size.height + image.origin.y) ,然后在CGContextDrawImage()之前将矩形的origin.y设置为0即可。谢谢! - David H
我曾经在使用ImageMagick时遇到过问题,其中iPhone相机导致了不正确的方向。原来这是由于图像文件中的方向标志,因此纵向图像的大小为960px x 640px,并将标志设置为“left”-左侧为顶部(或类似)。这个线程可能会有所帮助:https://dev59.com/YnM_5IYBdhLWcg3wt1k0 - Hari Honor
10
为什么不使用CGContextSaveGState()CGContextRestoreGState()来保存和恢复变换矩阵? - Ja͢ck

21

不必在UIImage上失去任何功能,您可以使用drawAtPoint:drawInRect:方法,同时仍然指定您的自定义上下文:

UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();

你还需要避免使用CGContextTranslateCTM或者CGContextScaleCTM函数来修改上下文,而第二个答案使用了这些函数。


这是一个很好的想法,但对我来说,它导致了一张上下颠倒且左右翻转的图像。我猜结果会因图像而异。 - Luke Rogers
也许这在后续的iOS版本中不再起作用了?当时对我来说它能够与所有东西兼容。 - Rivera
我认为问题实际上是由于我创建上下文的方式不对。一旦我开始使用UIGraphicsBeginImageContextWithOptions而不仅仅是初始化一个新的CGContext,它似乎就可以工作了。 - Luke Rogers

13
我使用这个纯的Core Graphics扩展,它可以正确处理图像矩形中的非零原点:

我使用这个Swift 5,纯的Core Graphics扩展,可以正确处理图像矩形中的非零原点:

extension CGContext {

    /// Draw `image` flipped vertically, positioned and scaled inside `rect`.
    public func drawFlipped(_ image: CGImage, in rect: CGRect) {
        self.saveGState()
        self.translateBy(x: 0, y: rect.origin.y + rect.height)
        self.scaleBy(x: 1.0, y: -1.0)
        self.draw(image, in: CGRect(origin: CGPoint(x: rect.origin.x, y: 0), size: rect.size))
        self.restoreGState()
    }
}

您可以像使用CGContext的常规draw(: in:)方法一样使用它:
ctx.drawFlipped(myImage, in: myRect)

12

相关的Quartz2D文档:https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW4

翻转默认坐标系

在UIKit绘图中,翻转将修改支撑CALayer以将具有LLO坐标系统的绘图环境与UIKit的默认坐标系统对齐。如果您只使用UIKit方法和函数进行绘制,则不需要翻转CTM。但是,如果您将Core Graphics或Image I / O函数调用与UIKit调用混合使用,则可能需要翻转CTM。

具体而言,如果您通过直接调用Core Graphics函数来绘制图像或PDF文档,则对象在视图上下文中呈倒置状态。您必须翻转CTM才能正确显示图像和页面。

要翻转绘制到Core Graphics上下文的对象,以便在UIKit视图中正确显示,您必须分两步修改CTM。您将原点平移至绘图区域的左上角,然后应用比例平移,将y坐标修改为-1。执行此操作的代码类似于以下内容:

CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);

10
如果有人对在自定义矩形中绘制图像的简单解决方案感兴趣:
 func drawImage(image: UIImage, inRect rect: CGRect, context: CGContext!) {

    //flip coords
    let ty: CGFloat = (rect.origin.y + rect.size.height)
    CGContextTranslateCTM(context, 0, ty)
    CGContextScaleCTM(context, 1.0, -1.0)

    //draw image
    let rect__y_zero = CGRect(origin: CGPoint(x: rect.origin.x, y:0), size: rect.size)
    CGContextDrawImage(context, rect__y_zero, image.CGImage)

    //flip back
    CGContextScaleCTM(context, 1.0, -1.0)
    CGContextTranslateCTM(context, 0, -ty)

}

图像将被缩放以填充矩形。


7

我不确定对于UIImage来说是否如此,但这种行为通常发生在坐标被翻转时。大多数OS X坐标系统的原点位于左下角,例如Postscript和PDF。但是CGImage坐标系统的原点位于左上角。

可能的解决方案可能涉及isFlipped属性或scaleYBy:-1仿射变换。


4

这是因为QuartzCore有一个“底部-左侧”的坐标系统,而UIKit则有一个“顶部-左侧”的坐标系统。

如果需要,在此情况下可以扩展CGContext

extension CGContext {
  func changeToTopLeftCoordinateSystem() {
    translateBy(x: 0, y: boundingBoxOfClipPath.size.height)
    scaleBy(x: 1, y: -1)
  }
}

// somewhere in render 
ctx.saveGState()
ctx.changeToTopLeftCoordinateSystem()
ctx.draw(cgImage!, in: frame)

1
这非常有用,可以与新的UIGraphicsImageRenderer一起使用! - user3069232

3
使用 Swift 代码进行补充回答 Quartz 2D 图形使用的坐标系原点位于左下角,而 iOS 中的 UIKit 使用的坐标系原点位于左上角。通常情况下一切都正常,但在进行某些图形操作时,您需要自己修改坐标系。文档 指出:

一些技术使用不同的默认坐标系设置其图形上下文,与 Quartz 使用的坐标系相比,这种坐标系是一个修改后的坐标系,在执行某些 Quartz 绘图操作时必须进行补偿。最常见的修改坐标系将原点放置在上左角,将 y 轴改为指向页面底部。

可以在以下两个自定义视图实例中看到这种现象,它们在其 drawRect 方法中绘制图像。

enter image description here

在左侧,图像是倒置的,在右侧,坐标系已经被平移和缩放,使得原点位于左上角。 倒置的图像
override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // draw image in context
    CGContextDrawImage(context, imageRect, image.CGImage)

}

修改后的坐标系

override func drawRect(rect: CGRect) {

    // image
    let image = UIImage(named: "rocket")!
    let imageRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)

    // context
    let context = UIGraphicsGetCurrentContext()

    // save the context so that it can be undone later
    CGContextSaveGState(context)

    // put the origin of the coordinate system at the top left
    CGContextTranslateCTM(context, 0, image.size.height)
    CGContextScaleCTM(context, 1.0, -1.0)

    // draw the image in the context
    CGContextDrawImage(context, imageRect, image.CGImage)

    // undo changes to the context
    CGContextRestoreGState(context)
}

3

UIImage 包含一个 CGImage 作为其主要内容成员,以及缩放和方向因素。由于 CGImage 及其各种函数都源自 OSX,它期望的坐标系与 iPhone 相比是倒置的。当您创建一个 UIImage 时,默认为倒置方向来进行补偿(您可以更改此设置!)。使用 .CGImage 属性来访问非常强大的 CGImage 函数,但最好使用 UIImage 方法来绘制到 iPhone 屏幕等。


1
你如何更改UIImage的默认上下翻转方向? - MegaManX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接