I'm trying to resize an image on a background thread and the app always crashes after a few low memory warnings. How can I rewrite the code below to fix this?
float max = 1024*1024;
NSData *pngData = UIImagePNGRepresentation(setImage);
while ([pngData length] > max) {
pngData = nil;
CGSize newSize = CGSizeMake(setImage.size.width*.9, setImage.size.height *.9);
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(#"scale: %f", (1024.0*1024.0)/((float)[pngData length]));
pngData = UIImagePNGRepresentation(image);
}
NSLog(#"image length: %i",[pngData length]);
[pngData writeToFile:imageLocation atomically:YES];
I have already tried doing this by calculating the scale and replace the .9 in the code with a scale value
float scale = (max)/((float)[pngData length]);
CGSize newSize = CGSizeMake(setImage.size.width*scale, setImage.size.height *scale);
This made the image too small.
The end goal is to take an image from the camera and save it to disk. I originally had to resize the image because I was getting a "Low Memory warning" when loading the image.
Your code causes an infinite loop and creates images until you run out of memory. Try something like this to fix the infinite loop:
float max = 1024*1024;
NSData *pngData = UIImagePNGRepresentation(setImage);
CGSize newSize = setImage.size;
while ([pngData length] > max) {
newSize = CGSizeMake(newSize.width * 0.9, newSize.height * 0.9);
UIGraphicsBeginImageContext(newSize);
[setImage drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(#"scale: %f", (1024.0*1024.0)/((float)[pngData length]));
pngData = UIImagePNGRepresentation(image);
image = nil;
}
NSLog(#"image length: %i",[pngData length]);
[pngData writeToFile:imageLocation atomically:YES];
Is there any reason why you need to do this by hand? If you use a UIImageView and set the image either with initWithImage: or with the image property and then change UIImageView's frame the image will resize accordingly
Related
i'm trying to figure out this problem but failed after doing every thing i found on OS or google. Problem is that when i convert UIImage to NSData using UIImageJPEGRepresentation or UIImagePNGRepresentation it increases the memory size to 30Mb (believe me or not).
Here is my code
myImage= image;
LoginSinglton*loginObj = [LoginSinglton sharedInstance];
NSError *error;
NSData *pngData = UIImageJPEGRepresentation(image, scaleValue); //scaleVale is 1.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0]; //Get the docs directory
self.imageCurrentDateAndTime =[self getTimeAndDate];
self.filePathAndDirectory = [documentsPath stringByAppendingPathComponent:#"Photos Dir"];
NSLog(#"Documents path %#",self.filePathAndDirectory);
if (![[NSFileManager defaultManager] createDirectoryAtPath:self.filePathAndDirectory
withIntermediateDirectories:NO
attributes:nil
error:&error])
{
NSLog(#"Create directory error: %#", error);
}
self.imageName= [NSString stringWithFormat:#"photo-%#-%#.jpg",loginObj.userWebID,self.imageCurrentDateAndTime];
NSString *filePath = [self.filePathAndDirectory stringByAppendingPathComponent:self.imageName];
[pngData writeToFile:filePath atomically:YES]; //Write the file
[self writeImageThumbnailToFolder:image];
[self writeImageHomeViewThumbnailToFolder:image];
I have tried following solution as well
UIImageJPEGRepresentation - memory release issue
1- Used #autoreleasepool
2- done pngData = nil;
but still facing that memory issue.
EDIT I think i'm not able to convey my problem. It's ok if UIImageJPEGRepresentation taking huge memory,but memory should back to it's earlier position after saving that image. Hope this will help you in detail.
Use a scaleValue of less than 1. Even 0.9 will massively reduce the memory footprint with minimal quality loss.
Try this:
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
If not got solution for your expectation, no need to worry try this too:
UIImage *small = [UIImage imageWithCGImage:original.CGImage scale:0.25 orientation:original.imageOrientation];
Get sample app form here for editing image or scale:
http://mattgemmell.com/2010/07/05/mgimageutilities/
To resize using minimum memory try Using CoreGraphics
SO answer by #Mina Nabil, see full answer for more details
#import <ImageIO/ImageIO.h>
-(UIImage*) resizedImageToRect:(CGRect) thumbRect {
CGImageRef imageRef = [inImage CGImage];
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef);
if (alphaInfo == kCGImageAlphaNone)
alphaInfo = kCGImageAlphaNoneSkipLast;
// Build a bitmap context that's the size of the thumbRect
CGContextRef bitmap = CGBitmapContextCreate(
NULL,
thumbRect.size.width, // width
thumbRect.size.height, // height
CGImageGetBitsPerComponent(imageRef), // really needs to always be 8
4 * thumbRect.size.width, // rowbytes
CGImageGetColorSpace(imageRef),
alphaInfo
);
// Draw into the context, this scales the image
CGContextDrawImage(bitmap, thumbRect, imageRef);
// Get an image from the context and a UIImage
CGImageRef ref = CGBitmapContextCreateImage(bitmap);
UIImage* result = [UIImage imageWithCGImage:ref];
CGContextRelease(bitmap); // ok if NULL
CGImageRelease(ref);
return result;
}
This question already has answers here:
UIImage: Resize, then Crop
(16 answers)
Closed 9 years ago.
I have app in which i capture the image it works fine but i want to reduce the size of the image i searched from net i found some code which is here
This is my code working fine when image captured
NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(pickedImage);
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
I want to reduce the size of picked image i got the following code but i am did not getting it use like it use newSize but where it is using the original image to compress
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
what you can do is....
NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
CGSize sizeCropped = CGSizeMake(602, 450);//you can adjust any size here...
pickedImage = [yourclassname imageWithImage:pickedImage scaledToSize:sizeCropped];//call below function...
+ (UIImage*)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
let me know it is working or not!!!!
Happy coding!!!!!
+(UIImage*)imageWithImage:(UIImage*)image scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
you can call this function where you want the compressed image using these lines of code.
CGSize firstSize = CGSizeMake(210.0,210.0);
UIImage *compImage=[mainclass imageWithImage:image scaledToSize:firstSize];
if you want to save that new Image with new size then after your code add that image in library with bellow line..
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo);
EX:
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(newImage, nil, nil, nil);
When a user selects an image from the their photo library, I'm resizing it and then uploading it to my server and then at some other point in the app the user can view all their photos. (I'm simplifying the work flow)
The uiimageview on the "detail" screen is 320 x 320. Based upon the below method should I be using:
UIImage *image = [UIImage imageWithCGImage:img];
or
UIImage *image = [UIImage imageWithCGImage:img scale:[UIScreen mainScreen].scale orientation:img.imageOrientation];
Part B would be when I request download the image (nsdata) should I use imageWithCGIImage or imageWithCGIImage:scale:orientation
- (UIImage *)resizedImageForUpload:(UIImage *)originalImage {
static CGSize __maxSize = {640, 640};
NSMutableData *data = [[NSMutableData alloc] initWithData:UIImageJPEGRepresentation(originalImage, 1.0)];
CFMutableDataRef dataRef = (__bridge CFMutableDataRef)data;
CGImageSourceRef imgSrc = CGImageSourceCreateWithData(dataRef, NULL);
CGFloat width = [originalImage maxDimensionForConstraintSize:__maxSize];
NSNumber *maxWidth = [NSNumber numberWithFloat:width];
NSDictionary *options = #{
(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageAlways : #YES,
(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform: #YES,
(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize : maxWidth
};
CFDictionaryRef cfOptions = (__bridge CFDictionaryRef)options;
CGImageRef img = CGImageSourceCreateThumbnailAtIndex(imgSrc, 0, cfOptions);
CFStringRef type = CGImageSourceGetType(imgSrc);
CGImageDestinationRef imgDest = CGImageDestinationCreateWithData(dataRef, type, 1, NULL);
CGImageDestinationAddImage(imgDest, img, NULL);
CGImageDestinationFinalize(imgDest);
UIImage *image = [UIImage imageWithCGImage:img];
CFRelease(imgSrc);
CGImageRelease(img);
CFRelease(imgDest);
return image;
}
It appears I've found the answer to my own question. The resizeImageForUpload shouldn't try to scale based upon the device. Since I'm defining a max size 640,640 (retina size for my 320,320 uiimageview) no other manipulation is necessary. I've added some caching for the images and I'm handing the scaling at that point:
UIImage *image = [UIImage imageWithData:imgData scale:[UIScreen mainScreen].scale];
and then return. The reason why I thought I had messed something up was that I was trying to scale an already scaled image. Lessons learned.
I'm not really sure I got the title exactly right, but I'm not sure where my problem is exactly.
I need to load an array of UIImages from an spritesheet, which I'll then use as animations in a UIImageView.
The spritesheet is generated with TexturePacker, which generates the huge atlas (2048x2048) and a json with the sprites descriptions.
Until now, I've had it working without issues, even loading 100 frames in just 0.5-0.8 secs which I was really happy with.
The problem is now I need to load the spritesheets from the Documents folder (they are downloaded, so can't be integrated in the app), so I have to use UIImage imageWithContentsOfFile instead of UIImage imagenamed.
This makes my loading time increase by 5-15 seconds depending the number of frames in the animation.
I'm guessing the problem is that because the image isn't being cached, is loading it for every frame, but I don't see why
This is the code:
// With this line, perfect
//UIImage *atlas = [UIImage imageNamed:#"media/spritesheets/default-pro7#2x.png"];
// But with this one, incredibly looooong loading times
UIImage *atlas = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"media/spritesheets/default-pro7#2x" ofType:#"png"]];
CGImageRef atlasCGI = [atlas CGImage];
for( NSDictionary* dict in frames) {
NSDictionary *frame = [dict objectForKey:#"frame"];
int x = [[frame objectForKey:#"x"] intValue];
int y = [[frame objectForKey:#"y"] intValue];
int width = [[frame objectForKey:#"w"] intValue];
int height = [[frame objectForKey:#"h"] intValue];
NSDictionary *spriteSize = [dict objectForKey:#"spriteSourceSize"];
int realx = [[spriteSize objectForKey:#"x"] intValue];
NSDictionary *size = [dict objectForKey:#"sourceSize"];
int realWidth = [[size objectForKey:#"w"] intValue];
int realHeight = [[size objectForKey:#"h"] intValue];
//Initialize the canvas size!
canvasSize = CGSizeMake(realWidth, realHeight);
bitmapBytesPerRow = (canvasSize.width * 4);
bitmapByteCount = (bitmapBytesPerRow * canvasSize.height);
bitmapData = malloc( bitmapByteCount );
//Create the context
context = CGBitmapContextCreate(bitmapData, canvasSize.width, canvasSize.height, 8, bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
// Clear background
CGContextClearRect(context,CGRectMake(0.0f,0.0f,canvasSize.width,canvasSize.height));
// Get spriteRect
CGRect cropRect = CGRectMake(x, y, width, height);
CGImageRef imageRef = CGImageCreateWithImageInRect(atlasCGI, cropRect);
//Draw the image into the bitmap context
CGContextDrawImage(context, CGRectMake(realx, 0, width, height), imageRef);
//Get the result image
resultImage = CGBitmapContextCreateImage(context);
UIImage *result = result = [UIImage imageWithCGImage:resultImage];
[images addObject:result];
//Cleanup
free(bitmapData);
CGImageRelease(resultImage);
CGImageRelease(imageRef);
}
I can even try an upload system profiler sessions which show where is taking so much time loading
Well, I finally figured out where the problem was. After looking more carefully with Instruments Time profiler, I noticed a lot of time was spent in copyImageBlockSetPNG , which was what made me believe first that I was loading the image from disk for every frame. turn out, somebody already had this problem Does CGContextDrawImage decompress PNG on the fly?, and it ended being true that I was loading the memory each frame, but not from disk, instead it was being uncompressed from memory in png format for every frame.
In that post theres a solution, which consists in writing the image to a context and getting from it an uncompressed copy of the image.
That works, but I found other way which I believe It's a little bit more efficient
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:(id)kCGImageSourceShouldCache];
NSData *imageData = [NSData dataWithContentsOfFile:#"path/to/image.png"]];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
CGImageRef atlasCGI = CGImageSourceCreateImageAtIndex(source, 0, (__bridge CFDictionaryRef)dict);
CFRelease(source);
Hope it helps!
I'm currently uploading an image to a server using Imgur on iOS with the following code:
NSData* imageData = UIImagePNGRepresentation(image);
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* fullPathToFile = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"SBTempImage.png"];
[imageData writeToFile:fullPathToFile atomically:NO];
[uploadRequest setFile:fullPathToFile forKey:#"image"];
The code works fine when run in the simulator and uploading a file from the simulator's photo library because I'm on a fast ethernet connection. However, the same code times out on the iPhone when selecting an image taken with the iPhone. So, I tried it by saving a small image from the web and attempting to upload that, which worked.
This leads me to believe the large images taken by the iPhone are timing out over the somewhat slow 3G network. Is there any way to compress/resize the image from the iPhone before sending it?
This snippet will resize the image:
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The variable newSize is a CGSize and can be defined like so:
CGSize newSize = CGSizeMake(100.0f, 100.0f);
A self-contained solution:
- (UIImage *)compressForUpload:(UIImage *)original scale:(CGFloat)scale
{
// Calculate new size given scale factor.
CGSize originalSize = original.size;
CGSize newSize = CGSizeMake(originalSize.width * scale, originalSize.height * scale);
// Scale the original image to match the new size.
UIGraphicsBeginImageContext(newSize);
[original drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *compressedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return compressedImage;
}
Thanks to #Tuan Nguyen.
To complement #Tuan Nguyen, this is maybe the fastest and most elegant way to do that.
To link to John Muchow's post at iphonedevelopertips.com , adding a category to a UIImage is a very very handy way to scale in a very fast fashion.
Just calling
UIImage *_image = [[[UIImage alloc] initWithData:SOME_NSDATA] scaleToSize:CGSizeMake(640.0,480.0)];
returns you a 640x480 representation image of your NSDATA ( that could be an online image ) without any more line of code.
Matt Gemmell's MGImageUtilities are very nice, resizing efficiently and with some effort-reducing methods.
In this code 0.5 means 50% ...
UIImage *original = image;
UIImage *compressedImage = UIImageJPEGRepresentation(original, 0.5f);
use this simple method NSData *data = UIImageJPEGRepresentation(chosenImage, 0.2f);
Swift implementation of Zorayr's function (with a bit of a change to include height or width constraints by actual units not scale):
class func compressForUpload(original:UIImage, withHeightLimit heightLimit:CGFloat, andWidthLimit widthLimit:CGFloat)->UIImage{
let originalSize = original.size
var newSize = originalSize
if originalSize.width > widthLimit && originalSize.width > originalSize.height {
newSize.width = widthLimit
newSize.height = originalSize.height*(widthLimit/originalSize.width)
}else if originalSize.height > heightLimit && originalSize.height > originalSize.width {
newSize.height = heightLimit
newSize.width = originalSize.width*(heightLimit/originalSize.height)
}
// Scale the original image to match the new size.
UIGraphicsBeginImageContext(newSize)
original.drawInRect(CGRectMake(0, 0, newSize.width, newSize.height))
let compressedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return compressedImage
}
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
+ (UIImage *)resizeImage:(UIImage *)image toResolution:(int)resolution {
NSData *imageData = UIImagePNGRepresentation(image);
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
CFDictionaryRef options = (__bridge CFDictionaryRef) #{
(id) kCGImageSourceCreateThumbnailWithTransform : #YES,
(id) kCGImageSourceCreateThumbnailFromImageAlways : #YES,
(id) kCGImageSourceThumbnailMaxPixelSize : #(resolution)
};
CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(src, 0, options);
CFRelease(src);
UIImage *img = [[UIImage alloc]initWithCGImage:thumbnail];
return img;
}
Swift 2.0 version of Jagandeep Singh method but need to convert data to image because NSData? is not converted UIImage automatically.
let orginalImage:UIImage = image
let compressedData = UIImageJPEGRepresentation(orginalImage, 0.5)
let compressedImage = UIImage(data: compressedData!)
NsData *data=UiImageJPEGRepresentation(Img.image,0.2f);
UIImage *image = [UIImage imageNamed:#"image.png"];
NSData *imgData1 = UIImageJPEGRepresentation(image, 1);
NSLog(#"Original --- Size of Image(bytes):%d",[imgData1 length]);
NSData *imgData2 = UIImageJPEGRepresentation(image, 0.5);
NSLog(#"After --- Size of Image(bytes):%d",[imgData2 length]);
image = [UIImage imageWithData:imgData2];
imgTest.image = image;
try to convert JPG by scaling factor. Here I am using 0.5
In my case:
Original --- Size of Image(bytes): 85KB & After --- Size of Image(bytes): 23KB
-(UIImage *) resizeImage:(UIImage *)orginalImage resizeSize:(CGSize)size
{
CGFloat actualHeight = orginalImage.size.height;
CGFloat actualWidth = orginalImage.size.width;
// if(actualWidth <= size.width && actualHeight<=size.height)
// {
// return orginalImage;
// }
float oldRatio = actualWidth/actualHeight;
float newRatio = size.width/size.height;
if(oldRatio < newRatio)
{
oldRatio = size.height/actualHeight;
actualWidth = oldRatio * actualWidth;
actualHeight = size.height;
}
else
{
oldRatio = size.width/actualWidth;
actualHeight = oldRatio * actualHeight;
actualWidth = size.width;
}
CGRect rect = CGRectMake(0.0,0.0,actualWidth,actualHeight);
UIGraphicsBeginImageContext(rect.size);
[orginalImage drawInRect:rect];
orginalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return orginalImage;
}
This is the method calling
UIImage *compimage=[appdel resizeImage:imagemain resizeSize:CGSizeMake(40,40)];
it returns image this image u can display any where...........
You should be able to make a smaller image by doing something like
UIImage *small = [UIImage imageWithCGImage:original.CGImage scale:0.25 orientation:original.imageOrientation];
(for a quarter-size image) then convert the smaller image to a PNG or whatever format you need.