CSS线性渐变不准确?

3
我的应用程序需要创建一个颜色板,可以包含从0度到360度的任何颜色色调。我目前使用以下代码来创建这个颜色板。让我们以120度(纯绿色)为例:

function drawPalette(hue) {
  var ctx = document.querySelector("canvas").getContext("2d");

  let w = ctx.canvas.width;
  let h = ctx.canvas.height;

  var grH = ctx.createLinearGradient(0, 0, w, 0);
  grH.addColorStop(0, '#fff');
  grH.addColorStop(1, 'hsl(' + hue + ', 100%, 50%)');

  ctx.fillStyle = grH;
  ctx.fillRect(0, 0, w, h);

  var grV = ctx.createLinearGradient(0, 0, 0, h);
  grV.addColorStop(0, 'rgba(0,0,0,0)');
  grV.addColorStop(1, '#000');

  ctx.fillStyle = grV;
  ctx.fillRect(0, 0, w, h);
}

drawPalette(120);
<canvas></canvas>

我遇到的问题是这不是一个完美的渐变。例如,右上方的颜色为#02FF02而不是#00FF00(纯绿色)。您可以使用取色器来自行查看。
有没有办法制作一个完美的线性渐变,可供颜色调色板使用?

你是怎么知道它是 #02FF02 的?在右上角(最边角)看起来像是纯绿色。 - hashBender
@hashBender 我截了一张屏幕截图,并使用了一个外部的吸管取色工具来查看。 - Ryan Peschel
通过那种方法,你可能得不到准确的结果。 - hashBender
1个回答

3
不,渐变返回了正确的结果。右上角的像素位于渐变的起点和终点之间,并具有插值后的值以反映这一点。
想象一个只有一个像素的画布并且是水平或垂直渐变。渐变的起点是[0,0],终点是[1,0]。我们唯一像素的中心应该具有开始和结束值之间一半的值。如果我们的开始值为255,255,255,结束值为0,255,0,则唯一的像素应为128,255,128,如下图所示:

function drawPalette(hue) {
  var ctx = document.querySelector("canvas").getContext("2d");

  let w = ctx.canvas.width;
  let h = ctx.canvas.height;

  var grH = ctx.createLinearGradient(0, 0, w, 0);
  grH.addColorStop(0, '#fff');
  grH.addColorStop(1, 'hsl(' + hue + ', 100%, 50%)');

  ctx.fillStyle = grH;
  ctx.fillRect(0, 0, w, h);

  
  let data = ctx.getImageData(w-1,0, 1, 1).data;
  console.log(data);

}

drawPalette(120);
<canvas width="1" height="1"></canvas>

如果我们有一个10像素乘以10像素的画布,渐变从0到10延伸,像素的最后一列的中间点距离9.5个像素。这意味着,渐变的插值值应该是渐变起始值和终止值之间路径的95%。使用与上文相同的渐变,应该是: 13, 255, 13:

function drawPalette(hue) {
  var ctx = document.querySelector("canvas").getContext("2d");

  let w = ctx.canvas.width;
  let h = ctx.canvas.height;

  var grH = ctx.createLinearGradient(0, 0, w, 0);
  grH.addColorStop(0, '#fff');
  grH.addColorStop(1, 'hsl(' + hue + ', 100%, 50%)');

  ctx.fillStyle = grH;
  ctx.fillRect(0, 0, w, h);

  
  let data = ctx.getImageData(w-1,0, 1, 1).data;
  console.log(data);

}

drawPalette(120);
<canvas width="10" height="10"></canvas>

随着画布变得更大,应用渐变后的第一个/最后一个值与渐变起始/终止值之间的差异会减小。如果您将示例中的画布大小更改为1000x1000,则右上角像素由于四舍五入而具有值0,255,0:

function drawPalette(hue) {
  var ctx = document.querySelector("canvas").getContext("2d");

  let w = ctx.canvas.width;
  let h = ctx.canvas.height;

  var grH = ctx.createLinearGradient(0, 0, w, 0);
  grH.addColorStop(0, '#fff');
  grH.addColorStop(1, 'hsl(' + hue + ', 100%, 50%)');

  ctx.fillStyle = grH;
  ctx.fillRect(0, 0, w, h);

  var grV = ctx.createLinearGradient(0, 0, 0, h);
  grV.addColorStop(0, 'rgba(0,0,0,0)');
  grV.addColorStop(1, '#000');

  ctx.fillStyle = grV;
  ctx.fillRect(0, 0, w, h);
  
  let data = ctx.getImageData(w-1,0, 1, 1).data;
  console.log(data);
  
}

drawPalette(120);
<canvas width="1000" height="1000"></canvas>

渐变包括起点和终点之间的像素。如果您不希望它们被渐变化,则需要修改渐变,使其从稍后的一个像素开始并在稍早的一个像素结束:

function drawPalette(hue) {
  var ctx = document.querySelector("canvas").getContext("2d");

  let w = ctx.canvas.width;
  let h = ctx.canvas.height;
  
  // to ensure we know we're covering the whole thing:
  ctx.fillRect(0,0,w,h)
  //

  var grH = ctx.createLinearGradient(1, 0, w-2, 0);
  grH.addColorStop(0, '#fff');
  grH.addColorStop(1, 'hsl(' + hue + ', 100%, 50%)');

  ctx.fillStyle = grH;
  ctx.fillRect(0, 0, w, h);

  
  let data = ctx.getImageData(w-1,0, 1, 1).data;
  console.log(data);

}

drawPalette(120);
<canvas width="100" height="100"></canvas>


在更小的画布尺寸下,关于插值值的正确性是正确的(我在600x300的画布上得到了00FF00)。Google的颜色选择器与您的选择器做了同样的事情,角落的绿色值是FE而不是FF。-你为什么需要颜色板的显示完美无缺呢?Google使用选择器计算值,并且选择器会到达完整的FF值。 - Rich DeBourke
我想我不需要完美的渐变,但是我需要更改我的代码来计算“正确”的值,而不是使用getImageData来确定所选颜色。 - Ryan Peschel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接