应用变换矩阵后计算路径中心

发布于 2024-10-31 18:24:32 字数 2916 浏览 2 评论 0原文

我在 Inkscape 中制作了一个徽标。为了学习,我想通过 SVG 中的动画支持使徽标中的轮子形状旋转。

实现旋转很容易,但我很难指定正确的旋转轴。形状是一个齿轮,我希望它绕其中心旋转。经过反复试验,xy 坐标 (47.1275, 1004.17)(其组件奇怪地不对称,但我猜这与 Inkscape 应用的变换矩阵有关)是一个很好的近似值(请参阅下面的 animateTransform 标签),但是我该如何从第一原则中得到这一点吗?

<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 321.281 150.799" xmlns:dc="http://purl.org/dc/elements/1.1/">
    <g transform="translate(-9.9178912,-891.57237)">
        <g transform="matrix(1.9522781,0,0,1.9522781,4.6434311,-1008.1558)">
            <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 47.1275 1004.17" to="45 47.1275 1004.17" dur="2s" fill="freeze" additive="sum" repeatCount="indefinite" />
            <g transform="matrix(0.65043772,0,0,0.65043772,-143.67477,980.4256)" stroke="#666" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none">
                <path stroke-linejoin="miter" d="m293.404-3.51576c-2.73916,0-5.41514,0.287192-8,0.8125v6.1875c-3.47484,0.838872-6.7198,2.18462-9.6875,4l-4.375-4.375c-2.24264,1.48612-4.29226,3.22977-6.1875,5.125s-3.63888,3.94486-5.125,6.1875l4.375,4.375c-1.81538,2.9677-3.16112,6.21265-4,9.6875h-6.1875c-0.5253,2.58486-0.8125,5.26083-0.8125,8s0.2872,5.41515,0.8125,8h6.1875c0.83888,3.47485,2.18462,6.7198,4,9.6875l-4.375,4.375c1.48612,2.24264,3.22976,4.29227,5.125,6.1875s3.94486,3.63888,6.1875,5.125l4.375-4.375c2.9677,1.81538,6.21266,3.16113,9.6875,4v6.1875c2.58486,0.525308,5.26082,0.8125,8,0.8125,2.73916,0,5.41514-0.287192,8-0.8125v-6.1875c3.47484-0.838872,6.7198-2.18462,9.6875-4l4.375,4.375c2.24264-1.48612,4.29226-3.22977,6.1875-5.125s3.63888-3.94486,5.125-6.1875l-4.375-4.375c1.81538-2.9677,3.16112-6.21266,4-9.6875h6.1875c0.5253-2.58485,0.8125-5.26083,0.8125-8s-0.2872-5.41515-0.8125-8h-6.1875c-0.83888-3.47485-2.18462-6.7198-4-9.6875l4.375-4.375c-1.48612-2.24264-3.22976-4.29227-5.125-6.1875s-3.94486-3.63888-6.1875-5.125l-4.375,4.375c-2.9677-1.81538-6.21266-3.16113-9.6875-4v-6.1875c-2.58486-0.525308-5.26084-0.8125-8-0.8125z" stroke-dashoffset="162" stroke="#666" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none"/>
            </g>
        </g>
    </g>
</svg>

根据我在规范中读到的内容,我会说应用的变换矩阵是

1.9522781  0              4.6434311
0          1.9522781  -1008.1558
0          0              1

平移变换后它们是否

0.65043772  0          -143.67477
0           0.65043772  980.4256
0           0             1

应用于 xyz 坐标 (-9.9178912,-891.57237,0)

我想上面的正确分析会让我得到所描述路径的左上角点,或者可能是第一个手柄的坐标。之后,是否必须解析路径来决定边界框,从而决定路径的中心(因为它涉及一个有点圆形的对象)?

这是不要尝试在自由创建的形状上手动制作动画的教训吗?

I made a logo in Inkscape. For learning I wanted to make a wheel shape in the logo rotate by the animation support in SVG.

It was easy to implement the rotation, but it was difficult for me to be able to specify the correct axis of rotation. The shape was a cog wheel and I wanted it to rotate around its center. Trial and error gave that the xy-coordinate (47.1275, 1004.17) (whose components are strangely asymmetric, but I guess that has to do with the transformation matrices Inkscape applies) was a good approximation (see animateTransform tag below), but how would I get that from first principles?

