Source/QuartzUtils.m
author Jens Alfke <jens@mooseyard.com>
Fri Mar 07 11:43:02 2008 -0800 (2008-03-07)
changeset 0 e9f7ba4718e1
child 1 3eb7be1dd7b6
permissions -rw-r--r--
Initial check-in into Mercurial. Branched from 1.0 release of Apple's sample code. No longer requires garbage collection. Fixed some memory leaks of CG objects. Fixed a bug when advancing to the 8th row in the Checkers game.
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@0
    24
jens@0
    25
jens@0
    26
CGColorRef kBlackColor, kWhiteColor, 
jens@0
    27
           kTranslucentGrayColor, kTranslucentLightGrayColor,
jens@0
    28
           kAlmostInvisibleWhiteColor,
jens@0
    29
           kHighlightColor;
jens@0
    30
jens@0
    31
jens@0
    32
__attribute__((constructor))        // Makes this function run when the app loads
jens@0
    33
static void InitQuartzUtils()
jens@0
    34
{
jens@0
    35
    kBlackColor = CGColorCreateGenericGray(0.0, 1.0);
jens@0
    36
    kWhiteColor = CGColorCreateGenericGray(1.0, 1.0);
jens@0
    37
    kTranslucentGrayColor = CGColorCreateGenericGray(0.0, 0.5);
jens@0
    38
    kTranslucentLightGrayColor = CGColorCreateGenericGray(0.0, 0.25);
jens@0
    39
    kAlmostInvisibleWhiteColor = CGColorCreateGenericGray(1, 0.05);
jens@0
    40
    kHighlightColor = CGColorCreateGenericRGB(1, 1, 0, 0.5);
jens@0
    41
}
jens@0
    42
jens@0
    43
jens@0
    44
void ChangeSuperlayer( CALayer *layer, CALayer *newSuperlayer, int index )
jens@0
    45
{
jens@0
    46
    // Disable actions, else the layer will move to the wrong place and then back!
jens@0
    47
    [CATransaction flush];
jens@0
    48
    [CATransaction begin];
jens@0
    49
    [CATransaction setValue:(id)kCFBooleanTrue
jens@0
    50
                     forKey:kCATransactionDisableActions];
jens@0
    51
jens@0
    52
    CGPoint pos = [newSuperlayer convertPoint: layer.position 
jens@0
    53
                      fromLayer: layer.superlayer];
jens@0
    54
    [layer retain];
jens@0
    55
    [layer removeFromSuperlayer];
jens@0
    56
    if( index >= 0 )
jens@0
    57
        [newSuperlayer insertSublayer: layer atIndex: index];
jens@0
    58
    else
jens@0
    59
        [newSuperlayer addSublayer: layer];
jens@0
    60
    layer.position = pos;
jens@0
    61
    [layer release];
jens@0
    62
jens@0
    63
    [CATransaction commit];
jens@0
    64
}
jens@0
    65
jens@0
    66
jens@0
    67
void RemoveImmediately( CALayer *layer )
jens@0
    68
{
jens@0
    69
    [CATransaction flush];
jens@0
    70
    [CATransaction begin];
jens@0
    71
    [CATransaction setValue:(id)kCFBooleanTrue
jens@0
    72
                     forKey:kCATransactionDisableActions];
jens@0
    73
    [layer removeFromSuperlayer];
jens@0
    74
    [CATransaction commit];
jens@0
    75
}    
jens@0
    76
jens@0
    77
jens@0
    78
CATextLayer* AddTextLayer( CALayer *superlayer,
jens@0
    79
                           NSString *text, NSFont* font,
jens@0
    80
                           enum CAAutoresizingMask align )
jens@0
    81
{
jens@0
    82
    CATextLayer *label = [[CATextLayer alloc] init];
jens@0
    83
    label.string = text;
jens@0
    84
    label.font = font;
jens@0
    85
    label.fontSize = font.pointSize;
jens@0
    86
    label.foregroundColor = kBlackColor;
jens@0
    87
    
jens@0
    88
    NSString *mode;
jens@0
    89
    if( align & kCALayerWidthSizable )
jens@0
    90
        mode = @"center";
jens@0
    91
    else if( align & kCALayerMinXMargin )
jens@0
    92
        mode = @"right";
jens@0
    93
    else
jens@0
    94
        mode = @"left";
jens@0
    95
    align |= kCALayerWidthSizable;
jens@0
    96
    label.alignmentMode = mode;
jens@0
    97
    
jens@0
    98
    CGFloat inset = superlayer.borderWidth + 3;
jens@0
    99
    CGRect bounds = CGRectInset(superlayer.bounds, inset, inset);
jens@0
   100
    CGFloat height = font.ascender;
jens@0
   101
    CGFloat y = bounds.origin.y;
jens@0
   102
    if( align & kCALayerHeightSizable )
jens@0
   103
        y += (bounds.size.height-height)/2.0;
jens@0
   104
    else if( align & kCALayerMinYMargin )
jens@0
   105
        y += bounds.size.height - height;
jens@0
   106
    align &= ~kCALayerHeightSizable;
jens@0
   107
    label.bounds = CGRectMake(0, font.descender,
jens@0
   108
                              bounds.size.width, height - font.descender);
jens@0
   109
    label.position = CGPointMake(bounds.origin.x,y+font.descender);
jens@0
   110
    label.anchorPoint = CGPointMake(0,0);
jens@0
   111
    
jens@0
   112
    label.autoresizingMask = align;
jens@0
   113
    [superlayer addSublayer: label];
jens@0
   114
    [label release];
jens@0
   115
    return label;
jens@0
   116
}
jens@0
   117
