Fork me on GitHub

8/10/2010

[Mac OS X] 如何得到 UIImage/CGImage 的 piexl 資料

透過 UIImagePickerControllerDelegate 取得影像的 UIImage 資料之後,該如何取得 UIImage 當中的像素資料,以進行接下來的影像處理,在此記錄一下。

首先參考的是這篇 Getting the pixel data from a CGImage object,當中提到的第一個方法 CGDataProviderCopyData 在 Mac OS X 10.5 以後才出現,相當方便但是無法確定自己得到的 color model 是否和影像一致,如當中的 WARNING 所說明:

WARNING: The pixel data returned by CGDataProviderCopyData has not been color matched and is in the format that the image is in, as described by the various CGImageGet functions (see for Getting Information About an Image more information)

比較保險的作法,也就是在 Mac OS X 10.5 之前的作法,是自行 create 一個 bitmap context,然後利用 CGDrawImage 將影像畫到這個你指定格式的 context 上頭,如此一來你就可以利用 CGBitmapContextGetData 來取得影像的像素資料!

IMPORTANT: 以下範例 creates a bitmap context with a 8-bits per component ARGB color space, draws the source image to this context, then retrieves the image bits in this color space from the context. Regardless of what the source image format is (CMYK, 24-bit RGB, Grayscale, and so on) it will be converted over to this color space. For more information about creating bitmap contexts for other pixel formats, see the Quartz 2D Programming Guide.

其中在利用 CGBitmapContextCreate 建立 bitmap context 時候有一個 colormodel 參數,該資料當中的 CGColorSpaceGenericRGB 已經被 deprecated 了,解決的方案是利用 CGColorSpaceCreateDeviceRGB() 取代,請參考 Warning: kCGColorSpaceGenericRGB is deprecated

針對像素處理完畢之後,需要由處理過後的 CGImage 創建一個新的 UIImage 然後指定到你要顯示的地方:
newImgRef = CGBitmapContextCreateImage(cgctx);
UIImage *newimg = [UIImage imageWithCGImage:newImgRef];
imageView.image = newimg;

參考上述兩點,整理的程式碼如下:

- (void)imagePickerController:(UIImagePickerController *)picker 
        didFinishPickingImage:(UIImage *)image
        editingInfo:(NSDictionary *)editingInfo 
{
 CGImageRef imageRef = image.CGImage;
 
 // Create the bitmap context
        cgctx = CreateARGBBitmapContext(imageRef);
        if (cgctx == NULL) 
        { 
        // error creating context
        return;
        }
 
 // Get image width, height.
        size_t w = CGImageGetWidth(imageRef);
        size_t h = CGImageGetHeight(imageRef);
        CGRect rect = {{0,0},{w,h}}; 
 
 // Draw the image to the bitmap context. Once we draw, the memory 
        // allocated for the context for rendering will then contain the 
        // raw image data in the specified color space.
        CGContextDrawImage(cgctx, rect, imageRef); 
 
 // Now we can get a pointer to the image data associated with the bitmap
        // context.
        void *data = CGBitmapContextGetData(cgctx);
 imagePtr = data;
 unsigned char A, R, G, B;
 
        if (imagePtr != NULL)
        { 
            // **** You have a pointer to the image data ****  
            // **** Do stuff with the data here ****
     for (int x = 0; x < w; x  ) {
         for (int y = 0; y < h; y  ) {
  
      A = imagePtr[(x (y*w))*4 + 0];
      R = imagePtr[(x (y*w))*4 + 1];
      G = imagePtr[(x (y*w))*4 + 2];
      B = imagePtr[(x (y*w))*4 + 3];  
  }
     }
        }
 
 newImgRef = CGBitmapContextCreateImage(cgctx);
 UIImage *newimg = [UIImage imageWithCGImage:newImgRef];
 
 
 // do something with selectedImage and originalImage
 imageView.image = newimg;
        [picker dismissModalViewControllerAnimated:YES];
}


其中的 CreateARGBBitmapContext 方法如下:

CGContextRef CreateARGBBitmapContext (CGImageRef inImage)
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
 
 // Get image width, height. We'll use the entire image.
    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
 
    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
 
    // Use the generic RGB color space.
    colorSpace = CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
    {
        fprintf(stderr, "Error allocating color space\n");
        return NULL;
    }
 
    // Allocate memory for image data. This is the destination in memory
    // where any drawing to the bitmap context will be rendered.
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL) 
    {
        fprintf (stderr, "Memory not allocated!");
        CGColorSpaceRelease( colorSpace );
        return NULL;
    }
 
    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits 
    // per component. Regardless of what the source image format is 
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate (bitmapData,
          pixelsWide,
          pixelsHigh,
          8,      // bits per component
          bitmapBytesPerRow,
          colorSpace,
          kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    {
        free (bitmapData);
        fprintf (stderr, "Context not created!");
    }
 
    // Make sure and release colorspace before returning
    CGColorSpaceRelease( colorSpace );
 
    return context;
}


參考資料:
如何把照片加到 iPhone Simulator 的 Library 當中
http://www.youtube.com/watch?v=1NqHnYGNAp8

OpenGL ES from the Ground Up: Table of Contents
http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-table-of.html

如何得到 CGImage 的 pixel data
http://developer.apple.com/mac/library/qa/qa2007/qa1509.html

Warning: kCGColorSpaceGenericRGB is deprecated
http://iphoneinaction.manning.com/iphone_in_action/2009/06/warning-kcgcolorspacegenericrgb-is-deprecated.html

Convert CGImage to UIImage
http://www.iphonedevsdk.com/forum/iphone-sdk-development/21806-convert-cgimage-uiimage.html

......

No comments:

Post a Comment