在SVG图像中使用屏幕坐标进行命中测试,转换为世界坐标

17

如何使用GDI+将鼠标坐标转换为世界坐标?或者得到使用GDI+绘制的SVG形状的边界框(或者更好的方式)?

无论如何,我一直在寻找SVG代码,并找到了:
http://development.mwcs.de/svgimage.html
这是第一个真正可用于SVG的Delphi组件,但我偏离了主题。

该组件使用GDI+显示圆形、曲线等。
GDI+使用矩阵将世界坐标、旋转和扭曲转换为屏幕坐标。
我理解这部分。您可以使用矩阵乘法进行转换。

问题在于
如果我将鼠标光标指向封闭形状:

  1. 我应该从哪里获取矩阵,使我鼠标的屏幕点转换为世界点,以便我可以对我在屏幕上看到的圆进行碰撞检测?
    所有这些GDI对象中有许多可供选择的矩阵。
  2. 请不要给我一些关于绘制位图并测试光标下的魔法颜色的东西,这不是我要找的。
  3. 如果有矩阵链,如何以正确(反转?)的顺序遍历它们,以便我的屏幕坐标被正确地引导到世界坐标?

换句话说
从SVG图像中读取的形状是原语,通过矩阵扭曲成屏幕坐标。如何将屏幕坐标反向转换为可以用来确定是否在形状内部的坐标。

请注意 我需要知道我在哪个形状中。
由于SVG图像的设置方式,每个形状都有一个id,我想使用它来查看我用鼠标击中了哪个区域。

编辑

或者

  1. 我能否获得每个形状的屏幕坐标下的边界矩形,以便我可以将我的鼠标坐标与其进行比较。
  • 我可以获取一个旧式的GDI区域,在其中使用屏幕坐标进行PtInRegion操作吗。
  • 希望你能帮助我找到一条在这些扭曲路径中前进的方法:-)。


    你基本上是在问如何将光标的字面屏幕位置(X,Y)转换为SVG显示对象内光标的相对位置(也是X,Y)吗? - LaKraven
    我在1996年使用Delphi 1编写了我的第一个程序,它是一个用于计算公顷面积和绘制多边形(方位角+距离)的软件。当时我编写了一些代码,将单词坐标转换为屏幕坐标,使用了像SetMapMode()、SetWindow(Org|Ext)、SetViewPort(Org|Ext)这样的调用。GDI+也使用这些调用吗? - Clóvis Valadares Junior
    不,GDI+使用矩阵进行转换。矩阵可以进行旋转和倾斜(新的),也可以进行平移和缩放(旧的)。 - Johan
    2个回答

    7

    我没有深入研究代码,但我可以在矩阵方面提供一些帮助(第3点)。

    我猜想使用了三个基本的变换矩阵:旋转矩阵、缩放矩阵和平移矩阵。我们将它们分别称为R、S和T。

    应用矩阵到点上有一个棘手的部分。比如说,您想要平移这个点,然后围绕原点中心旋转。换句话说,您想要将旋转应用于点的平移效果。因此,矩阵将按以下方式应用:

    R(T(P)) = R * T * P = S

    其中*表示矩阵乘法。请注意,相乘的矩阵顺序与您的意图相反。

    然而,如果您想进行逆变换,除了反转矩阵的顺序外,还必须计算它们的逆矩阵。我们先平移点,然后旋转 - 现在我们需要将其旋转回来,然后再平移回去:

    T^-1 ( R^-1 (S)) = T^-1 * R^-1 * S = P

    请注意,您不必计算每个矩阵的逆矩阵,因为显然 T^-1(x) = T(-x),R^-1(angle) = R(-angle)等等。但是,您需要推断出转换的参数,如果只能访问转换矩阵,则可能不容易。
    我猜想,世界坐标通过平移和缩放矩阵相结合转换为屏幕坐标。最后一个矩阵负责根据整个场景的缩放因素(以及可能的显示器DPI)将单位从世界坐标更改为像素。另一方面,平移矩阵反映了场景平移,并可以在缩放矩阵之前或之后应用;在第一种情况下,平移以世界坐标存储,在第二种情况下,平移以屏幕坐标存储。
    我还猜想,所有对象的变换都是在世界坐标中进行的(对我来说听起来更方便)。因此,您可以期望每个对象的点都会接受以下转换:
    W(S(R(T(P)))) = W * S * R * T * P,
    其中W是世界到屏幕的转换,S是缩放,R是旋转和T是平移。
    希望我至少有点帮助...

    更新于2011年4月17日

    好的,我现在已经查看了代码。SVG对象的PaintTo方法如下:

    procedure TSVG.PaintTo(Graphics: TGPGraphics; Bounds: TGPRectF;
      Rects: PRectArray; RectCount: Integer);
    var 
      M: TGPMatrix;
      MA: TMatrixArray;
    begin
      M := TGPMatrix.Create;
      try
        Graphics.GetTransform(M);
        try
          M.GetElements(MA);
    
          FInitialMatrix.Cells[0, 0] := MA[0];
          FInitialMatrix.Cells[0, 1] := MA[1];
          FInitialMatrix.Cells[1, 0] := MA[2];
          FInitialMatrix.Cells[1, 1] := MA[3];
          FInitialMatrix.Cells[2, 0] := MA[4];
          FInitialMatrix.Cells[2, 1] := MA[5];
          FInitialMatrix.Cells[2, 2] := 1;
    
          SetBounds(Bounds);
    
          Paint(Graphics, Rects, RectCount);
        finally
          Graphics.SetTransform(M);
        end;
      finally
        M.Free;
      end;
    end;
    

    在进行任何绘图之前,该方法会调用Graphics.GetTransform(M)。这个方法又会调用GdipGetWorldTransform,它似乎是WinAPI的GetWorldTransform的包装函数。 我猜这可能是一个好的起点 :)

    我已经有了执行矩阵变换的函数,这不是问题。我只是不知道从哪里获取用于世界->屏幕转换的矩阵(多个)。我不知道原始变换的顺序。因此,我无法创建“逆”矩阵以进行反向变换。 - Johan
    添加了更多信息。虽然仍然无法检索单个转换矩阵,但可以反转最终的世界变换矩阵并将其应用于屏幕点。我相信这值得一试。 - Spook

    -1

    使用GDI+时,您必须跟踪您正在做的/已经完成的工作,因为GDI+在像素绘制后会忘记除像素本身以外的所有内容。SVG单元必须保持跟踪绘制所有形状和设置GDI+视口所需的所有内容。因此,从SVG库中获取所需信息会更容易。


    变换不仅用于世界到视口的映射,还可以与各种绘图基元一起传递。即使笔也有一个变换属性。 - Brian
    回应您的答案,我知道问题是如何? - Johan

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接