HTML画布:平滑地着色黑白图像

时间:2023-02-08 21:17:49

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.

向下滚动以查看清晰的彩色版本。