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

 

 

你可能感兴趣的:(IOS4 note 16 (4) CGImage)