Memory leak on reading images from document directory - iphone

I am reading images from document directory in a loop.
for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
{
UIButton *btnImage = [UIButton buttonWithType:UIButtonTypeCustom];
btnImage.frame = CGRectMake(xRow, yRow, width, height);
[btnImage addTarget:self action:#selector(imageDetails:) forControlEvents:UIControlEventTouchUpInside];
btnImage.tag = iCount;
if(iCount<[arrImages count])
{
NSString *workSpacePath=[[self applicationDocumentsDirectory] stringByAppendingPathComponent:[arrImages objectAtIndex:iCount]];
[btnImage setBackgroundImage:[UIImage imageWithData:[NSData dataWithContentsOfFile:workSpacePath]] forState:UIControlStateNormal];
[scrollImages addSubview:btnImage];
}
}
On doing so, control goes to didRecieveMemoryWarning and application crashes. If I replace the image by a image from resource folder, application does not crashes. Why so?

The problem is that your buttons are maintaining a reference to the full resolution image. What you will need to do is scale down the image to your button dimensions and set that scaled down image as your button background. Something like this should do the trick:
Create a category on UIImage... Let's call it UIImage+Scaler.h
// UIImage+Scaler.h
#import <UIKit/UIKit.h>
#interface UIImage (Scaler)
- (UIImage*)scaleToSize:(CGSize)size;
#end
And the implementation:
// UIImage+Scaler.m
#import "UIImage+Scaler.h"
#define kBitsPerComponent 8
#define kBitmapInfo kCGImageAlphaPremultipliedLast
- (UIImage*)scaleToSize:(CGSize)size
{
CGBitmapInfo bitmapInfo = kBitmapInfo;
size_t bytesPerRow = size.width * 4.0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, size.width,
size.height, kBitsPerComponent,
bytesPerRow, colorSpace, bitmapInfo);
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
CGContextDrawImage(context, rect, self.CGImage);
CGImageRef scaledImageRef = CGBitmapContextCreateImage(context);
UIImage* scaledImage = [UIImage imageWithCGImage:scaledImageRef];
CGImageRelease(scaledImageRef);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return scaledImage;
}
OK, now back to your code. Like the other posters said you'll need an autorelease pool.
CGSize buttonSize = CGSizeMake(width, height);
for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
UIButton *btnImage = [UIButton buttonWithType:UIButtonTypeCustom];
btnImage.frame = CGRectMake(xRow, yRow, width, height);
[btnImage addTarget:self
action:#selector(imageDetails:)
forControlEvents:UIControlEventTouchUpInside];
btnImage.tag = iCount;
if(iCount<[arrImages count])
{
NSString *workSpacePath=[[self applicationDocumentsDirectory]
stringByAppendingPathComponent:
[arrImages objectAtIndex:iCount]];
UIImage* bigImage = [UIImage imageWithData:[NSData
dataWithContentsOfFile:workSpacePath]];
UIImage* smallImage = [bigImage scaleToSize:buttonSize];
[btnImage setBackgroundImage:smallImage forState:UIControlStateNormal];
[scrollImages addSubview:btnImage];
}
[pool drain]; // must drain pool inside loop to release bigImage
}
Hope this solves for problem.

Put your for loop in an autorelease pool:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for(...)
{
....
}
[pool release];

Use NSAutoreleasePool. Keep your code inside an autoreleasepool.
for(iArrCount=0;iArrCount<[arrImages count];iArrCount++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
......
......
[pool drain];
}

Related

Is it possible to use UIGraphicsBeginImageContext from connectionDidFinishLoading?

