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:
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
.
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.
Filed a bug with the MonoTouch team. Will post solution shortly.
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...