说在前面
在终端控制台打印字符图案我们见多了,那你有没有想过在控制台打印彩色图片呢?今天我们一起来看看怎么在终端控制台打印彩色图片。
效果展示
代码实现
实现思路
先将图片转为颜色点阵,再以彩色点阵的形式在终端中打印出来。
颜色转换
function hexToAnsi(hex) {
// 十六进制转 RGB
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// 转换为 ANSI 256色
return `\x1b[48;2;${r};${g};${b}m`;
}
将十六进制颜色值转换为 ANSI 转义序列,以便在终端中实现彩色输出。它首先从十六进制字符串中提取红(r)、绿(g)、蓝(b)分量,然后构建 ANSI 转义序列,该序列可以在支持 ANSI 颜色代码的终端中设置背景颜色。
图片转为颜色矩阵
调整图片大小
先对图片进行尺寸调整,将高度和宽度分别限制在 50 以内,同时保持图片的原始宽高比,这样更好在终端控制台呈现。
//将图片大小框高调整到不大于50,保存原有框高比
let originalInfo = await sharp(imagePath).metadata();
if (originalInfo.height > 50) {
const newHeight = 50;
const newWidth = Math.floor(
(originalInfo.width / originalInfo.height) * newHeight
);
await sharp(imagePath)
.resize(newWidth, newHeight)
.toFile("temp/output.jpg");
imagePath = "temp/output.jpg";
}
//将图片大小框宽调整到不大于50,保存原有框宽比
originalInfo = await sharp(imagePath).metadata();
if (originalInfo.width > 50) {
const newWidth = 50;
const newHeight = Math.floor(
(originalInfo.height / originalInfo.width) * newWidth
);
await sharp(imagePath).resize(newWidth, newHeight).toFile("temp/tmp.jpg");
imagePath = "temp/tmp.jpg";
}
读取图片数据
根据通道数(通常为 3 表示 RGB 或 4 表示 RGBA)遍历每个像素点,提取红、绿、蓝分量,并将其转换为十六进制颜色值,最终构建一个二维数组(颜色矩阵)来表示图片的颜色点阵。
const { data, info } = await sharp(imagePath)
.raw()
.toBuffer({ resolveWithObject: true });
const matrix = [];
const channels = info.channels; // 通道数(RGB=3, RGBA=4)
for (let y = 0; y < info.height; y++) {
const row = [];
for (let x = 0; x < info.width; x++) {
const idx = (y * info.width + x) * channels;
// 提取RGB(A)值
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
// 转换为十六进制(忽略透明度)
const hex = `#${[r, g, b]
.map((v) => v.toString(16).padStart(2, "0"))
.join("")}`;
row.push(hex);
}
matrix.push(row);
}
在终端绘制彩色点阵
遍历前面获取到的颜色矩阵,通过 hexToAnsi 函数将十六进制颜色转换为 ANSI 背景色,将其打印到终端控制台上。
function drawInConsole(matrix) {
const scaledWidth = matrix[0].length;
const scaledHeight = matrix.length;
let output = "";
for (let y = 0; y < scaledHeight; y++) {
for (let x = 0; x < scaledWidth; x++) {
const hex = matrix[y][x];
// 转换为ANSI背景色
const bgColor = hexToAnsi(hex) + " ·\x1b[0m";
output += bgColor;
}
output += "\n";
}
// 清屏并输出
stdout.write("\x1Bc"); // 清空控制台
stdout.write(output);
}
完整代码
const sharp = require("sharp");
const fs = require("fs");
const { stdout } = require("process");
//创建临时文件夹
function createTempFolder() {
if (!fs.existsSync("temp")) {
fs.mkdirSync("temp");
}
}
function hexToAnsi(hex) {
// 十六进制转 RGB
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// 转换为 ANSI 256色
return `\x1b[48;2;${r};${g};${b}m`;
}
/**
* 将图片转换为颜色点阵
* @param {string} imagePath 图片路径
* @returns {Promise<Array<Array<string>>>} 二维数组点阵(十六进制颜色值)
*/
async function imageToColorMatrix(imagePath) {
await createTempFolder();
//将图片大小框高调整到不大于50,保存原有框高比
let originalInfo = await sharp(imagePath).metadata();
if (originalInfo.height > 50) {
const newHeight = 50;
const newWidth = Math.floor(
(originalInfo.width / originalInfo.height) * newHeight
);
await sharp(imagePath)
.resize(newWidth, newHeight)
.toFile("temp/output.jpg");
imagePath = "temp/output.jpg";
}
//将图片大小框宽调整到不大于50,保存原有框宽比
originalInfo = await sharp(imagePath).metadata();
if (originalInfo.width > 50) {
const newWidth = 50;
const newHeight = Math.floor(
(originalInfo.height / originalInfo.width) * newWidth
);
await sharp(imagePath).resize(newWidth, newHeight).toFile("temp/tmp.jpg");
imagePath = "temp/tmp.jpg";
}
// 读取图片原始数据
const { data, info } = await sharp(imagePath)
.raw()
.toBuffer({ resolveWithObject: true });
// 创建二维数组
const matrix = [];
const channels = info.channels; // 通道数(RGB=3, RGBA=4)
for (let y = 0; y < info.height; y++) {
const row = [];
for (let x = 0; x < info.width; x++) {
const idx = (y * info.width + x) * channels;
// 提取RGB(A)值
const r = data[idx];
const g = data[idx + 1];
const b = data[idx + 2];
// 转换为十六进制(忽略透明度)
const hex = `#${[r, g, b]
.map((v) => v.toString(16).padStart(2, "0"))
.join("")}`;
row.push(hex);
}
matrix.push(row);
}
return matrix;
}
/**
* 在终端绘制彩色点阵
* @param {Array<Array<string>>} matrix 颜色矩阵
*/
function drawInConsole(matrix) {
const scaledWidth = matrix[0].length;
const scaledHeight = matrix.length;
let output = "";
for (let y = 0; y < scaledHeight; y++) {
for (let x = 0; x < scaledWidth; x++) {
const hex = matrix[y][x];
const bgColor = hexToAnsi(hex) + " ·\x1b[0m";
output += bgColor;
}
output += "\n";
}
stdout.write("\x1Bc");
stdout.write(output);
}
imageToColorMatrix("images/哪吒.jpg")
.then((matrix) => {
fs.writeFileSync("output.txt", JSON.stringify(matrix, null, 2));
drawInConsole(matrix);
})
.catch(console.error);
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容。
发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『
前端也能这么有趣
』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。