I'm trying to apply a blend filters to 2 images.
I've recently updated GPUImage to the last version.
To make things simple I've modified the example SimpleImageFilter.
Here is the code:
UIImage * image1 = [UIImage imageNamed:#"PGSImage_0000.jpg"];
UIImage * image2 = [UIImage imageNamed:#"PGSImage_0001.jpg"];
twoinputFilter = [[GPUImageColorBurnBlendFilter alloc] init];
sourcePicture1 = [[GPUImagePicture alloc] initWithImage:image1 ];
sourcePicture2 = [[GPUImagePicture alloc] initWithImage:image2 ];
[sourcePicture1 addTarget:twoinputFilter];
[sourcePicture1 processImage];
[sourcePicture2 addTarget:twoinputFilter];
[sourcePicture2 processImage];
UIImage * image = [twoinputFilter imageFromCurrentFramebuffer];
The image returned is nil.Applying some breakpoints I can see that the filter fails inside the method - (CGImageRef)newCGImageFromCurrentlyProcessedOutput the problem is that the framebufferForOutput is nil.I'm using simulator.
I don't get why it isn't working.
It seems that I was missing this command, as written in the documentation for still image processing:
Note that for a manual capture of an image from a filter, you need to
set -useNextFrameForImageCapture in order to tell the filter that
you'll be needing to capture from it later. By default, GPUImage
reuses framebuffers within filters to conserve memory, so if you need
to hold on to a filter's framebuffer for manual image capture, you
need to let it know ahead of time.
[twoinputFilter useNextFrameForImageCapture];
Related
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.
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.
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.
I'm trying to do the following to display an image instead of trying to access video when TARGET_IPHONE_SIMULATOR is true.
UIImage *image = [UIImage imageNamed:#"fake_camera"];
GPUImagePicture *fakeInput = [[GPUImagePicture alloc] initWithImage:image];
GPUImageBuffer *videoBuffer = [[GPUImageBuffer alloc] init];
[fakeInput processImage];
[fakeInput addTarget:videoBuffer];
[videoBuffer addTarget:self.backgroundImageView]; //backgroundImageView is a GPUImageView
This renders my backgroundImageView in black color without displaying my image.
If I send the output of fakeInput to backgroundImageView directly, I see the picture rendered normally in backgroundImageView.
What's going on here?
EDIT:
As Brad recommended I tried:
UIImage *image = [UIImage imageNamed:#"fake_camera"];
_fakeInput = [[GPUImagePicture alloc] initWithImage:image];
GPUImagePicture *secondFakeInput = [[GPUImagePicture alloc] initWithImage:image];
[_fakeInput processImage];
[secondFakeInput processImage];
[_fakeInput addTarget:_videoBuffer];
[secondFakeInput addTarget:_videoBuffer];
[_videoBuffer addTarget:_backgroundImageView];
I also tried:
UIImage *image = [UIImage imageNamed:#"fake_camera"];
_fakeInput = [[GPUImagePicture alloc] initWithImage:image];
[_fakeInput processImage];
[_fakeInput processImage];
[_fakeInput addTarget:_videoBuffer];
[_videoBuffer addTarget:_backgroundImageView];
None of this two approaches seems to work... should they?
A GPUImageBuffer does as its name suggests, it buffers frames. If you send a still photo to it, that one image is buffered, but is not yet sent out. You'd need to send a second image into it (or use -processImage a second time) to get the default buffer of one frame deep to display your original frame.
GPUImageBuffer really doesn't serve any purpose for still images. It's intended as a frame-delaying operation for video in order to do frame-to-frame comparisons, like a low-pass filter. If you need to do frame comparisons of still images, a blend is a better way to go.
I would like to create a GPUImageView to display a filter in real time (as opposed to keep reading imageFromCurrentlyProcessedOutput)
Is it possible to use GPUImage's GPUImageChromaKeyBlendFilter with a still source image automatically updating a GPUImageView?
Here is my code reading this into a UIImage;
UIImage *inputImage = [UIImage imageNamed:#"1.JPG"];
UIImage *backgroundImage = [UIImage imageNamed:#"2.JPG"];
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:inputImage];
GPUImageChromaKeyBlendFilter *stillImageFilter = [[GPUImageChromaKeyBlendFilter alloc] init];
[stillImageFilter setThresholdSensitivity:0.5];
[stillImageFilter setColorToReplaceRed:0.0 green:1.0 blue:0.0];
[stillImageSource addTarget:stillImageFilter];
[stillImageSource processImage];
UIImage *currentFilteredVideoFrame = [stillImageFilter imageByFilteringImage: backgroundImage ];
Everything I have tried so far requires you to add the 'backgroundImage' as a target to the filter (as you would if you were using the StillCamera). If you add the backgroundImage as a target, GPUImage just uses this new images as it's base image.
Can anyone help?
Thanks,
Don't use -imageByFilteringImage: with a two-input filter, like blends. It's a convenience method to quickly set up a small filter chain based on a UIImage and grab a UIImage out. You're not going to want it for something targeting a GPUImageView, anyway.
For the chroma key blend, you'll need to target your input image (the one with the color to be replaced) and background image to the blend, in that order using -addTarget, with GPUImagePicture instances for both. You then target your blend to the GPUImageView.
One note, you'll need to maintain strong references to your GPUImagePictures past the setup method, if you want to keep updating the filter after this point, so you may need to make them instance variables on your controller class.
Once you've set things up in this way, the result will go to your GPUImageView. Every time you call -processImage on one of the two images, the display in your GPUImageView will be updated. Therefore, you can call that after every change in filter settings, like if you had a slider to update filter values, and the image will be updated in realtime.