Source/QuartzUtils.m
author Jens Alfke <jens@mooseyard.com>
Thu Jul 17 13:29:04 2008 -0700 (2008-07-17)
changeset 19 3b750982ff39
parent 10 6c78cc6bd7a6
child 21 2eb229411d73
permissions -rw-r--r--
Don't load Checkers sound files till a game is displayed on a board; this speeds up launch.
     1 /*  This code is based on Apple's "GeekGameBoard" sample code, version 1.0.
     2     http://developer.apple.com/samplecode/GeekGameBoard/
     3     Copyright © 2007 Apple Inc. Copyright © 2008 Jens Alfke. All Rights Reserved.
     4 
     5     Redistribution and use in source and binary forms, with or without modification, are permitted
     6     provided that the following conditions are met:
     7 
     8     * Redistributions of source code must retain the above copyright notice, this list of conditions
     9       and the following disclaimer.
    10     * Redistributions in binary form must reproduce the above copyright notice, this list of
    11       conditions and the following disclaimer in the documentation and/or other materials provided
    12       with the distribution.
    13 
    14     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
    15     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
    16     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
    17     BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    18     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
    19     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
    20     CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
    21     THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    22 */
    23 #import "QuartzUtils.h"
    24 #import <QuartzCore/QuartzCore.h>
    25 #import "Piece.h"
    26 
    27 
    28 CGColorRef kBlackColor, kWhiteColor, 
    29            kTranslucentGrayColor, kTranslucentLightGrayColor,
    30            kTranslucentWhiteColor, kAlmostInvisibleWhiteColor,
    31            kHighlightColor;
    32 
    33 
    34 __attribute__((constructor))        // Makes this function run when the app loads
    35 static void InitQuartzUtils()
    36 {
    37     kBlackColor = CreateGray(0.0, 1.0);
    38     kWhiteColor = CreateGray(1.0, 1.0);
    39     kTranslucentGrayColor = CreateGray(0.0, 0.5);
    40     kTranslucentLightGrayColor = CreateGray(0.0, 0.25);
    41     kTranslucentWhiteColor = CreateGray(1, 0.25);
    42     kAlmostInvisibleWhiteColor = CreateGray(1, 0.05);
    43     kHighlightColor = CreateRGB(1, 1, 0, 0.5);
    44 }
    45 
    46 
    47 #if TARGET_OS_IPHONE
    48 CGColorRef CreateGray(CGFloat gray, CGFloat alpha)
    49 {
    50     CGColorSpaceRef graySpace = CGColorSpaceCreateDeviceGray();
    51     CGFloat components[2] = {gray,alpha};
    52     CGColorRef color = CGColorCreate(graySpace, components);
    53     CGColorSpaceRelease(graySpace);
    54     return color;
    55 }
    56 
    57 CGColorRef CreateRGB(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
    58 {
    59     CGColorSpaceRef rgbSpace = CGColorSpaceCreateDeviceRGB();
    60     CGFloat components[4] = {red,green,blue,alpha};
    61     CGColorRef color = CGColorCreate(rgbSpace, components);
    62     CGColorSpaceRelease(rgbSpace);
    63     return color;
    64 }
    65 #endif
    66 
    67 
    68 CGImageRef CreateCGImageFromFile( NSString *path )
    69 {
    70 #if TARGET_OS_IPHONE
    71     UIImage *uiImage = [UIImage imageWithContentsOfFile: path];
    72     if(!uiImage) NSLog(@"Warning: UIImage imageWithContentsOfFile failed on file %@",path);
    73     return CGImageRetain(uiImage.CGImage);
    74 #else
    75     CGImageRef image = NULL;
    76     CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: path];
    77     CGImageSourceRef src = CGImageSourceCreateWithURL(url, NULL);
    78     if( src ) {
    79         image = CGImageSourceCreateImageAtIndex(src, 0, NULL);
    80         CFRelease(src);
    81         if(!image) NSLog(@"Warning: CGImageSourceCreateImageAtIndex failed on file %@ (ptr size=%u)",path,sizeof(void*));
    82     }
    83     return image;
    84 #endif
    85 }
    86 
    87 
    88 CGImageRef GetCGImageNamed( NSString *name )
    89 {
    90 #if TARGET_OS_IPHONE
    91     name = name.lastPathComponent;
    92     UIImage *uiImage = [UIImage imageNamed: name];
    93     NSCAssert1(uiImage,@"Couldn't find bundle image resource '%@'",name);
    94     return uiImage.CGImage;
    95 #else
    96     // For efficiency, loaded images are cached in a dictionary by name.
    97     static NSMutableDictionary *sMap;
    98     if( ! sMap )
    99         sMap = [[NSMutableDictionary alloc] init];
   100     
   101     CGImageRef image = (CGImageRef) [sMap objectForKey: name];
   102     if( ! image ) {
   103         // Hasn't been cached yet, so load it:
   104         NSString *path;
   105         if( [name hasPrefix: @"/"] )
   106             path = name;
   107         else {
   108             NSString *dir = [name stringByDeletingLastPathComponent];
   109             name = [name lastPathComponent];
   110             NSString *ext = name.pathExtension;
   111             name = [name stringByDeletingPathExtension];
   112             path = [[NSBundle mainBundle] pathForResource: name ofType: ext inDirectory: dir];
   113             NSCAssert3(path,@"Couldn't find bundle image resource '%@' type '%@' in '%@'",name,ext,dir);
   114         }
   115         image = CreateCGImageFromFile(path);
   116         NSCAssert1(image,@"Failed to load image from %@",path);
   117         [sMap setObject: (id)image forKey: name];
   118         CGImageRelease(image);
   119     }
   120     return image;
   121 #endif
   122 }
   123 
   124 
   125 CGColorRef GetCGPatternNamed( NSString *name )         // can be resource name or abs. path
   126 {
   127     // For efficiency, loaded patterns are cached in a dictionary by name.
   128     static NSMutableDictionary *sMap;
   129     if( ! sMap )
   130         sMap = [[NSMutableDictionary alloc] init];
   131     
   132     CGColorRef pattern = (CGColorRef) [sMap objectForKey: name];
   133     if( ! pattern ) {
   134         pattern = CreatePatternColor( GetCGImageNamed(name) );
   135         [sMap setObject: (id)pattern forKey: name];
   136         CGColorRelease(pattern);
   137     }
   138     return pattern;
   139 }
   140 
   141 
   142 #if ! TARGET_OS_IPHONE
   143 
   144 BOOL CanGetCGImageFromPasteboard( NSPasteboard *pb )
   145 {
   146     return [NSImage canInitWithPasteboard: pb] 
   147         || [[pb types] containsObject: @"PixadexIconPathPboardType"];
   148 
   149     /*if( [[pb types] containsObject: NSFilesPromisePboardType] ) {
   150      NSArray *fileTypes = [pb propertyListForType: NSFilesPromisePboardType];
   151      NSLog(@"Got file promise! Types = %@",fileTypes);
   152      //FIX: Check file types
   153      return NSDragOperationCopy;
   154      }*/
   155 }    
   156 
   157 CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb, id<NSDraggingInfo>dragInfo )
   158 {
   159     CGImageSourceRef src = NULL;
   160     NSArray *paths = [pb propertyListForType: NSFilenamesPboardType];
   161     if( paths.count==1 ) {
   162         // If a file is being dragged, read it:
   163         CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: [paths objectAtIndex: 0]];
   164         src = CGImageSourceCreateWithURL(url, NULL);
   165 /*
   166     } else if( dragInfo && [[pb types] containsObject:NSFilesPromisePboardType] ) {
   167         NSString *dropDir = NSTemporaryDirectory();
   168         NSArray *filenames = [dragInfo namesOfPromisedFilesDroppedAtDestination: [NSURL fileURLWithPath: dropDir]];
   169         NSLog(@"promised files are %@ / %@", dropDir,filenames);
   170         src = nil; */
   171     } else if( [[pb types] containsObject: @"PixadexIconPathPboardType"] ) {
   172         // Candybar 3 (nee Pixadex) doesn't drag out icons in any normal image type.
   173         // It does support file-promises, but I couldn't get those to work using the Cocoa APIs.
   174         // So instead I'm using its custom type that provides the path(s) to its internal ".pxicon" files.
   175         // The icon is really easy to get from one of these: it's just file's custom icon.
   176         NSArray *files = [pb propertyListForType: @"PixadexIconPathPboardType"];
   177         if( files.count == 1 ) {
   178             NSString *path = [files objectAtIndex: 0];
   179             NSImage *icon = [[NSWorkspace sharedWorkspace] iconForFile: path];
   180             for( NSImageRep *rep in icon.representations ) {
   181                 if( [rep isKindOfClass: [NSBitmapImageRep class]] ) {
   182                     [rep retain];   //FIX: This leaks; but if the rep goes away, the CGImage breaks...
   183                     return [(NSBitmapImageRep*)rep CGImage];
   184                 }
   185             }
   186         }
   187     } else {
   188         // Else look for an image type:
   189         NSString *type = [pb availableTypeFromArray: [NSImage imageUnfilteredPasteboardTypes]];
   190         if( type ) {
   191             NSData *data = [pb dataForType: type];
   192             src = CGImageSourceCreateWithData((CFDataRef)data, NULL);
   193         }
   194     }
   195     if(src) {
   196         CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, NULL);
   197         CFRelease(src);
   198         return image;
   199     } else
   200         return NULL;
   201 }
   202 #endif
   203 
   204 
   205 CGImageRef CreateScaledImage( CGImageRef srcImage, CGFloat scale )
   206 {
   207     int width = CGImageGetWidth(srcImage), height = CGImageGetHeight(srcImage);
   208     if( scale > 0 ) {
   209         if( scale >= 4.0 )
   210             scale /= MAX(width,height);             // interpret scale as target dimensions
   211         width = ceil( width * scale);
   212         height= ceil( height* scale);
   213     }
   214 
   215     CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
   216     CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, 8, 4*width, space,
   217                                              kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast);
   218     CGColorSpaceRelease(space);
   219     CGContextSetInterpolationQuality(ctx,kCGInterpolationHigh);
   220     CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), srcImage);
   221     CGImageRef dstImage = CGBitmapContextCreateImage(ctx);
   222     CGContextRelease(ctx);
   223     return dstImage;
   224 }
   225 
   226 
   227 CGImageRef GetScaledImageNamed( NSString *imageName, CGFloat scale )
   228 {
   229     // For efficiency, loaded images are cached in a dictionary by name.
   230     static NSMutableDictionary *sMap;
   231     if( ! sMap )
   232         sMap = [[NSMutableDictionary alloc] init];
   233     
   234     NSArray *key = [NSArray arrayWithObjects: imageName, [NSNumber numberWithFloat: scale], nil];
   235     CGImageRef image = (CGImageRef) [sMap objectForKey: key];
   236     if( ! image ) {
   237         // Hasn't been cached yet, so load it:
   238         image = CreateScaledImage(GetCGImageNamed(imageName), scale);
   239         [sMap setObject: (id)image forKey: key];
   240         CGImageRelease(image);
   241     }
   242     return image;
   243 }
   244 
   245 
   246 float GetPixelAlpha( CGImageRef image, CGSize imageSize, CGPoint pt )
   247 {
   248     NSCParameterAssert(image);
   249 #if TARGET_OS_IPHONE
   250     // iPhone uses "flipped" (i.e. normal) coords, so images are wrong-way-up
   251     pt.y = imageSize.height - pt.y;
   252 #endif
   253     
   254     // Trivial reject:
   255     if( pt.x<0 || pt.x>=imageSize.width || pt.y<0 || pt.y>=imageSize.height )
   256         return 0.0;
   257     
   258     // sTinyContext is a 1x1 CGBitmapContext whose pixmap stores only alpha.
   259     static UInt8 sPixel[1];
   260     static CGContextRef sTinyContext;
   261     if( ! sTinyContext ) {
   262         sTinyContext = CGBitmapContextCreate(sPixel, 1, 1, 
   263                                              8, 1,     // bpp, rowBytes
   264                                              NULL,
   265                                              kCGImageAlphaOnly);
   266         CGContextSetBlendMode(sTinyContext, kCGBlendModeCopy);
   267     }
   268     
   269     // Draw the image into sTinyContext, positioned so the desired point is at
   270     // (0,0), then examine the alpha value in the pixmap:
   271     CGContextDrawImage(sTinyContext, 
   272                        CGRectMake(-pt.x,-pt.y, imageSize.width,imageSize.height),
   273                        image);
   274     return sPixel[0] / 255.0;
   275 }
   276 
   277 
   278 #pragma mark -
   279 #pragma mark PATTERNS:
   280 
   281 
   282 // callback for CreateImagePattern.
   283 static void drawPatternImage (void *info, CGContextRef ctx)
   284 {
   285     CGImageRef image = (CGImageRef) info;
   286     CGContextDrawImage(ctx, 
   287                        CGRectMake(0,0, CGImageGetWidth(image),CGImageGetHeight(image)),
   288                        image);
   289 }
   290 
   291 // callback for CreateImagePattern.
   292 static void releasePatternImage( void *info )
   293 {
   294     CGImageRelease( (CGImageRef)info );
   295 }
   296 
   297 
   298 CGPatternRef CreateImagePattern( CGImageRef image )
   299 {
   300     NSCParameterAssert(image);
   301     int width = CGImageGetWidth(image);
   302     int height = CGImageGetHeight(image);
   303     static const CGPatternCallbacks callbacks = {0, &drawPatternImage, &releasePatternImage};
   304     return CGPatternCreate (image,
   305                             CGRectMake (0, 0, width, height),
   306                             CGAffineTransformMake (1, 0, 0, 1, 0, 0),
   307                             width,
   308                             height,
   309                             kCGPatternTilingConstantSpacing,
   310                             true,
   311                             &callbacks);
   312 }
   313 
   314 
   315 CGColorRef CreatePatternColor( CGImageRef image )
   316 {
   317     CGPatternRef pattern = CreateImagePattern(image);
   318     CGColorSpaceRef space = CGColorSpaceCreatePattern(NULL);
   319     CGFloat components[1] = {1.0};
   320     CGColorRef color = CGColorCreateWithPattern(space, pattern, components);
   321     CGColorSpaceRelease(space);
   322     CGPatternRelease(pattern);
   323     return color;
   324 }
   325 
   326 
   327 #pragma mark -
   328 #pragma mark PATHS:
   329 
   330 
   331 void AddRoundRect( CGContextRef ctx, CGRect rect, CGFloat radius )
   332 {
   333     radius = MIN(radius, floorf(rect.size.width/2));
   334     float x0 = CGRectGetMinX(rect), y0 = CGRectGetMinY(rect),
   335     x1 = CGRectGetMaxX(rect), y1 = CGRectGetMaxY(rect);
   336     
   337     CGContextBeginPath(ctx);
   338     CGContextMoveToPoint(ctx,x0+radius,y0);
   339     CGContextAddArcToPoint(ctx,x1,y0, x1,y1, radius);
   340     CGContextAddArcToPoint(ctx,x1,y1, x0,y1, radius);
   341     CGContextAddArcToPoint(ctx,x0,y1, x0,y0, radius);
   342     CGContextAddArcToPoint(ctx,x0,y0, x1,y0, radius);
   343     CGContextClosePath(ctx);
   344 }
   345