jens@0
   118
jens@0
   119
CGImageRef CreateCGImageFromFile( NSString *path )
jens@0
   120
{
jens@0
   121
    CGImageRef image = NULL;
jens@0
   122
    CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: path];
jens@0
   123
    CGImageSourceRef src = CGImageSourceCreateWithURL(url, NULL);
jens@0
   124
    if( src ) {
jens@0
   125
        image = CGImageSourceCreateImageAtIndex(src, 0, NULL);
jens@0
   126
        CFRelease(src);
jens@0
   127
        if(!image) NSLog(@"Warning: CGImageSourceCreateImageAtIndex failed on file %@ (ptr size=%u)",path,sizeof(void*));
jens@0
   128
    }
jens@0
   129
    return image;
jens@0
   130
}
jens@0
   131
jens@0
   132
jens@0
   133
CGImageRef GetCGImageNamed( NSString *name )
jens@0
   134
{
jens@0
   135
    // For efficiency, loaded images are cached in a dictionary by name.
jens@0
   136
    static NSMutableDictionary *sMap;
jens@0
   137
    if( ! sMap )
jens@0
   138
        sMap = [[NSMutableDictionary alloc] init];
jens@0
   139
    
jens@0
   140
    CGImageRef image = (CGImageRef) [sMap objectForKey: name];
jens@0
   141
    if( ! image ) {
jens@0
   142
        // Hasn't been cached yet, so load it:
jens@0
   143
        NSString *path;
jens@0
   144
        if( [name hasPrefix: @"/"] )
jens@0
   145
            path = name;
jens@0
   146
        else {
jens@0
   147
            path = [[NSBundle mainBundle] pathForResource: name ofType: nil];
jens@0
   148
            NSCAssert1(path,@"Couldn't find bundle image resource '%@'",name);
jens@0
   149
        }
jens@0
   150
        image = CreateCGImageFromFile(path);
jens@0
   151
        NSCAssert1(image,@"Failed to load image from %@",path);
jens@0
   152
        [sMap setObject: (id)image forKey: name];
jens@0
   153
        CGImageRelease(image);
jens@0
   154
    }
jens@0
   155
    return image;
jens@0
   156
}
jens@0
   157
jens@0
   158
jens@0
   159
CGColorRef GetCGPatternNamed( NSString *name )         // can be resource name or abs. path
jens@0
   160
{
jens@0
   161
    // For efficiency, loaded patterns are cached in a dictionary by name.
jens@0
   162
    static NSMutableDictionary *sMap;
jens@0
   163
    if( ! sMap )
jens@0
   164
        sMap = [[NSMutableDictionary alloc] init];
jens@0
   165
    
jens@0
   166
    CGColorRef pattern = (CGColorRef) [sMap objectForKey: name];
jens@0
   167
    if( ! pattern ) {
jens@0
   168
        pattern = CreatePatternColor( GetCGImageNamed(name) );
jens@0
   169
        [sMap setObject: (id)pattern forKey: name];
jens@0
   170
        CGColorRelease(pattern);
jens@0
   171
    }
jens@0
   172
    return pattern;
jens@0
   173
}
jens@0
   174
jens@0
   175
jens@0
   176
CGImageRef GetCGImageFromPasteboard( NSPasteboard *pb )
jens@0
   177
{
jens@0
   178
    CGImageSourceRef src = NULL;
jens@0
   179
    NSArray *paths = [pb propertyListForType: NSFilenamesPboardType];
jens@0
   180
    if( paths.count==1 ) {
jens@0
   181
        // If a file is being dragged, read it:
jens@0
   182
        CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: [paths objectAtIndex: 0]];
jens@0
   183
        src = CGImageSourceCreateWithURL(url, NULL);
jens@0
   184
    } else {
jens@0
   185
        // Else look for an image type:
jens@0
   186
        NSString *type = [pb availableTypeFromArray: [NSImage imageUnfilteredPasteboardTypes]];
jens@0
   187
        if( type ) {
jens@0
   188
            NSData *data = [pb dataForType: type];
jens@0
   189
            src = CGImageSourceCreateWithData((CFDataRef)data, NULL);
jens@0
   190
        }
jens@0
   191
    }
