且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在不改变其图案的情况下更改 HTML5 Canvas 中图像的颜色

更新时间:2023-02-26 20:08:08

亮度保存

冒着看起来与现有答案相似的风险,我想使用稍微不同的方法指出一个小但重要的区别.

Luma preservation

At the risk of looking similar to the existing answer, I would like to point out a small but important difference using a slightly different approach.

关键是要保留图像中的亮度分量(即在这种情况下为阴影细节、皱纹等),因此需要两个步骤来通过 globalCompositeOperation(或或者,如果必须支持旧版浏览器,则使用手动方法在 RGB 和 HSL 颜色空间之间进行转换):

The key is to preserve the luma component in an image (ie. shadow details, wrinkles etc. in this case) so two steps are needed to control the look using blending modes via globalCompositeOperation (or alternatively, a manual approach using conversion between RGB and the HSL color-space if older browsers must be supported):

  • saturation":将改变下一个绘制元素的色度(强度、饱和度)并将其应用于画布上的现有内容,但保留亮度和色调.
  • hue":将从源中获取色度和亮度,但根据下一个绘制的元素更改色调或颜色.
  • "saturation": will alter the chroma (intensity, saturation) from the next drawn element and apply it to the existing content on the canvas, but preserve luma and hue.
  • "hue": will grab the chroma and luma from the source but alter the hue, or color if you will, based on the next drawn element.

由于这些是混合模式(忽略 Alpha 通道),我们还需要在最后一步使用合成来剪辑结果.

As these are blending modes (ignoring the alpha channel) we will also need to clip the result using composition as a last step.

color 混合模式也可以使用,但它会改变亮度,这可能是可取的,也可能不是.在许多情况下,这种差异可能很细微,但也非常明显,具体取决于失去亮度/阴影定义的目标色度和色调.

The color blending mode can be used too but it will alter luma which may or may not be desirable. The difference can be subtle in many cases, but also very obvious depending on target chroma and hue where luma/shadow definition is lost.

因此,为了获得同时保留亮度和色度的高质量结果,这些或多或少是主要步骤(假设画布是空的):

So, to achieve a good quality result preserving both luma and chroma, these are more or less the main steps (assumes an empty canvas):

// step 1: draw in original image
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, 0, 0);

// step 2: adjust saturation (chroma, intensity)
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "hsl(0," + sat + "%, 50%)";  // hue doesn't matter here
ctx.fillRect(0, 0);

// step 3: adjust hue, preserve luma and chroma
ctx.globalCompositeOperation = "hue";
ctx.fillStyle = "hsl(" + hue + ",1%, 50%)";  // sat must be > 0, otherwise won't matter
ctx.fillRect(0, 0, c.width, c.height);

// step 4: in our case, we need to clip as we filled the entire area
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img, 0, 0);

// step 5: reset comp mode to default
ctx.globalCompositeOperation = "source-over";

50% 的亮度 (L) 将保持原始亮度值.

单击复选框以查看对结果的影响.然后使用不同的色度和色调设置进行测试.

Click the checkbox to see the effect on the result. Then test with different chroma and hue settings.

var ctx = c.getContext("2d");
var img = new Image(); img.onload = demo; img.src = "//i.stack.imgur.com/Kk1qd.png";
function demo() {c.width = this.width>>1; c.height = this.height>>1; render()}

function render() {
  var hue = +rHue.value, sat = +rSat.value, l = +rL.value;
  
  ctx.clearRect(0, 0, c.width, c.height);
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, 0, 0, c.width, c.height);

  if (!!cColor.checked) {
    // use color blending mode
    ctx.globalCompositeOperation = "color";
    ctx.fillStyle = "hsl(" + hue + "," + sat + "%, 50%)";
    ctx.fillRect(0, 0, c.width, c.height);
  }
  else {
    // adjust "lightness"
    ctx.globalCompositeOperation = l < 100 ? "color-burn" : "color-dodge";
    // for common slider, to produce a valid value for both directions
    l = l >= 100 ? l - 100 : 100 - (100 - l);
    ctx.fillStyle = "hsl(0, 50%, " + l + "%)";
    ctx.fillRect(0, 0, c.width, c.height);
    
    // adjust saturation
    ctx.globalCompositeOperation = "saturation";
    ctx.fillStyle = "hsl(0," + sat + "%, 50%)";
    ctx.fillRect(0, 0, c.width, c.height);

    // adjust hue
    ctx.globalCompositeOperation = "hue";
    ctx.fillStyle = "hsl(" + hue + ",1%, 50%)";
    ctx.fillRect(0, 0, c.width, c.height);
  }
  
  // clip
  ctx.globalCompositeOperation = "destination-in";
  ctx.drawImage(img, 0, 0, c.width, c.height);

  // reset comp. mode to default
  ctx.globalCompositeOperation = "source-over";
}

rHue.oninput = rSat.oninput = rL.oninput = cColor.onchange = render;

body {font:16px sans-serif}

<div>
  <label>Hue: <input type=range id=rHue max=359 value=0></label>
  <label>Saturation: <input type=range id=rSat value=100></label>
  <label>Lightness: <input type=range id=rL max=200 value=100></label>
  <label>Use "color" instead: <input type=checkbox id=cColor></label>
</div>
<canvas id=c></canvas>