【问题标题】:How to flatten transforms in an SVG file programmatically?如何以编程方式展平 SVG 文件中的变换?
【发布时间】:2017-04-03 22:14:34
【问题描述】:

我需要编写一些 C# 代码,以编程方式对任何 SVG 文件进行展平转换。

此选项在 SVG 编辑器中可用,例如 Affinity Designer(约 40 美元/Mac)或 Inkscape (FOSS):

但是您如何以编程方式执行此操作?即使是好的SVG c# libraries 也没有提供方法来做到这一点。

在 SO(即herehereherehere)上有很多关于此的讨论。在那些建议一些工具,但没有人提供代码来做到这一点(除了一些对我没用的javascript sn-p,因为它使用浏览器API来获取转换后的坐标)。

我需要在 C# 代码中执行此操作,因为要操作 svg 元素,我需要那些独立于父变换的元素,包括椭圆弧、渐变、文本和 tspan 元素。

编辑澄清: 我不一定需要转换元素。我只需要在最深的子节点中存在变换矩阵,所以我不需要再考虑父母了。为此,我需要将所有父矩阵连接在一个矩阵中并作为单个变换属性分配给 svg 元素。例如,我不一定需要转换路径或弧线、将椭圆转换为路径或重新采样位图。我只需要能够为每个元素计算一个矩阵变换,它自己的,作为变换属性分配给它,而不是每次都考虑所有的父组变换。

【问题讨论】:

  • Inkscape 是开源的,可能不在 c# 中,但源代码可能会给你一些想法。
  • 我已经这样做了。我搜索了整个 Inkscape 源代码。我搜索了“连接”、“累积”、“递归”和“扁平化”。我没有找到一种特定方法来扁平化计算累积父变换的变换。也许是它在遍历节点时隐式执行的操作,我不知道。但我找不到。
  • 您是否意识到要正确实现这一点,您将不得不实现一个 SVG 解析器?展平所有变换并乘以坐标意味着您还必须准确地解析路径命令。还知道如何正确地将这些变换应用于其他 SVG 元素中的坐标。然后需要将矩形和椭圆等一些元素转换为路径。如果您需要处理图像,则需要将位图重新采样为其他位图。这可能是一项非常艰巨的工作 - 取决于您需要走多远。
  • @PaulLeBeau 我不需要转换元素。我只需要将变换矩阵作为最后一个叶子子节点的属性,所以我不需要再考虑父母了。递归地,我可以将所有矩阵连接到一个矩阵中,并将其分配给任何类型的 svg 元素。例如,我不需要将椭圆转换为路径,也不需要重新采样位图。我只需要能够为每个元素计算一个矩阵变换,而不必考虑所有父组变换。这可能吗?
  • 那是一个更简单的提议。看来你已经解决了。

标签: c# svg


【解决方案1】:

这是对我的问题的初步回答。但它仍然存在某些元素的问题(具有剪辑路径、掩码或过滤器属性的元素,或者使用 url 或 defs 的元素,在展平后似乎坐标损坏)。任何进一步的帮助和改进表示赞赏。

public void FlattenNodeRecursive(XElement item) {

            var children_elements = item.Elements();

            if ( IsSVGElem(item, "g") &&
                HasAttr(item,"transform") &&
                !item.IsEmpty &&
                item.Elements().Any((XElement inner) => { return IsSVGElem(inner, "path") || IsSVGElem(inner, "text") || IsSVGElem(inner, "g"); }) &&
                item.Elements().Any((XElement inner) => { return !IsSVGElem(inner, "tspan"); })
               ) {

                XAttribute parent_attribute = item.Attribute("transform");

                foreach (var inner in children_elements) {
                    if (HasAttr(inner, "transform")) {
                            inner.Attribute("transform").SetValue(ConcatenateTransforms((parent_attribute.Value.Trim() + " " + inner.Attribute("transform").Value.Trim())).Trim());
                        } else {
                            XAttribute attribute = new XAttribute("transform", parent_attribute.Value.Trim());
                            inner.Add(attribute);
                        }
                    }

                parent_attribute.Remove();

            }

            foreach(XElement xelem in children_elements){
                if(xelem.Elements() != null) {
                    FlattenNodeRecursive(xelem);
                }

            }
        }

注意:我没有发布 HasAttr 方法或 ConcatenateTransforms 方法源代码,因为它们非常简单,但如果有人需要,我会发布它们。

【讨论】:

  • 如果父元素有剪辑路径、掩码等,那么您可能必须在这些父元素上留下中间变换。否则它们将被留在错误的坐标空间中。或者,您可以将该中间转换复制到 clipPath 定义中,然后继续。但是,如果在多个地方使用了 clipPath,那么这也可能会导致损坏。
  • 这对我的项目来说是个问题。我需要让每个元素都独立于所有父元素(因为我需要将它们从 xml 树中分离出来并在另一个元素中使用它们......就像剪切和粘贴一样)。有没有办法解决这个问题?也许从所有父母那里复制剪辑路径、蒙版等并将它们放在元素节点中?但是我怎么知道哪些元素正在影响我的节点呢?
猜你喜欢
  • 2015-03-26
  • 2016-02-09
  • 2015-05-09
  • 1970-01-01
  • 2018-07-09
  • 2013-01-19
  • 1970-01-01
  • 2013-09-12
  • 2012-01-14
相关资源
最近更新 更多