When I create a CIImage by calling this routine I get the malloc error in the title. When I call initWithBitmapData directly (not within the createCIUimageFromData routine) then it works fine.
I have seen references to possible bugs in iOS that might be related, but I can't tell for sure and I certainly suspect my code more than Apple's!
My guess is somehow my additional redirection is screwing things up, but it's cleaner to have the separate routine than to embed the code wherever I need it.
Thank you.
Fails:
- (CIImage *) createCIimageFromData : (unsigned char *)pData width : (int32_t)width height : (int32_t)height
{
/*
Once we have the raw data, we convert it into a CIImage.
The following code does the required work.
*/
NSLog(#"entering createciimage\n");
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceGray();
NSData *_pixelsData = [NSData dataWithBytesNoCopy:pData length:(sizeof(unsigned char)*width*height) freeWhenDone:YES ];
CIImage *_dataCIImage = [[CIImage alloc] initWithBitmapData:_pixelsData bytesPerRow:(width*sizeof(unsigned char)) size:CGSizeMake(width,height) format:kCIFormatR8 colorSpace:colorSpaceRef];
CGColorSpaceRelease(colorSpaceRef);
/*
newImage is the final image
Do remember to release the allocated parts.
*/
NSLog(#"done ciimage\n");
return _dataCIImage;
}
Works:
void prepData(unsigned char *pData, // source-destination
int strideSrc, // stride
int width,
int height,
double amount,
int deltaLimit,
id owner)
{
//[owner createCIimageFromData:pData width:width height:height]; // <-- commented out
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceGray();
NSData *_pixelsData = [NSData dataWithBytesNoCopy:pData length:(sizeof(unsigned char)*width*height) freeWhenDone:YES ];
CIImage *_dataCIImage = [[CIImage alloc] initWithBitmapData:_pixelsData bytesPerRow:(width*sizeof(unsigned char)) size:CGSizeMake(width,height) format:kCIFormatR8 colorSpace:colorSpaceRef];
CGColorSpaceRelease(colorSpaceRef);
// . . .
}
Evidently the problem is caused when the NSData object attempts to free the data. To avoid the problem, use freeWhenDone:NO and then free the data after you're done with the CIImage.
Related
I'm writing some tests to detect changes to the lossless image formats (starting with PNG) and finding that on Linux and Windows the image loading mechanisms work as expected - but on iOS (haven't tried on macOS) the image data is always being very slightly changed if I load from a PNG file on disk or save to a PNG file on disk using Apples' methods.
If I create a PNG using any number of tools (GIMP/Paint.NET/whatever) and I use my cross platform PNG reading code to examine each pixel of the resulting loaded data - it matches exactly what I did in the tool (or programmatically generated with my cross platform PNG writing code.) Subsequent reloading into the creation tools yields the exactly same RGBA8888 components.
If I load the PNG from disk using Apple's:
NSString* pPathToFile = nsStringFromStdString( sPathToFile );
UIImage* pImageFromDiskPNG = [UIImage imageWithContentsOfFile:pPathToFile];
...then examine the resulting pixels it's similar but not the same. I would expect, like on other platforms, for the data to be identical.
Now, interestingly, if I load the data from the PNG using my code, and creating a UIImage with it (using some code I show below) I can use that UIImage and display it, copy it, whatever, and if I examine the pixel data - it's exactly what I gave it to begin with (which is why I think it's the loading saving part where Apple is modifying the image data.)
When I instruct it to save what I know to be a good UIImage with perfect pixel data, and then load that Apple saved image with my PNG loading code, I can see it's not exactly the same data. I have used several methods by which Apple suggests to save UIImage's to PNG (UIImagePNGRepresentation primarily.)
The only thing I can really think of is that Apple when loading or saving on iOS doesn't truly support RGBA8888 and is doing some sort of premultiply with the alpha channel - I speculate about this because when I first started using the code I posted below I was choosing
kCGImageAlphaLast
...instead of what I ultimately had to use
kCGImageAlphaPremultipliedLast
because the former is not supported on iOS for some reason.
Does anyone have any experience around this issue on iOS?
Cheers!
The code I use to push/pull RGBA8888 data into and out of UIImages is below:
- (unsigned char *) convertUIImageToBitmapRGBA8:(UIImage*)image dataSize:(NSUInteger*)dataSize
{
CGImageRef imageRef = image.CGImage;
// Create a bitmap context to draw the uiimage into
CGContextRef context = [self newBitmapRGBA8ContextFromImage:imageRef];
if(!context) {
return NULL;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
CGRect rect = CGRectMake(0, 0, width, height);
// Draw image into the context to get the raw image data
CGContextDrawImage(context, rect, imageRef);
// Get a pointer to the data
unsigned char *bitmapData = (unsigned char *)CGBitmapContextGetData(context);
// Copy the data and release the memory (return memory allocated with new)
size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context);
size_t bufferLength = bytesPerRow * height;
unsigned char *newBitmap = NULL;
if(bitmapData) {
*dataSize = sizeof(unsigned char) * bytesPerRow * height;
newBitmap = (unsigned char *)malloc(sizeof(unsigned char) * bytesPerRow * height);
if(newBitmap) { // Copy the data
for(int i = 0; i < bufferLength; ++i) {
newBitmap[i] = bitmapData[i];
}
}
free(bitmapData);
} else {
NSLog(#"Error getting bitmap pixel data\n");
}
CGContextRelease(context);
return newBitmap;
}
- (CGContextRef) newBitmapRGBA8ContextFromImage:(CGImageRef) image
{
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
uint32_t *bitmapData;
size_t bitsPerPixel = 32;
size_t bitsPerComponent = 8;
size_t bytesPerPixel = bitsPerPixel / bitsPerComponent;
size_t width = CGImageGetWidth(image);
size_t height = CGImageGetHeight(image);
size_t bytesPerRow = width * bytesPerPixel;
size_t bufferLength = bytesPerRow * height;
colorSpace = CGColorSpaceCreateDeviceRGB();
if(!colorSpace) {
NSLog(#"Error allocating color space RGB\n");
return NULL;
}
// Allocate memory for image data
bitmapData = (uint32_t *)malloc(bufferLength);
if(!bitmapData) {
NSLog(#"Error allocating memory for bitmap\n");
CGColorSpaceRelease(colorSpace);
return NULL;
}
//Create bitmap context
context = CGBitmapContextCreate( bitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big );
if( !context )
{
free( bitmapData );
NSLog( #"Bitmap context not created" );
}
CGColorSpaceRelease( colorSpace );
return context;
}
- (UIImage*) convertBitmapRGBA8ToUIImage:(unsigned char*) pBuffer withWidth:(int) nWidth withHeight:(int) nHeight
{
// Create the bitmap context
const size_t nColorChannels = 4;
const size_t nBitsPerChannel = 8;
const size_t nBytesPerRow = ((nBitsPerChannel * nWidth) / 8) * nColorChannels;
CGColorSpaceRef oCGColorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef oCGContextRef = CGBitmapContextCreate( pBuffer, nWidth, nHeight, nBitsPerChannel, nBytesPerRow , oCGColorSpaceRef, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrder32Big );
// create the image:
CGImageRef toCGImage = CGBitmapContextCreateImage(oCGContextRef);
UIImage* pImage = [[UIImage alloc] initWithCGImage:toCGImage];
return pImage;
}
Based on your source code, it appears that you are using BGRA (RGB + alpha channel) data which is imported from PNG source images. When you attach images to an iOS project, Xcode will pre-process each image to pre-multiply the RGB and A channel data for performance reasons. So, by the time the image is loaded on the iPhone device, the RGB values for non-opaque (not A = 255) pixels can be changed. The RGB numbers are modified, but the actual image data will come out the same when rendered to the screen by iOS. This is known as "straight alpha" vs "pre-multiplied alpha".
Store image data directly, don't use UIImage.pngData() to convert image to data, this method will change the pixel's rgb value if the pixel has alpha channel.
Look like the code snippet below, it use the #autoreleasepool block in this method.
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
// while downloading huge amount of images
// autorelease the bitmap context
// and all vars to help system to free memory
// when there are memory warning.
// on iOS7, do not forget to call
// [[SDImageCache sharedImageCache] clearMemory];
if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
return nil;
}
#autoreleasepool{
// do not decode animated images
if (image.images != nil) {
return image;
}
CGImageRef imageRef = image.CGImage;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
if (anyAlpha) {
return image;
}
// current
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
if (unsupportedColorSpace) {
colorspaceRef = CGColorSpaceCreateDeviceRGB();
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
// to create bitmap graphics contexts without alpha info.
CGContextRef context = CGBitmapContextCreate(NULL,
width,
height,
bitsPerComponent,
bytesPerRow,
colorspaceRef,
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
// Draw the image into the context and retrieve the new bitmap image without alpha
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
scale:image.scale
orientation:image.imageOrientation];
if (unsupportedColorSpace) {
CGColorSpaceRelease(colorspaceRef);
}
CGContextRelease(context);
CGImageRelease(imageRefWithoutAlpha);
return imageWithoutAlpha;
}
}
(the method is in SDWebImageDecoder.m, the version is SDWebImage
3.7.0).
I am confused with it, because these temp objects will be released after the method return, so is it necessary to use the autoreleasepool to release them only a little before? the autoreleasepool will also occupy the memory.
anyone can explain it, thanks!
Go through this apple doc. It is mentioned that
Three occasions when you might use your own autorelease pool blocks:
If you are writing a program that is not based on a UI framework, such as a command-line tool.
If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
If you spawn a secondary thread.
You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects. (See Autorelease Pool Blocks and Threads for details.)
I am not sure about the first point, but SDWebImage will surely use autoreleasepool for other two points.
The following code works the way I want it to, but every time I call it, Instruments tells me I have one CGImage memory leak. I've been having trouble understanding what to release and when. The following is from the #interface section of my file.
CGImageRef depthImageRef;
char *depthPixels;
NSData *depthData;
In the next code, I basically alter depthPixels and then store the result in a new depthImageRef.
size_t width = CGImageGetWidth(depthImageRef);
size_t height = CGImageGetHeight(depthImageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(depthImageRef);
size_t bitsPerPixel = CGImageGetBitsPerPixel(depthImageRef);
size_t bytesPerRow = CGImageGetBytesPerRow(depthImageRef);
for (int row = 0; row < height; row += 1) {
for (int bitPlace = 0; bitPlace < bytesPerRow; bitPlace += 4) {
CGPoint pointForHeight = CGPointMake((bitPlace/4) - place.x, row - place.y);
int distanceFromLocation = sqrt(pow(place.x - pointForHeight.x, 2) + pow(place.y - pointForHeight.y, 2));
int newHeight = blopHeight - (5 - sizeSlider.value)*distanceFromLocation;
NSInteger baseBitPlace = row*bytesPerRow + bitPlace;
CGFloat currentHeight = depthPixels[baseBitPlace];
if (newHeight > currentHeight) {
depthPixels[baseBitPlace] = newHeight;
}
}
}
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(depthImageRef);
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, depthPixels, [depthData length], NULL);
depthImageRef = CGImageCreate (
width,
height,
bitsPerComponent,
bitsPerPixel,
bytesPerRow,
colorspace,
bitmapInfo,
provider,
NULL,
false,
kCGRenderingIntentDefault
);
CGColorSpaceRelease(colorspace);
CFRelease(provider);
CGDataProviderRelease(provider);
I believe the leak is created because I keep creating depthImageRef but never release it. I've tried putting CGImageRelease(depthImageRef) at various places and setting depthImageRef to nil, and usually when I do this I get crashes. Thanks!
Probably, you must be turning the depthImageRef back to UIImage somewhere. Like
UIImage *depthImage = [UIImage imageWithCGImage:depthImageRef];
Once you have the depthImage you can release depthImageRef. It should not cause any crash.
If you are recreating depthImageRef multiple times by calling the above code, you are leaking memory by repeatedly creating CGImageRefs and not releasing them. If you are recreating dataImageRef you should release the old image immediately before creating the new one.
// works, even if depthImageRef is NULL such as in the initial case.
CGImageRelease(depthImageRef);
depthImageRef = CGImageCreate ( //...
Also be sure you are calling CGImageRelease in your dealloc method and freeing your depthPixels buffer in dealloc. CGDataProviderRelease won't release the buffer you pass into it unless you pass a callback to already release that buffer. You don't need the call for CFRelease(provider) since you are calling CGDataProviderRelease(provider).
All you have to make sure is to release the CGImageRef once you are done with it's use.
CGImageRelease(cgImage);
I'm trying to fetch an image of PDF page and edit it. Everything works fine, but there is an huge memory growth. Profiler says that there is no any memory leak. Also profiler says that 90% memory allocated at UIGraphicsGetCurrentContext() and UIGraphicsGetImageFromCurrentImageContext(). This code not runs in the loop and there is no need to wrap it with #autorelease.
if ((pageRotation == 0) || (pageRotation == 180) ||(pageRotation == -180)) {
UIGraphicsBeginImageContextWithOptions(cropBox.size, NO, PAGE_QUALITY);
}
else {
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(cropBox.size.height, cropBox.size.width), NO, PAGE_QUALITY);
}
CGContextRef imageContext = UIGraphicsGetCurrentContext();
[PDFPageRenderer renderPage:_PDFPageRef inContext:imageContext pagePoint:CGPointMake(0, 0)];
UIImage *pageImage = UIGraphicsGetImageFromCurrentImageContext();
[[NSNotificationCenter defaultCenter] postNotificationName:#"PAGE_IMAGE_FETCHED" object:pageImage];
UIGraphicsEndImageContext();
But debug shows that the memory growing occurs only when I start editing the fetched image. For image editing I use the Leptonica library. For example:
+(void) testAction:(UIImage *) image{
PIX * pix =[self getPixFromUIImage:image];
pixConvertTo8(pix, FALSE);
pixDestroy(&pix);
}
Before pixConvertTo8 app takes 13MB, after - 50MB. Obviously growth depends on image size. Converting method:
+(PIX *) getPixFromUIImage:(UIImage *) image{
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
UInt8 const* pData = (UInt8*)CFDataGetBytePtr(data);
Pix *myPix = (Pix *) malloc(sizeof(Pix));
CGImageRef myCGImage = [image CGImage];
myPix->w = CGImageGetWidth (myCGImage)-1;
myPix->h = CGImageGetHeight (myCGImage);
myPix->d = CGImageGetBitsPerPixel([image CGImage]) ;
myPix->wpl = CGImageGetBytesPerRow (myCGImage)/4 ;
myPix->data = (l_uint32 *)pData;
myPix->colormap = NULL;
myPix->text="text";
CFRelease(data);
return myPix;
}
P.S. Sorry for my terrible English.
I am getting the pixel colour values from touch points. I am successfully doing this but after sometimes app is giving the error( EXC_BAD_ACCESS(CODE=1,address=0x41f6864). Its memory allocation problem here is the source code for your reference.
- (UIColor *) getPixelColorAtLocation:(CGPoint)point {
UIColor* color = nil;
#try{
{
CGImageRef inImage = drawImage.image.CGImage;
// Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue
CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
if (cgctx == NULL)
{
return nil; /* error */
}
size_t w = CGImageGetWidth(inImage);
size_t h = CGImageGetHeight(inImage);
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, inImage);
// Now we can get a pointer to the image data associated with the bitmap
// context.
unsigned char *data = {0};
data=(unsigned char*) calloc(CGImageGetHeight(inImage) * CGImageGetWidth(inImage) , CGBitmapContextGetHeight(cgctx)*CGBitmapContextGetWidth(cgctx));
data= CGBitmapContextGetData (cgctx);
if( data !=NULL ) {
//offset locates the pixel in the data from x,y.
//4 for 4 bytes of data per pixel, w is width of one row of data.
int offset = 4*((w*round(point.y))+round(point.x));
// NSLog(#"%s111111",data);
int alpha = data[offset]; /////// EXC_BAD_ACCESS(CODE=1,address=0x41f6864)
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];
//NSLog(#"offset: %i colors: RGB A %i %i %i %i",offset,red,green,blue,alpha);
color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];
}
// When finished, release the context
//CGImageRelease(*data);
CGContextRelease(cgctx);
// Free image data memory for the context
if (data)
{
free(data);
}
}
#catch (NSException *exception) {
}
return color;
}
The memory management in your code appears to be wrong:
Declare data and pointlessly assign a value to it:
unsigned char *data = {0};
Allocate a memory block and store a reference to it in data - overwriting the pointless initialisation:
data = (unsigned char *)calloc(CGImageGetHeight(inImage) * CGImageGetWidth(inImage), CGBitmapContextGetHeight(cgctx) * CGBitmapContextGetWidth(cgctx));
Now get a reference to a different memory block and store it in data, throwing away the reference to the calloc'ed block:
data = CGBitmapContextGetData (cgctx);
Do some other stuff and then free the block you did not calloc:
free(data);
If you are allocating your own memory buffer you should pass it to CGBitmapContextCreate, however provided you are using iOS 4+ there is no need to allocate your own buffer.
As to the memory access error, you are doing no checks on the value of point and your calculation would appear to be producing a value of offset which is incorrect. Add checks on the values of point and offset and take appropriate action if they are out of bounds (you will have to decide what that should be).
HTH
The problem may cause by the point is out of image rect,so you can use
try{
int offset = 4*((w*round(point.y))+round(point.x));
int alpha = data[offset];
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];
color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f)
alpha:(alpha/255.0f)];
}catch(NSException e){
}
to avoid the EXC_BAD_ACCESS