UIImage bad quality in iOS Xamarin - ios

I have been using the code from here to add some text to my UIImage, however the quality on the image gets really bad. I have narrowed down the code a lot, to this:
public UIImage EditImage(UIImage myImage)
{
using (CGBitmapContext ctx = new CGBitmapContext(IntPtr.Zero, 75, 75, 8, 4 * 75, CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedFirst))
{
ctx.DrawImage(new CGRect(0, 0, 75, 75), myImage.CGImage);
return UIImage.FromImage(ctx.ToImage());
}
}
MyImage is a PNG image. I have added them like this:
MyImage.png (100x100 pixels)
MyImage#2.png (150x150 pixels)
MyImage#3.png (200x200 pixels)
I am not really sure which one of them is used, but when I inspect the image at runtime, the sizes are 75x75 nint (native int). That's why I set the CGBitmapContext height and width to 75.
My problem is that after processing myImage through this function the quality gets very poor. If I skip this process and just use myImage the quality is excellent, however I need the CGBitmapContext to add some text to the image.
Does anyone have any idea what is causing it to be bad quality, and how I can fix it? For the record, I am testing on an iPhone 6S.

You have multiple misunderstandings.
Lets start with the image sizes. If your base image is 100x100 the #2x version should be 200x200 because it is double the resolution. Following this schema your #3x should be 300x300. The operating-system will choose the right one depending on the capabilities of your device. With a 6S it should be #2x depending on how you load the image.
Lets move on to the way you are creating the CGBitmapContext. The source of your code contains exactly what you need but you changed it to fix values and now wonder why your image is 75x75. The bad quality comes right from that, because the system is now resizing your original image.

Related

Xcode #2x image suffix not showing as Retina in iOS

