使用 System.Drawing.Drawing2D 中的 Matrix class 来展平 SVG 转换
Using Matrix class from System.Drawing.Drawing2D to flatten SVG transformation
我有一个 SVG 文档,它的 RECT 标签带有 MATRIX/SCALE/TRANSLATE 转换:
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="300"
height="150"
id="svg2">
<rect
width="70"
height="30"
x="169.46515"
y="46.238182"
id="rect1"
style="fill:none;stroke:#000000;stroke-opacity:1" />
<rect
width="8"
height="2"
x="134.69983"
y="97.45443"
id="rect2"
style="fill:none;stroke:#000000;stroke-opacity:1" />
<rect
width="2"
height="2"
x="-99.804573"
y="125.0692"
transform="matrix(-2.6042412e-3,-0.9999966,0.9999966,-2.6042412e-3,0,0)"
id="rect3"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<rect
width="8"
height="2"
x="-100.87854"
y="129.95743"
transform="matrix(-6.2857322e-4,-0.9999998,0.9999418,-1.0789073e-2,0,0)"
id="rect4"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<rect
width="2"
height="8"
x="-99.81971"
y="134.43823"
transform="matrix(-2.6042412e-3,-0.9999966,0.9999966,-2.6042412e-3,0,0)"
id="rect5"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<path
d="M 82.592797,67.016361 C 82.597697,77.923711 76.726297,86.769781 69.481867,86.769781 C 62.237427,86.769781 56.366027,77.923711 56.370927,67.016361 C 56.366027,56.109011 62.237427,47.262941 69.481867,47.262941 C 76.726297,47.262941 82.597697,56.109011 82.592797,67.016361 z"
id="path22095"
style="fill:none;stroke:#0000ff;stroke-opacity:1" />
<rect
width="30"
height="70"
x="46.304344"
y="-239.40808"
transform="matrix(0,1,-1,0,0,0)"
id="rect6"
style="fill:none;stroke:#ff0000;stroke-opacity:1" />
<rect
width="2"
height="2"
x="121.03672"
y="97.452477"
id="rect7"
style="fill:none;stroke:#000000;stroke-opacity:1" />
</svg>
我想要 flatten/transform 坐标并完全摆脱 TRANSFORM 属性。
似乎 System.Drawing.Drawing2D.Matrix class 具有所有必需的方法,但由于某种原因结果看起来不正确。以下是我使用的代码:
PointF[] coordinates =
{
new PointF(float.Parse(element.Attribute("x").Value), float.Parse(element.Attribute("y").Value))
};
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
element.Attribute("x").Value = coordinates[0].X.ToString();
element.Attribute("y").Value = coordinates[0].Y.ToString();
至少在 RECT 的情况下,它似乎可以正确计算位置,但是因为 Width/Height 保持不变,所以看起来不同。当我尝试将 TransformPoints 应用于 Width/Height 时,它也没有帮助。
所以我的问题是是否有可能以某种方式找到原始坐标?
更新
正如@Cimbali 所建议的那样,我尝试了以下代码,但结果仍然不正确:
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
var a = Math.Sign(transform[0]) * Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sign(transform[3]) * Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("x").Value = Math.Round(coordinates[0].X, coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("y").Value = Math.Round(coordinates[0].Y, coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0})", angle);
谢谢
问题来自于旋转或倾斜,或这些类型的操作。
当移动或缩放一个矩形时,它仍然是一个矩形,因此您可以修改其坐标以包含这些效果,并通过仅修改 svg 中 <rect />
的属性将它们从变换中移除。然而,其他变换使它不再是一个真正的矩形,或者至少不是一个边平行于 x
和 y
轴的矩形。
有两种主要方法可以解决此问题:
- 在您的 svg 中保留
transform="rotate(angle)"
,如果您使用它们,最终还会保留 skew
。
为此,您必须将矩阵分解为不同的(乘法)元素,这将是非常重要的,并删除缩放和平移。
- 使用
<polygon />
而不是 <rect />
。这是更简单的选项。
后一个选项:多边形
我们需要矩形的 4 个角:{(x,y), (x+width,y), (x+width,y+height), (x, y+height)}
现在您可以将变换矩阵应用于所有这些点,就像您只对 (x,y)
所做的那样,并使用它们(保持顺序)绘制多边形。
原选项:继续轮换
在这个版本中,我们保留了rect
,并尝试将其transform
属性从矩阵简化为简单的旋转。我假设你只旋转、平移和缩放你的矩阵,没有倾斜(我已经尝试了一段时间但没有定论)。
然后变换矩阵 A 以列优先顺序写入其前 2 行(因为第 3 行始终是 0 0 1
)。因此,由于它包括缩放(沿 x a
和沿 y b
),然后旋转某个角度,其中 c = cos(angle)
和 s = sin(angle)
,它是写成:
| a*c -a*s tx | | transform[0] transform[2] transform[4] |
A = | b*s b*c ty | = | transform[1] transform[3] transform[5] |
| 0 0 1 | | 0 0 1 |
因此 from here 出现了此转换的伪代码:
- 将
transform[4]
添加到 <rect />
的 x
属性
- 将
transform[5]
添加到 <rect />
的 y
属性
a = sqrt( transform[0] * transform[0] + transform[2] * transform[2] )
b = sqrt( transform[1] * transform[1] + transform[3] * transform[3] )
angle = atan2(transform[1], transform[3]) * 180/PI
- 将矩形的宽度乘以
a
,将其高度乘以 b
- 最后,将
<rect />
的transform
属性设置为"rotate(angle)"
(如果angle
为非零),如果angle
为零。
从您的更新中获取并更正代码(唉,因为我自己不做 C#),我们得到:
element.Attribute("x").Value = Math.Round(coordinates[0].X + transform[4], coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("y").Value = Math.Round(coordinates[0].Y + transform[5], coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
var a = Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = - Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0})", angle);
else
element.RemoveAttribute("transform");
另一种方法是围绕 <rect />
的中心旋转而不是 (0,0)
是首先对坐标应用变换,因此如下:
PointF[] coordinates = { new PointF(
float.Parse(element.Attribute("x").Value),
float.Parse(element.Attribute("y").Value)
)};
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
element.Attribute("x").Value = coordinates[0].X.ToString();
element.Attribute("y").Value = coordinates[0].Y.ToString();
var a = Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = - Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0}, {1}, {2})", angle, coordinates[0].X + newWidth/2, coordinates[0].Y + newHeight/2);
else
element.RemoveAttribute("transform");
我有一个 SVG 文档,它的 RECT 标签带有 MATRIX/SCALE/TRANSLATE 转换:
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="300"
height="150"
id="svg2">
<rect
width="70"
height="30"
x="169.46515"
y="46.238182"
id="rect1"
style="fill:none;stroke:#000000;stroke-opacity:1" />
<rect
width="8"
height="2"
x="134.69983"
y="97.45443"
id="rect2"
style="fill:none;stroke:#000000;stroke-opacity:1" />
<rect
width="2"
height="2"
x="-99.804573"
y="125.0692"
transform="matrix(-2.6042412e-3,-0.9999966,0.9999966,-2.6042412e-3,0,0)"
id="rect3"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<rect
width="8"
height="2"
x="-100.87854"
y="129.95743"
transform="matrix(-6.2857322e-4,-0.9999998,0.9999418,-1.0789073e-2,0,0)"
id="rect4"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<rect
width="2"
height="8"
x="-99.81971"
y="134.43823"
transform="matrix(-2.6042412e-3,-0.9999966,0.9999966,-2.6042412e-3,0,0)"
id="rect5"
style="fill:none;stroke:#ff0000;stroke-width:1;stroke-opacity:1" />
<path
d="M 82.592797,67.016361 C 82.597697,77.923711 76.726297,86.769781 69.481867,86.769781 C 62.237427,86.769781 56.366027,77.923711 56.370927,67.016361 C 56.366027,56.109011 62.237427,47.262941 69.481867,47.262941 C 76.726297,47.262941 82.597697,56.109011 82.592797,67.016361 z"
id="path22095"
style="fill:none;stroke:#0000ff;stroke-opacity:1" />
<rect
width="30"
height="70"
x="46.304344"
y="-239.40808"
transform="matrix(0,1,-1,0,0,0)"
id="rect6"
style="fill:none;stroke:#ff0000;stroke-opacity:1" />
<rect
width="2"
height="2"
x="121.03672"
y="97.452477"
id="rect7"
style="fill:none;stroke:#000000;stroke-opacity:1" />
</svg>
我想要 flatten/transform 坐标并完全摆脱 TRANSFORM 属性。
似乎 System.Drawing.Drawing2D.Matrix class 具有所有必需的方法,但由于某种原因结果看起来不正确。以下是我使用的代码:
PointF[] coordinates =
{
new PointF(float.Parse(element.Attribute("x").Value), float.Parse(element.Attribute("y").Value))
};
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
element.Attribute("x").Value = coordinates[0].X.ToString();
element.Attribute("y").Value = coordinates[0].Y.ToString();
至少在 RECT 的情况下,它似乎可以正确计算位置,但是因为 Width/Height 保持不变,所以看起来不同。当我尝试将 TransformPoints 应用于 Width/Height 时,它也没有帮助。
所以我的问题是是否有可能以某种方式找到原始坐标?
更新
正如@Cimbali 所建议的那样,我尝试了以下代码,但结果仍然不正确:
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
var a = Math.Sign(transform[0]) * Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sign(transform[3]) * Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("x").Value = Math.Round(coordinates[0].X, coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("y").Value = Math.Round(coordinates[0].Y, coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0})", angle);
谢谢
问题来自于旋转或倾斜,或这些类型的操作。
当移动或缩放一个矩形时,它仍然是一个矩形,因此您可以修改其坐标以包含这些效果,并通过仅修改 svg 中 <rect />
的属性将它们从变换中移除。然而,其他变换使它不再是一个真正的矩形,或者至少不是一个边平行于 x
和 y
轴的矩形。
有两种主要方法可以解决此问题:
- 在您的 svg 中保留
transform="rotate(angle)"
,如果您使用它们,最终还会保留skew
。 为此,您必须将矩阵分解为不同的(乘法)元素,这将是非常重要的,并删除缩放和平移。 - 使用
<polygon />
而不是<rect />
。这是更简单的选项。
后一个选项:多边形
我们需要矩形的 4 个角:{(x,y), (x+width,y), (x+width,y+height), (x, y+height)}
现在您可以将变换矩阵应用于所有这些点,就像您只对 (x,y)
所做的那样,并使用它们(保持顺序)绘制多边形。
原选项:继续轮换
在这个版本中,我们保留了rect
,并尝试将其transform
属性从矩阵简化为简单的旋转。我假设你只旋转、平移和缩放你的矩阵,没有倾斜(我已经尝试了一段时间但没有定论)。
然后变换矩阵 A 以列优先顺序写入其前 2 行(因为第 3 行始终是 0 0 1
)。因此,由于它包括缩放(沿 x a
和沿 y b
),然后旋转某个角度,其中 c = cos(angle)
和 s = sin(angle)
,它是写成:
| a*c -a*s tx | | transform[0] transform[2] transform[4] |
A = | b*s b*c ty | = | transform[1] transform[3] transform[5] |
| 0 0 1 | | 0 0 1 |
因此 from here 出现了此转换的伪代码:
- 将
transform[4]
添加到<rect />
的 - 将
transform[5]
添加到<rect />
的 a = sqrt( transform[0] * transform[0] + transform[2] * transform[2] )
b = sqrt( transform[1] * transform[1] + transform[3] * transform[3] )
angle = atan2(transform[1], transform[3]) * 180/PI
- 将矩形的宽度乘以
a
,将其高度乘以b
- 最后,将
<rect />
的transform
属性设置为"rotate(angle)"
(如果angle
为非零),如果angle
为零。
x
属性
y
属性
从您的更新中获取并更正代码(唉,因为我自己不做 C#),我们得到:
element.Attribute("x").Value = Math.Round(coordinates[0].X + transform[4], coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
element.Attribute("y").Value = Math.Round(coordinates[0].Y + transform[5], coordinatesPrecision, MidpointRounding.AwayFromZero).ToString();
var a = Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = - Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0})", angle);
else
element.RemoveAttribute("transform");
另一种方法是围绕 <rect />
的中心旋转而不是 (0,0)
是首先对坐标应用变换,因此如下:
PointF[] coordinates = { new PointF(
float.Parse(element.Attribute("x").Value),
float.Parse(element.Attribute("y").Value)
)};
var matrix = new Matrix(transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]);
matrix.TransformPoints(coordinates);
element.Attribute("x").Value = coordinates[0].X.ToString();
element.Attribute("y").Value = coordinates[0].Y.ToString();
var a = Math.Sqrt(transform[0] * transform[0] + transform[2] * transform[2]);
var b = Math.Sqrt(transform[1] * transform[1] + transform[3] * transform[3]);
var angle = - Math.Atan2(transform[1], transform[3]) * 180 / Math.PI;
var newWidth = double.Parse(element.Attribute("width").Value) * a;
var newHeight = double.Parse(element.Attribute("height").Value) * b;
element.Attribute("width").Value = newWidth.ToString();
element.Attribute("height").Value = newHeight.ToString();
if (angle != 0)
element.Attribute("transform").Value = string.Format("rotate({0}, {1}, {2})", angle, coordinates[0].X + newWidth/2, coordinates[0].Y + newHeight/2);
else
element.RemoveAttribute("transform");