I am trying to color the black pixels of a black-and-white image on a canvas.
我试图在画布上为黑白图像的黑色像素着色。
The naive code I'm using is:
我正在使用的天真代码是:
function color_text(canvas, r, g, b, w, h) {
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imageData.data;
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
var redIndex = ((y - 1) * (canvas.width * 4)) + ((x - 1) * 4);
var greenIndex = redIndex + 1;
var blueIndex = redIndex + 2;
var alphaIndex = redIndex + 3;
if ((pixels[redIndex] < 240) && (pixels[greenIndex] < 240) && (pixels[blueIndex] < 240)) {
pixels[redIndex] = r;
pixels[greenIndex] = g;
pixels[blueIndex] = b;
}
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
I use < 240 as a detector for non-white pixels instead of exactly 255 because these are scanned in pieces of hand-drawn calligraphy, so a fudge factor is needed. Applying this algorithm to an image that is scaled down by canvas's native drawImage() produces results that look as good as the black-and-white image.
我使用<240作为非白色像素的检测器而不是255,因为这些是用手绘书法扫描的,因此需要一个软糖因子。将此算法应用于由画布的原生drawImage()缩小的图像,可生成与黑白图像一样好的结果。
However, canvas's native drawImage() leaves much to be desired, so instead, I scaled down the image with a slightly-modified version of the code provided in this answer. The black and white image produced by this code is beautiful, much better than canvas's native method. However, when I color the image with the above function, it looks awful again.
但是,canvas的原生drawImage()还有很多不足之处,所以我用这个答案中提供的稍微修改过的代码缩小了图像。这段代码生成的黑白图像非常漂亮,比canvas的原生方法要好得多。但是,当我使用上述功能为图像着色时,它看起来很糟糕。
A complete jsfiddle is here: http://jsfiddle.net/q9sd9w1k/
一个完整的jsfiddle在这里:http://jsfiddle.net/q9sd9w1k/
Any ideas on how I can color the high-quality version effectively?
关于如何有效地为高质量版本着色的任何想法?
Thanks.
谢谢。
2 个解决方案
#1
2
You should use HSL color space for coloring images. This will allow you to handle edge cases, literally, such as this where also anti-aliased pixels get colored correctly based on luminance value.
您应该使用HSL颜色空间来着色图像。这将允许您处理边缘情况,从字面上看,例如,抗锯齿像素也会根据亮度值正确着色。
The principle steps needed are:
所需的主要步骤是:
- Create a gray-scale version of the image
- 创建图像的灰度版本
- Decide which color you want to use (in HSL this will be a degree [0, 360] - you can convert the color you want to use from RGB to HSL as well).
- 确定要使用哪种颜色(在HSL中这将是[0,360]度 - 您可以将要使用的颜色从RGB转换为HSL)。
- Update a second buffer with the RGB converted from HSL using Hue, same saturation and the gray-scale value from the first buffer as lightness.
- 使用Hue更新第二个缓冲区,其中使用从HSL转换的RGB,相同的饱和度和第一个缓冲区中的灰度值作为亮度。
Example code with everything you need to do these steps - adopt as needed:
示例代码,包含执行这些步骤所需的一切 - 根据需要采用:
Convert to gray-scale:
转换为灰度:
var lumas = new Float32Array(width * height),
idata = ctx.getImageData(0, 0, width, height),
data = idata.data,
len = data.length,
i = 0,
cnt = 0;
for(; i < len; i += 4)
lumas[cnt++] = (data[i] * 0.2126 +
data[i+1] * 0.7152 +
data[i+2] * 0.0722) / 255; //normalized value
You will need a hsl2rgb function:
你需要一个hsl2rgb函数:
function hsl2rgb(h, s, l) {
var r, g, b, q, p;
h /= 360;
if (s === 0) {
r = g = b = l;
}
else {
function hue2rgb(p, q, t) {
t %= 1;
if (t < 0.1666667) return p + (q - p) * t * 6;
if (t < 0.5) return q;
if (t < 0.6666667) return p + (q - p) * (0.6666667 - t) * 6;
return p;
}
q = l < 0.5 ? l * (1 + s) : l + s - l * s;
p = 2 * l - q;
r = hue2rgb(p, q, h + 0.3333333);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 0.3333333);
}
return {
r: (r * 255 + 0.5) | 0,
g: (g * 255 + 0.5) | 0,
b: (b * 255 + 0.5) | 0
}
}
Then iterate over the luma buffer, pass in the value as l
, put the resulting rgb component with alpha set to 255 into a buffer for the canvas:
然后遍历亮度缓冲区,将值传递为l,将生成的alpha设置为255的rgb组件放入画布的缓冲区中:
var idata = ctx.createImageData(0, 0, width, height),
buffer = idata.data,
len = buffer.length,
hue = 90
sat = 0.5,
i = 0,
cnt = 0;
for(; i < len; i += 4) {
var color = hsl2rgb(h, s, lumas[cnt++]); // HSL to RGB
buffer[i ] = color.r;
buffer[i+1] = color.g;
buffer[i+2] = color.b;
buffer[i+3] = 255;
}
ctx.putImageData(idata, 0, 0);
#2
0
John-Paul Gignac offers the following answer, which works beautifully:
John-Paul Gignac提供以下答案,效果很好:
For each pixel, do the following:
对于每个像素,请执行以下操作:
c1_r = f1_r + (b1_r-f1_r)*c0_r/255
c1_g = f1_g + (b1_g-f1_g)*c0_g/255
c1_b = f1_b + (b1_b-f1_b)*c0_b/255
Where c1_r is the new red value of the pixel, c0_r is its old value, b1_r is the new background color's red value, and f1_r is the new foreground's red value. The assumption here is that the original background and foreground are white and black, respectively.
其中c1_r是像素的新红色值,c0_r是其旧值,b1_r是新背景颜色的红色值,f1_r是新前景的红色值。这里的假设是原始背景和前景分别是白色和黑色。
For comparison with the original, the new colouring function is:
为了与原始比较,新的着色功能是:
function color_text_gignac(canvas, r, g, b, w, h) {
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imageData.data;
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
var redIndex = ((y - 1) * (canvas.width * 4)) + ((x - 1) * 4);
var greenIndex = redIndex + 1;
var blueIndex = redIndex + 2;
var alphaIndex = redIndex + 3;
pixels[redIndex] = r + (255 - r) * pixels[redIndex] / 255;
pixels[greenIndex] = g + (255 - g) * pixels[greenIndex] / 255;
pixels[blueIndex] = b + (255 - b) * pixels[blueIndex] / 255;
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
Here's a complete working example: http://jsfiddle.net/moq9pd7h/2/
这是一个完整的工作示例:http://jsfiddle.net/moq9pd7h/2/
Scroll down to see the clear, coloured version.
向下滚动以查看清晰的彩色版本。
#1
2
You should use HSL color space for coloring images. This will allow you to handle edge cases, literally, such as this where also anti-aliased pixels get colored correctly based on luminance value.
您应该使用HSL颜色空间来着色图像。这将允许您处理边缘情况,从字面上看,例如,抗锯齿像素也会根据亮度值正确着色。
The principle steps needed are:
所需的主要步骤是:
- Create a gray-scale version of the image
- 创建图像的灰度版本
- Decide which color you want to use (in HSL this will be a degree [0, 360] - you can convert the color you want to use from RGB to HSL as well).
- 确定要使用哪种颜色(在HSL中这将是[0,360]度 - 您可以将要使用的颜色从RGB转换为HSL)。
- Update a second buffer with the RGB converted from HSL using Hue, same saturation and the gray-scale value from the first buffer as lightness.
- 使用Hue更新第二个缓冲区,其中使用从HSL转换的RGB,相同的饱和度和第一个缓冲区中的灰度值作为亮度。
Example code with everything you need to do these steps - adopt as needed:
示例代码,包含执行这些步骤所需的一切 - 根据需要采用:
Convert to gray-scale:
转换为灰度:
var lumas = new Float32Array(width * height),
idata = ctx.getImageData(0, 0, width, height),
data = idata.data,
len = data.length,
i = 0,
cnt = 0;
for(; i < len; i += 4)
lumas[cnt++] = (data[i] * 0.2126 +
data[i+1] * 0.7152 +
data[i+2] * 0.0722) / 255; //normalized value
You will need a hsl2rgb function:
你需要一个hsl2rgb函数:
function hsl2rgb(h, s, l) {
var r, g, b, q, p;
h /= 360;
if (s === 0) {
r = g = b = l;
}
else {
function hue2rgb(p, q, t) {
t %= 1;
if (t < 0.1666667) return p + (q - p) * t * 6;
if (t < 0.5) return q;
if (t < 0.6666667) return p + (q - p) * (0.6666667 - t) * 6;
return p;
}
q = l < 0.5 ? l * (1 + s) : l + s - l * s;
p = 2 * l - q;
r = hue2rgb(p, q, h + 0.3333333);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 0.3333333);
}
return {
r: (r * 255 + 0.5) | 0,
g: (g * 255 + 0.5) | 0,
b: (b * 255 + 0.5) | 0
}
}
Then iterate over the luma buffer, pass in the value as l
, put the resulting rgb component with alpha set to 255 into a buffer for the canvas:
然后遍历亮度缓冲区,将值传递为l,将生成的alpha设置为255的rgb组件放入画布的缓冲区中:
var idata = ctx.createImageData(0, 0, width, height),
buffer = idata.data,
len = buffer.length,
hue = 90
sat = 0.5,
i = 0,
cnt = 0;
for(; i < len; i += 4) {
var color = hsl2rgb(h, s, lumas[cnt++]); // HSL to RGB
buffer[i ] = color.r;
buffer[i+1] = color.g;
buffer[i+2] = color.b;
buffer[i+3] = 255;
}
ctx.putImageData(idata, 0, 0);
#2
0
John-Paul Gignac offers the following answer, which works beautifully:
John-Paul Gignac提供以下答案,效果很好:
For each pixel, do the following:
对于每个像素,请执行以下操作:
c1_r = f1_r + (b1_r-f1_r)*c0_r/255
c1_g = f1_g + (b1_g-f1_g)*c0_g/255
c1_b = f1_b + (b1_b-f1_b)*c0_b/255
Where c1_r is the new red value of the pixel, c0_r is its old value, b1_r is the new background color's red value, and f1_r is the new foreground's red value. The assumption here is that the original background and foreground are white and black, respectively.
其中c1_r是像素的新红色值,c0_r是其旧值,b1_r是新背景颜色的红色值,f1_r是新前景的红色值。这里的假设是原始背景和前景分别是白色和黑色。
For comparison with the original, the new colouring function is:
为了与原始比较,新的着色功能是:
function color_text_gignac(canvas, r, g, b, w, h) {
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imageData.data;
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
var redIndex = ((y - 1) * (canvas.width * 4)) + ((x - 1) * 4);
var greenIndex = redIndex + 1;
var blueIndex = redIndex + 2;
var alphaIndex = redIndex + 3;
pixels[redIndex] = r + (255 - r) * pixels[redIndex] / 255;
pixels[greenIndex] = g + (255 - g) * pixels[greenIndex] / 255;
pixels[blueIndex] = b + (255 - b) * pixels[blueIndex] / 255;
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
}
Here's a complete working example: http://jsfiddle.net/moq9pd7h/2/
这是一个完整的工作示例:http://jsfiddle.net/moq9pd7h/2/
Scroll down to see the clear, coloured version.
向下滚动以查看清晰的彩色版本。