Answers:
代替
CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);
用
[image drawInRect:CGRectMake(0, 0, 145, 15)];
在开始/结束CGcontext
方法的中间。
这会将具有正确方向的图像绘制到当前图像上下文中-我很确定这与UIImage
保持方向知识有关,而该CGContextDrawImage
方法在不了解方向的情况下获取了基础原始图像数据。
即使应用了我提到的所有内容,我仍然会在影像上留下戏剧性。最后,我只是使用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);
CGContextSaveGState()
以及CGContextRestoreGState()
保存和恢复转换矩阵?
两全其美,请使用UIImage drawAtPoint:
或drawInRect:
同时指定您的自定义上下文:
UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();
另外,您还避免使用第二个答案所做的CGContextTranslateCTM
或修改您的上下文CGContextScaleCTM
。
UIGraphicsBeginImageContextWithOptions
而不是仅仅启动一个new CGContext
,它似乎就可以工作了。
相关的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);
如果有人对在上下文中的自定义矩形中绘制图像的轻松解决方案感兴趣:
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)
}
图像将被缩放以填充矩形。
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)
}
Swift 3.0和4.0
yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)
无需更改
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];
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()
}
发生这种情况是因为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(纯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)
在我的项目过程中,我从肯德尔的答案跳到了克利夫的答案以解决从手机本身加载的图像的问题。
最后,我最终CGImageCreateWithPNGDataProvider
改为使用:
NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];
return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);
这并不方向问题的影响,你会从获得获得CGImage
从UIImage
它可以作为一个内容CALayer
顺利。
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()
}
}
斯威夫特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)
您还可以通过执行以下操作解决此问题:
//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.