I got these errors when the code below is executed:
[Switching to process 74682 thread 0x2003]
[Switching to process 74682 thread 0x207]
[Switching to process 74682 thread 0x720f]
--
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
UIImage = [[UIImage alloc] initWithData:self.resp_data;
CGSize itemSize = CGSizeMake(150);
UIGraphicsBeginImageContext(itemSize);
CGRect image_rect = CGRectMake(0.0,0.0,itemSize.width,itemSize.height);
[image drawInRect:image_rect];
self.image_view.image = UIGraphicsGetImageFromCurrentImageContext(); // image_view ivar is connected to a UIImageView in the View associated to this controller
self.resp_data = nil;
self.imageConnection = nil;
[image release];
}
Any idea what could be the issue and how to solve it ? (and of course the expected image does not show up)
Thx for helping,
Stephane
TESTED CODE:100% WORKS
#define kAppIconHeight 48
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Set appIcon and clear temporary data/image
UIImage *image = [[UIImage alloc] initWithData:self.resp_data];
if (image.size.width != kAppIconHeight && image.size.height != kAppIconHeight)
{
CGSize itemSize = CGSizeMake(kAppIconHeight, kAppIconHeight);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
self.image_view.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else
{
self.image_view.image = image;
}
self.resp_data = nil;
[image release];
// Release the connection now that it's finished
//self.imageConnection = nil;
// call our delegate and tell it that our icon is ready for display
// [delegate appImageDidLoad:self.indexPathInTableView];
}

why some UIimages don't show up in iphone

hi I am currently developing a small app on ios 4.3 , using objective c
as part of the app I need to manipulate an Image that I have downloaded from the web.
the following code shows up a missing image:
(the original is in a class but I just put this together as a test scenario so that it could be easily copy pasted)
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadImage:#"http://www.night-net.net/images/ms/microsoft_vista_home_basic.jpg"];
[self getCroped:CGRectMake(10, 50, 80, 160)];
[self getCroped:CGRectMake(90, 50, 80, 80)];
[self getCroped:CGRectMake(90, 130, 40, 80)];
[self getCroped:CGRectMake(130, 130, 40, 40)];
[self getCroped:CGRectMake(130, 170, 40, 40)];
}
-(void) loadImage : (NSString*) url
{
_data = [NSData dataWithContentsOfURL:
[NSURL URLWithString: url]];
}
-(UIImageView*) getCroped:(CGRect) imageSize{
UIImage *temp = [[UIImage alloc] initWithData:_data];
UIImage *myImage = [self resizedImage:temp and:CGSizeMake(160,160) interpolationQuality:kCGInterpolationHigh];
UIImage *image = [self croppedImage:myImage and:imageSize];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = image;
imageView.frame = imageSize;
[[self view] addSubview:imageView];
return imageView;
}
- (UIImage *)croppedImage:(UIImage*) image and: (CGRect)bounds {
CGImageRef imageRef = CGImageCreateWithImageInRect([image CGImage], bounds);
UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
return croppedImage;
}
- (UIImage *)resizedImage:(UIImage*) image and:(CGSize)newSize interpolationQuality:(CGInterpolationQuality)quality {
BOOL drawTransposed = NO;
return [self resizedImage:image
and:newSize
transform:[self transformForOrientation:newSize]
drawTransposed:drawTransposed
interpolationQuality:quality];
}
// Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size
// The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation
// If the new size is not integral, it will be rounded up
- (UIImage *)resizedImage:(UIImage*) image and:(CGSize)newSize
transform:(CGAffineTransform)transform
drawTransposed:(BOOL)transpose
interpolationQuality:(CGInterpolationQuality)quality {
CGRect newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height));
CGRect transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width);
CGImageRef imageRef = image.CGImage;
// Build a context that's the same dimensions as the new size
CGContextRef bitmap = CGBitmapContextCreate(NULL,
newRect.size.width,
newRect.size.height,
CGImageGetBitsPerComponent(imageRef),
0,
CGImageGetColorSpace(imageRef),
CGImageGetBitmapInfo(imageRef));
// Rotate and/or flip the image if required by its orientation
CGContextConcatCTM(bitmap, transform);
// Set the quality level to use when rescaling
CGContextSetInterpolationQuality(bitmap, quality);
// Draw into the context; this scales the image
CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef);
// Get the resized image from the context and a UIImage
CGImageRef newImageRef = CGBitmapContextCreateImage(bitmap);
UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
// Clean up
CGContextRelease(bitmap);
CGImageRelease(newImageRef);
return newImage;
}
// Returns an affine transform that takes into account the image orientation when drawing a scaled image
- (CGAffineTransform)transformForOrientation:(CGSize)newSize {
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, newSize.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
return transform;
}
at first I thought this is caused by a lack of memory, but I have tested for that and that doesnt seem to be the problem,thanks in advance ofir
I've had issues in the past with images not appearing within UIWebViews if they contain unicode characters in the filename. I wonder if this might be the same thing. Try renaming your image?
doing this should be possible and low on memory cost as I did the same test,using flash to create an iphone app that does the same thing, and it works.
but I would much prefer using objective c so the question still stands

