CGContextDrawImage 在传递 UIImage.CGImage 时将图像倒置

Posted

技术标签:

【中文标题】CGContextDrawImage 在传递 UIImage.CGImage 时将图像倒置【英文标题】:CGContextDrawImage draws image upside down when passed UIImage.CGImage 【发布时间】:2010-10-05 03:09:40 【问题描述】:

有谁知道为什么CGContextDrawImage 会颠倒我的图像?我正在从我的应用程序中加载图像:

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

然后简单地要求核心图形将其绘制到我的上下文中:

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

它呈现在正确的位置和尺寸,但图像是颠倒的。我一定在这里遗漏了一些非常明显的东西?

【问题讨论】:

【参考方案1】:

代替

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

使用

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

在您的开始/结束CGcontext 方法的中间。

这会将具有正确方向的图像绘制到您当前的图像上下文中 - 我很确定这与 UIImage 掌握方向知识而 CGContextDrawImage 方法获取底层原始图像有关不了解方向的数据。

【讨论】:

此解决方案不允许您在图像上使用 CG 函数,例如 CGContextSetAlpha(),而第二个答案允许。 好点,虽然大多数情况下您不需要自定义 alpha 级别来绘制图像(通常那些需要 alpha 的东西提前烘焙到图像中)。基本上我的座右铭是,尽可能使用少量代码,因为更多的代码意味着更多的错误机会。 嗨,你的开始/结束CGContext方法中间是什么意思,你能给我更多的示例代码吗?我正在尝试将上下文存储在某处并使用上下文在中绘制图像 虽然它可能适用于 Rusty,但 -[UIImage drawInRect:] 的问题是它不是线程安全的。对于我的特定应用程序,这就是我首先使用 CGContextDrawImage() 的原因。 澄清一下:Core Graphics 构建在 OS X 上,其坐标系与 ios 相比是颠倒的(y 从 OS X 的左下角开始)。这就是 Core Graphics 将图像颠倒绘制而 drawInRect 为您处理的原因【参考方案2】:

即使应用了我提到的所有内容,我仍然对图像进行了戏剧化处理。最后,我刚刚使用 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 东西 如果你正在使用drawLayer并且想在上下文中绘制一个CGImageRef,这里的这个技术只是希望医生命令!如果有图像的矩形,则 CGContextTranslateCTM(context, 0, image.size.height + image.origin.y),然后在 CGContextDrawImage() 之前将 rect.origin.y 设置为 0。谢谢! 我曾经遇到过 ImageMagick 问题,iPhone 摄像头导致方向不正确。事实证明它是在图像文件中设置方向标志,所以肖像图像是,比如 960px x 640px,标志设置为“左” - 因为左侧是顶部(或接近顶部)。该线程可能会有所帮助:***.com/questions/1260249/… 为什么不用CGContextSaveGState()CGContextRestoreGState()来保存和恢复变换矩阵呢?【参考方案3】:

两全其美,使用 UIImage 的 drawAtPoint:drawInRect: 同时仍指定您的自定义上下文:

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

您还避免使用CGContextTranslateCTMCGContextScaleCTM 修改您的上下文,第二个答案会这样做。

【讨论】:

这是个好主意,但对我来说,它产生的图像既是上下颠倒的,也是从左到右的。我猜结果会因图像而异。 也许这不再适用于后来的 iOS 版本?当时它适用于我的一切。 我认为这实际上取决于我如何创建上下文。一旦我开始使用UIGraphicsBeginImageContextWithOptions 而不是仅仅启动一个新的CGContext,它似乎就可以工作了。【参考方案4】:

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

【讨论】:

【参考方案5】:

如果有人对在上下文中以自定义矩形绘制图像的简单解决方案感兴趣:

 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)


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

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

这是因为 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)

【讨论】:

这非常有用,可以与新的 UIGraphicsImageRenderer 一起使用!【参考方案8】:

我使用这个 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)

【讨论】:

【参考方案9】:

UIImage 包含一个CGImage 作为其主要内容成员以及缩放和方向因素。由于CGImage 和它的各种功能都是从OSX 派生的,所以它需要一个与iPhone 相比颠倒的坐标系。当您创建 UIImage 时,它默认为上下颠倒的方向以进行补偿(您可以更改它!)。使用 .CGImage 属性可以访问非常强大的 CGImage 功能,但在 iPhone 屏幕等上绘图最好使用 UIImage 方法。

【讨论】:

如何更改 UIImage 的默认倒置方向?【参考方案10】:

Swift 代码的补充答案

Quartz 2D 图形使用原点在左下角的坐标系,而 iOS 中的 UIKit 使用原点在左上角的坐标系。通常一切正常,但是在进行一些图形操作时,您必须自己修改坐标系。 documentation 声明:

一些技术使用不同的设置它们的图形上下文 默认坐标系,而不是 Quartz 使用的坐标系。关系到 Quartz,这样的坐标系是修改后的坐标系, 执行某些 Quartz 绘图时必须进行补偿 操作。最常见的修改坐标系将 原点位于上下文的左上角并更改 y 轴 指向页面底部。

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

