在 lobste 看到一个帖子,人们分享了一个全手写的 blog。
博主用一个电子墨水屏的设备手写了文章,然后导出为 png,有趣的是添加超链接的方式。作者提到他不是很满意 png 的体积,有人建议他参考这篇文章优化图片体积,文章的代码都在这个 repo。
文章提到一个 least significant bits 的概念,一个颜色由 rgb 三色组成,每个颜色都是一个 0-255 的数字,其对应的二进制数字中,最后一个数字就是 LSB。文章提到为了拿到手写扫描图片的背景色的主要颜色值,作者把这些颜色的 rgb 通道的最后两个 bit 统一修改为 0。
修改一个颜色某个通道的 LSB,人眼几乎察觉不出来。把最后两个 bit 都修改为零的方法如下:
const decimal = 128;
const _decimal = decimal >> 2;
const __decimal = _decimal << 2;
下面的代码可以对比修改最后 2 个 bit 后图片颜色的变化,肉眼几乎看不出来:
const { createCanvas, loadImage, createImageData } = require("canvas");
const fs = require("fs");
const sizeOf = require("image-size");
const path = "./image.png";
const size = sizeOf(path);
const canvas = createCanvas(size.width, size.height);
const ctx = canvas.getContext("2d");
loadImage(path).then((image) => {
ctx.drawImage(
image,
0,
0,
size.width,
size.height,
0,
0,
size.width,
size.height
);
const imageData = ctx.getImageData(0, 0, size.width, size.height);
for (let i = 0; i < imageData.data.length; i++) {
const decimal = imageData.data[i];
const _d = decimal >> 2;
const __d = _d << 2;
imageData.data[i] = __d;
}
const data = createImageData(imageData.data, size.width);
const canvas2 = createCanvas(size.width, size.height);
const ctx2 = canvas2.getContext("2d");
ctx2.putImageData(data, 0, 0);
const buffer = canvas2.toBuffer();
fs.writeFileSync(`./out.${size.type}`, buffer, "binary");
});