Drawrect causing memory issues

Currently I am using UIView instead of UIImageview due to Memory consumption in large scale images. following is same code I am using.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
[myImage drawInRect:rect];
}
-(void) SetImage:(UIImage*) aImage
{
if(!aImage)
return;
if(myImage)
{
[myImage release];
myImage = nil;
}
myImage = [[[UIImage alloc]initWithCGImage:aImage.CGImage] retain];
[self setNeedsDisplay];
}
This is causing now memory leak of 8 MB ( checked with Instrument ) every time when Update and set the same image again. if I comment
[self setNeedsDisplay];
There is no leak. can anyone help me here if I am doing something wrong. OR can anyone help me to Subclass UIImageview to handle large scale image.
// Calling functions
-(void) FitToCardStart
{
UIImage* temp = ScaleImage([iImageBgView GetImage]);
[iImageBgView SetImage:temp];
[temp release];
temp = nil;
}
// ScaleImage
UIImage* ScaleImage(UIImage* image)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
int kMaxResolution = 1800;
CGImageRef imgRef = image.CGImage;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
if (width < kMaxResolution || height < kMaxResolution)
{
CGFloat ratio = width/height;
if (ratio > 1)
{
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else
{
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}
CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
UIImageOrientation orient = image.imageOrientation;
switch(orient)
{
case UIImageOrientationUp: //default
transform = CGAffineTransformIdentity;
break;
default:
[NSException raise:NSInternalInconsistencyException format:#"Invalid image orientation"];
}
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
CGContextConcatCTM(context, transform);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIImage* temp = [[[UIImage alloc] initWithCGImage:imageCopy.CGImage] retain];
CGImageRelease(imgRef);
CGContextRelease(context);
[pool release];
return temp;
}
Thanks,
Sagar
Your problem is this line:
myImage = [[[UIImage alloc]initWithCGImage:aImage.CGImage] retain];
alloc already gives you a retain count of 1, with the additional retain you end up with a retain count of 2 which is too high. Remove the retain and everything will be fine.
myImage = [[[UIImage alloc]initWithCGImage:aImage.CGImage] retain];
There's redundant retain in this line - as you're allocating new UIIMage object (use +alloc) method you don't need to extra retain it.
Edit: ScaleImage method has the same problem with redundant retain:
// remove extra retain here
UIImage* temp = [[[UIImage alloc] initWithCGImage:imageCopy.CGImage] retain];
// should be
UIImage* temp = [[UIImage alloc] initWithCGImage:imageCopy.CGImage];
And a code-style comment - it is better to indicate in your method names what memory management behavior required for returned objects - as image returned by your method needs to be released method name should contain something from "new", "alloc", "copy", "create"...
I suggest not creating a new image, but just keeping the aImage instance.
myImage = [aImage retain];
I you absolutely must make it a new instance, you are doing it in a very roundabout way.
Copying would be a much better alternative.
myImage = [aImage copy];

Load entire image and use CATiledLayer

I'm not sure I did understand that very well in the Apple doc. I'm considering using CATiledLayer to display a JPEG image. However, I only have an entire JPEG file at my disposal and no small tiles. Is it still possible to use a CATiledLayer and let it "tile" the JPEG?
Thanks!
No. You will have to tile them yourself, unfortunately. The WWDC 2010 videos on Core Animation discuss how to do this and in their sample code, they demonstrate how to use a CATileLayer when the tiles already exist.
Correction
I meant to say watch the Scroll View session. It's session 104 "Designing Apps with Scroll Views"
You may use This for making CATileLayer with image and Zoom functionality in scrollview.
- (void)viewDidLoad
{
[super viewDidLoad];
// You do not need to and must not autorelease [NSBundle mainBundle]
NSString *path = [[NSBundle mainBundle] pathForResource:#"img" ofType:#"jpg"];
NSData *data = [NSData dataWithContentsOfFile:path];
image = [UIImage imageWithData:data];
// CGRect pageRect = CGRectMake(0, 0, 1600, 2400);
CGRect pageRect = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.delegate = self;
tiledLayer.tileSize = CGSizeMake(256.0, 256.0);
tiledLayer.levelsOfDetail = 5; // was 1000, which makes no sense. Each level of detail is a power of 2.
tiledLayer.levelsOfDetailBias = 5; // was 1000, which also makes no sense.
// Do not change the tiledLayer.frame.
tiledLayer.bounds = pageRect; // I think you meant bounds instead of frame.
// Flip the image vertically.
tiledLayer.transform = CATransform3DMakeScale(1.0f, -1.0F, 1.0f);
myContentView = [[UIView alloc] initWithFrame:CGRectMake(500,400,image.size.width,image.size.height)];
[myContentView.layer addSublayer:tiledLayer];
// UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView=[[UIScrollView alloc]initWithFrame:CGRectMake(10, 10, 300, 400)];
scrollView.backgroundColor = [UIColor whiteColor];
// [scrollView setContentSize:CGSizeMake(image.size.width,image.size.height)];
[scrollView setContentSize:image.size];
scrollView.maximumZoomScale = 32; // = 2 ^ 5
scrollView.delegate = self;
// scrollView.contentSize = pageRect.size;
[scrollView addSubview:myContentView];
[self.view addSubview:scrollView];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return myContentView;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"img" ofType:#"jpg"];
NSData *data = [NSData dataWithContentsOfFile:path];
image = [UIImage imageWithData:data];
CGRect imageRect = CGRectMake (0.0, 0.0, image.size.width, image.size.height);
CGContextDrawImage (context, imageRect, [image CGImage]);
}
The easier way is to just downscale the image until it fits (I think devices support up to 2044x2044 or so). You can create subimages of a CGImage with CGImageCreateWithImageInRect()

iPhone: CATiledLayer/UIScrollView wont scroll after zooming and only zooms to anchor point

Here is the problem... I am using CA Tiled Layer to display a large jpg. The view loads okay, and when I go to scroll around, it works fine. However, as soon as I zoom in or out once, it scrolls to the top left (to the anchor point) and will not scroll at all. The zooming works fine, but I just cannot scroll.
Here is my code:
#import <QuartzCore/QuartzCore.h>
#import "PracticeViewController.h"
#implementation practiceViewController
//#synthesize image;
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"H-5" ofType:#"jpg"];
NSData *data = [NSData dataWithContentsOfFile:path];
image = [UIImage imageWithData:data];
CGRect pageRect = CGRectMake(0, 0, image.size.width, image.size.height);
CATiledLayer *tiledLayer = [CATiledLayer layer];
tiledLayer.anchorPoint = CGPointMake(0.0f, 1.0f);
tiledLayer.delegate = self;
tiledLayer.tileSize = CGSizeMake(1000, 1000);
tiledLayer.levelsOfDetail = 6;
tiledLayer.levelsOfDetailBias = 0;
tiledLayer.bounds = pageRect;
tiledLayer.transform = CATransform3DMakeScale(1.0f, -1.0f, 0.3f);
myContentView = [[UIView alloc] initWithFrame:self.view.bounds];
[myContentView.layer addSublayer:tiledLayer];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.delegate = self;
scrollView.contentSize = pageRect.size;
scrollView.minimumZoomScale = .2;
scrollView.maximumZoomScale = 1;
[scrollView addSubview:myContentView];
[self.view addSubview:scrollView];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return myContentView;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"H-5" ofType:#"jpg"];
NSData *data = [NSData dataWithContentsOfFile:path];
image = [UIImage imageWithData:data];
CGRect imageRect = CGRectMake (0.0, 0.0, image.size.width, image.size.height);
CGContextDrawImage (ctx, imageRect, [image CGImage]);
}
#end
See the above listed question