在左侧,图像是上下颠倒的,而在右侧,坐标系已被平移和缩放,因此原点位于左上角。

上下颠倒的图片

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)

【讨论】:

【参考方案11】:

Swift 3.0 和 4.0

yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)

无需修改

【讨论】:

【参考方案12】:

drawInRect 肯定是要走的路。这是另一个在执行此操作时会派上用场的小东西。通常图片和它要进入的矩形不符合。在这种情况下,drawInRect 将拉伸图片。这是一种快速而酷的方法,通过反转转换(这将适合整个事物)来确保图片的纵横比不会改变:

//Picture and irect don't conform, so there'll be stretching, compensate
    float xf = Picture.size.width/irect.size.width;
    float yf = Picture.size.height/irect.size.height;
    float m = MIN(xf, yf);
    xf /= m;
    yf /= m;
    CGContextScaleCTM(ctx, xf, yf);

    [Picture drawInRect: irect];

【讨论】:

【参考方案13】:

我们可以用同样的函数解决这个问题:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

[image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)];

UIGraphicsPopContext();

UIGraphicsEndImageContext();

【讨论】:

【参考方案14】:

Swift 3 CoreGraphics 解决方案

如果您出于任何原因想要使用 CG,而不是 UIImage,这个基于先前答案的 Swift 3 构造确实为我解决了这个问题:

if let cgImage = uiImage.cgImage 
    cgContext.saveGState()
    cgContext.translateBy(x: 0.0, y: cgRect.size.height)
    cgContext.scaleBy(x: 1.0, y: -1.0)
    cgContext.draw(cgImage, in: cgRect)
    cgContext.restoreGState()

【讨论】:

【参考方案15】:

在我的项目过程中,我从Kendall's answer 跳到Cliff's answer 以解决从手机本身加载的图像的问题。

最后我改用CGImageCreateWithPNGDataProvider

NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];

return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);

这不会受到从UIImage 获取CGImage 所带来的方向问题的影响,并且它可以毫无障碍地用作CALayer 的内容。

【讨论】:

【参考方案16】:
func renderImage(size: CGSize) -> UIImage 
    return UIGraphicsImageRenderer(size: size).image  rendererContext in
        // flip y axis
        rendererContext.cgContext.translateBy(x: 0, y: size.height)
        rendererContext.cgContext.scaleBy(x: 1, y: -1)

        // draw image rotated/offsetted
        rendererContext.cgContext.saveGState()
        rendererContext.cgContext.translateBy(x: translate.x, y: translate.y)
        rendererContext.cgContext.rotate(by: rotateRadians)
        rendererContext.cgContext.draw(cgImage, in: drawRect)
        rendererContext.cgContext.restoreGState()
    

【讨论】:

【参考方案17】:

Swift 5 答案基于@ZpaceZombor 的优秀答案

如果你有 UIImage,只需使用

var image: UIImage = .... 
image.draw(in: CGRect)

如果您有 CGImage,请使用下面的我的类别

注意:与其他一些答案不同,这个答案考虑到您要绘制的矩形可能有 y != 0。那些没有考虑到这一点的答案是不正确的,一般不会起作用案例。

extension CGContext 
    final func drawImage(image: CGImage, inRect rect: CGRect) 

        //flip coords
        let ty: CGFloat = (rect.origin.y + rect.size.height)
        translateBy(x: 0, y: ty)
        scaleBy(x: 1.0, y: -1.0)

        //draw image
        let rect__y_zero = CGRect(x: rect.origin.x, y: 0, width: rect.width, height: rect.height)
        draw(image, in: rect__y_zero)

        //flip back
        scaleBy(x: 1.0, y: -1.0)
        translateBy(x: 0, y: -ty)

    
 

这样使用:

let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = ..... 
context.drawImage(image: img, inRect: imageFrame)

【讨论】:

这几乎是一个完美的解决方案,但考虑将函数改为draw(image: UIImage, inRect rect:CGRect) 并在方法内部处理uiImage.cgimage【参考方案18】:

你也可以这样解决这个问题:

//Using an Image as a mask by directly inserting UIImageObject.CGImage causes
//the same inverted display problem. This is solved by saving it to a CGImageRef first.

//CGImageRef image = [UImageObject CGImage];

//CGContextDrawImage(context, boundsRect, image);

Nevermind... Stupid caching.

【讨论】:

以上是关于CGContextDrawImage 在传递 UIImage.CGImage 时将图像倒置的主要内容,如果未能解决你的问题,请参考以下文章

CGContextDrawImage 在 iPhone 4 上非常慢

巨大的内存峰值 - CGContextDrawImage

CGContextDrawImage 画大图很模糊

Swift 3 和 CGContextDrawImage

-[UIImage drawInRect:] / CGContextDrawImage() 不释放内存?

CGContextDrawImage 崩溃