0

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.

enter image description here

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.

Craig W
  • 4,390
  • 5
  • 33
  • 51
  • Ok, this obviously happens because of the modulo operations in `(p.X + w) % w` and `(p.Y + h) % h`. Do you understand what that code does? Why would you not find out whether "*the selection is beyond the image bounds*" before performing the coordinte transformation? I.e. if the result of TranslatePoint has values that are negative or larger than the width or height of `bs`. – Clemens Nov 23 '21 at 18:05
  • Right, but without the modulo operations the coordinates don't make sense, even when within the image bounds. For example if you remove them and then hover over the center, you get (6, 1.5). – Craig W Nov 23 '21 at 18:05
  • As a note, `TheGrid.TranslatePoint(e.GetPosition(TheGrid), TheImage)` is identical to `e.GetPosition(TheImage)`. – Clemens Nov 23 '21 at 18:07

1 Answers1

2

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}";
Clemens
  • 123,504
  • 12
  • 155
  • 268