Scaling issue in saving UIImage as NSData - iphone

I would like to save some image as nsdata into a plist and retrieve it later.
But I got a problem.
The problem is, if the UIImage with a scale of 2.0, when I load it again later with
[UIImage imageWithData:]
the image show 2x size
What I want is to like the behaviour of
[UIImage imageNamed:]
which will load according to the screen scale.
How can I do it?

I finally solve it by this code:
UIImage *image = [UIImage imageWithData:imageData];
if (isRetinaDisplay) {
image = [UIImage imageWithCGImage:[image CGImage] scale:2.0f orientation:UIImageOrientationUp];
}

You could take into account the possibility that the screen is Retina like so:
CGFloat screenScale = [UIScreen mainScreen].scale;
UIImage *image = [UIImage imageWithData:data scale:screenScale];
This code covers both cases (Retina / non-Retina) in one line.

Since iOS 6 you can also use
+ (UIImage *)imageWithData:(NSData *)data scale:(CGFloat)scale NS_AVAILABLE_IOS(6_0);

The scale property is read-only, but what you can do is subclass UIImageView to make it writable (or make it automatic according to what device it's running on).
// ImageView.h
#interface ImageView : UIImageView
// ImageView.m
CGFloat scaleProperty = 1.0;
#implementation ImageView
- (void)setScale:(CGFloat)scale
{
scaleProperty = scale;
}
- (CGFloat)scale
{
return scaleProperty;
}
#end

Related

How to Define UIImageView size as UIImage resolution?

I have scenario, in which I am getting images using Web Service and all images are in different resolution. Now my requirement is that I want resolution of each Images and using that I want to define size of UIImageView so I can prevent my Images from getting blurred
For example image resolution if 326 pixel/inch the imageview should be as size of that image can represent fully without any blur.
UIImage *img = [UIImage imageNamed:#"foo.png"];
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
UIImageView *imgView = [[UIImageView alloc] initWithFrame:rect];
[imgView setImage:img];
Image size IS it's resolution.
Your problem might be - retina display!
Check for Retina display and thus - make UIImageView width/height twice smaller (so that each UIImageView pixel would consist of four smaller UIImage pixels for retina display).
How to check for retina display:
https://stackoverflow.com/a/7607087/894671
How to check image size (without actually loading image in memory):
NSString *mFullPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
stringByAppendingPathComponent:#"imageName.png"];
NSURL *imageFileURL = [NSURL fileURLWithPath:mFullPath];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageFileURL, NULL);
if (imageSource == NULL)
{
// Error loading image ...
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], (NSString *)kCGImageSourceShouldCache, nil];
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (CFDictionaryRef)options);
NSNumber *mImgWidth;
NSNumber *mImgHeight;
if (imageProperties)
{
//loaded image width
mImgWidth = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
//loaded image height
mImgHeight = (NSNumber *)CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
CFRelease(imageProperties);
}
if (imageSource != NULL)
{
CFRelease(imageSource);
}
So - for example:
UIImageView *mImgView = [[UIImageView alloc] init];
[mImgView setImage:[UIImage imageNamed:#"imageName.png"]];
[[self view] addSubview:mImgView];
if ([UIScreen instancesRespondToSelector:#selector(scale)])
{
CGFloat scale = [[UIScreen mainScreen] scale];
if (scale > 1.0)
{
//iphone retina screen
[mImgView setFrame:CGRectMake(0,0,[mImgWidth intValue]/2,[mImgHeight intValue]/2)];
}
else
{
//iphone screen
[mImgView setFrame:CGRectMake(0,0,[mImgWidth intValue],[mImgHeight intValue])];
}
}
Hope that helps!
You can get image size using following code. So, first calculate downloaded image size and than make image view according to that.
UIImage *Yourimage = [UIImage imageNamed:#"image.png"];
CGFloat width = Yourimage.size.width;
CGFloat height = Yourimage.size.height;
Hope, this will help you..
UIImage *oldimage = [UIImage imageWithContentsOfFile:imagePath]; // or you can set from url with NSURL
CGSize imgSize = [oldimage size];
imgview.frame = CGRectMake(10, 10, imgSize.width,imgSize.height);
[imgview setImage:oldimage];
100% working ....
To solve this problem, we need to take care of the device's display resolution..
For example you have an image of resolution 326ppi which is same as of iPhone4, iPhone4S and iPod 4th Gen. So you can simply use solutions suggested by #Nit and #Peko. But for other devices(or for image with different resolution on these devices) you will need to apply maths to calculate size for better display.
Now suppose you have 260ppi(with dimensions W x H) image and you wish to display it on iPhone4S, so as the information contained in it per inches is less than the display resolution of iPhone so we will need to resize it by reducing image size by 326/260 factor. so now the size for imageView that you will use is
imageViewWidth = W*(260/326);
imageViewHeight = H*(260/326);
In general:
resizeFactor = imageResolution/deviceDisplayResolution;
imageViewWidth = W*resizeFactor;
imageViewHeight = H*resizeFactor;
Here I am considering when we set an image in imageView and resize it, it does not removes or adds pixels from image,
Let the UIImageView do the work by utilizing the contentMode property to do your image resizing for you.
You probably want to be displaying your UIImageView with a static sizing (the "frame" property) that represents the maximum size of the image you want to display, and allowing the images to resize within that frame relative to their own particular size requirements (overall size, aspect ratio, etc.). You can let the UIImageView do the heavy lifting for you of dealing with different sized images by mastering the contentMode property. It has many different settings, one of which is UIViewContentModeScaleAspectFit, which will downsize your image as necessary to fit within the UIImageView, which if the image is smaller, it will simply display centered. You can play with the setting to get the results you want.
Note that with this approach, there is nothing special you need to do to deal with scaling issues associated with a Retina display.
As per the requirement you stated in the question body, I believe you need not change UIImageView size.
Image can represent fully without any blur using this line of code:
imageView.contentMode = UIViewContentModeScaleAspectFit;

Add 2 UIImages into One UIImage

I am adding 2 images to each other and wanted to know if this is a good way to do this? This code works and looked to be powerful.
So, my question really is, It this good or is there a better way?
PS: Warning code written by a designer.
Call the function:
- (IBAction) {
UIImage *MyFirstImage = UIImage imageNamed: #"Image.png"];
UIImage *MyTopImage = UIImage imageNamed: #"Image2.png"];
CGFloat yFloat = 50;
CGFloat xFloat = 50;
UIImage *newImage = [self placeImageOnImage:MyFirstImage imageOver:MyTopImage x:&xFloat y:&yFloat];
}
The Function:
- (UIImage*) placeImageOnImage:(UIImage *)image topImage:(UIImage *)topImage x:(CGFloat *)x y:(CGFloat *)y {
// if you want the image to be added next to the image make this CGSize bigger.
CGSize newSize = CGSizeMake(image.size.width,image.size.height);
UIGraphicsBeginImageContext( newSize );
[topImage drawInRect:CGRectMake(*x,*y,topImage.size.width,topImage.size.height)];
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height) blendMode:kCGBlendModeDestinationOver alpha:1];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Looks OK. Perhaps you don't really need the CGFloat pointers, but that's fine, too.
The main idea is correct. There is no better way to do what you want.
Minuses:
1) Consider UIGraphicsBeginImageContextWithOptions method. UIGraphicsBeginImageContext isn't good for retina.
2) Don't pass floats as pointers. Use x:(CGFloat)x y:(CGFloat)y instead
You should use the begin context version, UIGraphicsBeginImageContextWithOptions, that allows you to specify options for scale (and pass 0 as the scale) do you don't lose any quality on retina displays.
If you want one image drawn on top of another image, just draw the one in back, then the one in front, exactly as if you were using paint. There is no need to use blend modes.
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
[topImage drawInRect:CGRectMake(*x,*y,topImage.size.width,topImage.size.height)];

Orientation does not behave correctly with Photo in ALAsset

I current have an app that uses ALAsssetsLibrary to fetch the photos. I have placed the photo to an image view and I am able to upload to the server. When I tested on the real device after taking some photos, I found out the photos that supposed to be taken in Portrait become a landscape.
Therefore, I called different function to get the CGImage like this:
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0 orientation:(UIImageOrientation)[representation orientation]];
The first tried out, I used this :
UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage]]
I thought the one with scale and orientation could give me the right orientation that the photo was taken. But it didn't give me the right solution.
Do I miss anything that is necessary to generate a correct orientation of photo?
The correct orientation handling depends on the iOS version you are using.
On iOS4 and iOS 5 the thumbnail is already correctly rotated, so you can initialize your UIImage without specifying any rotation parameters.
However for the fullScreenImage, the behavior is different for each iOS version. On iOS 5 the image is already rotated on iOS 4 not.
So on iOS4 you should use:
ALAssetRepresentation *defaultRep = [asset defaultRepresentation];
UIImage *_image = [UIImage imageWithCGImage:[defaultRep fullScreenImage]
scale:[defaultRep scale] orientation:(UIImageOrientation)[defaultRep orientation]];
On iOS5 the following code should work correctly:
ALAssetRepresentation *defaultRep = [asset defaultRepresentation];
UIImage *_image = [UIImage imageWithCGImage:[defaultRep fullScreenImage] scale:[defaultRep scale] orientation:0];
Cheers,
Hendrik
Try this code:-
UIImage* img = [UIImage imageWithCGImage:asset.thumbnail];
img = [UIImage imageWithCGImage:img.CGImage scale:1.0 orientation:UIImageOrientationUp];
This may help you.
My experience is limited to IOS 5.x but I can tell you that the thumbnail and fullscreen images are oriented properly. It's the fullresolutionimage that's horizontal when shot vertically. My solution is to use a category on uiimage that I got from here:
http://www.catamount.com/forums/viewtopic.php?f=21&t=967&start=0
It provides a nice rotating method on a UIImage like this:
UIImage *tmp = [UIImage imageWithCGImage:startingFullResolutionImage];
startingFullResolutionImage = [[tmp imageRotatedByDegrees:-90.0f] CGImage];
For fullResolutionImage, I'd like to provide a solution as follows,
ALAssetRepresentation *rep = [asset defaultRepresentation];
// First, write orientation to UIImage, i.e., EXIF message.
UIImage *image = [UIImage imageWithCGImage:[rep fullResolutionImage] scale:rep.scale orientation:(UIImageOrientation)rep.orientation];
// Second, fix orientation, and drop out EXIF
if (image.imageOrientation != UIImageOrientationUp) {
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
[image drawInRect:(CGRect){0, 0, image.size}];
UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
image = normalizedImage;
}
// Third, compression
NSData *imageData = UIImageJPEGRepresentation(image, 1.0);
imageData is what you want, and just upload it to your photo server.
By the way, if you think EXIF is useful, you can complement it to normalizedImage as you wish.

