Irregular Shaped Buttons And Alpha Masks

Jeff LaMarche wrote a nice iPhone devel posting about detecting hits in irregularly shaped buttons. It reminded me a bit how we solved hit testing in games, way back in the days.

I checked Jeff's code, which works pretty fine. The only thing that crossed my mind was that Jeff's hit testing allocates ARGB date for every hit test. There are a couple of simplifications possible, as I wrote here. First of all we only need the transparency information, and maybe it's better to store or cache the alpha mask. The reason: less data is being allocated. (Which may be moot, if your irregularly shaped images are small; and you have plenty of memory to burn on that iPad).

UPDATE: Jeff posted an update to his code, which you can find here

Anyway, in 2D graphics 101, if you just need the transparency mask, you blit the color image to a monochrome graphics context, and you have the mask. Here are my suggested changes:

CGContextRef CreateARGBBitmapContext (CGImageRef inImage)
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace = NULL; // tell we want  kCGImageAlphaOnly
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
    
    
    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
    bitmapBytesPerRow   = (pixelsWide * 1); // 8bpp
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
    
    bitmapData = calloc(1, bitmapByteCount );
    if (bitmapData == NULL) 
        {
            CGColorSpaceRelease( colorSpace );
            return nil;
        }
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,
                                     bitmapBytesPerRow,
                                     colorSpace,
                                      kCGImageAlphaOnly);
    if (context == NULL)
        {
            free (bitmapData);
            fprintf (stderr, "Context not created!");
        }
    CGColorSpaceRelease( colorSpace );
    
    return context;
}

- (NSData *) ARGBData {
    CGContextRef cgctx = CreateARGBBitmapContext(self.CGImage);
    if (cgctx == NULL) 
        return nil;
    
    size_t w = CGImageGetWidth(self.CGImage);
    size_t h = CGImageGetHeight(self.CGImage);
    CGRect rect = {{0,0},{w,h}}; 
    CGContextDrawImage(cgctx, rect, self.CGImage); 
    
    unsigned char *data = CGBitmapContextGetData (cgctx);
    CGContextRelease(cgctx); 
    if (!data)
        return nil;
    
    size_t dataSize = 1 * w * h; // 8 bits
    
    return [NSData dataWithBytes:data length:dataSize];
}

- (BOOL) isPointTransparent: (CGPoint) point {
    NSData *rawData = [self ARGBData];  // Or cache this
    if (rawData == nil)
        return NO;
    
    // just 8 bits per alpha component
    size_t bpp = 1;
    size_t bpr = self.size.width * 1;
    
    NSUInteger index = (point.x * bpp) + (point.y * bpr);
    unsigned char *rawDataBytes = (unsigned char *)[rawData bytes];
    
    return rawDataBytes[index] == 0;    
}