Bad Access in Background thread drawing - iphone

What I'm trying to Accomplish
Draw an image in a background thread
Convert the CGImage to a UIImage and add it to a UIImageView on the main thread.
Fade in the imageview, which is on a subclassed UICollectionViewCell from alpha value 0 to 1.
Do it all so theres no choppiness when scrolling the UICollectionView
The Problem
The behavior, at first, acts normally, but quickly digressed into unpredictableness, and usually and quickly resulted in EXC_BAD_ACCESS errors, happening somewhere in the process of converting uiimage to cgimage or vice versa.
The Code
//setup for background drawing
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context;
context = CGBitmapContextCreate(NULL, 250, backgroundHeight - 112, 8, 250 * 4, colorSpaceRef, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colorSpaceRef);
//Take the image I want drawn, convert to cgimage for background drawing
CGImageRef image = contentImage.CGImage;
CGContextDrawImage(context, CGRectMake(0, 0, 250, backgroundHeight - 112), image);
CGImageRelease(image);
CGImageRef outputImage = CGBitmapContextCreateImage(context);
imageRef = outputImage;
[self performSelectorOnMainThread:#selector(addImageToView) withObject:nil waitUntilDone:YES];
CGContextRelease(context);
CGImageRelease(outputImage);
The addImageToView Method simply create an image and adds to my UIImageView
UIImage * image = [UIImage imageWithCGImage:imageRef];
[photoView setImage:image];
These methods get called during the cellForRowAtIndexPath method, along with my method fadeInImage
[UIView animateWithDuration:.6 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
photoView.alpha = 1;
}completion:nil];
When I run, I get the Bad Access calls and crashing. I'm guessing it has something to do with the main thread and the background threads passing the images between one another. Any ideas? Thanks guys.

I think, as long as you didnt create an image with CGImageCreate or used CGImageRetain, you dont have to use CGImageRelease(image);. It should release automatically once you stop using it. Check it out.
Apple documentation

Don't release first image CGImageRelease(image);
And probably it is better to change:
CGImageRef outputImage = CGBitmapContextCreateImage(context);
imageRef = outputImage;
[self performSelectorOnMainThread:#selector(addImageToView) withObject:nil waitUntilDone:YES];
To:
CGImageRef outputImage = CGBitmapContextCreateImage(context);
UIImage* result = [UIImage imageWithCGImage:imageRef]
[self performSelectorOnMainThread:#selector(addImageToView:) withObject:result waitUntilDone:YES];
And then:
- (void) addImageToView:(UIImage*) image {
[photoView setImage:image];
}
Or even:
[photoView performSelectorOnMainThread:#selector(setImage:) withObject:result waitUntilDone:YES];

Related

Draw Text in Background thread ios

What I'm trying to Accomplish
I have a UICollectionView in which I'm trying to render all of the drawing in the background, then display when finished with a fade in animation.
I'm already doing this well with Images, but some of the drawing is text only. I need to size the size the text appropriately then draw it in the background.
It's potentially a lot of text and creates stuttering when done on main thread.
How I'm trying to do it
I was using CGBitmapContextCreate for images, so I tried to do it with text as well:
-(void)drawTextFromBundle
{
UIFont * font = [UIFont AvenirLTStdBlackObliqueWithSize:35]; //custom font
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, 250, backgroundHeight - 112, 8, 250 * 4, colorSpaceRef, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colorSpaceRef);
[_text drawInRect:CGRectMake(0, 0, 250, backgroundHeight - 112) withFont:font];
CGImageRef outputImage = CGBitmapContextCreateImage(context);
imageRef = outputImage;
[self performSelectorOnMainThread:#selector(finishDrawingImage) withObject:nil waitUntilDone:YES];
CGContextRelease(context);
CGImageRelease(outputImage);
});
}
Details
This is obviously not a right way to do it because I'm getting many errors, all involving Core Graphics text functions, similar to <Error>: CGContextSetFont: invalid context 0x0
I know there is UIGraphicsGetCurrentContext but I wasn't sure if this was thread safe, as I've heard it wasn't.
To note, this method is indeed getting called from within a -drawRect: method. The same exact context parameters are working for my images.
What can I do to get the text drawn to any customization I want, all done safely in the background? Bonus points if you can tell me how to do it while shrinking the text to fit the appropriate size.
Thanks again SO team.
When you change the UI , you need in the main thread (main queue).
try to put the below in the main queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, 250, backgroundHeight - 112, 8, 250 * 4, colorSpaceRef, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
CGColorSpaceRelease(colorSpaceRef);
dispatch_async(dispatch_get_main_queue(), ^{
[_text drawInRect:CGRectMake(0, 0, 250, backgroundHeight - 112) withFont:font];
CGImageRef outputImage = CGBitmapContextCreateImage(context);
imageRef = outputImage;
[self performSelectorOnMainThread:#selector(finishDrawingImage) withObject:nil waitUntilDone:YES];
});
CGContextRelease(context);
CGImageRelease(outputImage);
});

