Search code examples
wpfimagetranslationscaling

WPF - Converting point, taking Windows scale factor into account


I have written a small test app, in which I have an image, with a canvas over the top of it. The idea is that a user will be able to "draw" on the canvas. I have an image that is 839 pixels wide and 396 pixels high. I wrote code to capture the mouse position as I move it around the canvas, using e.GetPosition(this) inside the canvas.

The problem is, when I near the bottom-right corner of the image/canvas, I'm getting back position 559, 264, rather than position 839, 396. In other words, the values I'm getting back are scaled by 1.5, which is my Windows scale factor.

I've tried myCanvas.TranslatePoint() and myCanvas.PointFromScreen() and myCanvas.PointToScreen(). I don't want a screen-relative point; I want points relative to the image, not taking the Windows scaling factor into account.

If I simply multiply the values by 1.5, I get the bitmap-relative points that I'm looking for, but of course that's not a solution.

Is there a "Windows-approved" way to do this, short of reading the scaling factor from the registry and manually applying it? I'm not in UWP, so I can't use Windows.UI.ViewManagement.UISettings (at least, I don't think I can). I need this to work for both Windows 10 and Windows 11.

Can anyone help?

EDIT: I can't find a way to attach my solution to this question, but the entirety of my code is shown below. The only thing missing is the PNG file, but literally any image gives the same results.

 <Window x:Class="StackOverflow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:StackOverflow"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="500"
        Width="900">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*" />
      <RowDefinition Height="Auto" />
      <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    
    <Image x:Name="BackgroundImage"
           VerticalAlignment="Top"
           HorizontalAlignment="Left"
           Stretch="None"
           Source="/BingMap.PNG"
           MouseMove="BackgroundImage_MouseMove"/>

    <TextBlock x:Name="TB"
               Grid.Row="2" />
  </Grid>
</Window>

using System.Windows;
using System.Windows.Input;

namespace StackOverflow
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void BackgroundImage_MouseMove(object sender, MouseEventArgs e)
    {
      var pt = e.GetPosition(this);
      TB.Text = $"{pt.X:n0}, {pt.Y:n0}";
    }
  }
}

Solution

  • The image file certainly has different DPI than the standard 96, which is used by WPF's device independent pixels. The native, unstretched size in which a raster image is displayed is determined by its pixel dimensions each multiplied by 96/DPI.

    You may get around this simply by binding an Image element's Width and Height to the PixelWidth and PixelHeight of its Source.

    <Image Source="..."
        Width="{Binding Source.PixelWidth, RelativeSource={RelativeSource Self}}"
        Height="{Binding Source.PixelHeight, RelativeSource={RelativeSource Self}}"/>