Search code examples
objective-cbitmapuiimage

How to convert hex data to UIImage?


I like to display .cpbitmap (its the file formate iOS saves the wallpapers) in a UIImageView. The problem is I need to convert it. I already figure out that if you get its Data (NSData) and convert every bit you get the UIColor, so the first Bit is R, then B, then G and then Alpha (I think). Now I need to "draw" an UIImage out of the info. Does anyone know how to do this?

Here is the link to the .cpbitmap file: https://www.dropbox.com/s/s9v4lahixm9cuql/LockBackground.cpbitmap

It would be really cool if someone can help me,

Thanks

EDIT I found a working python script, is someone able to translate it to Objective

#!/usr/bin/python
from PIL import Image,ImageOps
import struct
import sys
if len(sys.argv) < 3:
    print "Need two args: filename and result_filename\n";
    sys.exit(0)
filename = sys.argv[1]
result_filename = sys.argv[2]

with open(filename) as f:
    contents = f.read()
    unk1, width, height, unk2, unk3, unk4 = struct.unpack('<6i', contents[-24:])
    im = Image.fromstring('RGBA', (width,height), contents, 'raw', 'RGBA', 0, 1)
    r,g,b,a = im.split()
    im = Image.merge('RGBA', (b,g,r,a))
    im.save(result_filename)

Solution

  • The basic process of converting RGBA data into an image is to create a CGDataProviderRef with the raw data, and then use CGImageCreate to create a CGImageRef, from which you can easily generate a UIImage. So, that gives you something like:

    - (UIImage *) imageForBitmapData:(NSData *)data size:(CGSize)size
    {
        void *          bitmapData;
        CGColorSpaceRef colorSpace        = CGColorSpaceCreateDeviceRGB();
        int             bitmapBytesPerRow = (size.width * 4);
        int             bitmapByteCount   = (bitmapBytesPerRow * size.height);
    
        bitmapData = malloc( bitmapByteCount );
        NSAssert(bitmapData, @"Unable to create buffer");
    
        [data getBytes:bitmapData length:bitmapByteCount];
    
        CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmapData, bitmapByteCount, releasePixels);
    
        CGImageRef imageRef = CGImageCreate(size.width,
                                            size.height,
                                            8,
                                            32,
                                            bitmapBytesPerRow,
                                            colorSpace,
                                            (CGBitmapInfo)kCGImageAlphaLast,
                                            provider,
                                            NULL,
                                            NO,
                                            kCGRenderingIntentDefault);
    
        UIImage *image = [UIImage imageWithCGImage:imageRef];
    
        CGImageRelease(imageRef);
        CGColorSpaceRelease(colorSpace);
        CGDataProviderRelease(provider);
    
        return image;
    }
    

    With a releasePixels function defined as follows:

    void releasePixels(void *info, const void *data, size_t size)
    {
        free((void*)data);
    }
    

    The only trick was identifying the dimensions of the bitmap. There are 4,142,592 bytes of image data (there is some extra stuff at the end of the file, which is self evident if you examine the file in hexadecimal in Xcode). That doesn't correlate to any standard device dimensions. But if you look at the possible values that divide evenly into 4,142,592, you get a couple of promising ones (496, 522, 558, 576, 696, 744, 899, 928, and 992). And if you just try those out, it becomes obvious that the image is 744 x 1392.

    You can then use those dimensions with the above method, and you get your image.


    While I discovered the size of the image empirically, I noticed that those dimensions were encoded at the end of the file. This is confirmed by your Python code, which suggests that the image width is the fifth from the last UInt32, and the height is the fourth from the last UInt32. Thus, you can use the above routine like so, extracting the dimensions from those two 32-bit integers encoded near the end of the file:

    NSString *path = [[NSBundle mainBundle] pathForResource:@"LockBackground" ofType:@"cpbitmap"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    NSAssert(data, @"no data found");
    
    UInt32 width;
    UInt32 height;
    [data getBytes:&width  range:NSMakeRange([data length] - sizeof(UInt32) * 5, sizeof(UInt32))];
    [data getBytes:&height range:NSMakeRange([data length] - sizeof(UInt32) * 4, sizeof(UInt32))];
    
    self.imageView.image = [self imageForBitmapData:data size:CGSizeMake(width, height)];