字符作畫,我用字符畫個冰墩墩
哈嘍,大家好啊,我是阿朗。
已經 2022 年了,最近北京冬奧會的吉祥物冰墩墩很火,據(jù)說一墩難求,各種視頻新聞應接不暇。程序員要有程序員的方式,今天我來用 Java 畫一個由字符組成的冰墩墩送給大家,這篇文章記錄字符圖案的生成思路以及過程。
下面是一個由字符W@#&8*0. 等字符組成的冰墩墩圖案。

1. 字符圖案思路
我們都知道數(shù)字圖片是一個二維圖像,它使用一個有限的二維數(shù)組保存每個像素點顏色信息,這些像素點的顏色信息通常使用 RGB 模式進行記錄。所以從本質上看,我們常見的圖片就是一個保存了像素信息的二維數(shù)組。

基于以上的圖片原理,我們可以發(fā)現(xiàn),如果想要把一個圖片轉換成字符圖案,只需要把每個像素點的顏色信息轉換成某個字符就可以了,所以可以理出把圖片轉換成字符圖案的步驟如下。
縮放圖片到指定大小,為了保證輸出的字符數(shù)量不會太多。 遍歷圖片的像素點,獲取每個像素點的顏色信息。 根據(jù)像素點的顏色信息,轉換成灰度(亮度)信息。 把亮度信息轉換成相應的字符。 輸出字符圖案,也就是打印二維字符數(shù)組。
2. 圖片的縮放
如上所述,我們既然想要把每個像素點的顏色信息轉換成某個字符,如果像素點過多的話,雖然會增加字符圖片的還原度,但是看起來會非常麻煩,因為那么多字符你的屏幕可能顯示不完。
因此,我們要先對圖片進行縮放,縮放到一定大小后再進行字符化。這里為了方便,直接使用 Java 自帶的圖片處理方式進行圖片縮放,下面的代碼示例都是指定寬度進行縮放,高度等比例計算后進行縮放。
Java 中調整圖片大小主要有兩種方式:
使用 java.awt.Graphics2D調整圖片大小。使用 Image.getScaledInstance調整圖片大小。
2.1. java.awt.Graphics2D
Graphics2D 是 Java 平臺提供的可以渲染二維形狀、文本、圖像的基礎類,下面是使用 Graphics2D 進行圖片大小調整的簡單示例。
/**
?*?圖片縮放
?*
?*?@param?srcImagePath??圖片路徑
?*?@param?targetWidth???目標寬度
?*?@return
?*?@throws?IOException
?*/
public?static?BufferedImage?resizeImage(String?srcImagePath,?int?targetWidth)?throws?IOException?{
????Image?srcImage?=?ImageIO.read(new?File(srcImagePath));
????int?targetHeight?=?getTargetHeight(targetWidth,?srcImage);
????BufferedImage?resizedImage?=?new?BufferedImage(targetWidth,?targetHeight,?BufferedImage.TYPE_INT_RGB);
????Graphics2D?graphics2D?=?resizedImage.createGraphics();
????graphics2D.drawImage(srcImage,?0,?0,?targetWidth,?targetHeight,?null);
????graphics2D.dispose();
????return?resizedImage;
}
/**
?*?根據(jù)指定寬度,計算等比例高度
?*
?*?@param?targetWidth???目標寬度
?*?@param?srcImage??????圖片信息
?*?@return
?*/
private?static?int?getTargetHeight(int?targetWidth,?Image?srcImage)?{
????int?targetHeight?=?srcImage.getHeight(null);
????if?(targetWidth?null))?{
????????targetHeight?=?Math.round((float)targetHeight?/?((float)srcImage.getWidth(null)?/?(float)targetWidth));
????}
????return?targetHeight;
}
代碼中的 BufferedImage.TYPE_INT_RGB 表示所使用的顏色模型,所有的顏色模型可以在 Java doc - Image 文檔中看到。
調整大小后的圖片可以通過以下方式保存。
BufferedImage?image?=?resizeImage("/Users/darcy/Downloads/bingdundun.jpeg",?200);
File?file?=?new?File("/Users/darcy/Downloads/bingdundun_resize.jpg");
ImageIO.write(image,?"jpg",?file);
下面把原圖為 416 x 500 的冰墩墩圖片縮放到 200 x 240 的效果。

