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.width
和canvas.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 的一半。
正确的亮度
不正确
- 它假定背景色为白色
- 亮度计算错误
未知背景颜色
很难匹配每个像素的 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 具有相同的计算亮度
我正在制作一个 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.width
和canvas.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 的一半。
正确的亮度
- 它假定背景色为白色
- 亮度计算错误
未知背景颜色
很难匹配每个像素的 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 具有相同的计算亮度