jens@0
   192
    if(src) {
jens@0
   193
        CGImageRef image = CGImageSourceCreateImageAtIndex(src, 0, NULL);
jens@0
   194
        CFRelease(src);
jens@0
   195
        return image;
jens@0
   196
    } else
jens@0
   197
        return NULL;
jens@0
   198
}    
jens@0
   199
jens@0
   200
jens@0
   201
float GetPixelAlpha( CGImageRef image, CGSize imageSize, CGPoint pt )
jens@0
   202
{
jens@0
   203
    // Trivial reject:
jens@0
   204
    if( pt.x<0 || pt.x>=imageSize.width || pt.y<0 || pt.y>=imageSize.height )
jens@0
   205
        return 0.0;
jens@0
   206
    
jens@0
   207
    // sTinyContext is a 1x1 CGBitmapContext whose pixmap stores only alpha.
jens@0
   208
    static UInt8 sPixel[1];
jens@0
   209
    static CGContextRef sTinyContext;
jens@0
   210
    if( ! sTinyContext ) {
jens@0
   211
        sTinyContext = CGBitmapContextCreate(sPixel, 1, 1, 
jens@0
   212
                                             8, 1,     // bpp, rowBytes
jens@0
   213
                                             NULL,
jens@0
   214
                                             kCGImageAlphaOnly);
jens@0
   215
        CGContextSetBlendMode(sTinyContext, kCGBlendModeCopy);
jens@0
   216
    }
jens@0
   217
    
jens@0
   218
    // Draw the image into sTinyContext, positioned so the desired point is at
jens@0
   219
    // (0,0), then examine the alpha value in the pixmap:
jens@0
   220
    CGContextDrawImage(sTinyContext, 
jens@0
   221
                       CGRectMake(-pt.x,-pt.y, imageSize.width,imageSize.height),
jens@0
   222
                       image);
jens@0
   223
    return sPixel[0] / 255.0;
jens@0
   224
}
jens@0
   225
jens@0
   226
jens@0
   227
#pragma mark -
jens@0
   228
#pragma mark PATTERNS:
jens@0
   229
jens@0
   230
jens@0
   231
// callback for CreateImagePattern.
jens@0
   232
static void drawPatternImage (void *info, CGContextRef ctx)
jens@0
   233
{
jens@0
   234
    CGImageRef image = (CGImageRef) info;
jens@0
   235
    CGContextDrawImage(ctx, 
jens@0
   236
                       CGRectMake(0,0, CGImageGetWidth(image),CGImageGetHeight(image)),
jens@0
   237
                       image);
jens@0
   238
}
jens@0
   239
jens@0
   240
// callback for CreateImagePattern.
jens@0
   241
static void releasePatternImage( void *info )
jens@0
   242
{
jens@0
   243
    CGImageRelease( (CGImageRef)info );
jens@0
   244
}
jens@0
   245
jens@0
   246
jens@0
   247
CGPatternRef CreateImagePattern( CGImageRef image )
jens@0
   248
{
jens@0
   249
    NSCParameterAssert(image);
jens@0
   250
    int width = CGImageGetWidth(image);
jens@0
   251
    int height = CGImageGetHeight(image);
jens@0
   252
    static const CGPatternCallbacks callbacks = {0, &drawPatternImage, &releasePatternImage};
jens@0
   253
    return CGPatternCreate (image,
jens@0
   254
                            CGRectMake (0, 0, width, height),
jens@0
   255
                            CGAffineTransformMake (1, 0, 0, 1, 0, 0),
jens@0
   256
                            width,
jens@0
   257
                            height,
jens@0
   258
                            kCGPatternTilingConstantSpacing,
jens@0
   259
                            true,
jens@0
   260
                            &callbacks);
jens@0
   261
}
jens@0
   262
jens@0
   263
jens@0
   264
CGColorRef CreatePatternColor( CGImageRef image )
jens@0
   265
{
jens@0
   266
    CGPatternRef pattern = CreateImagePattern(image);
jens@0
   267
    CGColorSpaceRef space = CGColorSpaceCreatePattern(NULL);
jens@0
   268
    CGFloat components[1] = {1.0};
jens@0
   269
    CGColorRef color = CGColorCreateWithPattern(space, pattern, components);
jens@0
   270
    CGColorSpaceRelease(space);
jens@0
   271
    CGPatternRelease(pattern);
jens@0
   272
    return color;
jens@0
   273
}