2.2. Image.getScaledInstance
這是 Java 原生功能調整圖片大小的另一種方式,使用這種方式調整圖片大小簡單方便,生成的圖片質量也不錯,代碼比較簡潔,但是這種方式的效率并不高。
/**
?*?圖片縮放
?*
?*?@param?srcImagePath??圖片路徑
?*?@param?targetWidth???目標寬度
?*?@return
?*?@throws?IOException
?*/
public?static?BufferedImage?resizeImage2(String?srcImagePath,?int?targetWidth)?throws?IOException?{
????Image?srcImage?=?ImageIO.read(new?File(srcImagePath));
????int?targetHeight?=?getTargetHeight(targetWidth,?srcImage);
????Image?image?=?srcImage.getScaledInstance(targetWidth,?targetHeight,?Image.SCALE_DEFAULT);
????BufferedImage?bufferedImage?=?new?BufferedImage(targetWidth,?targetHeight,?BufferedImage.TYPE_INT_RGB);
????bufferedImage.getGraphics().drawImage(image,?0,?0,?null);
????return?bufferedImage;
}
//?getTargetHeight?同?java.awt.Graphics2D?中示例代碼
代碼中的 Image.SCALE_DEFAULT 表示圖片縮放使用的算法,在Java doc - Image 文檔中可以查看所有可以使用的算法。
3. RGB 灰度計算
我們知道圖片是由像素點組成的,每個像素點存儲了顏色信息,通常是 RGB 信息,所以我們想要把每個像素點轉換成字符,也就是把像素點中的 RGB 信息的灰度表達出來,不同的灰度給出不同的字符進行表示。
比如我們把灰度分為 10 個等級,每個等級從高到低選擇一個字符進行標識。
'W',?'@',?'#',?'8',?'&',?'*',?'o',?':',?'.',?'?'
那么如何進行灰度計算呢?目前常見的計算方法有平均值法、加權均值法、伽馬校正法等。這里直接使用與伽馬校正線性相似的數(shù)學公式進行計算,這也是 MATLAB、 Pillow和 OpenCV 使用的方法。

4. 輸出字符圖片
前期準備已經完成了,我們已經把圖片進行了縮放,同時也知道了如何把圖片中的每個像素點上的 RGB 信息轉換成灰度值,那么我們只需要遍歷縮放后的圖片的 RGB 信息,進行灰度轉換,然后選擇對應的字符進行打印即可。
public?static?void?main(String[]?args)?throws?Exception?{
????BufferedImage?image?=?resizeImage("/Users/darcy/Downloads/bingdundun.jpeg",?150);
????printImage(image);
}
/**
?*?字符圖片打印
?*
?*?@param?image
?*?@throws?IOException
?*/
public?static?void?printImage(BufferedImage?image)?throws?IOException?{
????final?char[]?PIXEL_CHAR_ARRAY?=?{'W',?'@',?'#',?'8',?'&',?'*',?'o',?':',?'.',?'?'};
????int?width?=?image.getWidth();
????int?height?=?image.getHeight();
????for?(int?i?=?0;?i?????????for?(int?j?=?0;?j?????????????int?rgb?=?image.getRGB(j,?i);
????????????Color?color?=?new?Color(rgb);
????????????int?red?=?color.getRed();
????????????int?green?=?color.getGreen();
????????????int?blue?=?color.getBlue();
????????????//?一個用于計算RGB像素點灰度的公式
????????????Double?grayscale?=?0.2126?*?red?+?0.7152?*?green?+?0.0722?*?blue;
????????????double?index?=?grayscale?/?(Math.ceil(255?/?PIXEL_CHAR_ARRAY.length)?+?0.5);
????????????System.out.print(PIXEL_CHAR_ARRAY[(int)(Math.floor(index))]);
????????}
????????System.out.println();
????}
}
//?resizeImage?同第二部分代碼
這里我選擇一張冰墩墩的圖片,可以看到輸出后的效果。

5. 其他字符圖片
下面是一些其他圖片轉字符圖的效果展示。
2022 年,虎虎生威字符畫。

進擊的巨人人物 - 三笠字符畫。

一如既往,文章中的代碼存放在:github.com/niumoo/lab-notes
參考
[1] https://www.kdnuggets.com/2019/12/convert-rgb-image-grayscale.html
[2] https://en.wikipedia.org/wiki/Grayscale
Hello world : )?這篇文章就到這里了,我是阿朗,點贊和在看,動力無限,求關注。
點個在看,加油充電~??
