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

Posted

技术标签:

【中文标题】如何在不改变其图案的情况下改变 HTML5 Canvas 中图像的颜色【英文标题】:how to change the color of an image in a HTML5 Canvas without changing its pattern 【发布时间】:2017-12-24 12:46:32 【问题描述】:

我想改变这张图片的背景颜色,同时保持形状、效果和轮廓 the image.

<canvas id="canvas01"  ></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>

【问题讨论】:

看到这个小提琴:jsfiddle.net/pHwmL/1。与线程相关***.com/questions/10069171/… 您是在谈论更改图像的背景颜色,在您的情况下是透明的,还是图像的全局颜色?就像把你的绿色 T 恤变成红色一样。 我之前已经尝试过了,问题是画布本身包含很多图像,所以我只想为画布中的一个图像而不是整个画布着色。谢谢苏奇特库马尔.. 我说的是改变 T 恤的颜色(比如让它变成红色或蓝色),但我必须保持 T 恤的效果和形状。谢谢艾伦拉奎因 【参考方案1】:

亮度保存

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

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

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

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

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

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

// 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) 将保持原始亮度值。

现场示例

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

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>

【讨论】:

是的,它可以工作,但我对某些颜色还有另一个问题,例如,如果我选择一种颜色的 HSL 代码(例如白色 hsl(0, 0%, 100%))我不明白白色,但颜色不同。我该如何处理?谢谢K3N @M.Chaymae 是的,这是 HSL 模型的问题。如果没有色度数据(即 sat=0),它将无法工作。但是,您可以将亮度 (L) 与白色 t 的颜色减淡混合模式结合使用,并在需要黑色 t 时使用颜色加深。我更新了答案。 我还有一个问题,您的代码运行良好,但是当我尝试在同一个画布上绘制多个图像时发现问题,它看起来像 ibb.co/dCgpjk。如何删除代码中的那些灰色矩形。再次感谢您的帮助! @M.Chaymae 您可以使用剪辑步骤(上面代码中的最后一步,请参见注释// clip 下方的代码)。当然,假设正在使用的图像具有 alpha 通道(透明背景)。 我按照你告诉我的做了,但我有一个问题......我在同一个画布上有多个图像,所以其中一个图像出现,其他图像没有,但这次没有灰色矩形..我不知道它看起来不像图像,但没有灰色矩形【参考方案2】:

全局复合操作

2D 上下文属性ctx.globalCompositeOperation 对于广泛的图像处理任务非常有用。更多关于globalCompositeOperation at MDN

您可以将图像转换为画布,这样您就可以对其进行编辑。

function imageToCanvas(image)
    const c = document.createElement("canvas");
    c.width = image.width;
    c.height = image.height;
    c.ctx = c.getContext("2d"); // attach context to the canvas for eaasy reference
    c.ctx.drawImage(image,0,0);
    return c;

您可以使用globalCompositeOperation = "color" 为图像着色

function colorImage(image,color) // image is a canvas image
     image.ctx.fillStyle = color;
     image.ctx.globalCompositeOperation = "color";
     image.ctx.fillRect(0,0,image.width,image.height);
     image.ctx.globalCompositeOperation = "source-over";
     return image;

不幸的是,这也会覆盖 alpha 像素,因此您需要使用原始图像作为蒙版来恢复 alpha 像素。

function maskImage(dest,source)
     dest.ctx.globalCompositeOperation = "destination-in";
     dest.ctx.drawImage(source,0,0);
     dest.ctx.globalCompositeOperation = "source-over";
     return dest;

然后你有一个彩色图像

示例。

在他的示例中,我用一系列颜色为图像着色,并添加了一个函数来将图像的画布副本恢复到原始状态。如果您从页面获取图像作为元素,则使用naturalWidthnaturalHeight,因为widthheight 属性可能与图像分辨率不匹配。

const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => 
  colCopy = imageToCanvas(image);
  const scale = canvas.height / image.naturalHeight; 
  ctx.scale(scale, scale);
  ctx.drawImage(colCopy, 0, 0);
  for (var i = 32; i < 360; i += 32) 
    restoreImage(colCopy, image);
    colorImage(colCopy, "hsl(" + i + ",100%,50%)");
    maskImage(colCopy, image);
    ctx.drawImage(colCopy, 150 * i / 16, 0);
  




function imageToCanvas(image) 
  const c = document.createElement("canvas");
  c.width = image.naturalWidth;
  c.height = image.naturalHeight;
  c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
  c.ctx.drawImage(image, 0, 0);
  return c;


function restoreImage(dest, source) 
  dest.ctx.clearRect(0, 0, dest.width, dest.height);
  dest.ctx.drawImage(source, 0, 0);
  return dest;


function colorImage(dest, color)  // image is a canvas image
  dest.ctx.fillStyle = color;
  dest.ctx.globalCompositeOperation = "color";
  dest.ctx.fillRect(0, 0, dest.width, dest.height);
  dest.ctx.globalCompositeOperation = "source-over";
  return dest;


function maskImage(dest, source) 
  dest.ctx.globalCompositeOperation = "destination-in";
  dest.ctx.drawImage(source, 0, 0);
  dest.ctx.globalCompositeOperation = "source-over";
  return dest;
canvas 
  border: 2px solid black;
&lt;canvas id="canvas" width=600&gt;&lt;/canvas&gt;

图像在某些情况下可能会有点褪色,您可以使用类似于上图的合成操作将图像转换为对比度更高的黑白图像,并使用高对比度图像作为模板进行着色。

使用过滤器

大多数常见浏览器现在都支持具有色相偏移过滤器的画布过滤器。您可以使用它来将色调转换为您想要的值,但首先您需要知道图像的原始色调是什么。 (请参阅下面有关如何找到 HUE 的示例)

请参阅Canvas filters at MDN 了解兼容性以及如何使用画布过滤器。

以下函数将保留饱和度并仅改变色调。

// dest canvas to hold the resulting image
// source the original image
// hue The hue to set the dest image to
// sourceHue the hue reference point of the original image.
function colorImage(dest,source, hue , sourceHue)  // image is a canvas image
  dest.ctx.clearRect(0,0,dest.width, dest.height);
  dest.ctx.filter="hue-rotate("+((hue - sourceHue) | 0)+"deg)";
  dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
  return dest;

过滤器示例。

下面使用ctx.filter = "hue-rotate(30deg)"来旋转色相。我没有包含任何代码来查找图像原始色调,因此手动将其设置为 120。

const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => 
  colCopy = imageToCanvas(image);
  
  const scale = canvas.height / image.naturalHeight; 
  ctx.scale(scale, scale);
  ctx.drawImage(colCopy, 0, 0);
  for (var i = 32; i < 360; i += 32) 
    colorImage(colCopy,image,i,sourceHue);
    ctx.drawImage(colCopy, 150 * i / 16, 0);
  




function imageToCanvas(image) 
  const c = document.createElement("canvas");
  c.width = image.naturalWidth;
  c.height = image.naturalHeight;
  c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
  c.ctx.drawImage(image, 0, 0);
  return c;


function colorImage(dest,source, hueRotate , sourceHue)  // image is a canvas image
  dest.ctx.clearRect(0,0,dest.width, dest.height);
  dest.ctx.filter="hue-rotate("+((hueRotate - sourceHue) | 0)+"deg)";
  dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
  return dest;
canvas 
  border: 2px solid black;
&lt;canvas id="canvas" width=600&gt;&lt;/canvas&gt;

RGB 转色相

有很多答案可以帮助在 SO 上找到像素的色调。这里有一个特别详细的RGB to HSL conversion。

过滤器示例白色。

下面使用ctx.filter = "grayscale(100%)"去除饱和度,然后ctx.filter = "brightness(amount%)"改变亮度。这给出了从黑色到白色的一系列灰色。您也可以通过减少灰度量来对颜色执行相同的操作。

const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => 
  colCopy = imageToCanvas(image);
  
  const scale = canvas.height / image.naturalHeight; 
  ctx.scale(scale, scale);
  ctx.drawImage(colCopy, 0, 0);
  for (var i = 40; i < 240; i += 20) 
    grayImage(colCopy,image,i);
    ctx.drawImage(colCopy, 150 * ((i-40) / 12), 0);
  




function imageToCanvas(image) 
  const c = document.createElement("canvas");
  c.width = image.naturalWidth;
  c.height = image.naturalHeight;
  c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
  c.ctx.drawImage(image, 0, 0);
  return c;


function grayImage(dest,source, brightness)  // image is a canvas image
  dest.ctx.clearRect(0,0,dest.width, dest.height);
  dest.ctx.filter = "grayscale(100%)";
  dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
  dest.ctx.filter = "brightness(" + brightness +"%)";
  dest.ctx.drawImage(dest,0, 0, dest.width, dest.height);

  return dest;
canvas 
  border: 2px solid black;
&lt;canvas id="canvas" width=600&gt;&lt;/canvas&gt;

【讨论】:

您知道如何将此代码用于多个图像吗?我想在我的画布上添加更多图像并一次性为它们上色【参考方案3】:

在执行绘图操作之前,您可以在一行代码中组合过滤器,如下所示:

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

ctx.filter = 'hue-rotate(120deg) grayscale(10%) brightness(150%)';
ctx.drawImage(image, 10, 10, 180, 120);
<canvas id="canvas"></canvas>
<div style="display:none;">
  <img id="source"
       src="https://interactive-examples.mdn.mozilla.net/media/examples/gecko-320-213.jpg">
</div>

【讨论】:

以上是关于如何在不改变其图案的情况下改变 HTML5 Canvas 中图像的颜色的主要内容,如果未能解决你的问题,请参考以下文章

如何在不改变其内容位置的情况下弹出整个 Div

如何在不改变其位置的情况下在界面生成器中将子视图置于前面

在 Kotlin Native 中,如何在不使用 C 指针的情况下将对象保留在单独的线程中,并从任何其他线程中改变其状态?

C# - 如何在不改变 DateTime 格式的情况下改变文化

如何在不改变iphone中精灵原始值的情况下改变精灵的高度?

如何在不改变背景内容的情况下改变背景的不透明度?