iOS UImage 與 RGB 裸數據的相互轉換


iOS UImage 與 RGB 裸數據的相互轉換

Touch the data of image in iOS

Get data from a image

較簡單,根據已有的 image 的屬性,創建 CGBitmapContext, 這個 context 是帶有直接訪問的指針的。然后將 Image 繪制到這個 context, 得到裸數據。

Code:

    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(srcImg.CGImage);
    CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceRGB();

    float width = srcImg.size.width;
    float height = srcImg.size.height;

    // Get source image data
    uint8_t *imageData = (uint8_t *) malloc(width * height * 4);

    CGContextRef imageContext = CGBitmapContextCreate(imageData,
            width, height,
            8, static_cast<size_t>(width * 4),
            colorRef, alphaInfo);

    CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), srcImg.CGImage);
    CGContextRelease(imageContext);
    CGColorSpaceRelease(colorRef);

拿到指針就可以操作數據了。

需要注意的地方:alphaInfo, 通常我處理的圖像都是帶有透明度的,但是 RGBA 和 ARGB 都有遇到過,所以需要看清楚這個信息是哪一種,列舉如下:

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 */
};

一般來說我們的工程都會打開一個png圖片壓縮的選項,所以我們拿到的 image 一般來說是 premultiplied 的,也就是說,RGB的值已經是和alpha值相乘的結果了,在 OpenGL 紋理操作的時候要注意。

Create a image from raw data

1) Create BitmapContext and get image from it

	size_t bitsPerComponent = 8;
    size_t bitsPerPixel = 32;
    size_t bytesPerRow = static_cast<size_t>(4 * outWidth);
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    // set the alpha mode RGBA
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;

    ////
    // This method is much simple and without coordinate flip
    // You can use this method either
    // but the UIGraphicsBeginImageContext() is much more modern.
    ////

    CGContextRef cgBitmapCtx = CGBitmapContextCreate(outData,
            static_cast<size_t>(outWidth),
            static_cast<size_t>(outHeight),
            bitsPerComponent,
            bytesPerRow,
            colorSpaceRef,
            bitmapInfo);

    CGImageRef cgImg = CGBitmapContextCreateImage(cgBitmapCtx);

    UIImage *retImg = [UIImage imageWithCGImage:cgImg];

	CGContextRelease(cgBitmapCtx);
    CGColorSpaceRelease(colorSpaceRef);
    free(outData);

主要方法就是 CGBitmapContextCreate 直接將數據地址 outData 作為初始化參數提供,這樣這個 context 就是帶有正確數據的了,然后就直接獲得 CGImage 了。

2) CGDataProvider --> CGImage --> UIImage

這個方法的思路就是直接使用 CGImageCreate() 函數直接創建 CGImage.

    size_t bitsPerComponent = 8;
    size_t bitsPerPixel = 32;
    size_t bytesPerRow = static_cast<size_t>(4 * outWidth);
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();

    // set the alpha mode RGBA
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;

    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, outData, outDataLength, NULL);

    CGImageRef imageRef = CGImageCreate(outWidth, outHeight,
            bitsPerComponent, bitsPerPixel, bytesPerRow,
            colorSpaceRef, bitmapInfo, provider,
            NULL, NO, renderingIntent);
    
    UIImage *retImage1 = [UIImage imageWithCGImage:imageRef];

創建 provider 的時候無需回調函數,故直接提供 NULL.

創建 CGImage 時需要提供詳細的配置參數,其中部分參數和創建 CGBitmapContext 相同,額外需要提供的就是默認參數以及不需要使用的特性比如 decode array 等等。得到 CGImageRef 后可以直接得到 UIImage 對象,但是我發現我的同事寫了如下一段代碼:

    UIGraphicsBeginImageContext(outSize);
    // the same: UIGraphicsBeginImageContextWithOptions(outSize, NO, 1.0f);

    CGContextRef cgCtx = UIGraphicsGetCurrentContext();
    CGContextSetBlendMode(cgCtx, kCGBlendModeCopy);

    CGContextDrawImage(cgCtx, CGRectMake(0.0, 0.0, outWidth, outHeight), imageRef);

    UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

這段代碼的功能是:創建CGContext 然后將 CGimage 繪制到當前的 Context 上面,再得到 UIImage.

基本可以認為是 [UIImage imageWithCGImage:imageRef] 我的不知道為什么同事這樣寫,可能是這個方法是有什么坑的,我暫時沒有遇到。所以我將這段代碼先記下來,留着以后用。

關於 UIGraphicsBeginImageContext() 方法,apple 的介紹如下:

iOS Note: iOS applications should use the function UIGraphicsBeginImageContextWithOptions instead of using the low-level Quartz functions described here. If your application creates an offscreen bitmap using Quartz, the coordinate system used by bitmap graphics context is the default Quartz coordinate system. In contrast, if your application creates an image context by calling the function UIGraphicsBeginImageContextWithOptions, UIKit applies the same transformation to the context’s coordinate system as it does to a UIView object’s graphics context. This allows your application to use the same drawing code for either without having to worry about different coordinate systems. Although your application can manually adjust the coordinate transformation matrix to achieve the correct results, in practice, there is no performance benefit to doing so.

上面所述的 low-level Quartz function 就是我們上面用的 CGBitmapContextCreate 這一類方法。所以用這個新的方法直接就將 創建好的 bitmapContext 綁定到當前狀態。仍然調用 CGContextDrawImage() 函數,將 CGImage 繪制到指定的 context 上面。然后再獲取 UIImage. 總的來說,這個和我們之前的那一套原理是一樣的,應該說這樣是更新式的做法,推薦的做法。

參考

Quartz 2D Programming Guide

How do I create a CGImage with RGB data?

Converting RGB data into a bitmap in Objective-C++ Cocoa

CGImage to UIImage doesn't work


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM