传递 UIImage.CGImage 时,CGContextDrawImage 将图像倒置绘制

有人知道为什么 CGContextDrawImage会把我的图像倒过来吗?我正在从应用程序中加载一个图像:

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

然后简单地要求核心图形把它画到我的上下文中:

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

它呈现在正确的位置和尺寸,但图像是颠倒的。我一定是漏掉了什么很明显的东西?

124144 次浏览

我不确定是否适用于 UIImage,但是这种行为通常发生在坐标颠倒的时候。大多数 OS X 坐标系统的起源都在左下角,如 Postscript 和 PDF。但是 ABc1坐标系起源于左上角。

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

即使在应用了我提到的所有东西之后,我仍然对这些图像感兴趣。最后,我用 Gimp 创建了所有图片的“垂直翻转”版本。现在我不需要使用任何变形金刚。希望这不会在赛道上造成进一步的问题。

有人知道为什么吗 CGContextDrawImage 将绘制我的 我正在加载一个 从我的申请表中看到:

Quartz2d 使用了一个不同的坐标系统,其中原点位于左下角。因此,当 Quartz 绘制100 * 100图像的像素 x [5] ,y [10]时,该像素被绘制在左下角,而不是左上角。从而导致“翻转”的图像。

X 坐标系是匹配的,所以你需要翻转 y 坐标。

CGContextTranslateCTM(context, 0, image.size.height);

这意味着我们已经在 x 轴上将图像平移了0个单位,在 y 轴上将图像高度平移了0个单位。然而,仅此一项就意味着我们的图像仍然是上下颠倒的,只是在我们希望绘制的地方下面绘制了“ 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);

如果你的 image Rect 坐标与你的图像坐标不匹配,要小心,因为你可能会得到意想不到的结果。

将坐标转换回来:

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

而不是

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

使用

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

在 start/end CGcontext方法的中间。

这将把正确方向的图像绘制到你当前的图像上下文中——我很确定这与 UIImage保持方向知识有关,而 CGContextDrawImage方法获得底层原始图像数据却没有方向知识。

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

UIImage包含一个 CGImage作为其主要内容成员,以及缩放和定向因子。由于 CGImage及其各种功能都是从 OSX 衍生出来的,因此它预计,与 iPhone 相比,它的坐标系将是上下颠倒的。当您创建一个 UIImage时,它默认为一个倒置的方向来补偿(您可以改变这一点!).使用。CGImage属性访问非常强大的 CGImage函数,但绘制到 iPhone 屏幕等最好使用 UIImage方法。

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

UIGraphicsBeginImageContext(image.size);


UIGraphicsPushContext(context);


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


UIGraphicsPopContext();


UIGraphicsEndImageContext();

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

//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.

最好的方法是,使用 UIImage 的 drawAtPoint:drawInRect:,同时仍然指定自定义上下文:

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

另外,您可以避免使用 CGContextTranslateCTMCGContextScaleCTM修改上下文,第二个答案就是这样做的。

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

最后,我改用了 CGImageCreateWithPNGDataProvider:

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


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

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

用 Swift 代码补充答案

Quartz 2 d 图形使用的坐标系在左下角,而 iOS 中的 UIKit 使用的坐标系在左上角。通常一切都很正常,但是在进行一些图形操作时,你必须自己修改坐标系。文件指出:

一些技术使用不同的 比 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)
}

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

 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)


}

图像将被缩放以填充右边。

相关 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 以正确显示图像和页面。

若要翻转绘制到核心图形上下文的对象,以便在 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);

Swift 3.0 & 4.0

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

不需要改动

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

基于@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)

之所以会出现这种情况,是因为 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)

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