JavaEar 专注于收集分享传播有价值的技术资料

How do I write a CoreGraphics CGImageRef from custom data stream?

What i'm hoping to end with is a Core Graphics CGImageRef that is drawn to the screen in an overridden -(void)drawRect:(CGRect)rect method. Here's what i've got so far.

- (void)drawRect:(CGRect)rect
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    const CGFloat _x = CGRectGetMinX(rect);
    const CGFloat _y = CGRectGetMinY(rect);
    const CGFloat _w = CGRectGetWidth(rect);
    const CGFloat _h = CGRectGetHeight(rect);

    const CGFloat scale = [[UIScreen mainScreen] scale];

    const int pixelsWide = (_w * scale);
    const int pixelsHigh = (_h * scale);

    const size_t colorValues = 4;
    const int rawMemorySize = (pixelsWide * pixelsHigh * colorValues);

    // raw pixel data memory
    UInt8 pixelData[rawMemorySize];

    // fill the raw pixel buffer with arbitrary gray color for test

    for(size_t ui = 0; ui < rawMemorySize; ui++)
        pixelData[ui] = 255; // black

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CFDataRef rgbData = CFDataCreate(NULL, pixelData, rawMemorySize);
    CGDataProviderRef provider = CGDataProviderCreateWithCFData(rgbData);

    CGImageRef rgbImageRef = CGImageCreate(pixelsWide, pixelsHigh, 8, (colorValues * colorValues), (pixelsWide * colorValues), colorspace, kCGBitmapByteOrderDefault, provider, NULL, true, kCGRenderingIntentDefault);


    //Draw the bitmapped image
    CGContextDrawImage(currentContext, CGRectMake(_x, _y, pixelsWide, 64), rgbImageRef);

I'm thrown an EXC_BAD_ACCESS error within the for loop.

I think i'm close, but i'm not exactly sure. Any ideas what might be the culprit? Thank you in advance :)


  1. A couple more things to be aware of.

    The rect argument to drawRect is only the dirty rectangle, which may be smaller than self.bounds. Use self.bounds unless your code is optimized to only draw the dirty rectangle.

    Allocating the pixelData array on the stack is a bad idea, and may be the cause of the error. It all depends on how big the view is. I suspect that AaronGolden was using a smaller view than you were, and hence didn't have any problems. It's safer to calloc the array, and free it at the end after you're done using it. Note that calloc will clear the array to transparent black.

    Here's some sample code that I've used to do this sort of thing in the past. Even though the pixel array is created with a calloc, you can still access it like a standard array (e.g. pixels[100] = 0xff0000ff; is perfectly valid). However, for performance reasons, I typically use a pointers to access individual pixels.

    const CGFloat scale = [[UIScreen mainScreen] scale];
    int width  = self.bounds.size.width  * scale;
    int height = self.bounds.size.height * scale;
    uint32_t *pixels = calloc( width * height, sizeof(uint32_t) );
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate( pixels, width, height, 8, width * sizeof(uint32_t), colorSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast );
    uint8_t *bufptr = (uint8_t *)pixels;
    for ( int y = 0; y < height; y++)
        for ( int x = 0; x < width; x++ )
            bufptr[0] = redValue;
            bufptr[1] = greenValue;
            bufptr[2] = blueValue;
            bufptr[3] = alphaValue;
            bufptr += 4;
    CGImageRef newCGImage = CGBitmapContextCreateImage( context );
    CGContextRelease( context );
    CGColorSpaceRelease( colorSpace );
    free( pixels );
  2. 参考答案2
  3. Here are a couple of suggestions. I tried your code and didn't hit a bad access in the for loop, like you describe, but:

    You don't need a for loop to set everything to 255 like that. You can use memset:

    memset(pixelData, 255, sizeof(pixelData));

    And in your call to CGImageCreate, the argument immediately after your literal 8 is wrong. That's supposed to be the number of bits per pixel, which is 8 * colorValues not colorValues * colorValues. So create your image like:

    CGImageRef rgbImageRef = CGImageCreate(pixelsWide, pixelsHigh, 8, 8 * colorValues, pixelsWide * colorValues, colorspace, kCGBitmapByteOrderDefault, provider, NULL, true, kCGRenderingIntentDefault);

    Finally, I noticed that you put a comment saying the 255's corresponded to black, but that's not right. If you make all your pixel data bytes 255 your image should be full white. You draw your image with a height of 64 points in your CGContextDrawImage call, and indeed I see a white bar 64 points tall when I run your code with these corrections.