CommonCode/AppDrawing.c
/* |
File: AppDrawing.c |
Abstract: Sample Quartz drawing code. |
Version: <1.0> |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Computer, Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Computer, |
Inc. may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2006 Apple Computer, Inc., All Rights Reserved |
*/ |
#include "AppDrawing.h" |
#include "UIHandling.h" |
#include "math.h" |
/* Defines */ |
#define kOurImageFile CFSTR("ptlobos.tif") |
// For best performance make bytesPerRow a multiple of 16 bytes. |
#define BEST_BYTE_ALIGNMENT 16 |
#define COMPUTE_BEST_BYTES_PER_ROW(bpr) ( ( (bpr) + (BEST_BYTE_ALIGNMENT-1) ) & ~(BEST_BYTE_ALIGNMENT-1) ) |
/* Prototypes */ |
static void drawStrokedAndFilledRects(CGContextRef context); |
static void drawAlphaRects(CGContextRef context); |
static void drawCGImage(CGContextRef context, CFURLRef url); |
static void clipImageToEllipse(CGContextRef context, CFURLRef url); |
static void drawSimpleCGLayer(CGContextRef context); |
static void drawUncachedForLayer(CGContextRef context); |
static inline float DEGREES_TO_RADIANS(float degrees){ |
return degrees * M_PI/180; |
} |
static CGColorSpaceRef myGetGenericRGBSpace(void) |
{ |
// Only create the color space once. |
static CGColorSpaceRef colorSpace = NULL; |
if ( colorSpace == NULL ) { |
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); |
} |
return colorSpace; |
} |
static CGColorRef myGetBlueColor(void) |
{ |
// Only create the CGColor object once. |
static CGColorRef blue = NULL; |
if(blue == NULL){ |
// R,G,B,A |
float opaqueBlue[4] = { 0, 0, 1, 1 }; |
blue = CGColorCreate(myGetGenericRGBSpace(), opaqueBlue); |
} |
return blue; |
} |
static CGColorRef myGetGreenColor(void) |
{ |
// Only create the CGColor object once. |
static CGColorRef green = NULL; |
if(green == NULL){ |
// R,G,B,A |
float opaqueGreen[4] = { 0, 1, 0, 1 }; |
green = CGColorCreate(myGetGenericRGBSpace(), opaqueGreen); |
} |
return green; |
} |
static CGColorRef myGetRedColor(void) |
{ |
// Only create the CGColor object once. |
static CGColorRef red = NULL; |
if(red == NULL){ |
// R,G,B,A |
float opaqueRed[4] = { 1, 0, 0, 1 }; |
red = CGColorCreate(myGetGenericRGBSpace(), opaqueRed); |
} |
return red; |
} |
static void doDrawImageFile(CGContextRef context, Boolean doclip) |
{ |
// Only get the image URL the first time the function is called. |
static CFURLRef ourImageURL = NULL; |
if(ourImageURL == NULL){ |
CFBundleRef mainBundle = CFBundleGetMainBundle(); |
if(mainBundle){ |
ourImageURL = CFBundleCopyResourceURL(mainBundle, kOurImageFile, NULL, NULL); |
}else{ |
fprintf(stderr, "Can't get the app bundle!\n"); |
} |
} |
if(ourImageURL){ |
doclip ? clipImageToEllipse(context, ourImageURL) : drawCGImage(context, ourImageURL); |
}else{ |
fprintf(stderr, "Couldn't create the URL for our Image file!\n"); |
} |
} |
/* Dispatch Drawing */ |
void myDispatchDrawing(CGContextRef context, OSType drawingType) |
{ |
switch (drawingType){ |
case kCommandStrokedAndFilledRects: |
drawStrokedAndFilledRects(context); |
break; |
case kCommandAlphaRects: |
drawAlphaRects(context); |
break; |
case kCommandSimpleClip: |
doDrawImageFile(context, true); |
break; |
case kCommandDrawImageFile: |
doDrawImageFile(context, false); |
break; |
case kCommandDoUncachedDrawing: |
drawUncachedForLayer(context); |
break; |
case kCommandDoCGLayer: |
drawSimpleCGLayer(context); |
break; |
default: |
break; |
} |
} |
void drawStrokedAndFilledRects(CGContextRef context) |
{ |
// Make a CGRect that has its origin at (40,40) |
// with a width of 130 units and height of 100 units. |
CGRect ourRect = CGRectMake(40, 40, 130, 100); |
// Set the fill color to an opaque blue. |
CGContextSetFillColorWithColor(context, myGetBlueColor()); |
// Fill the rect. |
CGContextFillRect(context, ourRect); |
// Set the stroke color to an opaque green. |
CGContextSetStrokeColorWithColor(context, myGetGreenColor()); |
// Stroke the rect with a line width of 10 units. |
CGContextStrokeRectWithWidth(context, ourRect, 10); |
// Save the current graphics state. |
CGContextSaveGState(context); |
// Translate the coordinate system origin to the right |
// by 200 units. |
CGContextTranslateCTM(context, 200, 0); |
// Stroke the rect with a line width of 10 units. |
CGContextStrokeRectWithWidth(context, ourRect, 10); |
// Fill the rect. |
CGContextFillRect(context, ourRect); |
// Restore the graphics state to the previously saved |
// graphics state. This restores all graphics state |
// parameters to those in effect during the last call |
// to CGContextSaveGState. In this example that restores |
// the coordinate system to that in effect prior to the |
// call to CGContextTranslateCTM. |
CGContextRestoreGState(context); |
} |
/* |
Create a mutable path object that represents 'rect'. |
Note that this is for demonstrating how to create a simple |
CGPath object. The Quartz function CGPathAddRect would normally |
be a better choice for adding a rect to a CGPath object. |
*/ |
static CGPathRef createRectPath(CGRect rect) |
{ |
CGMutablePathRef path = CGPathCreateMutable(); |
// Start a new subpath. |
CGPathMoveToPoint(path, NULL, rect.origin.x, rect.origin.y); |
// ***** Segment 1 ***** |
CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, rect.origin.y); |
// ***** Segment 2 ***** |
CGPathAddLineToPoint(path, NULL, rect.origin.x + rect.size.width, |
rect.origin.y + rect.size.height); |
// ***** Segment 3 ***** |
CGPathAddLineToPoint(path, NULL, rect.origin.x, rect.origin.y + rect.size.height); |
// ***** Segment 4 is created by closing the path ***** |
CGPathCloseSubpath(path); |
return path; |
} |
static void drawAlphaRects(CGContextRef context) |
{ |
CGRect ourRect = CGRectMake(0, 0, 130, 100); |
int numRects = 6; |
float rotateAngle = 2*M_PI/numRects; |
float tint, tintAdjust = 1.0/numRects; |
/* Create the path object representing our rectangle. This |
example is for demonstrating the use of a CGPath object. |
For a simple rectangular shape, you'd typically use |
CGContextFillRect or CGContextStrokeRect instead of this |
approach. |
*/ |
CGPathRef path = createRectPath(ourRect); |
// Move the origin of coordinates to a location that allows |
// the drawing to be within the window. |
CGContextTranslateCTM(context, 2*ourRect.size.width, |
2*ourRect.size.height); |
// Set the fill color to a red color. |
CGContextSetFillColorWithColor(context, myGetRedColor()); |
for(tint = 1.0; 0 < tint ; tint -= tintAdjust){ |
// Set the global alpha to the tint value. |
CGContextSetAlpha(context, tint); |
// For a CGPath object that is a simple rect, |
// this is equivalent to CGContextFillRect. |
CGContextBeginPath(context); |
CGContextAddPath(context, path); |
CGContextFillPath(context); |
// These transformations are cummulative. |
CGContextRotateCTM(context, rotateAngle); |
} |
// Release the CGPath object when finished with it. |
CGPathRelease(path); |
} |
static void drawCGImage(CGContextRef context, CFURLRef url) |
{ |
// Create a CGImageSource object from 'url'. |
CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL); |
// Create a CGImage object from the first image in the file. Image |
// indexes are 0 based. |
CGImageRef image = CGImageSourceCreateImageAtIndex( imageSource, 0, NULL ); |
// Now that we've created the CGImage object, the imageSource is no longer |
// needed so release it. CGImageSource objects are CF objects so they are |
// released with CFRelease. |
CFRelease(imageSource); |
// Create a rectangle that has its origin at (100, 100) with the width |
// and height of the image itself. |
CGRect imageRect = CGRectMake(100, 100, CGImageGetWidth(image), CGImageGetHeight(image)); |
// Draw the image into the rect. |
CGContextDrawImage(context, imageRect, image); |
// Release the image object we created. In Mac OS X v10.2 and later, CG objects |
// are CF objects so you can use CFRelease to release them. Image objects can also |
// be released with CGImageRelease. |
CFRelease(image); |
} |
static void clipImageToEllipse(CGContextRef context, CFURLRef url) |
{ |
// Create a CGImageSource object from 'url'. |
CGImageSourceRef imageSource = CGImageSourceCreateWithURL(url, NULL); |
// Create a CGImage object from the first image in the file. Image |
// indexes are 0 based. |
CGImageRef image = CGImageSourceCreateImageAtIndex( imageSource, 0, NULL ); |
// Now that we've created the CGImage object, the imageSource is no longer |
// needed so release it. CGImageSource objects are CF objects so they are |
// released with CFRelease. |
CFRelease(imageSource); |
// Create a rectangle that has its origin at (100, 100) with the width |
// and height of the image itself. |
CGRect imageRect = CGRectMake(100, 100, CGImageGetWidth(image), CGImageGetHeight(image)); |
CGContextBeginPath(context); |
// Create an elliptical path corresponding to the image width and height. |
CGContextAddEllipseInRect(context, imageRect); |
// Clip to the current path. |
CGContextClip(context); |
// Draw the image into the rect, clipped by the ellipse. |
CGContextDrawImage(context, imageRect, image); |
// Release the image object we created. In Mac OS X v10.2 and later, CG objects |
// are CF objects so you can use CFRelease to release them. Image objects can also |
// be released with CGImageRelease. |
CFRelease(image); |
} |
static CGImageRef createRGBAImageFromQuartzDrawing(float dpi, OSType drawingCommand) |
{ |
// For generating RGBA data from drawing. Use a Letter size page as the |
// image dimensions. Typically this size would be the minimum necessary to |
// capture the drawing of interest. We want 8 bits per component and for |
// RGBA data there are 4 components. |
size_t width = 8.5*dpi, height = 11*dpi, bitsPerComponent = 8, numComps = 4; |
// Compute the minimum number of bytes in a given scanline. |
size_t bytesPerRow = width* bitsPerComponent/8 * numComps; |
// This bitmapInfo value specifies that we want the format where alpha is |
// premultiplied and is the last of the components. We use this to produce |
// RGBA data. |
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast; |
// Round to nearest multiple of BEST_BYTE_ALIGNMENT for optimal performance. |
bytesPerRow = COMPUTE_BEST_BYTES_PER_ROW(bytesPerRow); |
// Allocate the data for the bitmap. |
char *data = malloc( bytesPerRow * height ); |
// Create the bitmap context. Characterize the bitmap data with the |
// Generic RGB color space. |
CGContextRef bitmapContext = CGBitmapContextCreate( |
data, width, height, bitsPerComponent, bytesPerRow, |
myGetGenericRGBSpace(), bitmapInfo); |
// Clear the destination bitmap so that it is completely transparent before |
// performing any drawing. This is appropriate for exporting PNG data or |
// other data formats that capture alpha data. If the destination output |
// format doesn't support alpha then a better choice would be to paint |
// to white. |
CGContextClearRect( bitmapContext, CGRectMake(0, 0, width, height) ); |
// Scale the coordinate system so that 72 units are dpi pixels. |
CGContextScaleCTM( bitmapContext, dpi/72, dpi/72 ); |
// Perform the requested drawing. |
myDispatchDrawing(bitmapContext, drawingCommand); |
// Create a CGImage object from the drawing performed to the bitmapContext. |
CGImageRef image = CGBitmapContextCreateImage(bitmapContext); |
// Release the bitmap context object and free the associated raster memory. |
CGContextRelease(bitmapContext); |
free(data); |
// Return the CGImage object this code created from the drawing. |
return image; |
} |
void myExportCGDrawingAsPNG(CFURLRef url, OSType drawingCommand) |
{ |
float dpi = 300; |
// Create an RGBA image from the Quartz drawing that corresponds to drawingCommand. |
CGImageRef image = createRGBAImageFromQuartzDrawing(dpi, drawingCommand); |
CFTypeRef keys[2], values[2]; |
CFDictionaryRef properties = NULL; |
// Create a CGImageDestination object will write PNG data to URL. |
// We specify that this object will hold 1 image. |
CGImageDestinationRef imageDestination = |
CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, NULL); |
// Set the keys to be the x and y resolution properties of the image. |
keys[0] = kCGImagePropertyDPIWidth; |
keys[1] = kCGImagePropertyDPIHeight; |
// Create a CFNumber for the resolution and use it as the |
// x and y resolution. |
values[0] = values[1] = CFNumberCreate(NULL, kCFNumberFloatType, &dpi); |
// Create an properties dictionary with these keys. |
properties = CFDictionaryCreate(NULL, |
(const void **)keys, |
(const void **)values, |
2, |
&kCFTypeDictionaryKeyCallBacks, |
&kCFTypeDictionaryValueCallBacks); |
// Release the CFNumber the code created. |
CFRelease(values[0]); |
// Add the image to the destination, characterizing the image with |
// the properties dictionary. |
CGImageDestinationAddImage(imageDestination, image, properties); |
// Release the CGImage object that createRGBAImageFromQuartzDrawing |
// created. |
CGImageRelease(image); |
// Release the properties dictionary. |
CFRelease(properties); |
// When all the images (only 1 in this example) are added to the destination, |
// finalize the CGImageDestination object. |
CGImageDestinationFinalize(imageDestination); |
// Release the CGImageDestination when finished with it. |
CFRelease(imageDestination); |
} |
static CGLayerRef createCachedContent(CGContextRef c){ |
// The cached content will be 50x50 units. |
float width = 50, height = 50; |
CGLayerRef layer; |
// Create the layer to draw into. |
layer = CGLayerCreateWithContext(c, CGSizeMake(width, height), NULL); |
// Get the CG context corresponding to the layer. |
CGContextRef layerContext = CGLayerGetContext(layer); |
// Cache some very simple drawing just as an example. |
CGContextFillRect(layerContext, CGRectMake(0, 0, width, height) ); |
// The layer now contains cached drawing so return it. |
return layer; |
} |
static void drawSimpleCGLayer(CGContextRef context) |
{ |
// Create a CGLayer object that represents some drawing. |
CGLayerRef layer = createCachedContent(context); |
// Get the size of the layer created. |
CGSize s = CGLayerGetSize(layer); |
// Position the drawing to an appropriate location. |
CGContextTranslateCTM(context, 40, 100); |
int i; |
// Paint 4 columns of layer objects. |
for(i = 0 ; i < 4 ; i++){ |
// Draw the layer at the point that varies as the code loops. |
CGContextDrawLayerAtPoint(context, |
CGPointMake(2*(i+1)*s.width, 0), |
layer); |
} |
// Release the layer when finished drawing with it. |
CGLayerRelease(layer); |
} |
// The equivalent drawing as doSimpleCGLayer but without creating |
// a CGLayer object and caching that drawing to a layer. |
static void drawUncachedForLayer(CGContextRef context) |
{ |
CGRect r = CGRectMake(0, 0, 50, 50); |
CGContextTranslateCTM(context, 40, 100); |
int i; |
for(i = 0 ; i < 4 ; i++){ // Paint 4 columns of layer objects. |
// Adjust the origin as the code loops. Recall that |
// transformations are cummulative. |
CGContextTranslateCTM( context, 2*CGRectGetWidth(r), 0 ); |
CGContextFillRect(context, r); // Do the uncached drawing. |
} |
} |
// Create a PDF document at 'url' from the drawing represented by drawingCommand. |
void myCreatePDFDocument(CFURLRef url, OSType drawingCommand) |
{ |
// mediaRect represents the media box for the PDF document the code is |
// creating. The size here is that of a US Letter size sheet. |
const CGRect mediaRect = CGRectMake(0, 0, 8.5*72, 11*72); |
// Create a CGContext object to capture the drawing as a PDF document located |
// at 'url'. |
CGContextRef pdfContext = CGPDFContextCreateWithURL(url, &mediaRect, NULL); |
// Start capturing drawing on a page. |
CGContextBeginPage(pdfContext, &mediaRect); |
// Perform drawing for the first page. |
myDispatchDrawing(pdfContext, drawingCommand); |
// Tell the PDF context that drawing for the current page is finished. |
CGContextEndPage(pdfContext); |
/* If there were more pages they would be captured as: |
CGContextBeginPage(pdfContext, &mediaRect); |
DrawingForPage2(pdfContext); |
CGContextEndPage(pdfContext); |
CGContextBeginPage(pdfContext, &mediaRect); |
.... |
*/ |
// When done with drawing to the PDF context, the code needs to release it. |
// Without releasing the context the contents of the PDF document will not be |
// flushed properly. |
CGContextRelease(pdfContext); |
} |
Copyright © 2006 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2006-09-11