【问题标题】:Multiple points/colors gradient on HTML5 canvasHTML5 画布上的多个点/颜色渐变
【发布时间】:2014-03-12 15:20:45
【问题描述】:

我想在 html5 画布上使用由不同位置的几种不同颜色创建的渐变填充形状,例如 this picture

您对我如何做到这一点有任何想法吗?

【问题讨论】:

  • 多一点信息可能会更好。你需要它动画或改变它的大小吗?运行时会生成颜色和斑点吗?你有什么尝试?它将用于什么,背景?
  • 我正在绘制一个 voronoi 图,其中每个单元格都具有不同的颜色,并且我希望每个单元格之间的过渡“平滑”。它不需要动画。颜色和斑点是在运行时生成的。理想情况下,如果我可以像径向或线性渐变一样使用它,那将是完美的!

标签: javascript html5-canvas gradient


【解决方案1】:

搜索了一下,我从 Mozilla Development Network 找到了这个例子

function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');

    var radgrad = ctx.createRadialGradient(0,0,1,0,0,150);
    radgrad.addColorStop(0, '#A7D30C');
    radgrad.addColorStop(1, 'rgba(1,159,98,0)');

    var radgrad2 = ctx.createRadialGradient(0,150,1,0,150,150);
    radgrad2.addColorStop(0, '#FF5F98');
    radgrad2.addColorStop(1, 'rgba(255,1,136,0)');

    var radgrad3 = ctx.createRadialGradient(150,0,1,150,0,150);
    radgrad3.addColorStop(0, '#00C9FF');
    radgrad3.addColorStop(1, 'rgba(0,201,255,0)');

    var radgrad4 = ctx.createRadialGradient(150,150,1,150,150,150);
    radgrad4.addColorStop(0, '#F4F201');
    radgrad4.addColorStop(1, 'rgba(228,199,0,0)');

    ctx.fillStyle = radgrad4;
    ctx.fillRect(0,0,150,150);
    ctx.fillStyle = radgrad3;
    ctx.fillRect(0,0,150,150);
    ctx.fillStyle = radgrad2;
    ctx.fillRect(0,0,150,150);
    ctx.fillStyle = radgrad;
    ctx.fillRect(0,0,150,150);
}

基于此,您可以将每个单元格绘制为径向渐变,并使用完全透明的颜色作为其最后一步,以便与其他单元格更好地融合。

如果没有它,我认为您将需要根据它们与每个单元格的距离来计算每个像素颜色。

通常,如果在制作 voronoi 纹理时,将表面划分为网格,然后为每个顶点指定颜色,然后将像素的颜色与与构成其单元格的顶点的距离进行插值。

另请参阅http://www.raymondhill.net/voronoi/rhill-voronoi.html,了解在 html5 中实现真正的 voronoi。它是开源的,并根据 MIT 许可证获得许可,因此您可以使用它。

【讨论】:

  • 是的,“堆叠”多个径向渐变(最终颜色完全透明)应该可以解决问题。
  • 我不知道您需要,但请记住,这不是您绘制 voronoid 的方式,它是一种伪造与 voronoid 相似但省力的图像的方法。另外,如果您认为这回答了您的问题,我将很高兴被选为被接受(直到出现新的更好的答案);)。
  • 是的,这是一种解决方法。如果有很多细胞,我也担心性能会很差。每像素插值可能更好,但实现起来更复杂。我已经编辑了您的示例(请参阅我的原始帖子)以获得与图片中的示例相似的结果。答案被接受:)
  • 您不应该将答案添加到问题中,如果您想显示具有确切结果的答案,您可以编辑我的答案示例。
  • “rhill-voronoi”是我目前正在使用的实现!效果很好,我现在只需要使用渐变对结果进行着色。我已经编辑了你的答案和我的问题。
【解决方案2】:

您可以使用将初始点可以产生的所有可能的两种颜色线性渐变相乘(而不是相加)的原理。检查我的例子: https://codepen.io/tculda/pen/pogwpOw

