App crash due to memory warning when calling "GPUImagePicture ProcessImage" repeatedly - ios

I have implemented a group filter(GPUImageSepiaFilter, GPUImageExposureFilter, GPUImageSepiaFilter) for image editing. And I have one slider which is used to set custom "Exposure" (setExposure:) value. On the "didValueChanged" action method of slider, I am refreshing the image preview by calling "picture processImage". If I move the slider very fast or when repeatedly scrolling the slider, app crashes for sure due to memory issue.
- (void)viewDidLoad {
[super viewDidLoad];
self.originalPicture = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:#"IMG_0009.JPG"]];
self.filterGroup = [[GPUImageFilterGroup alloc] init];
GPUImageSepiaFilter *sepiaFilter = [[GPUImageSepiaFilter alloc] init];
[self.filterGroup addFilter:sepiaFilter];
GPUImageExposureFilter *pixellateFilter = [[GPUImageExposureFilter alloc] init];
[pixellateFilter setExposure:0.0f];
[self.filterGroup addFilter:pixellateFilter];
GPUImageSaturationFilter *saturation = [[GPUImageSaturationFilter alloc] init];
[self.filterGroup addFilter:saturation];
[sepiaFilter addTarget:pixellateFilter];
[pixellateFilter addTarget:saturation];
[self.filterGroup setInitialFilters:[NSArray arrayWithObjects:sepiaFilter, pixellateFilter,nil]];
[self.filterGroup setTerminalFilter:saturation];
[self.originalPicture addTarget:self.filterGroup];
GPUImageView *filterView = [[GPUImageView alloc] init];
self.view = filterView;
[self.filterGroup addTarget:filterView];
[self.originalPicture processImage];
[self.slider setMinimumTrackTintColor:[UIColor redColor]];
[self.slider setMaximumTrackTintColor:[UIColor greenColor]];
[self.view addSubview:self.slider];
}
- (IBAction)didChangeValue:(id)sender {
GPUImageExposureFilter *filter = (GPUImageExposureFilter *)[self.filterGroup filterAtIndex:1];
[filter setExposure:self.slider.value];
[self.originalPicture processImage];
}
Which is the best way to fix this? Or am I doing anything wrong?
Thanks,
Srinivas

Mine are just some advices about images and GPUImage:
The "disk" size image it doesn't represent the real image size, that's because it could have been compressed. The real image size, when it's decompressed in memory and the system ha to handle it is: height*width*n°channel*n°bit_for_each_channel
Images should be always loaded lazily, is useless have them around if you are not using them
Brad has made a huge change in its framework about framebuffer reuse that had a major improvement on how memory is handled, are you sure that you are using the last version on github?
have you tried to profile the app with allocation instruments, maybe the problem is somewhere else, with this tool you can see if the memory grows where you expect
imageNamed method caches images and even if they say that this memory will be evicted in memory pressure situation I never had the occasion to see that purge working
I'm not seeing anything wrong with your code in using GPUImage, but I would try to use smaller images (in pixel size) and first of all use allocations.

Related

iOS - Reduce GPUImage RAM usage

