简体   繁体   中英

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:

模糊,相信我

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?

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.

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.

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?

Here's a piece of xaml code

you can always use scale transform from code behind to scale the images to appropriate amount be it less or more.

<Image  Canvas.Left="150"  Height="170" Width="170" Visibility="Visible" Stretch="None">
                <Image.Source >
                    <BitmapImage UriSource="ms-appx:///Assets/rollingDieSprite.png"></BitmapImage>
                </Image.Source>

                <Image.RenderTransform>
                    <ScaleTransform ScaleX="4" ScaleY="4" x:Name="scaleTfDie"></ScaleTransform>
                </Image.RenderTransform>
            </Image>

in c# code behind you can go for the following

ScaleTransform sc = new ScaleTransform();
            sc.ScaleX = 0.9;
            sc.ScaleY = 0.9;
            imgDieRolling.RenderTransform = sc;

this will control the scaling . try using fill=none . Let me know if it works.

I found this issue quite problematic as well. I'm creating custom bitmaps and drawing them at different positions on the canvas. I couldn't find a way in the XAML, but I found a way using Direct2D. When you set up your D2DContext, there's a function called SetUnitMode(). The default unit mode is "DIPS" which causes all drawing to be scaled. By switching to PIXELS mode, the system stops scaling the drawing and does everything 1:1.

m_d2dContext->SetUnitMode(D2D1_UNIT_MODE_PIXELS);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM