středa 10. dubna 2013

Masking images on iOS (GLKit edition)

The idea is simple. You have a greyscale (mask) image and an opaque colored image and you want to combine them to an image where black areas from the mask image are transparent. My motivation was to save space when saving transparent images. JPEG is used for the color information and the transparency is restored from the mask. The process is easilly automated with ImageMagick's
convert $file -alpha extract $mask.png

Apple's documentation contains pretty nice articles on image masks, and tells you about the 'CGImageCreateWithMask' that seems to do exactly what you want. But there are two catches. First of all, the masked image should already have alpha channel (well, at least the various sources on the interwebs suggest so). The more important problem is that '[GLKTextureLoader textureWithCGImage: ....]' always dropped the alpha channel.
The solution I used is to create an offscreen Quartz bitmap context, clip it with an image mask, draw the opaque image, create image from the offscreen context and load that one. The other catch I discovered is that GLKTextureLoader ignores endianess information and you have to create the offscreen context with explicit 'kCGBitmapByteOrder32Little' .
Here comes the code. Please note that I am not really an experienced ObjC developer, so do not consider it 'best practices'.
        NSString* maskFile=;
        NSString* dataFile=;
        
        //load data
        CGDataProviderRef dataProvider=CGDataProviderCreateWithFilename([dataFile cStringUsingEncoding:NSASCIIStringEncoding]);
        CGImageRef dataImage=CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
        int width=CGImageGetWidth(dataImage);
        int height=CGImageGetHeight(dataImage);
        //load mask
        CGDataProviderRef maskProvider=CGDataProviderCreateWithFilename([maskFile cStringUsingEncoding:NSASCIIStringEncoding]);
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGImageRef maskImage=CGImageCreateWithPNGDataProvider(maskProvider, NULL, false, kCGRenderingIntentDefault);
        //create the masked image
        CGContextRef offscreenContext = CGBitmapContextCreate(NULL, width, height,8,width*4, colorSpace,
                                                              kCGImageAlphaPremultipliedFirst|kCGBitmapByteOrder32Little);
        CGContextClipToMask(offscreenContext, CGRectMake(0,0,width,height), maskImage);
        CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), dataImage);
        CGImageRef resultImage = CGBitmapContextCreateImage(offscreenContext);
        CGContextRelease(offscreenContext);
        
        //CGImageRef resultImage=dataImageWithAlpha;
        NSError* error;
        NSDictionary* opts=[NSDictionary new];
        GLKTextureInfo* info=[GLKTextureLoader textureWithCGImage:resultImage options:opts error:&error];
        if(info==nil){
            NSLog(@"Error during loading image %@. Exception %@ occured.",resultImage,error);
        }
        [opts release];
        //if you are going to load larger images, it might be worth releasing the intermediate images
        //as soon as they are not needed
        CGDataProviderRelease(maskProvider);
        CGDataProviderRelease(dataProvider);
        CGImageRelease(dataImage);
        CGImageRelease(maskImage);
        CGImageRelease(resultImage);
        CGColorSpaceRelease(colorSpace);
        return info;

Žádné komentáře:

Okomentovat