create UIImage from UILabel?

I can use the following code to create image from UIView.
However the same code won't capture the text of UILabel.(it captures label's background color though)
How can I create image of UILabel with text?
CGSize size = view.bounds.size;
CGContextRef context = CreateARGBBitmapContext(size);
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[view.layer renderInContext: context];
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage* img = [UIImage imageWithCGImage: imageRef];
CGImageRelease(imageRef);
CGContextRelease(context);
In the viewcontroller, select the label and on the right side, you should be able to set an image to the label.
Also, you might be having a problem with layers... [self addChild: label z:0 tag:1];
Just in case you don't know, z:0 means the layer. So something with z:-1 would be below a picture with z:0. You could just set the text ontop of the label

Erase a rect of an UIImageView

I want to know, how can I erase a custom rect (with, for example, an UIView in IB or something else) of an UIImageView in order to display an other UIImageView positioned underneath.
I didn't manage to do it using some response in the forum...
This will clear a rect in an image:
- (UIImage *)clearRect:(CGRect)rect inImage:(UIImage *)image {
if (UIGraphicsBeginImageContextWithOptions != NULL)
UIGraphicsBeginImageContextWithOptions([image size], NO, 0.0);
else
UIGraphicsBeginImageContext([image size]);
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
CGContextClearRect(context, rect);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
Just load your image and clear the rects before assigning it to the image view:
UIImage *image = [UIImage imageNamed:#"Image.png"];
UIImage *maskedImage = [self clearRect:CGRectMake(10.0, 10.0, 50.0, 50.0) inImage:image];
[imageView setImage:maskedImage];
probably not the best solution but you can do the other way and take the 4 parts around the rect separately and combine them afterwards without the inner rect. You would repeat this as long as you have rect's to crop out.
You cannot clear the UIImageView itself because this just draws the UIImage. So you have erase the rect in the UIImage. Create a bitmap context, draw the image into it. Erase the part you want to "see through" with CGContextClearRect. When create a new new image from the bitmap context.

How to scale up and crop a UIImage?

Here's the code I have but it's crashing ... any ideas?
UIImage *tempImage = [[UIImage alloc] initWithData:imageData];
CGImageRef imgRef = [tempImage CGImage];
[tempImage release];
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGRect bounds = CGRectMake(0, 0, width, height);
CGSize size = bounds.size;
CGAffineTransform transform = CGAffineTransformMakeScale(4.0, 4.0);
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, bounds, imgRef);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
What am I missing here? Basically just trying to scale image up and crop it to be same size as original.
Thanks
The problem is this line:
CGImageRef imgRef = [tempImage CGImage];
Or more precise, the direct follow-up of this line:
[tempImage release];
You are getting a CF object here, the CGImageRef. Core Foundation object only have the retain/release memory management, but no autoreleased objects. Hence, when you release the UIImage in the second row, the CGImageRef will be deleted as well. And this again means that it's undefined when you try to draw it down there.
I can think of three fixes:
use autorelease to delay the release of the image: [tempImage autorelease];
move the release to the very bottom of your method
retain and release the image using CFRetain and CFRelease.
Try this one:
-(CGImageRef)imageCapture
{
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect rect= CGRectMake(0,0 ,320, 480);
CGImageRef imageRef = CGImageCreateWithImageInRect([viewImage CGImage], rect);
return imageRef;
}
use the below line whenever you want to capture the screen
UIImage *captureImg=[[UIImage alloc] initWithCGImage:[self imageCapture]];

EXC_BAD_ACCESS in drawRect

The code below "sometimes" causes a crash (EXC_BAD_ACCESS) when run on the device. Never on the simulator.
To reproduce it I keep overlaying a modal view controller over my table view controller. It usually happens when the modal view controller is dismissed.
Any ideas why this happens?
CGContextRef context = UIGraphicsGetCurrentContext();
//set the background of the cell
[self.backgroundColor set];
CGContextFillRect(context, rect);
// get cached image
UIImage *image = [[ImageUtil sharedInstance] getImageByRouteType:route.type];
CGSize imageSize = CGSizeMake(IMAGE_WIDTH, IMAGE_WIDTH);
// DEBUGGER STOPS ON THIS NEXT LINE, image object is fine though
[image drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
[...]
Thanks
If you use drawInRect in a multiple tread situation like NSOperationQueue, try use a lock to avoid "drawInRect" is called in more than one thread. I met a similar issue and solved it in this way.
#synchronized([UIImage class]){
UIGraphicsBeginImageContext(newSize);
CGRect rect = CGRectMake(0.0, 0.0, newSize.width, newSize.height);
[self drawInRect: rect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}