185

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

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

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

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

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

4

18 回答 18

242

代替

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

采用

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

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

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

于 2009-02-06T20:46:51.277 回答
217

即使在应用了我提到的所有内容之后,我仍然有带有图像的戏剧。最后,我刚刚使用 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);
于 2009-02-04T12:49:36.610 回答
21

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

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

CGContextTranslateCTM您还避免使用第二个CGContextScaleCTM答案修改您的上下文。

于 2013-08-07T05:47:55.447 回答
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);
于 2016-04-08T06:11:33.833 回答
9

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

 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)

}

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

于 2016-02-18T14:20:40.980 回答
7

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

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

于 2009-02-03T10:34:26.477 回答
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)
于 2019-09-30T08:28:44.700 回答
4

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

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)
于 2019-10-20T06:00:26.430 回答
3

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

于 2010-07-31T23:26:32.087 回答
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)
}
于 2015-12-22T03:25:46.050 回答
2

斯威夫特 3.0 和 4.0

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

无需改动

于 2017-08-04T10:41:40.347 回答
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];
于 2010-02-22T05:57:20.200 回答
1

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

UIGraphicsBeginImageContext(image.size);

UIGraphicsPushContext(context);

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

UIGraphicsPopContext();

UIGraphicsEndImageContext();
于 2011-08-25T06:27:55.470 回答
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()
}
于 2017-08-31T20:05:04.873 回答
0

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

最后我最终CGImageCreateWithPNGDataProvider改用:

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

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

这不会受到从CGImagea获得的方向问题的影响UIImage,它可以用作 a 的内容而CALayer没有任何障碍。

于 2014-03-15T16:13:31.560 回答
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()
    }
}
于 2018-05-03T12:06:37.670 回答
0

基于@ZpaceZombor 的优秀答案的Swift 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)
于 2019-09-20T07:24:00.230 回答
-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.
于 2012-04-04T01:55:04.057 回答