I am creating a filter with GPUImage. The image displays fine. I also have a UISlider that the user can slide to change alpha to the filter.
Here is how I setup my filter:
-(void)setupFirstFilter
{
imageWithOpacity = [ImageWithAlpha imageByApplyingAlpha:0.43f image:scaledImage];
pictureWithOpacity = [[GPUImagePicture alloc] initWithCGImage:[imageWithOpacity CGImage] smoothlyScaleOutput:YES];
originalPicture = [[GPUImagePicture alloc] initWithCGImage:[scaledImage CGImage] smoothlyScaleOutput:YES];
multiplyBlender = [[GPUImageMultiplyBlendFilter alloc] init];
[originalPicture addTarget:multiplyBlender];
[pictureWithOpacity addTarget:multiplyBlender];
UIImage *pinkImage = [ImageFromColor imageFromColor:[UIColor colorWithRed:255.0f/255.0f green:185.0f/255.0f blue:200.0f/255.0f alpha:0.21f]];
pinkPicture = [[GPUImagePicture alloc] initWithCGImage:[pinkImage CGImage] smoothlyScaleOutput:YES];
overlayBlender = [[GPUImageOverlayBlendFilter alloc] init];
[multiplyBlender addTarget:overlayBlender];
[pinkPicture addTarget:overlayBlender];
UIImage *blueImage = [ImageFromColor imageFromColor:[UIColor colorWithRed:185.0f/255.0f green:227.0f/255.0f blue:255.0f/255.0f alpha:0.21f]];
bluePicture = [[GPUImagePicture alloc] initWithCGImage:[blueImage CGImage] smoothlyScaleOutput:YES];
secondOverlayBlend = [[GPUImageOverlayBlendFilter alloc] init];
[overlayBlender addTarget:secondOverlayBlend];
[bluePicture addTarget:secondOverlayBlend];
[secondOverlayBlend addTarget:self.editImageView];
[originalPicture processImage];
[pictureWithOpacity processImage];
[pinkPicture processImage];
[bluePicture processImage];
}
And when the slider is changed this gets called:
-(void)sliderChanged:(id)sender
{
UISlider *slider = (UISlider*)sender;
double value = slider.value;
[originalPicture addTarget:multiplyBlender];
[pictureWithOpacity addTarget:multiplyBlender];
UIImage *pinkImage = [ImageFromColor imageFromColor:[UIColor colorWithRed:255.0f/255.0f green:185.0f/255.0f blue:200.0f/255.0f alpha:value]];
pinkPicture = [[GPUImagePicture alloc] initWithCGImage:[pinkImage CGImage] smoothlyScaleOutput:NO];
[multiplyBlender addTarget:overlayBlender];
[pinkPicture addTarget:overlayBlender];
[overlayBlender addTarget:secondOverlayBlend];
[bluePicture addTarget:secondOverlayBlend];
[secondOverlayBlend addTarget:self.editImageView];
[originalPicture processImage];
[pictureWithOpacity processImage];
[pinkPicture processImage];
[bluePicture processImage];
}
The code above works fine. But the slide is slow and this is taking up to 170 MB or RAM. Before pressing to use filter it is around 30 MB RAM. How can I reduce the RAM by doing this filter?
I already reduce the image size.
Any help is greatly appreciated.
My first suggestion is to get rid of the single-color UIImages and their corresponding GPUImagePicture instances. Instead, use a GPUImageSolidColorGenerator, which does this solid-color generation entirely on the GPU. Make it output a small image size and that will be scaled up to fit your larger image. That will save on the memory required for your UIImages and avoid a costly draw / upload process.
Ultimately, however, I'd recommend making your own custom filter rather than running multiple blend steps using multiple input images. All that you're doing is applying a color modification to your source image, which can be done inside a single custom filter.
You could pass in your colors to a shader that applies two mix() operations, one for each color. The strength of each mix value would correspond to the alpha you're using in the above for each solid color. That would reduce this down to one input image and one processing step, rather than three input images and two steps. It would be faster and use significantly less memory.

GPUImage crashing in iOS 8

