C# – Transforming coordinates from an image control to the image source in WPF

cimagestretchwpf

I'm trying to learn WPF, so here's a simple question, I hope:

I have a window that contains an Image element bound to a separate data object with user-configurable Stretch property

<Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="{Binding ImageStretch}" />

When the user moves the mouse over the image, I would like to determine the coordinates of the mouse with respect to the original image (before stretching/cropping that occurs when it is displayed in the control), and then do something with those coordinates (update the image).

I know I can add an event-handler to the MouseMove event over the Image control, but I'm not sure how best to transform the coordinates:

void imageCtrl_MouseMove(object sender, MouseEventArgs e)
{
    Point locationInControl = e.GetPosition(imageCtrl);
    Point locationInImage = ???
    updateImage(locationInImage);
}

Now I know I could compare the size of Source to the ActualSize of the control, and then switch on imageCtrl.Stretch to compute the scalars and offsets on X and Y, and do the transform myself. But WPF has all the information already, and this seems like functionality that might be built-in to the WPF libraries somewhere. So I'm wondering: is there a short and sweet solution? Or do I need to write this myself?


EDIT I'm appending my current, not-so-short-and-sweet solution. Its not that bad, but I'd be somewhat suprised if WPF didn't provide this functionality automatically:

Point ImgControlCoordsToPixelCoords(Point locInCtrl, 
    double imgCtrlActualWidth, double imgCtrlActualHeight)
{
    if (ImageStretch == Stretch.None)
        return locInCtrl;

    Size renderSize = new Size(imgCtrlActualWidth, imgCtrlActualHeight);
    Size sourceSize = bitmap.Size;

    double xZoom = renderSize.Width / sourceSize.Width;
    double yZoom = renderSize.Height / sourceSize.Height;

    if (ImageStretch == Stretch.Fill)
        return new Point(locInCtrl.X / xZoom, locInCtrl.Y / yZoom);

    double zoom;
    if (ImageStretch == Stretch.Uniform)
        zoom = Math.Min(xZoom, yZoom);
    else // (imageCtrl.Stretch == Stretch.UniformToFill)
        zoom = Math.Max(xZoom, yZoom);

    return new Point(locInCtrl.X / zoom, locInCtrl.Y / zoom);
}

Best Answer

It would probably be easier if you used a ViewBox. For example:

<Viewbox Stretch="{Binding ImageStretch}">
    <Image Name="imageCtrl" Source="{Binding MyImage}" Stretch="None"/>
</Viewbox>

Then when you go and call GetPosition(..) WPF will automatically account for the scaling.

void imageCtrl_MouseMove(object sender, MouseEventArgs e) 
{ 
    Point locationInControl = e.GetPosition(imageCtrl);
}