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