I have a table where the cells' image views are being populated by images that have been previously pulled down off a server. So:
[[cell imageView] setImage:[[[UIImage alloc] initWithContentsOfFile:filePath] autorelease]];
Where "filePath" is the location of these images. Working beautifully, until I decided to be clever and add retina display images to my server. These images (double-sized, obviously) are being displayed, but are shrunk. I had labelled them image#2x.png, hoping that the iPhone would just know what to do with them, but obviously that doesn't work in this context.
I've looked at the discussions, and am guessing I need to do something with the contentsScale of the cell's imageView, like matching it to the screen scale, but I'm not sure exactly how to do this. Any help appreciated.
From server you cannot download automatically retina images.
You need a control like
if (iphone == 4) image=img#2x.png
else image=img.png
to get correct images.
You would need to set the scale factor of the image correctly. Please check the scale property in UIImage
If you load an image from a file whose
name includes the #2x modifier, the
scale is set to 2.0. If the filename
does not include the modifier but is
in the PNG or JPEG format and has an
associated DPI value, a corresponding
scale factor is computed and reflected
in this property. You can also specify
an explicit scale factor when
initializing an image from a Core
Graphics image. All other images are
assumed to have a scale factor of 1.0.
So you can read your image as above, get the CGImage from it and create a new UIImage using + (UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation.
A nicer way could be to check the DPI of your retina image and make it 144dpi with a graphics program. According to the scale property documentation this might work, too.
Related
After reading Apple's own HIG (specifically the 'Creating images for retina..' section) and looking at similar answers and suggested blogs - I can't seem to get a good solid fix on how properly design images for retina.
To best explain, let me set up a scenario:
I have a UIButton that is 44 x 44 points that I laid out in IB.
I go to photoshop, create an image that is 44 x 44 pixels. I save that as image#2x.png
I save another, without the #2x.png appended on the file name.
In the code, I do something like ... [UIImage imageNamed:#"image"]
From what I have read, it seems that DPI doesn't matter. I do also understand that retina images are, of course, double in scale to the original image. So for the scenario above, was the image#2x.png supposed to be 88 x 88 pixels instead of 44 x 44 pixels? In the [UIImage imageNamed:#"image"], do I need to specify the #2x in the image's name, or does xcode take care of that?
Going with the scenario I described above, can someone either correct me or confirm that this is the correct pixel dimensions the image?
The scenario you described is wrong.
Here are the right:
Assume you want a 44pt x 44pt button to place in IB;
Go to Photoshop create a 88px x 88px image named with #2x;
Create another image that 44px x 44px without addition #2x;
In your IB or code, set the image use normal name, which without #2x
in the image name. System will display the normal image and #2x
image properly.
To create button which IB size is 44x44 you need two images:
Image.png which size is 44x44
Image#2x.png which size is 88x88
iOS will automatically choose proper file when you use the base name
To load that image in code you simply use line below:
[UIImage imageNamed:#"Image"]
and again, iOS will get proper image
I'm trying to get the cropped version of an image that's pulled using ALAsset. Specifically, I'm selecting items from the user's Photo Library and then uploading them. The issue is that in the library thumbnail view, iOS is showing us the cropped version. When you select that thumbnail and pull that image's asset using ALAsset, I get the full resolution version.
I did some research and couldn't find anything that helps in getting a second coordinate system of where the cropping happens.
To test it, you need iOS5 to edit the image in your library. Select an image in your image library, select "Edit", and crop the image. When you get the ALAsset you'll get the full image, and if you sync using iPhoto, iPhoto also pulls the full image. Also, you can re-edit the image and undo your crop.
This is how I'm getting the image:
UIImage *tmpImage = [UIImage imageWithCGImage:[[asset defaultRepresentation] fullResolutionImage]];
That gives me the full resolution image, obviously. There's a fullScreenImage flag which scales the full resolution image to the size of the screen. That's not what I want.
The ALAssetRepresenation class has a scale field, but that's a float value, which is also what I don't want.
If anyone can tell me where this cropped coordinate system can be found, I'd appreciate it.
Your Options:
Option 1 (ALAssetLibrary)
Use the - (CGImageRef)fullScreenImage method of AlAssetRepresentation.
Pros:
All the hard work is done for you, you get an image that looks just like the one in the Photos app. This includes cropping, and other changes. Easy.
Cons:
The resolution is "screen size", only as big as the device you are using, not the full possible resolution of the cropped image. If this doesn't concern you, then this is the perfect option.
Option 2 (ALAssetLibrary)
Extract the cropping data using the AdjustmentXMP key in the image's metadata (what #tom is referring to). Apply the crop.
Benefit:
It is possible to get a cropped image at the best possible resolution
Cons
You only get the cropping edits, not any other adjustments (like red-eye)
Who knows what Apple will support in the future in "Edit" mode, you may have to apply more edits in the future.
It's complicated, you first have to parse the XML data to read the crop rectangle, crop the unrotated image, and then apply the rotation.
Option 3 (Wishful Thinking)
Beg Apple to include a method like fullResolutionEditedImage which gives you the best possible quality photo, with all edits applied.
Pros:
Everything magically solved.
Cons:
Apple may never add this method.
Option 4 (UIImagePickerController)
This option only applies if you are using the image picker, you can't use it directly with the asset library
In the NSDictionary returned by -(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
You can extract the full sized, adjusted image from the UIImagePickerControllerOriginalImage key. Save this image somewhere. Then, instead of retrieving the image from the asset library, load the copy you made.
Pros:
You get the full size image, with adjustments
This is the only option Apple gives us for getting the full size image with all adjustments (like red-eye, etc), and not just the crop. This is particularly important in iOS 7 with the introduction of filters that can drastically alter the image.
Cons:
Can only be used with the image picker (not ALAssetRepresentation)
You must keep around a full-sized copy of the image. Depending on the number of such images, the disk usage by your app could grow substantially.
Update for iOS 7: you may wish to consider Option 4, or Option 1, as iOS 7 supports many operations now like filters, and your users will probably notice if they are missing. These two options support filters (and other edits), with Option 4 giving you a higher resolution result.
When a photo has been cropped with the iOS Photos App, the cropping coordinates can be found in the ALAssetRepresentation's metadata dictionary. fullResolutionImage will give you the uncropped photo, you have to perform the cropping yourself.
The AdjustmentXMP metadata contains not only the cropping coordinates but also indicates if auto-enhance or remove-red-eyes has been applied.
As of iOS 6.0 CIFilter provides filterArrayFromSerializedXMP:inputImageExtent:error: Probably you can use the ALAssetRepresentation's AdjustmentXMP metadata here and apply the CIFilter onto the ALAssetRepresentation's fullResolutionImage to recreate the modified image.
Be aware that the iOS Photos App handles JPG and RAW images differently. For JPG images a new ALAsset with the XMP metadata is stored in the Camera Roll. For RAW images an ALAssetRepresentation is added to the original ALAsset. I'm not sure if this additional ALAssetRepresentation is the modified image and if it has the AdjustmentXMP metadata. In addition to JPG and RAW images you should also test the behaviour for RAW+JPG images.
I'm working on a comic viewer app that downloads the latest content from a server. It downloads a single file regardless of the screen scale. What I'd like to do is make this file work correctly on both screens.
What's the procedure for this and how should I size the photos to fit? The trouble I'm having is that the graphics are retina screen size, but the iPhone doesn't interpret them as such. That means they're displayed twice as large as they should be.
CGImageRef cgImage = [myImage cgImage];
UIImage *retinaImage = [UIImage imageWithCGImage:cgImage scale:2.0 orientation:UIImageOrientationUp];
You could also change your image's dpi value in an image editor to make UIImage recognize the scale automatically.
Generally though, you should use lower-resolution images on devices that don't have a retina display because otherwise you're wasting precious memory.
For normal screen, you can resize them programmatically, by adding a scale category to the UIImage class by example, there are many code samples on stackoverflow like :
UIImage resize (Scale proportion)
For retina, you need to set the scale of the UIImage to let the ios know for what screen size it is used (you can set this with the initWithCGImage:scale:orientation: method of UIImage).
I am working on an iphone app and targeting iOS 4.0 or later. I am trying to add an image to UIImageView, and image is in jpeg format. This is what I have done so far..
UIImageView *bgImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 246.0)];
bgImageView.image = [UIImage imageNamed:#"background"];
[self.view addSubview:bgImageView];
[bgImageView release];
I have added two images,
background.jpg (as normal 1x image)
background#2x.jpg (for 2x / retina
display).
But when I build and run, everything runs fine, except there is no background. All my other widgets are there, but there is a white background and image is not showing.
Next I created two png images for my background, added it to the project and ran. This time my background was shown. I was perplexed because according to documentation jpg image can be used in iphone with UIImage.
I tried again with my jpg images and changed the code above to this
UIImageView *bgImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 246.0)];
bgImageView.image = [UIImage imageNamed:#"background.jpg"]; //added extension
[self.view addSubview:bgImageView];
[bgImageView release];
When I run the above code, background image is showing, but 2x images are not loaded. That shows my jpg image is fine, and it is not the culprit. So when I just give image name without extension, png files (both 1x and 2x)are loaded on respective devices, jpg files are not loaded at all.
Looks like a bug (either in sdk or documentation). iOS 4 is here for a good year. why no one noticed it? Also is there any option for me other than to convert all my images to png?
EDIT :
Well, two days in to the question, zero answers. I have already converted all my images to png, and that part of my project is done (as my project couldn't wait..). Still, for my own sake and for sake of everyone who faces this in future, I would like to know more about this problem. At least someone can confirm that this is indeed an apple bug. I am starting a bounty so that more people can see this thread.
EDIT AGAIN
2 days remaining for this bounty to end. Thanks for everyone who responded. But let me clarify one thing. I have already said that I have converted all my images to png and I moved along. I just wanted some confirmation that imageNamed treats jpg and png files same way. Documentation certainly says it will(unless Apple has written it somewhere other than UIImage documentation). If documentation is correct then my code failed me because of two possible reasons
It is a bug in imageNamed. If this is the reason, then I dont have too much option other than file a bug report, change my images to png or write a wrapper(like Chandan has said in his answer) and move on..
There is some problem with the jpeg image I used. Well I am not a photoshop guy. But I used
RGBA jpg image. If image does matter I am ready to ask my designer
friend and provide more info.
One more thing. This post also just tells about this problem. But he dont know the reason behind. So I am not alone.
EDIT : THE END
Well it is something related to the image itself. I googled and downloaded some sample jpeg images and played with it. Some of them shown up correctly and some doesn't. Since the boundy time is up, I am giving the boundy to PengOne who tried to reproduce the bug and successfully loaded jpg image without extension, there by making me to work with more images. Thanks for everyone who tried to help.
According to the UIImage Class Reference:
Discussion
This method looks in the system caches for an image object with the
specified name and returns that object if it exists. If a matching
image object is not already in the cache, this method loads the image
data from the specified file, caches it, and then returns the
resulting object.
On a device running iOS 4 or later, the behavior is identical if the
device’s screen has a scale of 1.0. If the screen has a scale of 2.0,
this method first searches for an image file with the same filename
with an #2x suffix appended to it. For example, if the file’s name is
button, it first searches for button#2x. If it finds a 2x, it loads
that image and sets the scale property of the returned UIImage object
to 2.0. Otherwise, it loads the unmodified filename and sets the scale
property to 1.0. See iOS Application Programming Guide for more
information on supporting images with different scale factors. Special
Considerations
On iOS 4 and later, the name of the file is not required to specify
the filename extension. Prior to iOS 4, you must specify the filename
extension.
Since you're targeting iOS 4.0 and later, you should not need the filename extension. I tried to reproduce this potential bug, but it worked as expected for me without the filename extensions.
Here are some ideas for what may have gone wrong to create this effect in your app:
It's possible that the problem comes from the cache if you changed your images at some point.
If you choose not to use the filename extensions, and you have both "background.jpg" and "background.png" as options, the preference appears to be to use the ".png" file.
If you set the target to iOS 4.0 and later after first running the app, then the filename extension would have been required and the image may have been cached blank, which leads back to theory 1.
That's the best I can make of this.
Write a wrapper to get the image like
-(UIImage*) getImage:(NSString*)imageName{
UIImage *image;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00) {
// RETINA DISPLAY
NSString *jpegFile = [imageName stringByAppendingString:#"#2x.jpg"];
image = [UIImage imageNamed:jpegFile];
}
else{
NSString *jpegFile = [imageName stringByAppendingString:#".jpg"];
image = [UIImage imageNamed:jpegFile];
}
return image;
}
And from your code call
bgImageView.image = getImage(#"background");
With the dawn of Xcode 6 I have found that JPG images might not render at all on certain iOS7 devices (they may work on some but not on others, or they may not work on any, and you may see a different result on a different developers machine, there doesn't seem to be any consistency). This is regardless of whether the image is just referenced in UIImageView in a XIB or programmatically with [UIImage imageNamed:] (which returns nil).
After scratching my head for a long time, the solution was to rename the *.jpg file extension to *.png (whilst obviously leaving the file contents as JPG data).
HTH
You just need to provide the full name of image.
bgImageView.image = [UIImage imageNamed:#"background.jpg"];
don't leave off the extension.
As for the #2x, it is not necessary to call it out anywhere in the code. If you code correctly there is no need to test for a retina display as some here have suggested.
You can use jpgs, just tried it myself and it worked. Only suggestion is how you are instantiating it.
Try it this way:
stockImage = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"Morbo.jpg"]];
stockImage.frame = CGRectMake(0,0, self.view.bounds.size.width, self.view.bounds.size.height) ;
[self.view addSubview:stockImage];
This is the image I used:
http://futurama.wikia.com/wiki/File:Morbo.jpg
I've got some image generating code that uses UIGraphicsBeginImageContext(), UIGraphicsGetImageFromCurrentImageContext() and UIImagePNGRepresentation() to do some drawing, then save it to disk as a PNG for later use.
Does UIImagePNGRepresentation() take into account scale? As in, if I have an image that is 20 points wide, will the resultant PNG be 20 pixels or 40 pixels?
Also, when I display these images, I use [UIImage imageWithContentsOfFile:] and [image drawInRect:]. Is there a way to hint to those methods to use higher resolution drawing?
Per the iPhone Application Programming Guide you should use UIGraphicsBeginImageContextWithOptions with a scale of 2.0 to create a properly scaled context for the iPhone 4.
As for the second part of your question, I believe you should save the resulting png with the #2x suffix on the base name (e.g., myImage#2x.png). Then when you load it back in using UIImage, its size and scale will be correctly set. Otherwise your image will be loaded at scale 1.0 and be twice as large (in points) as you expect it to be. This section of the same document goes into a bit of detail regarding high-resolution images for devices with high-resolution displays.