JavaScript - 获取单个字符的亮度

JavaScript - Get brightness of single character

我正在制作一个 image/video 到 ASCII 的转换器。为此,我需要获得我将使用的每个角色的平均黑暗度。我修改了 this 问题的答案,该问题获取图像的平均亮度。但是一直说亮度为0,我哪里做错了?

//this is the character I will get the darkness of
const char = "#";

const canvas = document.querySelector("canvas"),
  ctx = canvas.getContext("2d")

const charWidth = canvas.width,
  charHeight = canvas.height;

ctx.font = "30px Arial";

//for centering text
ctx.textAlign = "center";
ctx.textBaseline = "middle";

//draws text to canvas
ctx.fillText(char, charWidth / 2, charHeight / 2);

let colorSum = 0;

const imageData = ctx.getImageData(0, 0, charWidth, charHeight);
const data = imageData.data;
let r, g, b, avg;

//loops through image data
for (let x = 0, len = data.length; x < len; x += 4) {
  //r, g, and b are always 0
  r = data[x];
  g = data[x + 1];
  b = data[x + 2];
  
  avg = Math.floor((r + g + b) / 3);
  colorSum += avg;
}

const brightness = Math.floor(colorSum / (charWidth * charHeight));
console.log(brightness);
canvas {
  width: 200px;
  height: 200px;
  outline: 1px solid #000000;
}
<canvas width="30" height="30"></canvas>

对于初学者,请不要在 canvas 节点上设置 CSS 属性——这会拉伸和扭曲图像。仅使用HTML元素属性canvas.widthcanvas.height获取和设置canvas.

的宽高

至于主要问题,默认情况下,canvas 上的所有 rgb 值均为 0(黑色)。如果您打印 data[x+3],您会看到 alpha 通道在 0-255 之间变化,但这从未被考虑在内。

根据您的喜好,简单地平均此通道中的 alpha,或在绘制文本之前用白色(非透明背景色)填充 canvas,然后使用 rgb 作为你在做。

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const char = "#";
ctx.font = "30px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(char, canvas.width / 2, canvas.height / 2);

const {data} = ctx.getImageData(
  0, 0, canvas.width, canvas.height
);
let colorSum = 0;

for (let i = 0; i < data.length; i += 4) {
  colorSum += data[i+3];
}

const brightness = Math.floor(
  255 - (colorSum / (canvas.width * canvas.height))
);
console.log(brightness);
canvas {
  outline: 1px solid #000000;
}
<canvas width="30" height="30"></canvas>

您可以尝试使用 ctx.fillRect(0, 0, canvas.width / 2, canvas.height); 而不是字符,您将得到 127 作为平均值,这是预期的,因为您已经用黑色填充了 canvas 的一半。

正确的亮度

不正确

  1. 它假定背景色为白色
  2. 亮度计算错误

未知背景颜色

很难匹配每个像素的 canvas 背景颜色,因此最好的解决方案是避免使用透明像素。这可以通过在添加文本之前用非透明背景颜色填充 canvas 来完成。

sRGB

计算亮度 (lum) 很复杂。

  • 首先将 sRGB 值转换为感知校正线性值。
  • 然后将所有像素的计算值相加。
  • 除以像素数得到平均线性值。
  • 将结果值转换回 sRGB

详情请见wiki page

示例

示例显示了一组字符的正确亮度计算。

const BG_COLOR = "white";
const CHAR_COLOR = "black";
const char = "#";
const width = 30;
const height = 32;

const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (el, ...sibs) => sibs.reduce((el, sib)=>(el.appendChild(sib), el), el);

const sC2Lin = (val) => {
    val /= 255;
    return val <= 0.04045 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
}
const RGB2Gama = (r, g, b) => {
    return sC2Lin(r) * 0.212655 +
           sC2Lin(g) * 0.715158 +
           sC2Lin(b) * 0.072187;
}
const brightToHex = b => {
    b = Math.round(b);
    const hex = b.toString(16).padStart(2,"0");
    return "#" + hex + hex + hex;
}

function charBrightness(ctx, char, color = CHAR_COLOR, bgColor = BG_COLOR) {
    ctx.font = "38px Arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, width, height);
    ctx.fillStyle = color;
    ctx.fillText(char, width / 2, height * (1.9 / 3));
    const d = ctx.getImageData(0, 0, width, height).data;
    
    var bSum = 0, i = 0;
    while (i < d.length) {
        bSum += RGB2Gama(d[i++], d[i++], d[i++]);
        i++;
    }
    const meanGam = bSum / (d.length / 4);
      return (meanGam <= 0.0031308 ? 
          meanGam * 12.92 : 
          1.055 * meanGam ** (1 / 2.4) - 0.055) * 255;
}
function testChar(char) {
    const can1 = tag("canvas", {width, height});
    const can2 = tag("canvas", {width, height});
    const ctx1 = can1.getContext("2d");
    const ctx2 = can2.getContext("2d");

    const bright = Math.round(charBrightness(ctx1, char));
    console.log("Brightness: '" + char + "' " + Math.round(bright));
    ctx2.fillStyle = brightToHex(bright);
    ctx2.fillRect(0, 0, width, height);
    append(document.body, can1, can2)
}
testChar("#")
testChar("$")
testChar("H")
testChar("J")
testChar("L")
testChar("I")
testChar("i")
testChar("-")
testChar(".")
canvas {
  border: 1px solid #000000;
  margin-right: 2px;
}

更多

还有一个问题很难解决。

一个字符的感知亮度不仅仅是像素的平均值,还有一个几何分量。我们的感知降低了暗线和暗区小亮区之间的亮线亮度

注意示例字符对 #、$ 和 L、J 具有相同的计算亮度