javascript - SVG 三 Angular 形的插值填充

标签 javascript html vue.js svg

我的目标是在 SVG 中创建一个任意三 Angular 形,其中红色、黄色和绿色各有一个顶点,并根据顶点的颜色插值填充颜色。

与DirectX、OpenGL等提供的早期RGB三 Angular 形教程非常相似:

enter image description here

我的可以很好地处理锐 Angular 三 Angular 形:

decent-looking triangle

但对于钝 Angular 三 Angular 形来说就不那么重要了:

enter image description here

我创建了以下 SVG,并使用 VueJS 进行数据绑定(bind):

            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600">
                <defs>
                    <radialGradient id="red" gradientUnits="userSpaceOnUse" :cx="points[0].x" :cy="points[0].y"
                        :r="radius(points[0], points[1], points[2])">
                        <stop offset="0%" stop-color="#ff0000ff" />
                        <stop offset="100%" stop-color="#7fff0000" />
                    </radialGradient>

                    <radialGradient id="green" gradientUnits="userSpaceOnUse" :cx="points[1].x" :cy="points[1].y"
                        :r="radius(points[1], points[0], points[2])">
                        <stop offset="0%" stop-color="#00ff00ff" />
                        <stop offset="100%" stop-color="#ff7f0000" />
                    </radialGradient>

                    <radialGradient id="yellow" gradientUnits="userSpaceOnUse" :cx="points[2].x" :cy="points[2].y"
                        :r="radius(points[2], points[0], points[1])">
                        <stop offset="0%" stop-color="#ffff00ff" />
                        <stop offset="100%" stop-color="#7f7f0000" />
                    </radialGradient>
                </defs>

                <path :d="svgTriangle" fill="url(#red)" />
                <path :d="svgTriangle" fill="url(#yellow)" />
                <path :d="svgTriangle" fill="url(#green)" />
            </svg>

点是在 SVG 的 800x600 空间内随机生成的:

for (let p = 0; p < 3; p++) {
    this.points[p] = {
        id: `p${p}`,
        x: Math.floor(Math.random() * 800),
        y: Math.floor(Math.random() * 600)
    };
}

半径计算是基于到其他2个顶点中点的线的长度:

radius: function (me, other1, other2) {
    const mid = {
        x: (other1.x + other2.x) / 2,
        y: (other1.y + other2.y) / 2
    };
    return Math.sqrt(Math.abs(me.x - mid.x) ** 2 + Math.abs(me.y - mid.y) ** 2);
}

我认为问题在于黄色和绿色(在红色之后渲染)具有更长的半径并且基本上隐藏了红色。线性渐变也好不了多少。由于梯度方法可能存在缺陷,那么使用 SVG 是否有更好的方法?

我知道使用 Canvas/WebGL ( example ) 是可能的,但是使用 SVG(也许使用混合滤镜)也可以完成同样的事情吗?或者如果我想要这种类型的插值,我需要使用 Canvas/WebGL

编辑:在使用 SVG 的所有边缘情况下,我无法完全实现我想要的颜色混合,因此最终我转向了 Canvas 和 WebGL。

最佳答案

编辑:除了我在这个答案中解释的近似值之外,似乎还有一个实际上正确的解决方案 - 看 here .

有两个问题:

  1. 正如您提到的,绿色渐变的半径太长。
  2. 绿色渐变渲染在其他两个渐变之上(黄色渐变渲染在红色渐变之上),因此,即使在等边三 Angular 形中,颜色也会不平衡。

我会尽力帮助解决前者。好消息,你绝对可以使用渐变! gradientTransform 属性允许渐变为椭圆形而不是圆形,这为您提供了更多选择。您可以使用

        <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="800" height="600">
            <defs>
                <radialGradient id="red"  gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[0], points[1], points[2])">
                    <stop offset="0%" stop-color="#ff0000ff" />
                    <stop offset="100%" stop-color="#7fff0000" />
                </radialGradient>

                <radialGradient id="green" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[1], points[2], points[0])">
                    <stop offset="0%" stop-color="#00ff00ff" />
                    <stop offset="100%" stop-color="#ff7f0000" />
                </radialGradient>

                <radialGradient id="yellow" gradientUnits="userSpaceOnUse" cx="0" cy="0" r="1"
                    :gradientTransform="transformation(points[2], points[0], points[1])">
                    <stop offset="0%" stop-color="#ffff00ff" />
                    <stop offset="100%" stop-color="#7f7f0000" />
                </radialGradient>
            </defs>

            <path :d="svgTriangle" fill="url(#red)" />
            <path :d="svgTriangle" fill="url(#yellow)" />
            <path :d="svgTriangle" fill="url(#green)" />
        </svg>

用这个函数代替radius:

transformation: function (me, other1, other2) {
    const side1vector = { 
        x: other1.x - me.x,
        y: other1.y - me.y
    };
    const side2vector = { 
        x: other2.x - me.x,
        y: other2.y - me.y
    };
    const matrix = [
        side1vector.x * Math.sqrt(3)/2,
        side1vector.y * Math.sqrt(3)/2,
        side2vector.x - 0.5*side1vector.x,
        side2vector.y - 0.5*side1vector.y,
        me.x,
        me.y
    ];
    return "matrix(" + matrix.join(" ") + ")";
}

这应该可以满足您的要求。

说明:每个径向渐变最初以点 [0;0] 为中心,半径为 1。然后应用仿射变换,将中心 [0;0] 发送到相应的顶点,并发送点 [2*√3/3;0]和[√3/3;1]到其他顶点(你可以自己检查)。由于这些点位于原始渐变之外(距离 [0;0] 比 1 更远),因此其他顶点也将位于变换后的渐变之外,因此渐变永远不会隐藏它们。

此外,在等边三 Angular 形的情况下,此代码会产生与您的代码相同的结果。如果您使用此代码填充任何其他三 Angular 形,它将与您填充一个等边三 Angular 形,然后通过某种仿射变换将其压缩为另一个三 Angular 形的形状相同(这是因为仿射变换的组合仍然是仿射变换和一个特定对象的仿射变换由三个变换点(在本例中为顶点)的位置唯一定义。重要的结果是每种颜色在每个三 Angular 形中覆盖总面积的相同“百分比”,因此无需担心红色会完全丢失。

关于javascript - SVG 三 Angular 形的插值填充,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63045250/

相关文章:

vue.js - 如何使用 vue v3 项目将 boostrap v5 (js + css) 导入 nuxt.js v2.14?

javascript - 当我将 v-model 属性添加到复选框时,Check-all 停止工作

javascript - 如何在 Material UI 中设置 Material 卡的最小高度?

javascript - 如何检查一个列表是否是另一个有顺序的列表的子列表?

javascript - AngularUI map 绑定(bind)事件

javascript - 用于验证 ASP.NET 数据注释中格式化货币最小值的正则表达式

php - 相同的 CSS 在不同的域上提供不同的字体大小

javascript - Angularjs - 有没有办法以编程方式点击 HTML <select> 元素?

javascript - 使用 js 更改输入 slider 的颜色渐变?

php - Vue 资源发布请求内容类型