HTML5画布扫描渐变

时间:2018-08-20 12:28:38

标签: html5 canvas gradient

HTML5画布似乎不支持“扫描渐变”,即颜色停止围绕中心旋转而不是从中心发出的渐变。

有什么方法可以在画布上模拟扫描渐变?我想我可以用很多小的线性渐变来做类似的事情,但是那时候我基本上是在自己渲染渐变。

Example of sweep gradient

1 个答案:

答案 0 :(得分:1)

实际上,没有内置这种东西。

不确定这些“ 很多小的线性渐变”是什么想法,但实际上您只需要一个,即圆的周长,就只能得到正确的颜色使用。

尽管您需要很多东西,但是线条,因为我们将使用linearGradient中的纯色围绕中心点绘制线条。

因此要渲染此图像,您只需移动到中心点,然后使用线性渐变中的纯色绘制一条线,然后旋转并重复。

要获取linearGradient的所有颜色,只需绘制它并将其ImageData映射为CSS颜色即可。

尽管困难的部分在于,要拥有一个行为像CanvasGradient的对象,我们需要将其设置为fillStyle或strokeStyle。 这可以通过返回CanvasPattern来实现。另一个困难是,梯度实际上无限大。非重复模式不是。
我没有找到解决此问题的好方法,但是作为一种解决方法,我们可以使用目标画布的大小作为限制。

这是一个粗略的实现:

<configuration debug="true" scan="true" scanPeriod="5 seconds">

    <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">

         <filter class="ch.qos.logback.classic.filter.LevelFilter">
            ...         
         </filter>

        ...         
         <subject>Testing %logger{20} - %m</subject>

        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%-5level %logger-%msg%n</pattern>
        </layout>
    </appender>

    <appender name="EMAIL_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
       <appender-ref ref="EMAIL"/>
    </appender>

    <root level="ERROR">
        <appender-ref ref="EMAIL_ASYNC" />
    </root>
</configuration>
class SweepGrad {
  constructor(ctx, x, y) {
    this.x = x;
    this.y = y;
    this.target = ctx;
    this.colorStops = [];
  }
  addColorStop(offset, color) {
    this.colorStops.push({offset, color});
  }
  render() {
    // get the current size of the target context
    const w = this.target.canvas.width;
    const h = this.target.canvas.width;
    const x = this.x;
    const y = this.y;
    // get the max length our lines can be
    const maxDist = Math.ceil(Math.max(
      Math.hypot(x, y),
      Math.hypot(x - w, y),
      Math.hypot(x - w, y - h),
      Math.hypot(x, y - h)
    ));
    // the circumference of our maxDist circle
    // this will determine the number of lines we will draw
    // (we double it to avoid some antialiasing artifacts at the edges)
    const circ = maxDist*Math.PI*2 *2;
  
    // create a copy of the target canvas
    const canvas = this.target.canvas.cloneNode();
    const ctx = canvas.getContext('2d');

    // generate the linear gradient used to get all our colors
    const linearGrad = ctx.createLinearGradient(0, 0, circ, 0);
    this.colorStops.forEach(stop => 
     linearGrad.addColorStop(stop.offset, stop.color)
    );
    const colors = getLinearGradientColors(linearGrad, circ);
    // draw our gradient
    ctx.setTransform(1,0,0,1,x,y);

    for(let i = 0; i<colors.length; i++) {
      ctx.beginPath();
      ctx.moveTo(0,0);
      ctx.lineTo(maxDist, 0);
      ctx.strokeStyle = colors[i];
      ctx.stroke();
      ctx.rotate((Math.PI*2)/colors.length);
    }
    // return a Pattern so we can use it as fillStyle or strokeStyle
    return ctx.createPattern(canvas, 'no-repeat');
  }

}
// returns an array of CSS colors from a linear gradient
function getLinearGradientColors(grad, length) {
  const canvas = Object.assign(document.createElement('canvas'), {width: length, height: 10});
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = grad;
  ctx.fillRect(0,0,length, 10);
  return ctx.getImageData(0,0,length,1).data
    .reduce((out, channel, i) => {
      const px_index = Math.floor(i/4);
      const px_slot = out[px_index] || (out[px_index] = []);
      px_slot.push(channel);
      if(px_slot.length === 4) {
         px_slot[3] /= 255;
         out[px_index] = `rgba(${px_slot.join()})`;
      }
      return out;
    }, []);
}

// How to use
const ctx = canvas.getContext('2d');

const redblue = new SweepGrad(ctx, 70, 70);
redblue.addColorStop(0, 'red');
redblue.addColorStop(1, 'blue');
// remeber to call 'render()' to get the Pattern back
// maybe a Proxy could handle that for us?
ctx.fillStyle = redblue.render();
ctx.beginPath();
ctx.arc(70,70,50,Math.PI*2,0);
ctx.fill();

const yellowgreenred = new SweepGrad(ctx, 290, 80);
yellowgreenred.addColorStop(0, 'yellow');
yellowgreenred.addColorStop(0.5, 'green');
yellowgreenred.addColorStop(1, 'red');
ctx.fillStyle = yellowgreenred.render();
ctx.fillRect(220,10,140,140);

// just like with gradients, 
// we need to translate the context so it follows our drawing
ctx.setTransform(1,0,0,1,-220,-10);
ctx.lineWidth = 10;
ctx.strokeStyle = ctx.fillStyle;
ctx.stroke(); // stroke the circle
canvas{border:1px solid}

但是要注意,所有这些在计算上都非常繁琐,因此请确保偶尔使用它并缓存生成的渐变/图案。