UIImageView rotates my UIImage

I'm trying to show an image taken through the camera in Portrait mode but I always get it shown on my UIImageView in Landascape mode. The image is scaled before being added to the UIImageView but it seems this is not the problem as I tried many different solutions found on the web (even some quite smart ones like the one coming from Trevor's Bike Shed).
Here is my code:
UIImage *image = [UIImage imageWithContentsOfFile:imgPath];
CGRect newFrame = [scrollView frame];
UIImage *resizedImage = [ImageViewController imageFromImage:image scaledToSize:newFrame.size];
imageView = [[UIImageView alloc] initWithFrame:newFrame];
[imageView setImage:resizedImage];
[scrollView setContentSize:imageView.frame.size];
[scrollView addSubview:imageView];
[imageView release];
imgPath is the path of the image coming as a parameter and scrollView is an IBOutlet linked to a UIScrollView.
Is there something I'm missing about the UIImageView?
As I wrote above, it seems that the problem is not related to the scaling...
Are you certain that imageOrientation is set as you expect on resizedImage? Incorrect scaling can absolutely mess up imageOrientation.
There is an imageOrientation property for a UIImage. Check out the docs. It can be set through the initializer:
UIImage *rotatedImage = [UIImage imageWithCGImage:[resizedImage CGImage] scale:1 orientation:UIImageOrientationUp]
Creates and returns an image object with the specified scale and
orientation factors.
+ (UIImage *)imageWithCGImage:(CGImageRef)imageRef scale:(CGFloat)scale orientation:(UIImageOrientation)orientation
Parameters:
imageRef The Quartz image object.
scale The scale factor to use when interpreting the image data.
Specifying a scale factor of 1.0 results in an image whose size
matches the pixel-based dimensions of the image. Applying a different
scale factor changes the size of the image as reported by the size
(page 12) property.
orientation The orientation of the image data. You can use this
parameter to specify any rotation factors applied to the image.
Return Value A new image object for the specified Quartz image, or
nil if the method could not initialize the image from the specified
image reference.
UIImage* image=[UIImage imageNamed:#"abc.jpg"];
UIImageOrientation orientation=image.imageOrientation;
Use image in imageView or where ever you want to use,
UIImageView* imageView=[[UIImageView alloc] initWithImage:image];
After that reset the image to orientation it was before usage
UIImage* image1=[UIImage imageWithCGImage:[image CGImage] scale:1.0 orientation:orientation];
image1 is your image in the orientation it was before

Uiimage from UIView: higher than on-screen resolution?

I've got a UIView which I'm rendering to a UIImage via the typical UIGraphicsBeginImageContextWithOptions method, using a scale of 2.0 so the image output will always be the "retina display" version of what would show up onscreen, regardless of the user's actual screen resolution.
The UIView I'm rendering contains both images and text (UIImages and UILabels).  The image is appearing in the rendered UIImage at its full resolution, and looks great.  But the UILabels appear to have been rasterized at a 1.0 scale and then upscaled to 2.0, resulting in blurry text.
Is there something I'm doing wrong, or is there some way to get the text to render nice and crisp at the higher scale level?  Or is there some way to do this other than using the scaling parameter of UIGraphicsBeginImageContextWithOptions that would have better results?   Thanks!
The solution is to change the labels's contentsScale to 2 before you draw it, then set it back immediately thereafter. I just coded up a project to verify it, and its working just fine making a 2x image in a normal retina phone (simulator). [If you have a public place I can put it let me know.]
EDIT: the extended code walks the subviews and any container UIViews to set/unset the scale
- (IBAction)snapShot:(id)sender
{
[self changeScaleforView:snapView scale:2];
UIGraphicsBeginImageContextWithOptions(snapView.bounds.size, snapView.opaque, 2);
[snapView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imageDisplay.image = img; // contentsScale
imageDisplay.contentMode = UIViewContentModeScaleAspectFit;
[self changeScaleforView:snapView scale:1];
}
- (void)changeScaleforView:(UIView *)aView scale:(CGFloat)scale
{
[aView.subviews enumerateObjectsUsingBlock:^void(UIView *v, NSUInteger idx, BOOL *stop)
{
if([v isKindOfClass:[UILabel class]]) {
v.layer.contentsScale = scale;
} else
if([v isKindOfClass:[UIImageView class]]) {
// labels and images
// v.layer.contentsScale = scale; won't work
// if the image is not "#2x", you could subclass UIImageView and set the name of the #2x
// on it as a property, then here you would set this imageNamed as the image, then undo it later
} else
if([v isMemberOfClass:[UIView class]]) {
// container view
[self changeScaleforView:v scale:scale];
}
} ];
}
Try rendering to an image with double size, and then create the scaled image:
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
// Do stuff
UImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
newImage=[UIImage imageWithCGImage:[newImage CGImage] scale:2.0 orientation:UIImageOrientationUp];
Where:
size = realSize * scale;
I have been struggling with much the same oddities in the context of textview to PDF rendering. I found out that there are some documented properties on the CALayer objects which make up the view. Maybe setting the rasterizationScale of the relevant (sub)layer(s) helps.