如何从鼠标坐标转换为 TransformedBitmap 的像素坐标?

Posted

技术标签:

【中文标题】如何从鼠标坐标转换为 TransformedBitmap 的像素坐标?【英文标题】:How do I convert from mouse coordinates to pixel coordinates of a TransformedBitmap? 【发布时间】:2022-01-01 16:04:39 【问题描述】:

我的问题与How to get correct position of pixel from mouse coordinates? 类似,但需要注意的是图像可能是TransformedBitmap,可以在其中应用翻转和旋转,并且仍然返回原始图像的像素坐标。

我的Window 的设计是这样的:

    <DockPanel>
        <Label DockPanel.Dock="Bottom" Name="TheLabel" />
        <Image DockPanel.Dock="Top" Name="TheImage" Stretch="Uniform" RenderOptions.BitmapScalingMode="NearestNeighbor" MouseMove="TheImage_MouseMove" />
    </DockPanel>

代码隐藏如下:

        public MainWindow()
        
            InitializeComponent();

            const int WIDTH = 4;
            const int HEIGHT = 3;
            byte[] pixels = new byte[WIDTH * HEIGHT * 3];
            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 = bs;
        

        private void TheImage_MouseMove(object sender, MouseEventArgs e)
        
            Point p = e.GetPosition(TheImage);
            if (TheImage.Source is TransformedBitmap tb)
                TheLabel.Content = tb.Transform.Inverse.Transform(new Point(p.X * tb.PixelWidth / TheImage.ActualWidth, p.Y * tb.PixelHeight / TheImage.ActualHeight)).ToString();
            else if (TheImage.Source is BitmapSource bs)
                TheLabel.Content = new Point(p.X * bs.PixelWidth / TheImage.ActualWidth, p.Y * bs.PixelHeight / TheImage.ActualHeight).ToString();
        

当悬停在未转换图像的右下角(我将其涂成蓝色以便于跟踪)时,您可以正确看到 (~4, ~3) 的坐标,即图像尺寸。

但是,一旦您应用了转换,例如将 TheImage.Source = bs; 更改为 TheImage.Source = new TransformedBitmap(bs, new RotateTransform(90.0));,将鼠标悬停在蓝色上就会得到 (~4, ~0)。

我认为可以查看变换的实际矩阵值,并确定如何在所有各种情况下调整点,但似乎应该有一个使用逆变换更简单的解决方案。

【问题讨论】:

【参考方案1】:

当 Image 元素渲染 TransformedBitmap 时,Transform 的中心点被有效地忽略,位图被移动到适当的坐标范围内。

由于这种转变不适用于点的变换,因此您必须对其进行补偿,例如像这样:

var p = e.GetPosition(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)
    
        var w = tb.Source.PixelWidth;
        var h = tb.Source.PixelHeight;

        p = tb.Transform.Inverse.Transform(p);

        p = new Point((p.X + w) % w, (p.Y + h) % h);
    

    TheLabel.Content = $"x: (int)p.X, y: (int)p.Y";

但是,当变换中心设置为(0,0) 以外的值时,上述代码将无法正常工作。

为了使其适用于具有任意中心点的变换,您必须清除变换矩阵中的偏移值:

var p = e.GetPosition(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)
    
        var w = tb.Source.PixelWidth;
        var h = tb.Source.PixelHeight;
        var t = tb.Transform.Value;
        t.Invert();
        t.OffsetX = 0d;
        t.OffsetY = 0d;

        p = t.Transform(p);

        p = new Point((p.X + w) % w, (p.Y + h) % h);
    

    TheLabel.Content = $"x: (int)p.X, y: (int)p.Y";

【讨论】:

谢谢!如果将Image 嵌入到具有背景的Grid 中并将MouseMove 事件移动到Grid,则像素坐标在图像边界之外不再准确,因为它只给出0 的答案..宽度和0..高度。知道如何解决这个问题吗? 有多种方法。最简单的方法是将事件处理程序附加到另一个与 Image 具有相同尺寸的嵌套父元素。否则有像 TransformToDescendant 或类似的方法。 不确定我是否关注。我可以将点从父网格转换为图像没问题。但是例如,当用户将鼠标悬停在旋转图像的右侧时,我希望 y 坐标为负值,以反映它们在原始方向的图像边界之上。我应该编辑我的问题还是开始一个新问题? 最好写一个新的。

以上是关于如何从鼠标坐标转换为 TransformedBitmap 的像素坐标?的主要内容,如果未能解决你的问题,请参考以下文章

gluUnProject 尝试将鼠标坐标转换为 opengl 坐标的奇怪行为

如何通过鼠标移动事件获取相机位置并在three.js中转换为屏幕坐标? [关闭]

从视口坐标转换为 [-1,1]^2 顶点 [OpenGL]

窗口调整大小后将鼠标位置转换为 2D openGL 中的世界坐标

将鼠标坐标转换为世界空间坐标

将 Glut 鼠标坐标转换为 opengl