绘制一个带有渐变单元="objectBoundingBox"的SVG矩形.在Canvas上使用内置变换功能或不更改色标? [英] Draw an SVG rectangle with gradientUnits="objectBoundingBox" on Canvas without using the built in transform function or changing the color stops?

查看:70
本文介绍了绘制一个带有渐变单元="objectBoundingBox"的SVG矩形.在Canvas上使用内置变换功能或不更改色标?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里是具有线性渐变的SVG,它使用objectBoundingBox渐变单位:

Here is an SVG that has a linear gradient that uses objectBoundingBox gradientUnits:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="myGradient" x1="0%" y1="0%" x2="100%" y2="100%" gradientUnits="objectBoundingBox">
      <stop offset="40%" stop-color="yellow" />
      <stop offset="50%" stop-color="black" />
      <stop offset="60%" stop-color="red" />
    </linearGradient>
  </defs>

  <rect x="10" y="10" width="20" height="10" fill="url('#myGradient')" />
</svg>

我需要在画布上绘制它.

I need to draw this on a Canvas.

如果使用transform方法,我可以在Canvas上绘制渐变:

I can draw the gradient on a Canvas if I use the transform method:

const canvas = document.getElementById('canvasBuiltInScale');
const ctx = canvas.getContext('2d');

function draw(x0, y0, x1, y1) {
  ctx.save();

  // create a square 1x1 gradient
  const gradient = ctx.createLinearGradient(0, 0, 1,  1);
  gradient.addColorStop(0.4, 'yellow');
  gradient.addColorStop(0.5, 'black');
  gradient.addColorStop(0.6, 'red');

  // scale it up to the size of the bbox
  const width = x1 - x0;
  const height = y1 - y0;

  ctx.transform(width, 0, 0, height, x0, y0);
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, 1, 1);  
  ctx.restore();
}

draw(10, 10, 40, 30);

但是不幸的是,客户不希望我使用transform方法.

But unfortunately the customer does not want me to use the transform method.

相反,我可以在画布上绘制相同的渐变色,而使用的是原始缩放比例.

I can draw the same gradient on a Canvas with a home rolled scaling instead.

const canvas = document.getElementById('canvasHomeRolledScale');
const ctx = canvas.getContext('2d');

function draw(x0, y0, x1, y1) {
  const width = x1-x0;
  const height = y1-y0;

  // The problem is that with userSpace coordinates, the normal to the gradient vector from x0,y0 to x1,y1 will not go between x1,y0 and x0,y1
  // I perform a home baked geometric calculation to find the normal vector to [x1-x0, y1-y0] since its normal vector will pass through [x1-x0, y1-y0]
  const gradient = ctx.createLinearGradient(
    x0 + (width - height) / 2, 
    y0 + (height - width) / 2, 
    x0 + (width - height) / 2 + height, 
    y0 + (height - width) / 2 + width
  );

  gradient.addColorStop(rescale(0.4), 'yellow');
  gradient.addColorStop(rescale(0.5), 'black');
  gradient.addColorStop(rescale(0.6), 'red');
  ctx.fillStyle = gradient;
  ctx.fillRect(x0, y0, width, height);

  // The normal vector calculated above has the right direction, but not the right amplitude.
  // Here I guy guessed that I could use pythagoras theorem to arrive at the correct scale
  function rescale(percent) {
    const max = Math.max(height, width)
    const min = Math.min(height, width)
    const f = (
      Math.sqrt(
        Math.pow(max, 2) + Math.pow(min, 2)
      ) / 
      Math.sqrt(
        Math.pow(max, 2) + Math.pow(max, 2)
      )
    );
    const midPoint = 0.5;
    return midPoint - (midPoint-percent) * f
  }
}

draw(10, 10, 40, 30);

但是我不允许更改色标的百分比.

But I am not allowed to change the percentage of the color stops.

两种情况下的反对意见都是有效的反对意见,应该有一个更简单,更优雅的解决方案.因此,我问这里的聪明人是否有解决方案:

  • 不使用转换方法
  • 不更改色标

推荐答案

如果您正在寻找坐标的转换方法,那就可以了:

If you are looking for a transformation of coordinates, this would do the trick:

const canvas = document.getElementById('canvasBuiltInScale');
const ctx = canvas.getContext('2d');

function tcoord(x0, y0, x1, y1){
  let xc = (x1 + x0) / 2;
  let yc = (y1 + y0) / 2;
  let dx = (x1 - x0) / 2;
  let dy = (y1 - y0) / 2;
  let rx0 = xc - dy;
  let ry0 = yc - dx;
  let rx1 = xc + dy;
  let ry1 = yc + dx;
  let result = [rx0,ry0,rx1,ry1];
  return result;
}

function draw(x0, y0, x1, y1) {
  ctx.save();
  let c = tcoord(x0, y0, x0 + x1, y0 + y1);
  const gradient = ctx.createLinearGradient(c[0], c[1], c[2],  c[3]);
  gradient.addColorStop(0.4, 'yellow');
  gradient.addColorStop(0.5, 'black');
  gradient.addColorStop(0.6, 'red');
  ctx.fillStyle = gradient;
  ctx.fillRect(x0, y0, x1, y1);  
  ctx.restore();
}

draw(10, 10, 80, 60);

<canvas id="canvasBuiltInScale" width="300" height="300">
</canvas>

对于它的价值,我发现您的 transform 解决方案更优雅.

For what it is worth, I find your transform solution way more elegant.

评论后编辑

理所当然的是,如果我们更改渐变的起点和终点,则还需要转换渐变步长.我已经用一种解决方案分叉了小提琴( https://jsfiddle.net/ftadpu3c/3/).它使用了一个称为 transformGradient 的新函数.由于此转换取决于第一个,因此在 tcoord 中有一个参数被计算.我还对样式进行了一些更改,以使其更加一致.传递给 draw 的第三和第四参数是宽度和高度,而不是坐标.

It stands to reason that if we change the start and end points of the gradient, we also need to transform the gradient steps. I have forked the fiddle with a solution ( https://jsfiddle.net/ftadpu3c/3/ ). It uses a new function called transformGradient. Since this transformation depends on the first one, there is one parameter that is calculated in tcoord. I also changed the style a little bit to make it more consistent. The third and fourth parameters passed to draw are a width and a height, rather than coordinates.

编辑2 我陷入了这样一个观念,即必须保持变换后的点到矩形中心的距离.当然,这是不正确的.通过选择合适的距离,就不必变换渐变.请参阅 https://jsfiddle.net/uwshL43f/

Edit 2 I was caught up in the notion that the distance of the transformed points to the center of the rectangle had to be maintained. Of course, that is not true. By choosing a suitable distance, it will not be necessary to transform the gradient. See second fork at https://jsfiddle.net/uwshL43f/

这篇关于绘制一个带有渐变单元="objectBoundingBox"的SVG矩形.在Canvas上使用内置变换功能或不更改色标?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