Reputation: 1785
Xamarin provides some sample code for doing simple adjustments to an image in iOS:
This code updates the image only when the user lets go of the slider knob - not the continuous updating we normally expect.
However when I make the following change I reliably get SIGSEGV faults on hardware.
//sliderC.TouchUpInside += HandleValueChanged;
//sliderS.TouchUpInside += HandleValueChanged;
//sliderB.TouchUpInside += HandleValueChanged;
sliderC.ValueChanged += HandleValueChanged;
sliderS.ValueChanged += HandleValueChanged;
sliderB.ValueChanged += HandleValueChanged;
I expect that this is "overloading" the code in some way. How would you implement image adjustments that avoid this problem? Is there a lower-level approach, or do other apps simply use a much lower-rez version of the image for adjustments?
Upvotes: 1
Views: 1149
Reputation: 74144
Here is a quickie version that I did that is 'more' realtime (This is a recording from the sim, the device (6s) is smooth depending upon initial image size.
Create a single viewcontroller iOS app from the template and add a UIImageView and three sliders to the storyboard so it looks like the animate gif.
I created a simple class to store the ColorCtrl values (brightness, contrast, saturation:
public class ColorCtrl
{
public float s;
public float b;
public float c;
}
Than in the ViewDidLoad
method, do some setup:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
string filePath = Path.Combine (NSBundle.MainBundle.BundlePath, "hero.jpg");
originalImage = new CIImage (new NSUrl (filePath, false));
colorCtrls = new CIColorControls ();
colorCtrls.Image = originalImage;
// Create the context only once, and re-use it
var contextOptions = new CIContextOptions ();
contextOptions.UseSoftwareRenderer = false; // gpu vs. cpu
// On save of the image, create a new context with highqual attributes and re-apply the filter...
contextOptions.HighQualityDownsample = false;
contextOptions.PriorityRequestLow = false; // high queue order it
contextOptions.CIImageFormat = (int)CIFormat.ARGB8; // use 32bpp, vs. 64|128bpp
context = CIContext.FromOptions (contextOptions);
}
Then for the three slider's change handlers.
Within those I 'hack' a busy flag in order to skip the image transformation if the last transform is not done get. If we are not busy, then do an await call on our async transform method.
Note: I said 'hack', I am mean it, in a best practice way, this should pump transform requests to a queue and the queue handler would summarize all the pending items in the queue, flush them and do the transform.
Note: I added the "async" to the generated event handlers so I can await
the image transform.
Note: The three slider's handlers are all the same except for the value that they are assigning; colorCtrlV.b | colorCtrlV.s | colorCtrlV.c
Note: You can down-sample large image at the moment the user does a touch down, perform the transforms on that, and on touch up, transform the original full-size image...
async partial void brightnessChange (UISlider sender)
{
if (!busy) {
busy = true;
colorCtrlV.b = sender.Value;
this.imageView.Image = await FilterImageAsync (colorCtrlV);
busy = false;
}
}
Ok, now for actual transform, a fairly simple Run.Task so we can get this work off the main thread and NOT block the UI. This way the sliders will not stutter as the user slides their finger, BUT due to the 'busy' flag/hack in the slider's handler we might skip some of those events (we should add a handler for the touch drag exit so we process the 'last' user's requested value....)
// async Task.Run() - not best practice - just a demo
async Task<UIImage> FilterImageAsync (ColorCtrl value)
{
if (transformImage == null)
transformImage = new Func<UIImage>(() => {
colorCtrls.Brightness = colorCtrlV.b;
colorCtrls.Saturation = colorCtrlV.s;
colorCtrls.Contrast = colorCtrlV.c;
var output = colorCtrls.OutputImage;
var cgImage = context.CreateCGImage (output, originalImage.Extent);
var filteredImage = new UIImage (cgImage);
return filteredImage;
});
UIImage image = await Task.Run<UIImage>(transformImage);
return image;
}
Personally For this type of realtime image transformations I prefer to do it via OpenGL-ES using GPUImage as the screen's interaction at the 60z refresh rate is as smooth as butter, but it is a lot more work than using CoreImage
filters.
Upvotes: 4