I am having difficulties with retina images.
The screenshot below shows the UICollectionView with a UIImageView contained within each UICollectionViewCell.
Within the app I have a large image 512x512 pixels called travel.png.
The green circle shows what is displayed on the app when I name this file: travel.png. The blue circle shows what I see when I update the image name to be travel#2x.png (i.e. retina naming).
I was hoping due to the large size of the image (512x512) that simply adding the #2x suffix would be enough to convert it to twice the definition (i.e. retina) but as you can see from the two screenshots, both version images show as non-retina.
How can I update the image so that it will display in retina?
travel.png:
travel#2x.png:
* Updated *
Following request in comments below:
I load this image by calling the following function:
// Note - when this method is called: contentMode is set to .scaleAspectFit & imageName is "travel"
public func setImageName(imageName: String, contentMode: ContentMode) {
self.contentMode = contentMode
if let image = UIImage(named: imageName) {
self.image = image
}
}
Here is how the image appears in Xcode before the app renders it (as you can see it is high enough definition):
The reason why you see the low quality image is anti-aliasing. When you provide images bigger then an actual frame of UIImageView (scaleAspectFit mode) the system will automatically downscale them. During scaling some anti-aliasing effects can be added at curve shapes. To avoid the effect you should provide the exact image size you want to display on the screen.
To detect if UIImageView autoscale the image you can switch on Debug->Color Misaligned Images at Simulator menu:
Now all scaled images will highlight at simulator with yellow color. Each highlighted image may have anti-aliasing artifacts and affect CPU usage for scaling algorithms:
To resolve the issue you should use exact sizes. So the system will use them directly without any additional calculations. For example, if your button have 80x80px size you should add three images to assert catalog with following sizes and dpi: 80x80px (72 dpi), 160x160px (144 dpi) and 240x240px (216 dpi):
Now the image will be drawn at the screen without downscaling with much better visual quality:
If your intention is to have just one image for all the sizes, I would suggest it having under Assets.xcassets. It is easy to create the folder structures and manage media assets here.
Steps
On clicking + icon, you will displayed a list of actions. Choose to create a New folder.
Choosing the new folder that is created, click on the + icon again and click on New Image Set.
Choose the imageset. And choose the attributes inspector.
Select Single Scale, under Scales.
Drag and drop the image.
Rename the image name and folder names as you wish.
Now you can use this image using the image name for all the screen sizes.
TL;DR;
Change the view layer's minificationFilter to .trilinear
imageView.layer.minificationFilter = .trilinear
as illustrated by the device screenshot below
As Anton's answer correctly pointed out, the aliasing effet you observe is caused by the large difference in dimensions between the source image and the image view it's displayed in. Adding the #2x suffix won't change anything if you do not change the dimensions of the source image itself.
That said there is an easy way to improve the situation without resizing the original image: CALayer offers some control over the method used by the graphics back-end to resize images : minificationFilter and magnificationFilter. The first one is relevant in your case since the image size is being reduced. The default value is CALayerContentsFilter.linear, just switch to .trilinear for a much better result (more info on those wikipedia pages). This will require more GPU power (thus battery), especially if you apply it on many images.
You should really consider resizing the images before displaying them, either statically or at run-time (and maybe cache the resized versions). In addition to the bad visual quality, using such large images in quantities in your UI will decrease performance and waste lots of memory, leading to potentially other issues.
I have fixed, #DarshanKunjadiya issue.
Make sure (if you are already using assets):
Make sure images are not un-assigned
Now use images in storyboard or code without extensions. (e.g. "image" NOT "image.png")
If you are not using images from assets, move them to assets.
Demo Projects
Hope it helps.
Let me know of your feedback.
I think images without the #2x and #3x are rendered for devices with low resolutions (like the iphone 4 an 3G).
The solution I think is to always use the .xcassets file or to add the #2x or #3X in the names of your images.
In iOS, content is placed on the screen based on the iOS coordinate system. for displaying an image on a standard resolution system having 1:1 pixel density we should supply image at #1x resolution. for higher resolution displays the pixel density will be a scale factor of 2.0, 3.0 which refers in the iOS system as #2x and #3x respectively. That is high-resolution displays demands images with higher density.
For example, if you want to display an image of size 128x128 in standard resolution. You have to supply the #2x and #3x size image of the same. ie., 256x256 at #2x version and 384x384 image at #3x version.
In the following screenshot, I have supplied an image of size 256x256 for 2x version to display a 128x128 pixel image in iPhone 6s. iPhone 6s render images at #2x size. Using the three version of images such as 1x, 2x and 3x with asset catalogue will resolve your issues. So the iPhone will automatically render the correct sized image automatically with the screen resolution.

Make UIButton image appear crisp-perfect on retina display

So I've done lots of reading on how to achieve perfect image quality on UIButton's .imageView property in iOS. Here's my example ->
I've got an UIButton 24x24 points as per the following line:
myButton = [UIButton buttonWithType:UIButtonTypeCustom];
myButton.frame = CGRectMake(82, 8, 24, 24);
myButton.contentMode = UIViewContentModeCenter;
buttonImage = [UIImage imageNamed:#"myImage.png"];
ogImage = [buttonImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
I then have the original image sized to 46x46 pixels (twice 23x23, 24x24 on button for even size to prevent iOS auto-aliasing), then the image#2x at 92x92 pixels. Im testing on an iPhone 6s (obviously retina display) and am still seeing some jaggedness on my UIButton's image. What am I doing wrong here? Am I still not understanding how to achieve perfect retina quality?
Here's an image, Im hoping it displays well for example:
Not sure if this is going to help, but this is my personal preference. If for example, I have a UIButoon of size 24x24, I always generate 24x24, 48x48, and 72x72 images. I rely on my Adobe Illustrator to creat pixel perfect images. Always check your images in pixel preview mode and make sure edges are aligned with the pixels. If not, it can produce artifacts that you see in xCode.
If it's what you want in Illustrator then it's what you get in xCode.

Is it ok to scale down UIImage's?

I've been told not to scale down images, but always to use them in their original resolution.
I have a custom UITableViewCell that has a 'Respond Action' on the cell. The UIImageView in Storyboard is 16x16.
Now originally I was using a 256x256 image, and my UIImageView has an Aspect Ratio constraint, and a height constraint of 16.
Then it was suggested to me to not ever scale down images, but to use an image that fit the exact size I needed. So a 16x16 was designed for me, and I used that. It looks awfully blurry though.
The results are here (The font next to it is 11.0 point to give you an idea of it's size):
What is the correct way to go about this? Should you not scale down images? What is the reason? It looks much better than the 16x16.
Scale your image down before adding it to your project...
Take your 256x256 png image and scale it to 1x, 2x, and 3x size.
1x should equal the size that you need your image to display in the view.
2x and 3x will support retina and retina HD displays.
ex:
Name: image.png | Size: 16x16
Name: image#2x.png | Size: 32x32
Name: image#3x.png | Size: 48x48
Drop these images into your Images.xcassets
see: Specifying High-Resolution Images in iOS
also: Icon and Image Sizes
You don't need to scale down images, but it's a good idea. If you're cramming a 256x256 in a 16x16 means the user is downloading 65kb vs <1kb of data. Or if it's in your bundle, then your app is that much 'heavier'.
But it depends what screen resolution you're using. Even an iPhone 4 uses retina, which means your 16x16 image view should contain a 32x32 image. You should also supply a 48x48 for iPhone 6+ screens. This will make your images look great on each screen size.
Use Images.xcassets to manage your images.
You shouldn't scale down the images ideally, but you also need to cosier the screen resolution. Old devices are 1x, retina devices are 2x and he latest iPhone 6 devices use 3x images which means you should have 16x16, 32x32 and 48x48 images to be used by each device (preferably managed in an image asset). What you're seeing at the moment is a 1x image being scaled up to 2x or 3x so it's blurry. Previously you had a large image scaled down which is bad for memory usage but looks sharp.
If you are using an image from your asset catalog, provided to yourself during development. You don't want to scale down images at runtime as it will add unnecessary overhead to your app. Rather have the assets sized to the required dimensions and add them to your xcimage catalog for 1x, 2x and 3x displays. Think optimisation in terms of memory and bundle size, what's the minimum size required to make it work and look good? If your image is being displayed in a 20x20 square, the optimal sizes will always be 20x20, 40x40 and 60x60 for 1x, 2x and 3x displayed.
Now fetching images from the network is an entirely different ballgame. You will find that sometimes you might want to scale down the image, but only when the image you fetch is bigger than what you need to display, like when you're trying to fit in a 2000x2000px image in a 36x26px square. You want to do the scale down for three reasons:
it will reduce virtual memory usage.
it will reduce storage memory if you persist the image.
you will save yourself from image downsampling artifacts that appear when the system tries to render the image at runtime.
Best thing to do here is to scale down the image as soon as you download from the network and before using it anywhere within your app.
Hope this helps!
There is a purist answer for this and then the answer which saves you from spending your life converting images all day.
The following is a discussion on the topic of scaling.
iOS - Different images based on device OR scaling the same image?
If you want to avoid having to create too many images, then using x2 images with a resolution of x2 the expected point size usage is a reasonable compromise as that covers the majority of current devices. You would only then be scaling down for iPhone 3s and up for iPhone 6+. You only need large XxY images if you expect to use large images. Again I would go for x2 resolution for those. Often worth having an iPhone and iPad version of an image in case your expect usage on iPad is to use a bigger XxY point size.
As noted in the link, the impact of scaling depends on the image. I would only worry about the images you think look bad when scaled. So far I have not found scaling to be an issue and much easier than creating lots of bespoke PNGs.
Scale it down before set it:
Swift extension:
extension UIImage{
// returns a scaled version of the image
func imageScaledToSize(size : CGSize, isOpaque : Bool) -> UIImage{
// begin a context of the desired size
UIGraphicsBeginImageContextWithOptions(size, isOpaque, 0.0)
// draw image in the rect with zero origin and size of the context
let imageRect = CGRect(origin: CGPointZero, size: size)
self.drawInRect(imageRect)
// get the scaled image, close the context and return the image
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage
}
}
Example:
aUIImageView.image = aUIImage.imageScaledToSize(aUIImageView.bounds.size, isOpaque : false)
Set isOpaque to true if the image has no alpha: drawing will have better performance.

resizableImageWithCapInsets / asset catalog slicing confuses units and pixels

Apologies for posting an image-related question as a new user, I need more reputation in order to include pics in my post :)
I am trying to resize an image without scaling its corners. I tried using resizableImageWithCapInsets as well as slicing through an asset catalog (although the latter only supports a deployment target of iOS 7+, which really does not make it a solution option...). I am using an image named "ViewHeaderTest.png" which is 44x100 pixels. The caps/insets should be a 16x16 pixel square in each corner.
This is the code:
UIImageView *headerTest = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ViewHeaderTest.png"]];
headerTest.image = [headerTest.image resizableImageWithCapInsets:UIEdgeInsetsMake(8, 8, 8, 8) resizingMode:UIImageResizingModeStretch];
headerTest.frame = CGRectMake(0, 0, 22, 50);
Code as well as asset catalog slicing produce the same result for me, which, oddly, does not appear mentioned anywhere else on stackoverflow: The caps / insets work fine, but they are scaled to double their original size. Basically it appears that resizableImageWithCapInsets takes returns an image of twice its original proportions.
Any takers? :)
Solution found:
As suspected, this is a retina issue. To avoid xcode confusing retina and non-retina images when using cap insets,
Specity image name without file extension, i.e. #"FileName" instead of #"FileName.png"
Have retina AND "normal" version of the file in corresponding resolutions in your project. I.e. have FileName.png (100px x 100 px) AND FileName#2x.png (200px x 200px) in the project.
I'm happy that it works, so I have not checked if either of these is redundant :)

What is the point of the #2x for Retina display apps?

I understand that the Retina display has 2x as many pixels as the non retina displays, but what is difference between using the #2x version and taking and taking the 512 x 512 image and constraining it via the size of the frame ?
To Clarify:
if I have a button that is 72 x 72 The proper way to display that on an iPhone is to have a
image.png = 72x72
image#2x.png = 144 x 144 <---Fixed :) TY
But why not just use 1 image:
image.png = 512x512
and do something like this:
UIImageView *myImage = [[UIImageView alloc] init ];
[myImage setImage:[UIImage imageNamed:#"image.png"]];
[myImage setFrame:CGRectMake(50, 50, 72, 72)];
I am sure there is a good reason, I just dont know what it is, other then possibly a smaller app file size?
Thanks for the education!
There are several good reasons for sizing your images correctly, but the main one would have to be image clarity: When resizing images, you often end up w/ artifacts that make a picture look muddy or pixelated. By creating the images at the correct size, you'll know exactly what the end user will see on his or her screen.
Another reason would simply be to cut down on the overall file size of your binary: a 16x16 icon takes up orders of magnitude fewer bytes than a 512x512 image.
And if you need a third reason: Convenience methods such as [UIImage imageWithName:#"xxxx"] produce images of actual size and usually do not need additional frame/bounds code to go along with them. If you know the size, you can save yourself a lot of headache.
Because images may not be displayed correctly when resized. Also because larger images use more memory. But if both these are not issues for you, you can use one image for both retina and non retina displays.
Because large images consume a lot of memory and CPU/GPU cycles. Another reason is that scaling down an image causes pixel-level quality issues.
Besides the extra memory and CPU, downsampling an image is inherently lossy. Nice crisply rendered lines turn to crud.
The #2x naming convention exists in case the source image is exactly the same size as the displayed image. Then you can have a 57x57 app icon for non-retina iPhone, and 114x114 app icon for retina display iPhones.
The main advantage of using 2 images is, that both pictures can be handcrafted from the designers so everything looks fine and no up- or downscaling code needed, which cost energy, slows performance and may contains bugs.

Resources