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();
}
Related
I am creating a native app on Xcode 4.3 and deploying to target iOS 5. The app is a basically a greeting card creator. I am having trouble figuring out how to save a portion of the screen from within the app.
What I want to do is this:
I am offering the user a button, that says "e-mail". when they click the button, the app should 'save' their card as an image and then 'paste' that into an email body.
The reason this is different than other answers on this website is that the area I want to save is made up of 4 'elements'. There is a background graphic that is the tilted card background, then there is a text field where users can type a message and then next to that is a picture area where they can choose their own picture to put on the card.
Here is a photo of what I am talking about:
http://marklopezdesigns.com/mydownloadz!/screenshotCard3.png
How do I save a 'composite' high res of these?
And then how do I get that into an email body message?
The reason i am asking how to 'save' it is because I want to be able to offer users another button that says "save to camera roll" and "send as message". I figure if I can understand how to save this high-res to a variable, then I should be off and running.
Thanks in advance for the help.
========
Here's the solution below
========
...so after a bit of fiddling. Finally got what I wanted. Here's the codebase I have in my method that fires upon touch of the "Save to Album" button:
- (IBAction)savePhoto{
CGRect rect;
rect = CGRectMake(11,50 ,305, 262);
UIView *cardViewer = [[UIView alloc] initWithFrame:rect];
UIGraphicsBeginImageContext(cardViewer.bounds.size);
//make view background transparent
cardViewer.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.0];
cardViewer.opaque = NO;
//stuff items into a subview for capturing
[self.view addSubview:cardViewer];
[cardViewer addSubview:self.tiltedCard];
[cardViewer addSubview:self.bigCardView];
[cardViewer addSubview:self.cardWords];
[cardViewer addSubview:self.photoView];
[cardViewer.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRef imageRef = CGImageCreateWithImageInRect([viewImage CGImage], rect);
UIImage *img = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
//put everything back where it belongs
[self.view addSubview:self.tiltedCard];
[self.view addSubview:self.bigCardView];
[self.view addSubview:self.cardWords];
[self.view addSubview:self.photoView];
[cardViewer removeFromSuperview];
}
To capture just an area of the screen, specify the bounds using CGRectMake.
CGRect rect = CGRectMake(50, 50, 100, 100);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
In the example above we are capturing a 100px by 100px region beginning at x:50px and y:50px.
maybe get the picture of the layer by rendering the layer context and grabbing the image, I use this for displaying fancy animations
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
To capture just an area of the screen, use the UIView or SubView and just specify the bounds using CGRectMake.
CGRect rect = CGRectMake(10, 10, 200, 200);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.YourView.layer renderInContext:context]; // Make sure here you are using the greetingcardView rather than self.view
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Note: self.view will capture the whole screen so simply render the view wherever.
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];
I want to take screenshot programatically. So that i write code for it like,
CGContextRef context = [self createBitmapContextOfSize:self.frame.size];
//not sure why this is necessary...image renders upside-down and mirrored
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, self.frame.size.height);
CGContextConcatCTM(context, flipVertical);
[self.layer renderInContext:context];
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage* background = [UIImage imageWithCGImage: cgImage];
CGImageRelease(cgImage);
self.currentScreen = background;
Here all code is in custom UIView of UIViewController. Now when i play UIImageView png sequence animation on UIView, then i didn't get updated changes in UIImageView which is subview of custom UIView, why? I got only result is UIImageView with first UIImage.
Basically i need to create an video of my game play like talking tom application.
Take the screenshot of your ImageView with something like this:
[[[yourImageView layer] presentationLayer] renderInContext:context];
Use NSTimer for calling that getScreenshot function.
Hope this helps
you should probably take a look at AVFoundation and specifically AVAssetWriter as a way of creating videos of your screen content.
Following code is use for the capture the image into the iPhone for that following code to be use.
CGRect rect = CGRectMake(0.0, 0.0,self.view.frame.size.width,self.view.frame.size.height-44);
NSValue *rectObj = [NSValue valueWithCGRect:rect];
CGRect rectRestored = [rectObj CGRectValue];
UIGraphicsBeginImageContext(CGSizeMake(rectRestored.size.width, rectRestored.size.height-44));
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);
Also store the image into the gallery.
Ive an image with particular resolution appearing on Iphone screen. I need to resize that image by getting info. as an alert from user(Width and height). Cud anyone help me with block of code for this????
WARNING: code typed in answer box, not tested. It's pretty close, though.
NSSize newSize = [self getSizeFromUser]; // you have to write this part.
CGRect theRect = CGRectMake (0, 0, newSize.width, newSize.height);
// Put image into new context
UIGraphicsBeginImageContext (newSize);
[theImage drawInRect: theRect];
//get the image from the context
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
[[compositeImage retain] autorelease];
UIGraphicsEndImageContext();
My original question:
I'm creating a simple drawing
application and need to be able to
draw over existing, previously drawn content in my drawRect.
What is the proper way to draw on top of existing content
without entirely replacing it?
Based on answers received here and elsewhere, here is the deal.
You should be prepared to redraw the
entire rectangle whenever drawRect
is called.
You cannot prevent the contents from being erased by doing the following:
[self setClearsContextBeforeDrawing: NO];
This is merely a hint to the graphics engine that there is no point in having it pre-clear the view for you, since you will likely need to re-draw the whole area anyway. It may prevent your view from being automatically erased, but you cannot depend on it.
To draw on top of your view without erasing, do your drawing to an off-screen bitmap context (which is never cleared by the system.) Then in your drawRect, copy from this off-screen buffer to the view.
Example:
- (id) initWithCoder: (NSCoder*) coder {
if (self = [super initWithCoder: coder]) {
self.backgroundColor = [UIColor clearColor];
CGSize size = self.frame.size;
drawingContext = [self createDrawingBufferContext: size];
}
return self;
}
- (CGContextRef) createOffscreenContext: (CGSize) size {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width*4, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
return context;
}
- (void)drawRect:(CGRect) rect {
UIGraphicsPushContext(drawingContext);
CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
UIImage *uiImage = [[UIImage alloc] initWithCGImage:cgImage];
UIGraphicsPopContext();
CGImageRelease(cgImage);
[uiImage drawInRect: rect];
[uiImage release];
}
TODO: can anyone optimize the drawRect so that only the (usually tiny) modified rectangle region is used for the copy?
It is fairly common to draw everything in an offscreen image, and simply display this image when drawing the screen. You can read: Creating a Bitmap Graphics Context.
On optimizing drawRect
Try this:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
CGContextClipToRect(context, rect);
CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), cgImage);
CGImageRelease(cgImage);
}
After that you should also comment these lines in your code:
//CGContextTranslateCTM(context, 0, size.height);
//CGContextScaleCTM(context, 1.0, -1.0);
Also created a separate question to be sure that it's the optimal way.
This seems like a better method than I've been using. That is, in a touches event I make a copy of the view about to be updated. Then in drawRect I take that image and draw it to the view and make my other view changes at the same time.
But this seems inefficient but the only way I figured out how to do it.
This prevents your view from being erased before drawRect is done:
[self.layer setNeedsDisplay];
Also, I find it is better to do all the drawing in the drawRect method (unless you have a good reason not to). Drawing offscreen and transferring takes more time and adds more complexity then simply drawing everything once.