Reputation: 4061
We are creating several custom filters using a combination of GPUImage and CIFIlter. The image we are filtering is around 2048 X 2048 pixels. The following code eats up around 300MB of app memory. We need to chain the filters together to get the desired effects, but the image's memory footprints are never getting released. Can someone advise?
UIImage *filteredImage = [self getFilteredImage:initialImage Min:11 Gamma:1.09 Max:226 MinOut:46 MaxOut:208];
filteredImage = [self getFilteredImage:filteredImage Min:34 Gamma:.91 Max:188 MinOut:12 MaxOut:220 forColor:@"red"];
filteredImage = [self getFilteredImage:filteredImage Min:18 Gamma:.89 Max:209 MinOut:32 MaxOut:215 forColor:@"green"];
filteredImage = [self getFilteredImage:filteredImage Min:9 Gamma:1.1 Max:216 MinOut:1 MaxOut:245 forColor:@"blue"];
//Levels
filteredImage = [self getFilteredImage:filteredImage Min:54 Gamma:1.28 Max:232 MinOut:44 MaxOut:179];
filteredImage = [self getFilteredImage:filteredImage Min:15 Gamma:.92 Max:221 MinOut:39 MaxOut:211 forColor:@"red"];
filteredImage = [self getFilteredImage:filteredImage Min:0 Gamma:.9 Max:244 MinOut:15 MaxOut:255 forColor:@"green"];
filteredImage = [self getFilteredImage:filteredImage Min:0 Gamma:1 Max:248 MinOut:16 MaxOut:237 forColor:@"blue"];
+(UIImage*)getFilteredImage: (UIImage*)image Min:(float)min Gamma:(float)gamma Max:(float)max MinOut:(float)minOut MaxOut:(float)maxOut forColor: (NSString*) color
{
GPUImagePicture *gpuImage = [[GPUImagePicture alloc] initWithImage:image];
GPUImageLevelsFilter *levelsFilter = [[GPUImageLevelsFilter alloc] init];
if ([color isEqualToString: @"red"])
{
[levelsFilter setRedMin:[self convertFloat:min] gamma:gamma max:[self convertFloat:max] minOut:[self convertFloat:minOut] maxOut:[self convertFloat:maxOut]];
}else if([color isEqualToString: @"green"])
{
[levelsFilter setGreenMin:[self convertFloat:min] gamma:gamma max:[self convertFloat:max] minOut:[self convertFloat:minOut] maxOut:[self convertFloat:maxOut]];
}
else if([color isEqualToString: @"blue"])
{
[levelsFilter setBlueMin:[self convertFloat:min] gamma:gamma max:[self convertFloat:max] minOut:[self convertFloat:minOut] maxOut:[self convertFloat:maxOut]];
}
else
{
[levelsFilter setMin:[self convertFloat:min] gamma:gamma max:[self convertFloat:max] minOut:[self convertFloat:minOut] maxOut:[self convertFloat:maxOut]];
}
[gpuImage addTarget:levelsFilter];
[gpuImage processImage];
return [levelsFilter imageFromCurrentlyProcessedOutputWithOrientation:image.imageOrientation];
}
Upvotes: 1
Views: 457
Reputation: 170317
You really don't want to be creating new images at each step like you're doing here. Not only does going from a UIImage to GPUImage, then back to a UIImage, cost memory due to the creation of intermediary images, it also is very slow because of the need to copy data to and from the GPU (with slow passes through Core Graphics on both ends).
Instead, you want to try to do as much as you can in one pass, then chain filters in order. There's also no need to set red, green, and blue levels separately, which will reduce the number of passes (and intermediary images) required by a third.
The following code is functionally equivalent to the above:
GPUImagePicture *gpuImage = [[GPUImagePicture alloc] initWithImage:image];
GPUImageLevelsFilter *levelsFilter1 = [[GPUImageLevelsFilter alloc] init];
[levelsFilter1 setMin:[self convertFloat:11] gamma:1.09 max:[self convertFloat:226] minOut:[self convertFloat:46] maxOut:[self convertFloat:208]];
GPUImageLevelsFilter *levelsFilter2 = [[GPUImageLevelsFilter alloc] init];
[levelsFilter2 setRedMin:[self convertFloat:34] gamma:0.91 max:[self convertFloat:188] minOut:[self convertFloat:12] maxOut:[self convertFloat:220]];
[levelsFilter2 setGreenMin:[self convertFloat:18] gamma:0.89 max:[self convertFloat:209] minOut:[self convertFloat:32] maxOut:[self convertFloat:215]];
[levelsFilter2 setBlueMin:[self convertFloat:9] gamma:1.1 max:[self convertFloat:216] minOut:[self convertFloat:1] maxOut:[self convertFloat:245]];
GPUImageLevelsFilter *levelsFilter3 = [[GPUImageLevelsFilter alloc] init];
[levelsFilter3 setMin:[self convertFloat:54] gamma:1.28 max:[self convertFloat:232] minOut:[self convertFloat:44] maxOut:[self convertFloat:179]];
GPUImageLevelsFilter *levelsFilter4 = [[GPUImageLevelsFilter alloc] init];
[levelsFilter4 setRedMin:[self convertFloat:15] gamma:0.92 max:[self convertFloat:221] minOut:[self convertFloat:39] maxOut:[self convertFloat:211]];
[levelsFilter4 setGreenMin:[self convertFloat:0] gamma:0.9 max:[self convertFloat:244] minOut:[self convertFloat:15] maxOut:[self convertFloat:255]];
[levelsFilter4 setBlueMin:[self convertFloat:0] gamma:1 max:[self convertFloat:248] minOut:[self convertFloat:16] maxOut:[self convertFloat:237]];
[gpuImage addTarget:levelsFilter1];
[levelsFilter1 addTarget:levelsFilter2];
[levelsFilter2 addTarget:levelsFilter3];
[levelsFilter3 addTarget:levelsFilter4];
[levelsFilter4 prepareForImageCapture];
[gpuImage processImage];
return [levelsFilter4 imageFromCurrentlyProcessedOutputWithOrientation:image.imageOrientation];
yet is far faster, and will use a lot less memory. I threw in a -prepareForImageCapture
on the last filter, which enables a direct memory mapping between the last filter and your output UIImage. This further reduces memory usage, and speeds image processing.
If you can, I'd rethink some of the color level application here. Do you really need four passes over your image to balance out levels? There's probably a much better way to do this in a single pass, even if it requires a custom filter be written for this.
Also, if you're going to do this kind of adjustment regularly, I'd recommend not allocating new levels filters for each image, but simply attaching them to your different input picture each time.
As one more thing, I might recommend making that -convertFloat method a compiler defined function, if only to clean up the code a little.
Upvotes: 2
Reputation: 6115
Take a look at autoreleasepools:
Especially the part about: "Use Local Autorelease Pool Blocks to Reduce Peak Memory Footprint"
I'm not going to post a complete solution in your particular case but this is definitely "the way to go" to avoid memory peaks
Upvotes: 0