IOS4 note 16 (4) CGImage
CGImage
?
The Core Graphics version of UIImage is CGImage (actually a CGImageRef). They are?easily converted to one another: a UIImage has a CGImage property that accesses its?Quartz image data, and you can make a UIImage from a CGImage using imageWithCGImage: or initWithCGImage:.
?
A CGImage lets you create a new image directly from a rectangular region of the image.(It also lets you apply an image mask, which you can’t do with UIImage.) I’ll demonstrate by ?splitting ?the ?image of Mars ?in half and drawing ?the ?two halves ?separately:
UIImage* mars = [UIImage imageNamed:@"Mars.png"];
// extract each half as a CGImage
CGSize sz = [mars size];
CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],
? ? ? ? ? ? ? ? ??? ? ? CGRectMake(0,0,sz.width/2.0,sz.height));
CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],? ??? ? ?? ? ? ? ? ? ? ? ??CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));
// draw each CGImage into an image context
UIGraphicsBeginImageContext(CGSizeMake(sz.width*1.5, sz.height));
?
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);
CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(marsLeft); CGImageRelease(marsRight);
?
?
As already mentioned, Core Graphics functions that operate in a graphics context require us to specify this context; our call to UIGraphicsBeginImageContext did not supply?us with a reference to the resulting context,but it did make the resulting context the?current context, which we can always obtain through UIGraphicsGetCurrentContext.?Observe also that we must ?follow the appropriate memory management rules ?for C?functions: wherever we generate something ?through a ?function with “Create” in its?name, we later call the corresponding “Release” function.
?
But there’s a problem with the previous example: the drawing is upside-down. It isn’t?rotated; it’s mirrored top to bottom, or, to use the technical term, flipped. This phenomenon can arise when you create a CGImage and then draw ?it with CGContextDrawImage and is due to a mismatch in the native coordinate systems of the source?and target contexts.
?
There are various ways of compensating for this mismatch between the coordinate?systems. One is to draw the CGImage into an intermediate UIImage and extract another CGImage from that.?
Utility for flipping an image drawing:
CGImageRef flip (CGImageRef im) {
? ? CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));
? ? UIGraphicsBeginImageContext(sz);
? ? CGContextDrawImage(UIGraphicsGetCurrentContext(),
? ??CGRectMake(0, 0, sz.width, sz.height),
? ??im);
? ? CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
? ? UIGraphicsEndImageContext();
? ? return result;
}
Armed with the utility function, we can now draw the halves of?Mars the right way up in the previous example:
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));
CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), flip(marsRight));
?
?
Why Flipping Happens
?
The ultimate source of accidental flipping is that Core Graphics comes from the Mac?OS X world, where the coordinate system’s origin is located by default at the bottom?left and ?the positive y-direction is upward, whereas on iOS the origin is located by?default at the top left and the positive y-direction is downward. In most drawing situations, no problem arises, because the coordinate system of the graphics context is?adjusted ?to compensate. Thus, the default coordinate system for drawing in a Core?Graphics context on iOS has the origin at the top left, just as you expect. But creating?and drawing a CGImage exposes the issue.
?
?
?
Another solution is to wrap the CGImage in a UIImage and draw using the UIImage?drawing methods discussed in the previous section. Those same two lines might then?be replaced with this:
[[UIImage imageWithCGImage:marsLeft] drawAtPoint:CGPointMake(0,0)];
[[UIImage imageWithCGImage:marsRight] drawAtPoint:CGPointMake(sz.width,0)];
?
?
?
?
Yet another solution is to apply a transform to the graphics context before drawing the?CGImage, effectively flipping the context’s internal coordinate system. This is elegant,?but can be confusing if there are other transforms in play.?
?
A further problem is that our code draws incorrectly on a high-resolution device if there?is a high-resolution version of our image file. The reason is that a UIImage has a?scale property, but a CGImage doesn’t. When you call a UIImage’s CGImage method,?therefore, you can’t assume that the resulting CGImage is the same size as the original?UIImage; a UIImage’s size property is the same for a single-resolution image and its?double-resolution counterpart, but the CGImage of a double-resolution image is twice?as large in both dimensions as the CGImage of the corresponding single-resolution?image.
?
So, in extracting a desired piece of the CGImage, we must either multiply all appropriate?values by the scale or express ourselves in terms of the CGImage’s dimensions. In this?case, as we are extracting the left and right halves of the image, the latter is obviously?the simpler course. So here’s a version of our original code that draws correctly on either?a single-resolution or a double-resolution
device:
?
UIImage* mars = [UIImage imageNamed:@"Mars.png"];
CGSize sz = [mars size];
// Derive CGImage and use its dimensions to extract its halves
CGImageRef marsCG = [mars CGImage];
CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));
CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,
? ? ? ? ? ? ? ? ? ? ? ?CGRectMake(0,0,szCG.width/2.0,szCG.height));
CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG,
? ? ? ? ? ? ? ? ? ? ? ? CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));
// Use double-resolution graphics context if possible
?
UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0.0);
// The rest is as before, calling flip() to compensate for flipping
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));
CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), flip(marsRight));
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(marsLeft); CGImageRelease(marsRight);
?
Our flip compensation utility works here, but our other solution does?not. If you’re doing to derive a UIImage from a CGImage where scale matters, you have?to provide the scale by calling imageWithCGImage:scale:orientation (only on iOS 4.0?or later) instead of imageWithCGImage:. So our second solution now looks like this:
[[UIImage imageWithCGImage:marsLeft
? ? ? ? ? ? ? ? ?? ?? ? ?scale:[mars scale]
? ? ? ? ? ? ? ?? ? ? ?orientation:UIImageOrientationUp]
drawAtPoint:CGPointMake(0,0)];
[[UIImage imageWithCGImage:marsRight
? ? ? ? ? ?? ? ? ? ? ? ?scale:[mars scale]
? ? ? ? ? ? ? ?? ? ? ?orientation:UIImageOrientationUp]
? ?? ? ?drawAtPoint:CGPointMake(sz.width,0)];
?
?