Image decoding

Common image compression formats are MAINLY PNG and JPEG. In the development of iOS programs, it is generally not necessary to obtain the decoded data of a picture, but if you need to operate on pixels, you may need to know how to obtain relevant pixel values. A bitmap, or image, is made up of multiple pixels arranged. When we decode PNG and JPG, we should get a set of pixel data that makes up the decoded bitmap. Image decoding can be treated as a decompression, so the bitmap takes up more space. The bitmap takes up more space, which is easy to calculate. The area of the image * bytes per pixel.

+ (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;
Copy the code

Normally you load an image using imageWithContentsOfFile, so it’s not going to be decoded here when you create the UIImage, it’s going to be decoded when the UIImage is drawn.

CGImage

CGImage is defined as a bitmap or image mask, meaning that pixel values can be read using CGImage. The following can be used when reading directly from CGImage pixels.

NSData *imageData = [NSData dataWithContentsOfFile:imagePath];
CFDataRef dataRef = (__bridge CFDataRef)imageData;
CGImageSourceRef source = CGImageSourceCreateWithData(dataRef, nil);
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0.nil);
width = (int)CGImageGetWidth(cgImage);
height = (int)CGImageGetHeight(cgImage);
size_t pixelCount = width * height;
CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
CFDataRef data = CGDataProviderCopyData(provider);

CFRelease(data);
CGImageRelease(cgImage);             
CFRelease(source);
Copy the code

This method of reading pixels does not specify the color format, but instead reads the combination of pixels made up of the original color space of the image. Note that all created data needs to be released by calling the corresponding release. The method used here to create cgImages can also set the caches of cgImages. And when we get data from CGImage’s dataProvider, currently we can only copy a copy to CFDataRef instead of reading it directly.

CGBitmapContext

Take RGB color space as an example, there may be so much color-related data above, CGImage has related methods to read the corresponding data, but some methods are supported after iOS12.

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrderDefault  = (0 << 12),
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)}CG_AVAILABLE_STARTING(10.0.2.0);

typedef CF_ENUM(uint32_t, CGImagePixelFormatInfo) {
    kCGImagePixelFormatMask      = 0xF0000,
    kCGImagePixelFormatPacked    = (0 << 16),
    kCGImagePixelFormatRGB555    = (1 << 16), /* Only for RGB 16 bits per pixel */
    kCGImagePixelFormatRGB565    = (2 << 16), /* Only for RGB 16 bits per pixel */
    kCGImagePixelFormatRGB101010 = (3 << 16), /* Only for RGB 32 bits per pixel */
    kCGImagePixelFormatRGBCIF10  = (4 << 16), /* Only for RGB 32 bits per pixel */
} CG_AVAILABLE_STARTING(10.14.12.0);

typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
    kCGBitmapAlphaInfoMask = 0x1F,

    kCGBitmapFloatInfoMask = 0xF00,
    kCGBitmapFloatComponents = (1 << 8),

    kCGBitmapByteOrderMask     = kCGImageByteOrderMask,
    kCGBitmapByteOrderDefault  = kCGImageByteOrderDefault,
    kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
    kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
    kCGBitmapByteOrder16Big    = kCGImageByteOrder16Big,
    kCGBitmapByteOrder32Big    = kCGImageByteOrder32Big
} CG_AVAILABLE_STARTING(10.0.2.0);

#ifdef __BIG_ENDIAN__
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else    /* Little endian. */
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif
Copy the code

The composition of individual pixels in an image can be arranged in many ways according to color space, arrangement, and space, so in order to avoid too much processing, CGBitmapContext is probably a user-friendly way. CGBitmapContext can be used to bitmap into the memory canvas. Each pixel on the canvas is arranged according to the color space specified in CGBitmapContext. When CGBitmapContext is created, we pass in some parameters that specify what canvas we want.

CGContextRef CGBitmapContextCreate(
// The memory address to render in the canvas, this size should be at least bytesPerRow * height,
// If NULL is passed, this method clears space for the bitmap.
void *data, 
// Bitmap width
size_t width, 
// Bitmap height
size_t height, 
// The number of bits occupied by each element in a pixel. In a 32-bit RGB format, the number of bits occupied by an element is 8
size_t bitsPerComponent, 
// The number of bytes per line of the bitmap. If NULL is passed to data, 0 is passed to calculate automatically
size_t bytesPerRow, 
// Color space
CGColorSpaceRef space, 
// Other relevant information, such as whether the alpha channel is included, the position of the alpha channel in the pixel, whether the elements in the pixel are integer or floating point, etc.
uint32_t bitmapInfo);
Copy the code

Some configurations are not supported when creating CGBitmapContext. For the supported format, see the document Graphics Contexts. For example, using kCGImageAlphaNone to generate a canvas without an alpha channel is not allowed for images without an alpha channel. But use kCGImageAlphaNoneSkipLast to ignore the alpha channel is possible. When you want to directly obtain non-premultiplied pixel data such as kCGImageAlphaLast is not allowed, only premultiplied pixel data can be used. Here we control the color space to BGRA, using the following parameters.

uint8_t* bitmapData = (uint8_t *)calloc(pixelCount * 4.sizeof(uint8_t));
int bytesPerRow = 4 * width;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(a);CGContextRef context = CGBitmapContextCreate(bitmapData, width, height, 8, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
if(! context) {return nullptr;
}
CGContextDrawImage(context, CGRectMake(0.0, width, height), cgImage);

CGColorSpaceRelease(colorSpace);             
CGContextRelease(context);
Copy the code

The RGB elements are then manipulated to obtain non-pre-multiplied pixel data.

auto iter = bitmapData;
int temp;
for (int i = 0; i < pixelCount; i++) {
    uint8_t alpha = *(iter + 3);
    if(alpha ! =0) {
        for (int j = 0; j < 3; j++) {
            temp = *iter * 255;
            *iter++ = temp /alpha;
        }
        iter++;
    }else {
        iter += 4; }}Copy the code

There should be plenty of room for optimization, such as skipping certain actions for images without transparency.