I am trying to take a retina screenshot programmatically and I have tried every approach found online, but I was not able to get the screenshot to be retina.
I understand the following private API:
UIGetScreenImage();
cannot be used as Apple will reject your app. However, this method returns exactly what I need (640x960 screenshot of the screen).
I have tried this method on my iPhone 4 as well as the iPhone 4 simulator on retina hardware, but the resulting image is always 320x480.
-(UIImage *)captureView
{
AppDelegate *appdelegate = [[UIApplication sharedApplication]delegate];
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)])
UIGraphicsBeginImageContextWithOptions(appdelegate.window.bounds.size, NO, 0.0);
else
UIGraphicsBeginImageContext(appdelegate.window.bounds.size);
[appdelegate.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(#"SIZE: %#", NSStringFromCGSize(image.size));
NSLog(#"scale: %f", [UIScreen mainScreen].scale);
return image;
}
I have also tried the Apple recommended way:
- (UIImage*)screenshot
{
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(#"Size: %#", NSStringFromCGSize(image.size));
return image;
}
But it also returns a non retina image:
2012-12-23 19:57:45.205 PostCard[3351:707] size: {320, 480}
Is there something obvious I'm missing? How come there methods that are suppose to take retina screenshot return me non retina screenshots?
Thanks in advance!
I don't see anything wrong in your code. Apart from image.size, have you tried logging image.scale? Is it 1 or 2? If it's 2, it is actually a retina image.
UIImage.scale represents the scale of the image. So an image with UIImage.size being 320×480 and UIImage.scale being 2 has an actual size of 640×960. From Apple's doc:
If you multiply the logical size of the image (stored in the size property) by the value in this property, you get the dimensions of the image in pixels.
It's the same idea as when you load an image into a UIImage with the #2x modifier. For example:
a.png (100×80) => size=100×80 scale=1
b#2x.png (200×160) => size=100×80 scale=2
Related
On Apples Technical notes site, they posted code to screen capture. Everything works, except it does not capture files that are being previewed using QLPreviewController under iOS 6.
I'm wondering if they are using some new OpenGL ES to render the file preview so that it could not capture? In theory, this piece of code should be able to capture anything on screen right??
From http://developer.apple.com/library/ios/#qa/qa1703/_index.html
- (UIImage*)screenshot
{
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
this doesnt sound similiar but this actually doesnt work for the same reason that this doesnt work: iOS 6 UIGestures (Tap) stops working with QLPreviewController
since ios 6 the QLPreviewController is actually a completely seperate app (seperate process and everything)
=> so when you push that, your whole app moves to the bg, including its window.
=> QlController is never really part of your window
I have an image and i am cropping part of it. The problem is that in the simulator it is displayed correctly, but on the device it is much more zoomed in. It's quite a bit difference. What am i doing wrong? (first image is from the simulator and second from the iphone device)
// create bounds and initialise default image
CGRect imageSizeRectangle = CGRectMake(0, 0, 300, 300);
UIImage *df_Image = [UIImage imageNamed:#"no_selection.png"];
self.imageView = [[UIImageView alloc] initWithFrame:imageSizeRectangle];
[imageView setImage:df_Image];
[self.view addSubview:imageView];
//crop image
CGRect test = CGRectMake(0, 0, 150,150);
CGImageRef imageRef = CGImageCreateWithImageInRect([photo.image CGImage], test);
UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
The problem here is that retina devices are 2x the size of normal devices. You could check if the device is retina or not with the following method;
+(BOOL)iPhoneRetina{
return ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] && ([UIScreen mainScreen].scale == 2.0))?1:0;
}
And increase/decrease the size of your rect according to the BOOL value returned.
Note* displayLinkWithTarget:selector: is just a random method that works in iOS 4.0+ but not previous versions. You don't need to pay much attention to it.
Edit*
CGRect rect;
if([self iPhoneRetina]){rect = CGRectMake(0,0,300,300);}//Retina
else{rect = CGRectMake(0,0,150,150);}//Non retina
//Then the rest of your code
if you want to simplize your code you may use
CGRectMake(0,0,[UIScreen mainScreen].scale*150,[UIScreen mainScreen].scale*150)
In my app I'm using a screenshot method. On my iPad 2 it's very fast (about 130 ms) to execute this method. But on the new iPad (certainly due to the highest resolution and the same CPU) it's taking like 700 ms ! Is there a way to optimize my method ? Perhaps there's a way to work directly with graphic card ?
Here's my screenshot method :
- (UIImage *)image {
CGSize imageSize = self.bounds.size;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) UIGraphicsBeginImageContextWithOptions(imageSize, NO, [UIScreen mainScreen].scale);
else UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, [self center].x, [self center].y);
CGContextConcatCTM(context, [self transform]);
CGContextTranslateCTM(context, -[self bounds].size.width * [[self layer] anchorPoint].x, -[self bounds].size.height * [[self layer] anchorPoint].y);
[[self layer] renderInContext:context];
CGContextRestoreGState(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Thanks for your help.
I think it was the developer of Flipboard talking about this issue on a podcast. It is a real issue with the iPad 3, because they've quadrupled the pixels.
What he was doing was taking the screenshot ahead of time in the background, not when the user initiated the action - in his case when the user "flipped" the page.
I don't know whether this will help you in your case, but it is certainly a viable approach for many cases.
This may or may not be sufficient for your application but one option is to scale the screenshot down, e.g. pass 0.5 as your scale factor to UIGraphicsBeginImageContextWithOptions. 4x faster but the tradeoff is a loss of detail/resolution.
In my app I display the camera and I am taking screenshots of certain perts using UIGetScreenImage, (I tried UIGraphicsGetImageFromCurrentImageContext and it works great for screenshots on almost any part of my app but for the camera view it will just return a blank white image) ... Anyways, I fear Apple will reject my app because of UIGetScreenImage... How can I take a "screenshot" of a 50px by 50px box from the upper left corner of the camera without using this method? I searched and all I could find was "AVCaptureSession" and I couldn't find much about what that does, or if it's even what I'm looking for... Any insight? :) Thanks guys!!!
It doesn't get much clearer than Apple's docs on how to capture the camera's view. Yes, this does involve the class AVCaptureSession.
If you actually need a screenshot of the interface, you should take at the docs for that. Cut-and-paste code from the link (if this does not work, you should submit a bug report to Apple):
Update: It appears that this approach is no longer supported on newer versions of iOS. The second link is now broken as well.
- (UIImage*)screenshot
{
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Since iOS7 you can use :
drawViewHierarchyInRect
UIImage *image;
UIGraphicsBeginImageContext(self.view.frame.size);
[self.view drawViewHierarchyInRect:self.view.frame afterScreenUpdates:YES];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I got this code to be able to take a screenshot of the view.
UIGraphicsBeginImageContext(scrollView.bounds.size);
[scrollView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData * data = UIImagePNGRepresentation(image);
However, even if I set the context to be 320x480, parts of the scroll view are still not shown. The view that the scrollview manages can perfectly fit within the 320x480 frame, but parts of it are covered by the status bar, navBar and TabBar.
I would like to take full screen (320x480) screenshot of the view with the parts of the view, covered by status bar, TabBar and NavBar visible. Any pointers on how to do this?
An extra question, which may be related: the resulting image is using x1 scale, and looks very blurry on the retina display, which scales takes a larger image and scales it down. This means I'll need to render the 640x960 screenshot to reproduce the original crisp quality. How would I go about doing that?
Thank you!
I found the following on this site: http://www.icodeblog.com/2009/07/27/1188/
UIGraphicsBeginImageContext(YourView.frame.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(viewImage, nil, nil, nil);
You may also want to check out this (Apple's example of how to make a screenshot): http://developer.apple.com/library/ios/#qa/qa1703/_index.html
First make a screenshot of the whole screen:
// Create a graphics context with the target size
// On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
// On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0.0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
Then crop it to the right size
CGImageRef subImageRef = CGImageCreateWithImageInRect(screenshot.CGImage, rect);
CGRect smallBounds = CGRectMake(0, 64, 320, 372); //You should remove the hard coded numbers
UIGraphicsBeginImageContext(smallBounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, smallBounds, subImageRef);
UIImage* cropped = [UIImage imageWithCGImage:subImageRef];
UIGraphicsEndImageContext();