且构网

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

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

更新时间:2022-11-04 12:12:48

亮度保留

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

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

  • saturation":将改变下一个绘制元素的色度(强度、饱和度)并将其应用于画布上的现有内容,但保留亮度和色调.
  • hue":将从源获取色度和亮度,但会根据下一个绘制的元素改变色调或颜色.

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

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

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

//第一步:绘制原图ctx.globalCompositeOperation =源代码";ctx.drawImage(img, 0, 0);//步骤 2:调整饱和度(色度、强度)ctx.globalCompositeOperation =饱和度";ctx.fillStyle = "hsl(0," + sat + "%, 50%)";//色调在这里无关紧要ctx.fillRect(0, 0);//第三步:调整色调,保留亮度和色度ctx.globalCompositeOperation =色调";ctx.fillStyle = "hsl(" + 色调 + ",1%, 50%)";//sat 必须是 >0,否则无所谓ctx.fillRect(0, 0, c.width, c.height);//第 4 步:在我们的例子中,我们需要在填充整个区域时进行裁剪ctx.globalCompositeOperation =目的地";ctx.drawImage(img, 0, 0);//步骤 5:将补偿模式重置为默认值ctx.globalCompositeOperation =源代码";

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

现场示例

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

var ctx = c.getContext("2d");var img = new Image();img.onload = 演示;img.src = "//i.stack.imgur.com/Kk1qd.png";function demo() {c.width = this.width>>1;c.height = this.height>>1;使成为()}函数渲染(){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);如果(!!cColor.checked){//使用颜色混合模式ctx.globalCompositeOperation = "颜色";ctx.fillStyle = "hsl(" + 色调 + "," + sat + "%, 50%)";ctx.fillRect(0, 0, c.width, c.height);}别的 {//调整亮度"ctx.globalCompositeOperation = l = 100 ?l - 100 : 100 - (100 - l);ctx.fillStyle = "hsl(0, 50%, " + l + "%)";ctx.fillRect(0, 0, c.width, c.height);//调整饱和度ctx.globalCompositeOperation = "饱和度";ctx.fillStyle = "hsl(0," + sat + "%, 50%)";ctx.fillRect(0, 0, c.width, c.height);//调整色调ctx.globalCompositeOperation = "色调";ctx.fillStyle = "hsl(" + 色调 + ",1%, 50%)";ctx.fillRect(0, 0, c.width, c.height);}//夹子ctx.globalCompositeOperation = "目的地";ctx.drawImage(img, 0, 0, c.width, c.height);//重置补偿默认模式ctx.globalCompositeOperation = "source-over";}rHue.oninput = rSat.oninput = rL.oninput = cColor.onchange = 渲染;

body {font:16px sans-serif}

<label>Hue:<input type=range id=rHue max=359 value=0></label><label>饱和度:<input type=range id=rSat value=100></label><label>亮度:<input type=range id=rL max=200 value=100></label><label>使用color"代替:<input type=checkbox id=cColor></label>

<canvas id=c></canvas>

I want to change the background color of this image while keeping the form, the effects and the contour of the image.

<canvas id="canvas01" width="1200" height="800"></canvas>
<script>
    function drawImage(imageObj,x, y, width, height){
        var canvas = document.getElementById('canvas01');
        var context = canvas.getContext('2d');
        context.drawImage(imageObj, x, y, width, height);
    }
    var image = new Image();
    image.onload = function(){
        drawImage(this, 400, 100, 320, 450);
    };
    image.src ="images/658FFBC6.png";
</script>

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.

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": 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.

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

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% lightness (L) will keep the original luma value.

Live Example

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>