问题
Does anyone know why CGContextDrawImage
would be drawing my image upside down? I am loading an image in from my application:
UIImage *image = [UIImage imageNamed:@\"testImage.png\"];
And then simply asking core graphics to draw it to my context:
CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);
It renders in the right place, and dimensions, but the image is upside down. I must be missing something really obvious here?
回答1:
Instead of
CGContextDrawImage(context, CGRectMake(0, 0, 145, 15), image.CGImage);
Use
[image drawInRect:CGRectMake(0, 0, 145, 15)];
In the middle of your begin/end CGcontext
methods.
This will draw the image with the correct orientation into your current image context - I'm pretty sure this has something to do with the UIImage
holding onto knowledge of the orientation while the CGContextDrawImage
method gets the underlying raw image data with no understanding of orientation.
回答2:
Even after applying everything I have mentioned, I've still had dramas with the images. In the end, i've just used Gimp to create a 'flipped vertical' version of all my images. Now I don't need to use any Transforms. Hopefully this won't cause further problems down the track.
Does anyone know why CGContextDrawImage would be drawing my image upside down? I am loading an image in from my application:
Quartz2d uses a different co-ordinate system, where the origin is in the lower left corner. So when Quartz draws pixel x[5], y[10] of a 100 * 100 image, that pixel is being drawn in the lower left corner instead of the upper left. Thus causing the 'flipped' image.
The x co-ordinate system matches, so you will need to flip the y co-ordinates.
CGContextTranslateCTM(context, 0, image.size.height);
This means we have translated the image by 0 units on the x axis and by the images height on the y axis. However, this alone will mean our image is still upside down, just being drawn "image.size.height" below where we wish it to be drawn.
The Quartz2D programming guide recommends using ScaleCTM and passing negative values to flip the image. You can use the following code to do this -
CGContextScaleCTM(context, 1.0, -1.0);
Combine the two just before your CGContextDrawImage
call and you should have the image drawn correctly.
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);
Just be careful if your imageRect co-ordinates do not match those of your image, as you can get unintended results.
To convert back the coordinates:
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageRect.size.height);
回答3:
Best of both worlds, use UIImage's drawAtPoint:
or drawInRect:
while still specifying your custom context:
UIGraphicsPushContext(context);
[image drawAtPoint:CGPointZero]; // UIImage will handle all especial cases!
UIGraphicsPopContext();
Also you avoid modifying your context with CGContextTranslateCTM
or CGContextScaleCTM
which the second answer does.
回答4:
Relevant Quartz2D docs: https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html#//apple_ref/doc/uid/TP40010156-CH14-SW4
Flipping the Default Coordinate System
Flipping in UIKit drawing modifies the backing CALayer to align a drawing environment having a LLO coordinate system with the default coordinate system of UIKit. If you only use UIKit methods and function for drawing, you shouldn’t need to flip the CTM. However, if you mix Core Graphics or Image I/O function calls with UIKit calls, flipping the CTM might be necessary.
Specifically, if you draw an image or PDF document by calling Core Graphics functions directly, the object is rendered upside-down in the view’s context. You must flip the CTM to display the image and pages correctly.
To flip a object drawn to a Core Graphics context so that it appears correctly when displayed in a UIKit view, you must modify the CTM in two steps. You translate the origin to the upper-left corner of the drawing area, and then you apply a scale translation, modifying the y-coordinate by -1. The code for doing this looks similar to the following:
CGContextSaveGState(graphicsContext);
CGContextTranslateCTM(graphicsContext, 0.0, imageHeight);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
CGContextDrawImage(graphicsContext, image, CGRectMake(0, 0, imageWidth, imageHeight));
CGContextRestoreGState(graphicsContext);
回答5:
If anyone is interested in a no-brainer solution for drawing an image in a custom rect in a 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)
}
The image will be scaled to fill the rect.
回答6:
I'm not sure for UIImage
, but this kind of behaviour usually occurs when coordinates are flipped. Most of OS X coordinate systems have their origin at the lower left corner, as in Postscript and PDF. But CGImage
coordinate system has its origin at the upper left corner.
Possible solutions may involve an isFlipped
property or a scaleYBy:-1
affine transform.
回答7:
UIImage
contains a CGImage
as its main content member as well as scaling and orientation factors. Since CGImage
and its various functions are derived from OSX, it expects a coordinate system that is upside down compared to the iPhone. When you create a UIImage
, it defaults to an upside-down orientation to compensate (you can change this!). Use the .CGImage
property to access the very powerful CGImage
functions, but drawing onto the iPhone screen etc. is best done with the UIImage
methods.
回答8:
Supplemental answer with Swift code
Quartz 2D graphics use a coordinate system with the origin in the bottom left while UIKit in iOS uses a coordinate system with the origin at the top left. Everything usually works fine but when doing some graphics operations, you have to modify the coordinate system yourself. The documentation states:
Some technologies set up their graphics contexts using a different default coordinate system than the one used by Quartz. Relative to Quartz, such a coordinate system is a modified coordinate system and must be compensated for when performing some Quartz drawing operations. The most common modified coordinate system places the origin in the upper-left corner of the context and changes the y-axis to point towards the bottom of the page.
This phenomenon can be seen in the following two instances of custom views that draw an image in their drawRect
methods.
On the left side, the image is upside-down and on the right side the coordinate system has been translated and scaled so that the origin is in the top left.
Upside-down image
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)
}
Modified coordinate system
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)
}
回答9:
Swift 3.0 & 4.0
yourImage.draw(in: CGRect, blendMode: CGBlendMode, alpha: ImageOpacity)
No Alteration needed
回答10:
We can solve this problem using the same function:
UIGraphicsBeginImageContext(image.size);
UIGraphicsPushContext(context);
[image drawInRect:CGRectMake(gestureEndPoint.x,gestureEndPoint.y,350,92)];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
回答11:
drawInRect
is certainly the way to go. Here's another little thing that will come in way useful when doing this. Usually the picture and the rectangle into which it is going to go don't conform. In that case drawInRect
will stretch the picture. Here's a quick and cool way to make sure that the picture's aspect ration isn't changed, by reversing the transformation (which will be to fit the whole thing in):
//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];
回答12:
Swift 3 CoreGraphics Solution
If you want to use CG for whatever your reason might be, instead of UIImage, this Swift 3 construction based on previous answers did solve the issue for me:
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()
}
回答13:
During the course of my project I jumped from Kendall's answer to Cliff's answer to solve this problem for images that are loaded from the phone itself.
In the end I ended up using CGImageCreateWithPNGDataProvider instead:
NSString* imageFileName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"clockdial.png"];
return CGImageCreateWithPNGDataProvider(CGDataProviderCreateWithFilename([imageFileName UTF8String]), NULL, YES, kCGRenderingIntentDefault);
This doesn't suffer from the orientation issues that you would get from getting the CGImage
from a UIImage
and it can be used as the contents of a CALayer
without a hitch.
回答14:
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()
}
}
回答15:
Swift 5 answer based on @ZpaceZombor's excellent answer
If you have a UIImage, just use
var image: UIImage = ....
image.draw(in: CGRect)
If you have a CGImage use my category below
Note: Unlike some other answers, this one takes into account that the rect you want to draw in might have y != 0. Those answers that don't take that into account are incorrect and won't work in the general case.
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)
}
}
Use like this:
let imageFrame: CGRect = ...
let context: CGContext = ....
let img: CGImage = .....
context.drawImage(image: img, inRect: imageFrame)
回答16:
It happens because QuartzCore has the coordinate system "bottom-left", while UIKit – "top-left".
In the case you can extend 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)
回答17:
I use this Swift 5, pure Core Graphics extension that correctly handles non-zero origins in image rects:
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()
}
}
You can use it exactly like CGContext
's regular draw(: in:)
method:
ctx.drawFlipped(myImage, in: myRect)
回答18:
You can also solve this problem by doing this:
//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.
来源:https://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage