Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinRT: How do I ensure Images are drawn in a pixel-perfect way on a Canvas?

I am adding Image instances to a Canvas in Windows Runtime environment and my image keeps getting scaled up when in 140 and 180 scale resolution displays, it looks perfect in scale resolution 100. I tried creating 3 PNG images, one for each scale size: 100, 140, 180 but it still scales them up and they look blurry. I created a test image with 4 black pixels on a cyan background and I took a screenshot from the simulator, see how the image is blurry, my original image has just 4 perfect black pixels:

it is blurry, trust me

I tried changing the stretch mode of my Image objects, but it does nothing. I'm using this code to add the images at runtime:

var bitmapImage = new BitmapImage();
StorageFile bitmapFile = await StorageFile.GetFileFromApplicationUriAsync(imageUri);
await bitmapImage.SetSourceAsync(await bitmapFile.OpenReadAsync());

Image image = new Image{ Source = bitmapImage};
image.SetValue(Canvas.LeftProperty, x);
image.SetValue(Canvas.TopProperty, y);
canvas.Children.Add(image);

How do I get the images to draw pixel perfectly in the canvas without scaling and at the exact x/y coordinates I want?

like image 906
satur9nine Avatar asked Sep 20 '13 21:09

satur9nine


3 Answers

I think I have a workaround, but it requires two steps:

First I have to load the image using a more a standard way that doesn't involve getting the file path like so.

var bitmapImage = new BitmapImage(imageUri);

Somehow this must retain more information internally that this image came from a file with the corresponding ResolutionScale for the current display. Thus when it is drawn by the canvas it is not scaled at all. However this only solves half the problem.

The next problem is that the x, y coordinates used to specify where the image is drawn are being scaled so the image is drawn in the wrong place, further than where I wanted. The only thing I can figure to do is unscale them first like so:

var resScale = DisplayInformation.GetForCurrentView().ResolutionScale;
Image image = new Image{ Source = bitmapImage};
image.SetValue(Canvas.LeftProperty, (x * 100.0) / (int)resScale);
image.SetValue(Canvas.TopProperty, (y * 100.0) / (int)resScale);
canvas.Children.Add(image);

The whole thing seems a bit crazy but it seems to work so far... Anyone have a better solution or an explanation why all this is necessary? Seems like the Canvas class needs an unscaled mode that doesn't mess with the images or coordinates across different resolution displays.

UPDATE: This doesn't work perfectly, using a double to store the value results in precision loss and sometimes there are anti-aliasing artifacts. This is not acceptable if you want pixel perfect graphics. I am still looking for a perfect solution.

like image 199
satur9nine Avatar answered Nov 14 '22 15:11

satur9nine


There are a few more things that might help with your solution.

  1. Use UseLayoutRounding="False" on your image.
  2. Put your Canvas in a full-screen Viewbox, then set the Canvas Width and Height to the screen resolution. You'd use unscaled Canvas.Left/Top values in this case.
  3. Use Direct2D/Direct3D for rendering.

Good luck.

like image 2
Filip Skakun Avatar answered Nov 14 '22 14:11

Filip Skakun


You can change the Stretch property to "None", If you image is still meshed-up:

You should look at what DPI it is saved on. WPF tries to be DPI-independend, so it tries to draw an image of 5"x5" on every monitor the same size. Even when the resolution is higher, it still should be 5"x5" only a high resolution would render(rasterize) the image in higher quality.

Here's some info: http://www.wpflearningexperience.com/?p=41

How do I convert a WPF size to physical pixels?

like image 1
Jeroen van Langen Avatar answered Nov 14 '22 14:11

Jeroen van Langen