Context Coordinate System
When you work primarily in UIKit, the coordinate system starts at the top-left corner of the screen (0, 0) and extends to the right and down. In Quartz, the coordinate system starts at the bottom left of the screen.
Look at Figure 1-11. It depicts a square {20, 20, 40, 40} drawn in UIKit (left) and Quartz (right). In each case, the object begins 20 points off the origin. However, as you can see, that origin differs in each circumstance.
Figure 1-11 The UIKit origin (top left) is different from the Quartz origin (bottom left).
Which origin holds sway depends on how you created the context. If you built it using any of the UIKit functions, the origin is at the top left. If you used CGBitmapContextCreate(), the origin is at the bottom left.
Flipping Contexts
You can adjust Core Graphics contexts to use the UIKit origin. Listing 1-10 shows the code, which implements these steps:
- Push the CGContextRef onto the UIKit graphics stack.
- Flip the context vertically. You do this by building a transform that scales and translates the original context.
- Concatenate that transform to the context’s current transformation matrix (CTM). This adjusts the context’s coordinate system to mimic UIKit, letting you draw starting from the top-left corner.
- Perform any drawing operations using the new coordinate system.
- Pop the graphics stack.
You can work directly in Quartz without applying this coordinate workaround. Most UIKit developers, however, appreciate the ability to use a single system that matches drawing to familiar view placement tasks. Some developers create dual macros, defined to the same flipping function. This enables them to visually match a QUARTZ_ON request to a QUARTZ_OFF one. The routine doesn’t change, but a developer gains a sense of the current state for code inspection.
Listing 1-10 contains a secondary flipping routine, one that does not require you to supply a context size in points. Quite honestly, it’s a bit of a hack, but it does work because the image it retrieves will use the same size and scale as the context.
You can also retrieve a context’s size by calling CGBitmapContextGetHeight() and CGBitmapContextGetWidth() and dividing the number of pixels these functions return by the screen scale. This assumes that you’re working with a bitmap context of some kind (like the one created by UIGraphicsBeginImageContextWithOptions()) and that you’re matching the screen’s scale in that context.
Listing 1-10 Adjusting Coordinate Origins
// Flip context by supplying the size void FlipContextVertically(CGSize size) { CGContextRef context = UIGraphicsGetCurrentContext(); if (context == NULL) { NSLog(@"Error: No context to flip"); return; } CGAffineTransform transform = CGAffineTransformIdentity; transform = CGAffineTransformScale(transform, 1.0f, -1.0f); transform = CGAffineTransformTranslate(transform, 0.0f, -size.height); CGContextConcatCTM(context, transform); } // Flip context by retrieving image void FlipImageContextVertically() { CGContextRef context = UIGraphicsGetCurrentContext(); if (context == NULL) { NSLog(@"Error: No context to flip"); return; } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); FlipContextVertically(image.size); } // Query context for size and use screen scale // to map from Quartz pixels to UIKit points CGSize GetUIKitContextSize() { CGContextRef context = UIGraphicsGetCurrentContext(); if (context == NULL) return CGSizeZero; CGSize size = CGSizeMake( CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context)); CGFloat scale = [UIScreen mainScreen].scale; return CGSizeMake( size.width / scale, size.height / scale); } // Initialize the UIKit context stack UIGraphicsPushContext(context); // Flip the context vertically FlipContextVertically(size); // Draw the test rectangle. It will now use the UIKit origin // instead of the Quartz origin. CGRect testRect = CGRectMake(20, 20, 40, 40); UIBezierPath *path = [UIBezierPath bezierPathWithRect:testRect]; [greenColor set]; [path fill]; // Pop the context stack UIGraphicsPopContext();