5

字符作画,我用字符画个冰墩墩

 2 years ago
source link: https://segmentfault.com/a/1190000041398208
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

文章持续更新,可以关注公众号程序猿阿朗或访问未读代码博客
本文 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。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK