ctx.globalCompositeOperation = "乘"
您可以使用复合方法并避免丢失动态范围,因为 frnt 的答案会这样做,0.6 的 alpha 会显着降低与原始蒙版的对比度。
有许多混合选项,但对于您不想让白色透出而只想添加暗度的图像,请使用混合模式“正片叠底”,此方法将为您提供最佳对比度。
ctx.fillStyle = ??? // the background colour you want
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // fill canvas with colour. This is now the destination
// Destination pixels get multiplied by source pixels
// nC = dC * (sC / 255); ( seperate for each channel RGB. no effect on alpha )
// where nC is the new colour, dC is destination colour and, sC is source colour;
// White pixels make no change, all others reduce the the colour
ctx.globalCompositeOperation = "multiply";
// draw the image over the top.
ctx.drawImage(img, 0, 0, canvas.width, cvtx.canvas.height);
ctx.globalCompositeOperation = "source-over"; // reset to default
最佳质量
有一种更好的方法,但它涉及获取像素数据并在乘法之前对每个像素进行光子计数。这将更接近原始蒙版。公式为nC =Math.sqrt(dC * dC * (sC * sC / (255*255)));,其中nC 是新颜色,dC 是目标颜色,sC 是源颜色。应用到每个通道 RGB 忽略 alpha。
// r,g,b are the background colour
// img is a loaded image to convert
var r = ?, g = ?, b = ?;
// create canvas and context for image
var c = document.createElement("canvas");
c.width = img.width;
c.height = img.height;
var ctx = c.getContext("2d");
// draw image onto the canvas
ctx.drawImage(img, 0,0);
// get the pixel data
var data = ctx.getImageData(0,0,c.width,c.height);
var d = data.data;
// Convert background colour to photon count and normalise
r = r * r / (255 * 255);
g = g * g / (255 * 255);
b = b * b / (255 * 255);
var i = 0, len = d.length;;
// for each pixel do the multiply using photon counts
while(i < len){
d[i] = Math.sqrt(d[i] * d[i++] * r);
d[i] = Math.sqrt(d[i] * d[i++] * g);
d[i] = Math.sqrt(d[i] * d[i++] * b);
i ++;
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);
通过 alpha 复合。
在您还可以使用 alpha 合成之后,乘法可能不是您想要的效果。有两种方法,简单和光子计数。请注意,这两种方法都假定背景颜色的 alpha 值为 255,并且仅适用于该值。
获取像素的 alpha 的简单方法。如果您设置了 alpha 通道并且只使用了 canvas source-over(默认)混合功能,它是如何完成的。
// assume you have got the pixel data etc.. see above snipets
var amount = ?; // the mixing amount
var i = 0, len = d.length;;
// for each pixel do the alpha blend
while(i < len){
var rr = d[i]; // get source channels
var gg = d[i + 1];
var bb = d[i + 2];
var alpha = (rr + gg + bb) / ( 3 * 255); // calculate alpha by finding the mean
// alpha is inverted but also need to clamp alpha as floating point may give a value too high so clamp and invert
alpha = Math.min(1, 1 - alpha);
alpha *= amount; // set the mix amount
var aInv = 1 - alpha; // invert again
// each channel is the sum of background and image colour time their respective mix amounts
d[i++] = aInv * r + alpha * rr;
d[i++] = aInv * g + alpha * gg;
d[i++] = aInv * b + alpha * bb;
d[i++] = 255; // in this case alpha is always 255
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);
以及正确的方法(高品质专业混音的做法)
// assume you have got the pixel data etc.. see above snipets
var amount = ?; // the mixing amount
// Convert background colour to photon count and normalise
r = r * r / (255 * 255);
g = g * g / (255 * 255);
b = b * b / (255 * 255);
var i = 0, len = d.length;;
// for each pixel do the alpha blend using photon counts
while(i < len){
var rr = d[i]; // get source channels
var gg = d[i + 1];
var bb = d[i + 2];
rr *= rr;
gg *= gg;
bb *= bb;
var alpha = (rr * + gg + bb) / ( 3 * 255 * 255); // calculate alpha by finding the mean in photon space
// alpha is inverted but also need to clamp alpha as floating point may give a value too high so clamp and invert
alpha = Math.min(1, 1 - alpha);
alpha *= amount; // set the mix amount
var aInv = 1 - alpha; // invert again
// each channel is the sum of background and image colour time their respective mix amounts
d[i++] = Math.sqrt(aInv * r + alpha * rr);
d[i++] = Math.sqrt(aInv * g + alpha * gg);
d[i++] = Math.sqrt(aInv * b + alpha * bb);
d[i++] = 255; // in this case alpha is always 255
}
// put the image data back onto the canvas.
ctx.putImageData(data,0,0);
为什么要使用光子计数颜色模型。
我使用的颜色模型基于设备显示屏发射的光子数。每个颜色通道都有一个从 0 到 255 的值,但这些值与显示器的实际输出不匹配,也不代表相机(输入设备)捕获的光子数。它们是光子通量的平方根。如果您通过简单的线性平均值混合颜色并且不考虑这一点,则生成的颜色将比应有的颜色更暗(这在图像具有高色调对比度时尤其明显)并且对比度曲线将变宽。当您直接操作像素以获得最佳结果时,始终将 r、g、b 值平方,进行混合、混合等。准备好后,通过计算结果的平方根将值转换回对数表示。
本视频将详细讲解Computer Color is Broken