<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 321.281 150.799" xmlns:dc="http://purl.org/dc/elements/1.1/">
    <g transform="translate(-9.9178912,-891.57237)">
        <g transform="matrix(1.9522781,0,0,1.9522781,4.6434311,-1008.1558)">
            <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 47.1275 1004.17" to="45 47.1275 1004.17" dur="2s" fill="freeze" additive="sum" repeatCount="indefinite" />
            <g transform="matrix(0.65043772,0,0,0.65043772,-143.67477,980.4256)" stroke="#666" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none">
                <path stroke-linejoin="miter" d="m293.404-3.51576c-2.73916,0-5.41514,0.287192-8,0.8125v6.1875c-3.47484,0.838872-6.7198,2.18462-9.6875,4l-4.375-4.375c-2.24264,1.48612-4.29226,3.22977-6.1875,5.125s-3.63888,3.94486-5.125,6.1875l4.375,4.375c-1.81538,2.9677-3.16112,6.21265-4,9.6875h-6.1875c-0.5253,2.58486-0.8125,5.26083-0.8125,8s0.2872,5.41515,0.8125,8h6.1875c0.83888,3.47485,2.18462,6.7198,4,9.6875l-4.375,4.375c1.48612,2.24264,3.22976,4.29227,5.125,6.1875s3.94486,3.63888,6.1875,5.125l4.375-4.375c2.9677,1.81538,6.21266,3.16113,9.6875,4v6.1875c2.58486,0.525308,5.26082,0.8125,8,0.8125,2.73916,0,5.41514-0.287192,8-0.8125v-6.1875c3.47484-0.838872,6.7198-2.18462,9.6875-4l4.375,4.375c2.24264-1.48612,4.29226-3.22977,6.1875-5.125s3.63888-3.94486,5.125-6.1875l-4.375-4.375c1.81538-2.9677,3.16112-6.21266,4-9.6875h6.1875c0.5253-2.58485,0.8125-5.26083,0.8125-8s-0.2872-5.41515-0.8125-8h-6.1875c-0.83888-3.47485-2.18462-6.7198-4-9.6875l4.375-4.375c-1.48612-2.24264-3.22976-4.29227-5.125-6.1875s-3.94486-3.63888-6.1875-5.125l-4.375,4.375c-2.9677-1.81538-6.21266-3.16113-9.6875-4v-6.1875c-2.58486-0.525308-5.26084-0.8125-8-0.8125z" stroke-dashoffset="162" stroke="#666" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="7.68713093" fill="none"/>
            </g>
        </g>
    </g>
</svg>

From what I've read in the specification I would say the transformation matrices applied are

1.9522781  0              4.6434311
0          1.9522781  -1008.1558
0          0              1

and

0.65043772  0          -143.67477
0           0.65043772  980.4256
0           0             1

Are they applied on the xyz-coordinate (-9.9178912,-891.57237,0) after the translation transformation?

I guess a correct analysis above would get me the top left point of the path described, or maybe the coordinate for the first handle. After that, does one have to parse the path to decide the bounding-box and thereby the center (since it concerns a somewhat circular object) of the path?

Is it all a lesson in not trying to manually do animation on freely created shapes?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

丑丑阿 2024-11-07 18:24:32

我认为转换将从最内层向外应用,因此 transform="translate(-9.9178912,-891.57237)" 将最后完成。但是,如果将动画放在最里面的区域(即路径本身内),则可以忽略其他转换:

   <g transform1>
     <g transform2>
       <g transform3>
         <path d="coordinates">
           <animateTransform your transformation here>
         </path>
       </g>
     </g>
   </g>

然后您只需要找到路径的中心,这在 Inkscape 中很容易做到,但在 Inkscape 中却很难做到。 -fly (相关问题在这里:以编程方式如何使用 java 获取 SVG 文档中的形状宽度)。

就我个人而言,我会在 svg 中使用脚本,这样您就可以使用 getBBox 来查找形状的边界框。如果将以下元素添加到 SVG 中,则可以使任何带有 id="cog" 的元素围绕其中心旋转:

