iphone - Memory warning results in crash when creating layers with images -
i'm creating app takes photo each second , saves image disk. when enough photos has been taken, app creates video of images using avfoundation framework. done creating layer each image, add animation hides layer after time combine these layers single layer held avvideocompositioncoreanimationtool
. after creating avvideocomposition
holds animation tool, end using avassetexportsession
export avmutablecomposition
.
however, while creating layers app give 2 memeory warnings , crash if have 50-60 photos or more. use below code create layers. means 60 images, below loop run 60 times. once each image. imagelayer
created contents of photo, animation applied layer , layer added sublayer layer
.
calayer *layer = [calayer layer]; layer.frame = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); layer.bounds = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); (nsstring *imagepath in imagepaths) { cmtime endtime = cmtimeadd(cursortime, cmtimemake(1.0f, (cgfloat)fps)); uiimage *theimage = [[uiimage alloc] initwithcontentsoffile:imagepath]; cgfloat scalefactor = min(rendersize.width / theimage.size.width, rendersize.height / theimage.size.height); cgsize newsize = cgsizemake(theimage.size.width * scalefactor, theimage.size.height * scalefactor); uiimage *resizedimage = [theimage resizedimagetosize:newsize]; cgimageref image = resizedimage.cgimage; cgsize imagesize = resizedimage.size; calayer *imagelayer = [calayer layer]; imagelayer.frame = cgrectmake((rendersize.width - imagesize.width) * 0.50f, (rendersize.height - imagesize.height) * 0.50f, imagesize.width, imagesize.height); imagelayer.bounds = cgrectmake((rendersize.width - imagesize.width) * 0.50f, (rendersize.height - imagesize.height) * 0.50f, imagesize.width, imagesize.height); imagelayer.contents = (__bridge id)image; imagelayer.begintime = (cgfloat)cursortime.value / (cgfloat)cursortime.timescale; cabasicanimation *hideanimation = [cabasicanimation animationwithkeypath:@"hidden"]; hideanimation.fromvalue = @(no); hideanimation.tovalue = @(yes); hideanimation.additive = no; hideanimation.removedoncompletion = no; hideanimation.begintime = (cgfloat)endtime.value / (cgfloat)endtime.timescale; hideanimation.fillmode = kcafillmodeboth; [imagelayer addanimation:hideanimation forkey:nil]; [layer addsublayer:imagelayer]; cursortime = endtime; }
it seems code use memory. can tell me how can optimize code or in other way avoid memory warnings , avoid crash?
update per daij-djans answer.
the autoreleasepool did not change thing. tried writing custom layer , draw image myself. allow me render more images still ends giving memory warnings , crashing app. turns out -drawincontext:
not called unless -setneedsdisplay
called on layer. class looks following:
photolayer.h
#import <quartzcore/quartzcore.h> @interface photolayer : calayer @property (nonatomic, copy) nsstring *path; @end
photolayer.m
#import "photolayer.h" #import <imageio/imageio.h> @implementation photolayer - (void)drawincontext:(cgcontextref)context { [super drawincontext:context]; // retrieve image file path cgdataproviderref dataprovider = cgdataprovidercreatewithcfdata((__bridge cfdataref)[nsdata datawithcontentsoffile:self.path]); cgimageref image = cgimagecreatewithjpegdataprovider(dataprovider, null, true, kcgrenderingintentdefault); cgsize imagesize = cgsizemake(cgimagegetwidth(image), cgimagegetheight(image)); // create transformation orientation cgimagesourceref source = cgimagesourcecreatewithdataprovider(dataprovider, null); cfdictionaryref dictionaryref = cgimagesourcecopypropertiesatindex(source, 0, null); cgaffinetransform transform; nsinteger orientation = [(nsnumber *)cfdictionarygetvalue(dictionaryref, kcgimagepropertyorientation) integervalue]; switch (orientation) { case 1: // flip vertically transform = cgaffinetransformmake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, self.bounds.size.height); break; case 3: // flip horizontally transform = cgaffinetransformmake(-1.0f, 0.0f, 0.0f, 1.0f, self.bounds.size.width, 0.0f); break; case 6: // rotate 90 degrees , flip vertically transform = cgaffinetransformmake(0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f); break; case 8: // rotate -90 degrees , flip vertically transform = cgaffinetransformmake(0.0f, -1.0f, -1.0f, 0.0f, self.bounds.size.height, self.bounds.size.width); break; default: // unknown orientation, consider landscape right transform = cgaffinetransformmake(1.0f, 0.0f, 0.0f, -1.0f, 0.0f, self.bounds.size.height); nslog(@"unknown orientation when drawing image in layer."); break; } // calculate amount scale image cgfloat scalefactor = min(self.bounds.size.width / imagesize.width, self.bounds.size.height / imagesize.height); // calculate new size , positiondepending on orientation cgsize newsize = cgsizezero; cgpoint pos = cgpointzero; if (orientation == 1 || orientation == 3) { newsize = cgsizemake(imagesize.width * scalefactor, imagesize.height * scalefactor); pos = cgpointmake((self.bounds.size.width - newsize.width) * 0.50f, (self.bounds.size.height - newsize.height) * 0.50f); } else { imagesize = cgsizemake(imagesize.height, imagesize.width); newsize = cgsizemake(imagesize.height * scalefactor, imagesize.width * scalefactor); pos = cgpointmake((self.bounds.size.height - newsize.width) * 0.50f, (self.bounds.size.width - newsize.height) * 0.50f); } // apply transformation cgcontextconcatctm(context, transform); // draw image cgcontextdrawimage(context, cgrectmake(pos.x, pos.y, newsize.width, newsize.height), image); // clean cgdataproviderrelease(dataprovider); cgimagerelease(image); cfrelease(source); cfrelease(dictionaryref); } - (void)dealloc { _path = nil; } @end
i use layer below. instead of setting contents of layer, set path image , draw it. draw it, need call -setneedsdisplay
.
calayer *layer = [calayer layer]; layer.frame = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); layer.bounds = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); (nsstring *imagepath in imagepaths) { @autoreleasepool { cmtime endtime = cmtimeadd(cursortime, cmtimemake(1.0f, (cgfloat)fps)); photolayer *photolayer = [[photolayer alloc] init]; photolayer.path = imagepath; photolayer.frame = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); photolayer.bounds = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); photolayer.begintime = (cgfloat)cursortime.value / (cgfloat)cursortime.timescale; cabasicanimation *hideanimation = [cabasicanimation animationwithkeypath:@"hidden"]; hideanimation.fromvalue = @(no); hideanimation.tovalue = @(yes); hideanimation.additive = no; hideanimation.removedoncompletion = no; hideanimation.begintime = (cgfloat)endtime.value / (cgfloat)endtime.timescale; hideanimation.fillmode = kcafillmodeboth; [photolayer addanimation:hideanimation forkey:nil]; [layer addsublayer:photolayer]; [photolayer setneedsdisplay]; cursortime = endtime; } } calayer *parentlayer = [calayer layer]; calayer *videolayer = [calayer layer]; parentlayer.frame = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); videolayer.frame = cgrectmake(0.0f, 0.0f, rendersize.width, rendersize.height); [parentlayer addsublayer:videolayer]; [parentlayer addsublayer:layer];
when number of imagepaths large, each imagepath load original, make new resized image, layer , animation.
thats lot of memory accumulate on time.
- you should try , wrap content of loop inside
@autoreleasepool {...}
if not enough,
i'd lazily load images. make subclass of calayer, pass url , let load when displayed! (implement in drawincontext: in subclaass
something like
@myurllayer : calayer @property(copy) nsstring *path; @end @implementation myurllayer - (void)drawincontext:(cgcontextref)ctx { //load image , draw it! //... } @end
btw if not using arc leaking originalimage , thats prob
Comments
Post a Comment