I have implemented a filter tool mechanism which has many filters. Each filter contains 2-3 different filters i.e. i am using GPUImageFilterGroup for this. Now when i updated GPU Image Library for iOS 8 compatible it shows "Instance Method prepareForImageCapture not found" and app crashes.
I also tried to implement the following code
GPUImageFilterGroup *filter = [[GPUImageFilterGroup alloc] init];
GPUImageRGBFilter *stillImageFilter1 = [[GPUImageRGBFilter alloc] init];
// [stillImageFilter1 prepareForImageCapture];
stillImageFilter1.red = 0.2;
stillImageFilter1.green = 0.8;
[stillImageFilter1 useNextFrameForImageCapture];
[(GPUImageFilterGroup *)filter addFilter:stillImageFilter1];
GPUImageVignetteFilter *stillImageFilter2 = [[GPUImageVignetteFilter alloc] init];
// [stillImageFilter1 prepareForImageCapture];
stillImageFilter2.vignetteStart = 0.32;
[stillImageFilter1 useNextFrameForImageCapture];
[(GPUImageFilterGroup *)filter addFilter:stillImageFilter2];
[stillImageFilter1 addTarget:stillImageFilter2];
[(GPUImageFilterGroup *)filter setInitialFilters:[NSArray arrayWithObject:stillImageFilter1]];
[(GPUImageFilterGroup *)filter setTerminalFilter:stillImageFilter2];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:image];
[stillImageSource addTarget:(GPUImageFilterGroup *)filter];
[stillImageSource processImage];
UIImage *img = [(GPUImageFilterGroup *)filter imageFromCurrentFramebuffer];
Its returning nil image. Can anyone tell me whats the correct way!!!
Thanks in advance.
First, that wasn't a crash for iOS 8. You haven't updated your copy of GPUImage in a while, and that method was removed months ago in an update unrelated to any iOS compatibility. The reasons for this are explained here and I'll once again quote the relevant paragraph:
This does add one slight wrinkle to the interface, though, and I've
changed some method names to make this clear to anyone updating their
code. Because framebuffers are now transient, if you want to capture
an image from one of them, you have to tag it before processing. You
do this by using the -useNextFrameForImageCapture method on the filter
to indicate that the next time an image is passed down the filter
chain, you're going to want to hold on to that framebuffer for a
little longer to grab an image out of it. -imageByFilteringImage:
automatically does this for you now, and I've added another
convenience method in -processImageUpToFilter:withCompletionHandler:
to do this in an asynchronous manner.
As you can see, -prepareForImageCapture was removed because it was useless in the new caching system.
The reason why your updated code is returning nil is that you've called -useNextFrameForImageCapture on the wrong filter. It needs to be called on your terminal filter in the group (stillImageFilter2) and only needs to be called once, right before you call -processImage. That signifies that this particular framebuffer needs to hang around long enough to have an image captured from it.
You honestly don't need a GPUImageFilterGroup in the above, as it only complicates your filter chaining.

GPUImage Memory Accumalation

I am using this code to generate 5 blur images using GPUImage and it seems like there is a memory accumulation of about 20MB which never gets released. Am I doing something wrong?
Here is my code:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GPUImageFastBlurFilter *blurFilter = [[GPUImageFastBlurFilter alloc] init];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:[image copy]];
[stillImageSource addTarget:blurFilter];
CGFloat maxBlur = 12.0;
for (int i=0; i < BLUR_STEPS; i++) {
if (!self.stopBlurOperation) { //stops blur operation on close
UIImageView *imageView;
if (i < self.blurredImageViews.count) {
imageView = (UIImageView *)self.blurredImageViews[i];
blurFilter.blurRadiusInPixels = maxBlur * (i+1) / BLUR_STEPS;
[blurFilter useNextFrameForImageCapture];
[stillImageSource processImage];
UIImage *blurredImage = [blurFilter imageFromCurrentFramebuffer];
dispatch_async( dispatch_get_main_queue(), ^{
[imageView setImage:blurredImage];
});
blurredImage = nil;
}
}
}
[blurFilter removeAllTargets];
[stillImageSource removeAllTargets];
[GPUImageContext setActiveShaderProgram:nil];
blurFilter = nil;
stillImageSource = nil;
});
First, you appear to be using an old version of the framework, as GPUImageFastBlurFilter hasn't existed in there for months. The latest code from the repository uses a new framebuffer caching memory model, which is significantly more efficient in most applications.
Second, that's an extremely inefficient way to run multiple blur passes. Going to and from UIImages requires transferring data to and from the GPU, which is slow, and also requires redrawing using Core Graphis, which is even slower. Again, the code in the framework from the last several months has efficient means of generating large-radius blurs without any artifacting you may have seen before, making the above loop unnecessary.
Finally, you're running a tight loop in the above, and generating at least one autoreleased UIImage at each pass in the loop. Without an autorelease pool to drain in there somewhere, you're going to keep building those up in memory while that loop runs. However, as I said, you can remove all of this and not worry about the memory accumulation if you just update to the latest code in the repository.

Change brightness of an image via uislider and gpuimage filter

