QCAR :: Image for UIImage - CoreGraphics and crash saving
I am using Vuforia trying to get pixels from camera instance and convert to UIImage
- (UIImage *)createUIImage:(const QCAR::Image *)qcarImage
{
int width = qcarImage->getWidth();
int height = qcarImage->getHeight();
int bitsPerComponent = 8;
int bitsPerPixel = QCAR::getBitsPerPixel(QCAR::RGB888);
int bytesPerRow = qcarImage->getBufferWidth() * bitsPerPixel / bitsPerComponent;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNone;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, qcarImage->getPixels(), QCAR::getBufferSize(width, height, QCAR::RGB888), NULL);
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
CGImageRef imageRefRetain = CGImageRetain(imageRef);
UIImage *image = [UIImage imageWithCGImage:imageRefRetain];
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef);
CGImageRelease(imageRef);
return image;
}
After getting the image, I add it to the mutable array.
After getting an image from a mutable array, I get a crash on every method i.e..
UIImageView setImage:
or even calling
NSData* comppressedData = UIImageJPEGRepresentation(image,1);
The only way to make my application non-crash is to add the following
- (UIImage *)createUIImage:(const QCAR::Image *)qcarImage
{
...
UIImage *image = [UIImage imageWithCGImage:imageRefRetain];
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpaceRef);
CGImageRelease(imageRef);
NSData* comppressedData = UIImageJPEGRepresentation(image,1);
UIImage* jpegImage = [UIImage imageWithData:comppressedData];
return jpegImage;
}
So I think the issue is with the release of the CGImageRef. I really need to understand what is going on and why the ref image is not valid after the existing method.
source to share
The problem is that it qcarImage
goes out of scope and takes the raw image data with it. Copy the image buffer and specifyCGDataProviderRef
NSData * imageData = [NSData dataWithBytes:qcarImage->getPixels()
length:QCAR::getBufferSize(width, height, QCAR::RGB888)];
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, imageData.bytes, imageData.length, NULL);
CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
//This was leaking memory, commenting out
//CGImageRef imageRefRetain = CGImageRetain(imageRef);
UIImage *image = [UIImage imageWithCGImage:imageRef];
source to share
The central issue is that creating a CGImageRef using the CGDataProvider wrapper doesn't copy the data, which it just wraps with a base address, which will disappear when Vuforia decides it needs it. It is very important to note that copying data will have significant success.
QCAR :: Image to CGImageRef
Note. I avoid mixing Obj-C in this method.
CGImageRef CGImageCreateWithQCARImage(const QCAR::Image *image, float rotation)
{
CGImageRef quartzImage = NULL;
if (image) {
QCAR::PIXEL_FORMAT pixelFormat = image->getFormat();
CGColorSpaceRef colorSpace = NULL;
switch (pixelFormat) {
case QCAR::RGB888:
colorSpace = CGColorSpaceCreateDeviceRGB();
break;
case QCAR::GRAYSCALE:
colorSpace = CGColorSpaceCreateDeviceGray();
break;
case QCAR::YUV:
case QCAR::RGB565:
case QCAR::RGBA8888:
case QCAR::INDEXED:
OARLogError("Image format conversion not implemented.");
break;
case QCAR::UNKNOWN_FORMAT:
OARLogError("Image format unknown.");
break;
}
if (colorSpace != NULL) {
size_t bitsPerComponent = 8;
size_t width = image->getWidth();
size_t height = image->getHeight();
const void *baseAddress = image->getPixels();
size_t totalBytes = QCAR::getBufferSize(width, height, pixelFormat);
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
size_t bitsPerPixel = QCAR::getBitsPerPixel(pixelFormat);
size_t bytesPerPixel = 4;
size_t bytesPerRow = image->getStride();
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
bitsPerComponent,
bytesPerPixel * width,
colorSpace,
bitmapInfo);
CFDataRef dataRef = CFDataCreate(NULL, (const UInt8 *)baseAddress, totalBytes);
CGDataProviderRef provider = CGDataProviderCreateWithCFData(dataRef);
CGImageRef intermediateImage = CGImageCreate(width,
height,
bitsPerComponent,
bitsPerPixel,
bytesPerRow,
colorSpace,
bitmapInfo,
provider,
NULL,
false,
renderingIntent);
CGFloat halfWidth = width / 2.f;
CGFloat halfHeight = height / 2.f;
CGContextTranslateCTM(context, halfWidth, halfHeight);
CGContextRotateCTM(context, rotation);
CGContextScaleCTM(context, -1.f, -1.f);
CGContextDrawImage(context, CGRectMake(-halfWidth, -halfHeight, width, height), intermediateImage);
CGImageRelease(intermediateImage);
quartzImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(provider);
CFRelease(dataRef);
}
}
return quartzImage;
}
CGImageRef to UIImage
Then again on the ground Obj-C.
QCAR::Image qcarImage = ...;
CGImageRef imageRef = CGImageCreateWithQCARImage(qcarImage);
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
You may be asking yourself why allocate the context bitmap ( NULL
) rather than wrap existing bytes ( baseAddress
)? The answer is that you cannot create a context with weakness 2. 24 bits per pixel is not allowed and you will get a null context if you try to set bytesPerRow to be "pixel width" * 3 bytes per pixel. So we create an image and then paint it in the context of the alpha restoration.
source to share