function getProjectionDistance(a, b, c){
    const k2 = b.x*b.x - b.x*a.x + b.y*b.y -b.y*a.y;
    const k1 = a.x*a.x - b.x*a.x + a.y*a.y -b.y*a.y;
    const ab2 = (a.x - b.x)*(a.x - b.x) + (a.y - b.y) * (a.y - b.y);
    const kcom = (c.x*(a.x - b.x) + c.y*(a.y-b.y));
    const d1 = (k1 - kcom) / ab2;
    const d2 = (k2 + kcom) / ab2;
    return {d1, d2};
}

function limit01(value){
    if(value < 0){
        return 0;
    }
    if(value > 1){
        return 1;
    }
    return value;
}
function paddingleft0(v, v_length){
    while( v.length < v_length){
        v = '0' + v;
    }
    return v;
}

function getWeightedColorMix(points, ratios){
    let r = 0;
    let g = 0;
    let b = 0;
    for( [ind, point] of points.entries()){
        r += Math.round(parseInt(point.c.substring(1,3), 16) * ratios[ind]);
        g += Math.round(parseInt(point.c.substring(3,5), 16) * ratios[ind]);
        b += Math.round(parseInt(point.c.substring(5,7), 16) * ratios[ind]);
    }
        
    let result = '#' + paddingleft0(r.toString(16),2) + paddingleft0(g.toString(16),2) + paddingleft0(b.toString(16),2);

    return result;
}

/**
 * Given some points with color attached, calculate the color for a new point
 * @param  p The new point position {x: number, y: number}
 * @param  points The array of given colored points [{x: nember, y: number, c: hexColor}]
 * @return hex color string -- The weighted color mix
 */
function getGeometricColorMix( p, points ){
    let colorRatios = new Array(points.length);
    colorRatios.fill(1);
    for ( [ind1, point1] of points.entries()){
        for ( [ind2, point2] of points.entries()){
            if( ind1 != ind2){
                d  = getProjectionDistance(point1, point2, p);
                colorRatios[ind1] *= limit01(d.d2);
            }
        }

    }
      let totalRatiosSum = 0;
      colorRatios.forEach(c => totalRatiosSum += c);
      colorRatios.forEach((c,i) => colorRatios[i] /= totalRatiosSum);
    c = getWeightedColorMix(points, colorRatios);
    return c;
}

let points = [
    {x:10, y:10, c:"#FF0000"},
    {x:70, y:150, c:"#FFFF00"},
    {x:224, y:300, c:"#00FF00"},
    {x:121, y:100, c:"#00FFFF"},
    {x:160, y:10, c:"#FF00FF"},
]; // these are the starting points for drawing the gradient


var canv = document.getElementById("myCanvas");
var ctx = canv.getContext("2d");
let xcs = points.map( p => p.x);
let ycs = points.map( p => p.y);
let xmin = Math.min(...xcs);
let xmax = Math.max(...xcs);
let ymin = Math.min(...ycs);
let ymax = Math.max(...ycs);
let x, y;
let mixColor;

// iterate all the pixels between the given points
for( x = xmin; x < xmax; x++ ){
    for( y = ymin; y < ymax; y++ ){
        mixColor = getGeometricColorMix({x:x, y:y}, points);
        ctx.fillStyle = mixColor;
        ctx.fillRect(x, y, 1, 1);
    }
}

【讨论】:

  • 嗯,从技术上讲,您是在对各个渐变进行加权求和,每个渐变在两点之间的不透明度从完全变为零,对吗?您的代码看起来很酷(结果也很酷),我将详细了解并了解它的工作原理。我想知道是否有一些更有效的方法可以得到类似的结果,因为您的代码是每个像素 O(n^2),n 是点数。谢谢
  • 我将您的代码转换为 Java。也很优化。 code.botcompany.de/1034870
猜你喜欢
  • 1970-01-01
  • 2018-01-21
  • 2012-08-25
  • 2011-09-19
  • 1970-01-01
  • 2016-10-10
  • 1970-01-01
  • 2012-06-19
  • 1970-01-01
相关资源
最近更新 更多