<script type="text/ecmascript"><![CDATA[
  var svgNS = "http://www.w3.org/2000/svg";

  function init(evt)
  {
    if ( window.svgDocument == null )
    {
        svgDocument = evt.target.ownerDocument;
    }
    addRotateTransform('cog');
  }

  function addRotateTransform(target_id)
  {
    var element_to_rotate = svgDocument.getElementById(target_id);
    var my_transform = svgDocument.createElementNS(svgNS, "animateTransform");

    var bb = element_to_rotate.getBBox();
    var cx = bb.x + bb.width/2;
    var cy = bb.y + bb.height/2;

    my_transform.setAttributeNS(null, "attributeName", "transform");
    my_transform.setAttributeNS(null, "attributeType", "XML");
    my_transform.setAttributeNS(null, "type", "rotate");
    my_transform.setAttributeNS(null, "dur", "4s");
    my_transform.setAttributeNS(null, "repeatCount", "indefinite");
    my_transform.setAttributeNS(null, "from", "0 "+cx+" "+cy);
    my_transform.setAttributeNS(null, "to", "360 "+cx+" "+cy);

    element_to_rotate.appendChild(my_transform);
    my_transform.beginElement();
  }
]]></script>

您还需要将 onload="init(evt)" 作为属性添加到 SVG 标记。例如,

<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns="http://www.w3.org/2000/svg"
     version="1.1"
     xmlns:cc="http://creativecommons.org/ns#"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 321.281 150.799"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     onload="init(evt)">

这将在首次加载 SVG 时调用 init() 函数。 init() 函数调用 addRotateTransform() 来查找具有给定 id 的元素。然后,它使用 getBBox() 找到该对象的中心,并添加具有相关中心的 animateTransform 方法。您可以更改 dur 属性,该属性决定完整旋转的速度。

它可能看起来有很多代码,但我认为这是确定路径中心的最简单方法。它还意味着可以通过将 addRotateTransform('whatever-id'); 添加到 init() 函数来轻松添加其他旋转元素。

I think the transformations will be applied from the innermost outward, so transform="translate(-9.9178912,-891.57237)" will be done last. But you can ignore the other transformations if you put your animation in the innermost region, i.e. within the path itself:

   <g transform1>
     <g transform2>
       <g transform3>
         <path d="coordinates">
           <animateTransform your transformation here>
         </path>
       </g>
     </g>
   </g>

Then you just need to find the centre of your path, which is easy to do in Inkscape, but tricky to do on-the-fly (related question here: programmatically How to get shape width in SVG document using java).

Personally, I'd use a script within the svg so you can use getBBox to find the bounding box of your shape. If you add the following element into your SVG you can make any element with the id="cog" turn about its centre:

<script type="text/ecmascript"><![CDATA[
  var svgNS = "http://www.w3.org/2000/svg";

  function init(evt)
  {
    if ( window.svgDocument == null )
    {
        svgDocument = evt.target.ownerDocument;
    }
    addRotateTransform('cog');
  }

  function addRotateTransform(target_id)
  {
    var element_to_rotate = svgDocument.getElementById(target_id);
    var my_transform = svgDocument.createElementNS(svgNS, "animateTransform");

    var bb = element_to_rotate.getBBox();
    var cx = bb.x + bb.width/2;
    var cy = bb.y + bb.height/2;

    my_transform.setAttributeNS(null, "attributeName", "transform");
    my_transform.setAttributeNS(null, "attributeType", "XML");
    my_transform.setAttributeNS(null, "type", "rotate");
    my_transform.setAttributeNS(null, "dur", "4s");
    my_transform.setAttributeNS(null, "repeatCount", "indefinite");
    my_transform.setAttributeNS(null, "from", "0 "+cx+" "+cy);
    my_transform.setAttributeNS(null, "to", "360 "+cx+" "+cy);

    element_to_rotate.appendChild(my_transform);
    my_transform.beginElement();
  }
]]></script>

You also need to add onload="init(evt)" as an attribute to the SVG tag. e.g.

<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns="http://www.w3.org/2000/svg"
     version="1.1"
     xmlns:cc="http://creativecommons.org/ns#"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 321.281 150.799"
     xmlns:dc="http://purl.org/dc/elements/1.1/"
     onload="init(evt)">

This will call the init() function when the SVG is first loaded. The init() function calls addRotateTransform() which find the element with a given id. It then finds the centre of that object using getBBox() and adds an animateTransform method with the relevant centres. You can change the dur attribute which determines the speed of a full rotation.

It might seem like a lot of code, but I think it's the easiest way to determine the centre of a path. It also means to can easily add other rotating elements by add addRotateTransform('whatever-id'); to the init() function.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文