MonoTouch: Doubling Appearance Image size when Hue

2019-02-15 14:57发布

I am setting the NavBar's background with this code which works great in Retina and non-Retina displays. There is a @2x and normal image. So, all good:

UINavigationBar.Appearance.SetBackgroundImage(
     GetImage(ImageTheme.menubar), UIBarMetrics.Default);

Now, when I apply this ChangeHue() transformation to the image to adjust its hue, on Retina displays the image is twice the size. Non-Retina displays are fine:

    UINavigationBar.Appearance.SetBackgroundImage(
       ChangeHue(GetImage(ImageTheme.menubar)), UIBarMetrics.Default);
    ...

    UIImage ChangeHue(UIImage originalImage){
        var hueAdjust = new CIHueAdjust() {
            Image = CIImage.FromCGImage(originalImage.CGImage),
            Angle = hue * (float)Math.PI / 180f // angles to radians
        };

        var output = hueAdjust.OutputImage;
        var context = CIContext.FromOptions(null);
        var cgimage = context.CreateCGImage(output, output.Extent);
        var i = UIImage.FromImage(cgimage);
        return i;
}

Here is the result in Non-Retina and Retina displays after the Hue is applied:

Non-Retina

Retina

4条回答
走好不送
2楼-- · 2019-02-15 15:12

I never tried for CoreImage but, for CoreGraphics, you need to use UIGraphics.BeginImageContextWithOptions and specify 0 for scaling (so it will be done automagically for both Retina and non-Retina displays).

So the first thing I would try is to replace your:

var context = CIContext.FromOptions(null);

with the following block:

UIGraphics.BeginImageContextWithOptions (new SizeF (size, size), false, 0);
using (var c = UIGraphics.GetCurrentContext ()) {
   var context = CIContext.FromContext (c);
   ...
}
UIGraphics.EndImageContext ();

UPDATE: FromContext is not available in iOS (it's OSX specific) so the above code won't work.

查看更多
混吃等死
3楼-- · 2019-02-15 15:17

Filed a bug with the MonoTouch team. Will post solution shortly.

查看更多
该账号已被封号
4楼-- · 2019-02-15 15:18

Ignore those HACKs, and edit this line in your ChangeHue method:

var i = UIImage.FromImage(cgimage);

to do this instead:

float scale = 1f;
if (UIScreen.MainScreen.RespondsToSelector (new MonoTouch.ObjCRuntime.Selector ("scale"))) {
    scale = UIScreen.MainScreen.Scale; // will be 2.0 for Retina 
}
var i = new UIImage(cgimage, scale, UIImageOrientation.Up);

This should return a UIImage object that is has the correct 'scale' information to be properly displayed in the UINavigationBar.

查看更多
狗以群分
5楼-- · 2019-02-15 15:30

I've got three 'hacks' to suggest for now:

Hack #1

To force the extent to trim the image by replacing this line:

var cgimage = context.CreateCGImage(output, output.Extent);

with this:

var extent = output.Extent;
if (UIScreen.MainScreen.RespondsToSelector (new MonoTouch.ObjCRuntime.Selector("scale"))) {
    if (UIScreen.MainScreen.Scale == 2f) {
        extent = new System.Drawing.RectangleF(extent.X, extent.Y, extent.Width / 2f, extent.Height / 2f);
    }
}
var cgimage = context.CreateCGImage(output, extent);

HOWEVER you 'lose' the Retina resolution on the adjusted image (it uses the @2x image as the source, but only displays the bottom-left quadrant of it after applying the filter, thanks to the image origin starting at the bottom-left).

Hack #2

Along the same lines, you can scale the image returned from the ChangeHue method so that it doesn't expand beyond the navigation bar:

var hued = ChangeHue (navBarImage);
if (hued.RespondsToSelector(new MonoTouch.ObjCRuntime.Selector("scale")))
    hued = hued.Scale (new System.Drawing.SizeF(320, 47));
UINavigationBar.Appearance.SetBackgroundImage (hued, UIBarMetrics.Default);

UNFORTUNATELY you 'lose' the Retina resolution again, but at least the image is displayed correctly (just downsampled to 320 wide).

Hack #3

You could save the filtered image to disk and then set the UIAppearance using the image file on 'disk'. The code would look like this:

bool retina = false;
if (UIScreen.MainScreen.RespondsToSelector (new MonoTouch.ObjCRuntime.Selector ("scale"))) {
    if (UIScreen.MainScreen.Scale == 2f) {
        retina = true;
    }
}
if (retina) {
    NSError err; // unitialized
    UIImage img = ChangeHue (navBarImage);
    img.AsPNG ().Save ("tempNavBar@2x.png", true, out err);
    if (err != null && err.Code != 0) {
        // error handling
    }
    UINavigationBar.Appearance.SetBackgroundImage (UIImage.FromFile ("tempNavBar.png"), UIBarMetrics.Default);
} else {
    UINavigationBar.Appearance.SetBackgroundImage (ChangeHue (navBarImage), UIBarMetrics.Default);
}

The benefit of this final hack is that the image looks correct (ie. Retina resolution is preserved).

I'm still looking for the "perfect" solution, but at least these ideas 'fix' your problem one-way-or-another...

查看更多
登录 后发表回答