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