CGContextDrawImage在传递UIImage.CGImage时将图像上下颠倒


179

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

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

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

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

它在正确的位置和尺寸进行渲染,但是图像是颠倒的。我一定会在这里遗漏一些明显的东西吗?

Answers:


238

代替

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

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

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

这会将具有正确方向的图像绘制到当前图像上下文中-我很确定这与UIImage保持方向知识有关,而该CGContextDrawImage方法在不了解方向的情况下获取了基础原始图像数据。


19
此解决方案不允许您在图像上使用CG函数,例如CGContextSetAlpha(),而第二个答案可以。
samvermette,2010年

2
好一点,尽管大多数情况下您不需要自定义Alpha级别来绘制图像(通常,那些需要Alpha的对象会提前烘焙到图像中)。基本上,我的座右铭是,尽量使用一些代码,因为更多的代码意味着更多的错误机会。
肯德尔·赫尔姆斯特·盖尔纳

嗨,您是什么意思,在您的begin / end CGContext方法中间,您能给我更多示例代码吗?我正在尝试将上下文存储在某处,并使用上下文在其中绘制图像
vodkhang 2010年

2
虽然可能对Rusty有用,但-[UIImage drawInRect:]存在线程安全的问题。对于我的特定应用程序,这就是为什么我首先使用CGContextDrawImage()的原因。
奥利2010年

2
需要说明的是:Core Graphics构建在OS X上,其坐标系与iOS相比是上下颠倒的(从OS X的左下角开始)。这就是Core Graphics颠倒绘制图像而drawInRect为您处理图像的原因
borchero

213

即使应用了我提到的所有内容,我仍然会在影像上留下戏剧性。最后,我只是使用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东西
PeanutPower 2010年

3
如果您使用的是drawLayer,并且想将CGImageRef绘制到上下文中,则此技术只是希望医生订购!如果具有图像的rect,则CGContextTranslateCTM(context,0,image.size.height + image.origin.y),然后在CGContextDrawImage()之前将rect.origin.y设置为0。谢谢!
David H

我曾经遇到过ImageMagick问题,iPhone相机导致方向错误。事实证明,这是图像文件中的方向标记,因此肖像图像的大小为960px x 640px,标记设置为“ left”(左侧)-因为左侧是顶部(或接近顶部)。该线程可能会有所帮助:stackoverflow.com/questions/1260249/...
哈日卡勒姆·辛格

8
您为什么不使用CGContextSaveGState()以及CGContextRestoreGState()保存和恢复转换矩阵?
2014年

20

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

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

另外,您还避免使用第二个答案所做的CGContextTranslateCTM或修改您的上下文CGContextScaleCTM


这是一个好主意,但对我来说,它产生的图像既上下颠倒又从左到右。我猜结果会因图像而异。
路加·罗杰斯

也许这不再与更高的iOS版本一起使用?当时,它对我来说都是可行的。
里维拉

我认为这实际上取决于我如何创建上下文。一旦我开始使用UIGraphicsBeginImageContextWithOptions而不是仅仅启动一个new CGContext,它似乎就可以工作了。
路加·罗杰斯

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仿射变换。


3

UIImage包含一个CGImage作为其主要内容成员以及缩放比例和方向因子。由于CGImage其各种功能均来自OSX,因此与iPhone相比,它期望的坐标系是颠倒的。创建时UIImage,它默认采用上下颠倒的方向进行补偿(可以更改!)。使用 。CGImage属性可以访问非常强大的CGImage功能,但是最好使用这些UIImage方法来绘制到iPhone屏幕等上。


1
如何更改UIImage的默认上下颠倒方向?
MegaManX

3

Swift代码的补充答案

Quartz 2D图形使用原点位于左下角的坐标系,而iOS中的UIKit使用原点位于左上角的坐标系。一切正常,但进行一些图形操作时,您必须自己修改坐标系。该文档指出:

某些技术使用与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)
}


2

我们可以使用相同的函数解决此问题:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

1

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

1

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

1

发生这种情况是因为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

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

0

在我的项目过程中,我从肯德尔的答案跳到了克利夫的答案以解决从手机本身加载的图像的问题。

最后,我最终CGImageCreateWithPNGDataProvider改为使用:

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

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

这并不方向问题的影响,你会从获得获得CGImageUIImage它可以作为一个内容CALayer顺利。


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

0

斯威夫特5答案基于@ZpaceZombor的出色答案的答案

如果您有UIImage,请使用

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

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

注意:与其他答案不同,该答案考虑到您要绘制的rect可能具有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
火星,

-5

您还可以通过执行以下操作解决此问题:

//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.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.