I wrote this code to change the brightness of an UIImage via an UISlider and the GPUImageBrightnessFilter. But every time I'll test it the app crashes.
My code:
- (IBAction)sliderBrightness:(id)sender {
CGFloat midpoint = [(UISlider *)sender value];
[(GPUImageTiltShiftFilter *)brightnessFilter setTopFocusLevel:midpoint - 0.1];
[(GPUImageTiltShiftFilter *)brightnessFilter setBottomFocusLevel:midpoint + 0.1];
[sourcePicture processImage];
}
- (void) brightnessFilter {
UIImage *inputImage = imgView.image;
sourcePicture = [[GPUImagePicture alloc] initWithImage:inputImage smoothlyScaleOutput:YES];
brightnessFilter = [[GPUImageTiltShiftFilter alloc] init];
// sepiaFilter = [[GPUImageSobelEdgeDetectionFilter alloc] init];
GPUImageView *imageView = (GPUImageView *)self.view;
[brightnessFilter forceProcessingAtSize:imageView.sizeInPixels]; // This is now needed to make the filter run at the smaller output size
[sourcePicture addTarget:brightnessFilter];
[brightnessFilter addTarget:imageView];
[sourcePicture processImage];
}
Let me make an alternative architectural suggestion. Instead of creating a GPUImagePicture and GPUImageBrightnessFilter each time you change the brightness, then saving that out as a UIImage to a UIImageView, it would be far more efficient to reuse the initial picture and filter and render that to a GPUImageView.
Take a look at what I do in the SimpleImageFilter example that comes with GPUImage. For the tilt-shifted image that's displayed to the screen, I create a GPUImagePicture of the source image once, create one instance of the tilt-shift filter, and then send the output to a GPUImageView. This avoids the expensive (both performance and memory-wise) process of going to a UIImage and then displaying that in a UIImageView, and will be much, much faster. While you're at it, you can use -forceProcessingAtSize: on your filter to only render as many pixels as will be displayed in your final view, also speeding things up.
When you have the right settings for filtering your image, and you want the final UIImage out, you can do one last render pass to extract the processed UIImage. You'd set your forced size back to 0 right before doing that, so you now process the full image.

Multiple Filters using GPUImage Library

I am trying to apply 3 filters to an image.
One rgbFilter which is has its values constant, a brightness filter and a saturation filter, both of which should be able to be modified and the image should update.
I have followed the advice here.
I have setup a UIView using IB and set its class to GPUImageView. For some reason the image doesnt show.
My steps are as follows:
self.gpuImagePicture = [[GPUImagePicture alloc] initWithImage:image];
[self.gpuImagePicture addTarget:self.brightnessFilter];
[self.brightnessFilter addTarget:self.contrastFilter];
[self.contrastFilter addTarget:self.imageView];
and then I call this which sets the constant values on the rgb filter
[self setRGBFilterValues]
I setup my filters before this using:
- (void) setupFilters
{
self.brightnessFilter = [[GPUImageBrightnessFilter alloc] init];
self.contrastFilter = [[GPUImageContrastFilter alloc] init];
self.rgbFilter = [[GPUImageRGBFilter alloc] init];
}
Am I missing a step or why is the image just displaying nothing?
You're missing one step. You need to call -processImage on your GPUImagePicture instance to get it to propagate through the filter chain.
You also need to call this anytime you change values within your filter chain and wish to update the final output.
For my first time using this GPUImage library, it took me way too long to figure out how to simply apply multiple filters to a single image. The link provided by the OP does help explain why the API is relatively complex (one reason: you must specify the order in which the filters are applied).
For future reference, here's my code to apply two filters:
UIImage *initialImage = ...
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:initialImage];
GPUImageSaturationFilter *saturationFilter = [[GPUImageSaturationFilter alloc] init];
saturationFilter.saturation = 0.5;
[stillImageSource addTarget:saturationFilter];
GPUImageGaussianBlurFilter *blurFilter = [[GPUImageGaussianBlurFilter alloc] init];
blurFilter.blurRadiusInPixels = 10;
[saturationFilter addTarget:blurFilter];
GPUImageFilter *lastFilter = blurFilter;
[lastFilter useNextFrameForImageCapture];
[stillImageSource processImage];
UIImage *processedImage = [lastFilter imageFromCurrentFramebuffer];

Resources