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

时间:2009-02-03 10:23:11

标签: ios cocoa-touch core-graphics cgcontextdrawimage

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

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

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

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

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

18 个答案:

答案 0 :(得分:232)

而不是

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

使用

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

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

这会将具有正确方向的图像绘制到当前图像上下文中 - 我很确定这与UIImageCGContextDrawImage方法得到时保持方向知识有关基础原始图像数据,不了解方向。

请注意,您将在许多方面遇到此问题,但一个具体示例是处理地址簿用户图像。

答案 1 :(得分: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);

答案 2 :(得分:20)

两个世界中最好的,使用UIImage的drawAtPoint:drawInRect:,同时仍指定自定义上下文:

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

另外,您无法使用CGContextTranslateCTMCGContextScaleCTM来修改您的上下文,而第二个答案就是这样。

答案 3 :(得分:10)

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

答案 4 :(得分:7)

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

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

答案 5 :(得分: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 :(得分:3)

Swift代码的补充答案

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

  

某些技术使用不同的技术设置其图形上下文   默认坐标系比Quartz使用的坐标系。关系到   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)
}

答案 7 :(得分:3)

Swift 3.0& 4.0

./configure

不需要改变

答案 8 :(得分:3)

我们可以使用相同的功能解决这个问题:

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();

答案 9 :(得分:3)

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

答案 10 :(得分: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];

答案 11 :(得分: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()
}

答案 12 :(得分:0)

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

最后,我最终使用了CGImageCreateWithPNGDataProvider

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

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

这不会受到从CGImage获取UIImage所带来的方向问题的影响,并且可以用作CALayer的内容而不是{{1}}顺利。

答案 13 :(得分: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()
    }
}

答案 14 :(得分:0)

基于@ZpaceZombor的出色答案迅速获得5个答案

如果您有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)

答案 15 :(得分:0)

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

答案 16 :(得分:0)

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

答案 17 :(得分:-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.