Similar to How do I convert from mouse coordinates to pixel coordinates of a TransformedBitmap? but with the added wrinkle that my Image
is actually embedded in a larger parent Grid
, which has a background, and I would like the pixel coordinates to also be accurate when hovering in the regions beyond the bounds of the image.
Here is my XAML:
<DockPanel>
<Label DockPanel.Dock="Bottom" Name="TheLabel" />
<Grid DockPanel.Dock="Top" Name="TheGrid" Background="Gray" MouseMove="TheGrid_MouseMove">
<Image Name="TheImage" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
</DockPanel>
And here is the code:
public MainWindow()
{
InitializeComponent();
const int WIDTH = 4;
const int HEIGHT = 3;
byte[] pixels = new byte[WIDTH * HEIGHT * 3];
// top-left corner red, bottom-right corner blue for orientation
pixels[0] = Colors.Red.B;
pixels[1] = Colors.Red.G;
pixels[2] = Colors.Red.R;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 0] = Colors.Blue.B;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 1] = Colors.Blue.G;
pixels[(WIDTH * (HEIGHT - 1) + (WIDTH - 1)) * 3 + 2] = Colors.Blue.R;
BitmapSource bs = BitmapSource.Create(WIDTH, HEIGHT, 96.0, 96.0, PixelFormats.Bgr24, null, pixels, WIDTH * 3);
TheImage.Source = new TransformedBitmap(bs, new RotateTransform(90.0));
}
private void TheGrid_MouseMove(object sender, MouseEventArgs e)
{
Point p = TheGrid.TranslatePoint(e.GetPosition(TheGrid), TheImage);
if (TheImage.Source is BitmapSource bs)
{
p = new Point(p.X * bs.PixelWidth / TheImage.ActualWidth, p.Y * bs.PixelHeight / TheImage.ActualHeight);
if (TheImage.Source is TransformedBitmap tb)
{
Matrix inverse = tb.Transform.Value;
inverse.Invert();
inverse.OffsetX = 0.0;
inverse.OffsetY = 0.0;
p = inverse.Transform(p);
int w = tb.Source.PixelWidth;
int h = tb.Source.PixelHeight;
p = new Point((p.X + w) % w, (p.Y + h) % h);
}
TheLabel.Content = p.ToString();
}
}
For the most part this works well, but if you hover in the grey to the left of the rotated image (roughly where the X is in the screenshot below), you get a y-coordinate (0.5) that makes it look like you are in the image, when in reality you are outside, and the y-coordinate should be higher than the image height to reflect this.
This is important because I'm trying to allow the user to select an ROI, and I need to know when the selection is beyond the image bounds, though I still want to allow it.
You may perform the transformation on a "test point" inside the image bounds (e.g. the center) and the modulo operation on the transformed test point. Then use the offset between the transformed test point and the adjusted (by modulo) test point to adjust the actual point.
var p = e.GetPosition(TheImage);
p = new Point(
p.X * bs.PixelWidth / TheImage.ActualWidth,
p.Y * bs.PixelHeight / TheImage.ActualHeight);
if (TheImage.Source is TransformedBitmap tb)
{
var inverse = tb.Transform.Value;
inverse.Invert();
inverse.OffsetX = 0.0;
inverse.OffsetY = 0.0;
var w = tb.Source.PixelWidth;
var h = tb.Source.PixelHeight;
var v = new Vector(bs.PixelWidth / 2, bs.PixelHeight / 2); // test point
var v1 = inverse.Transform(v); // transformed test point
var v2 = new Vector((v1.X + w) % w, (v1.Y + h) % h); // adjusted
p = inverse.Transform(p) - v1 + v2; // add adjusting offset
}
TheLabel.Content = $"x: {p.X:F2}, y: {p.Y:F2}";