字元作畫,我用字元畫個冰墩墩

程式猿阿朗發表於2022-02-14
文章持續更新,可以關注公眾號程式猿阿朗或訪問未讀程式碼部落格
本文 Github.com/niumoo/JavaNotes 已經收錄,歡迎Star。

哈嘍,大家好啊,我是阿朗。

已經 2022 年了,最近北京冬奧會的吉祥物冰墩墩很火,據說一墩難求,各種視訊新聞應接不暇。程式設計師要有程式設計師的方式,今天我來用 Java 畫一個由字元組成的冰墩墩送給大家,這篇文章記錄字元圖案的生成思路以及過程。

下面是一個由字元W@#&8*0. 等字元組成的冰墩墩圖案。

字元冰墩墩

1. 字元圖案思路

我們都知道數字圖片是一個二維影像,它使用一個有限的二維陣列儲存每個畫素點顏色資訊,這些畫素點的顏色資訊通常使用 RGB 模式進行記錄。所以從本質上看,我們常見的圖片就是一個儲存了畫素資訊的二維陣列。

畫素圖片 - 來自維基百科

基於以上的圖片原理,我們可以發現,如果想要把一個圖片轉換成字元圖案,只需要把每個畫素點的顏色資訊轉換成某個字元就可以了,所以可以理出把圖片轉換成字元圖案的步驟如下。

  1. 縮放圖片到指定大小,為了保證輸出的字元數量不會太多。
  2. 遍歷圖片的畫素點,獲取每個畫素點的顏色資訊。
  3. 根據畫素點的顏色資訊,轉換成灰度(亮度)資訊。
  4. 把亮度資訊轉換成相應的字元。
  5. 輸出字元圖案,也就是列印二維字元陣列。

2. 圖片的縮放

如上所述,我們既然想要把每個畫素點的顏色資訊轉換成某個字元,如果畫素點過多的話,雖然會增加字元圖片的還原度,但是看起來會非常麻煩,因為那麼多字元你的螢幕可能顯示不完。

因此,我們要先對圖片進行縮放,縮放到一定大小後再進行字元化。這裡為了方便,直接使用 Java 自帶的圖片處理方式進行圖片縮放,下面的程式碼示例都是指定寬度進行縮放,高度等比例計算後進行縮放。

Java 中調整圖片大小主要有兩種方式:

  1. 使用 java.awt.Graphics2D 調整圖片大小。
  2. 使用 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;
}

/**
 * 根據指定寬度,計算等比例高度
 *
 * @param targetWidth   目標寬度
 * @param srcImage      圖片資訊
 * @return
 */
private static int getTargetHeight(int targetWidth, Image srcImage) {
    int targetHeight = srcImage.getHeight(null);
    if (targetWidth < srcImage.getWidth(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 的效果。

Java 圖片縮放

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', ':', '.', ' '

那麼如何進行灰度計算呢?目前常見的計算方法有平均值法、加權均值法、伽馬校正法等。這裡直接使用與伽馬校正線性相似的數學公式進行計算,這也是 MATLABPillowOpenCV 使用的方法。

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 < height; i++) {
        for (int j = 0; j < width; 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

參考

https://www.kdnuggets.com/201...

https://en.wikipedia.org/wiki...

訂閱

可以微信搜一搜程式猿阿朗或訪問未讀程式碼部落格閱讀。
本文 Github.com/niumoo/JavaNotes 已經收錄,歡迎Star。

相關文章