带有 ObjectBoundingBox 单位的 SVG 渐变和带有旋转的 gradientTransform

SVG Gradients with ObjectBoundingBox units and gradientTransform with rotation

尝试在 javascript 中使用 canvas 绘图复制 SVG 渐变 我遇到了这个用例:

<svg xmlns="http://www.w3.org/2000/svg" width="760" height="760"
         xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380 380">
<defs>
<linearGradient id="three_stops_4" gradientTransform="rotate(90)">
    <stop offset="0%" style="stop-color: #ffcc00;"/>
    <stop offset="33.3%" style="stop-color: #cc6699; stop-opacity: 1"/>
    <stop offset="100%" style="stop-color: #66cc99; stop-opacity: 1"/>
</linearGradient>
<polygon id="base" points="5,5 35,5 35,40 5,40 5,35 30,35 30,10 5,10" />
<rect width="35" height="80" id="base2" />
</defs>
  <polygon transform="translate(0, 45)" points="5,5 80,5 80,40 5,40 5,35 75,35 75,10 5,10" style="fill: url(#three_stops_4); stroke: black;"/>
</svg>

渲染成这样:

现在这些是规格: https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementGradientUnitsAttribute

我不明白的是: 1)对象边界框变换应用于梯度坐标,然后应用梯度变换。

2) 将gradientTransform应用于对象边界框并变换梯度坐标。

规范似乎说要考虑第一个选项,但重点是虽然渐变应该从对象的整个宽度延伸,但我在渲染中清楚地看到它在旋转时延伸了对象的高度。

有一个额外的拉伸(压缩)似乎来自对象边界框的纵横比,在旋转之后应用。

有谁知道渲染器应该如何表现?

规范中说(在 gradientTransform 下):

This additional transformation matrix is post-multiplied to (i.e., inserted to the right of) any previously defined transformations, including the implicit transformation necessary to convert from object bounding box units to user space.

因此,根据您对矩阵乘法的看法,两个答案都是正确的。以我的思考方式(从右到左应用转换)#2 是正确的。首先应用梯度变换。然后应用 objectBoundingBox 变换。在伪代码中:

ctx.transform(gradientTransform)
ctx.transform(objectBoundingBoxTransform)

但这也取决于您使用的是什么 2D 渲染库,以及您使用的是预乘还是 post- 乘法。一些渲染库允许您在渐变对象上设置变换矩阵。例如,在 Android 你可以这样做:

LinearGradient grad = new LinearGradient(...);
grad.setLocalMatrix(gradientTransformMatrix);

接受的答案是引导我找到解决方案的答案。 问题是将一些 SVG 特性转换为 JS canvas 渲染。 这很难,有时使用简单的渐变根本不可能。

对于应用于填充形状的渐变,并且是边界框单元并且还具有 gradientTrasnfrom 属性,您应该:

var t = myGradientTransform;
MakeAPathForMyShape(ctx);
ctx.transform(width, 0, 0, height, minX, minY);
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5], t[6]);
ctx.fill();

如果您使用 ctx.fill,这在大多数情况下有效,如果您使用 fillRect 或 fillText,则无效,因为您不能将转换放在跟踪路径和填充操作的中间。

现在让我们谈谈中风。 如果您将上面的代码与笔划一起使用,您将对笔划宽度应用渐变变换,这可能是您不想做的事情。 如果 X 和 Y 之间的缩放比例不均匀,则无法通过更改 ctx.strokeWidth;

进行补偿

我不得不退而求其次的解决方案是创建一个模式并应用它。

所以我为我的形状计算了一个边界框,并创建了一个具有相同大小的 canvas。 然后使用它的周长创建一条与 canvas 一样大的路径,然后我在该上下文中使用上面的代码。 此时我有一个矩形,和我的形状一样大,应用了变换后的渐变。

现在我从中创建一个模式:

ctx.strokeStyle = ctx.createPattern(tmpCanvas, 'no-repeat');
ctx.stroke();

这样我就可以将变换应用到渐变而不是笔划大小。

在研究中,我还评估了变换梯度法线的想法,并再次找到在非变换中生成变换梯度的点 space。这适用于线性渐变,但不适用于径向渐变,它获得椭圆形状并且需要经典的 transnform 才能工作。