Bootstrap

基于Java的图片字符画(含动图)

一、介绍

        字符画是一种纯由字符组成,在文本编辑器中行列排开的二维字符矩阵中,整体展示出可识别的图案。特点是无色,画面的最小单元是字符而非像素。可由基本的文本编辑器模拟图像。

        字符画的展示:

        

                                         蜡笔小心表情转换为字符画后的效果

字符画
原图片

二、原理  

        图片文件的本质是像素矩阵,每个点储存的有该点的色彩信息。计算机在处理图片时将每个点解析为不同的颜色,并显示在屏幕上构成了视觉上的图片。

        单个像素点的颜色由三原色RGB三个变量决定。若RGB皆为0,即十六进制下的#000000,没有颜色填充的点,该点颜色为纯黑。RGB皆为255,即十六进制下的#FFFFFF,填充满三原色,混合为白色。

        灰度是表明像素点明暗程度的数值,也即黑白图像中点的颜色深度,范围在0到255,纯白为255 ,纯黑为0。灰度值指的是单个像素点的亮度。灰度值越大表示越亮。  

        字符画的实现是将源图片的RGB矩阵转换为灰度矩阵,再根据灰度的明暗程度选择不同密度的字符替换掉像素点,并打印该字符矩阵,即可得到字符画。          越亮的点,其占位字符密度越低,越暗的点,其占位字符密度高,即可在宏观程度上体现整幅图片的明暗变化。 

        像素点由明到暗,相对于的字符集由稀疏到密集,定义拥有该特性的字符集。

这里提供一种有效的字符集:

char[] ss = " `.^,:~\"<!ct+{i7?u30pw4A8DX%#HWM".toCharArray();

        该字符集从0下标开始,随着下标的递增,字符像素的密度也逐渐增大,模拟出了明暗的渐变效果,因此可作为灰度的映射。

三、实现

        Java中用于处理图片的类是Image类,Image是一个抽象类,其实现类BufferedImage可以将图片加载到内存当中,其所表示的图片在内存中拥有一个缓冲区,因此可以很方便的操作图片。并且该实现类也提供了绘图相关的api。

        本程序只需要获取图片的宽高属性,以及各像素点属性。

        我们需要将字符通过IO流输出到指定文本文件内,之后打开该文件即可查看。因此采用字符缓冲流进行文件操作。

 BufferedWriter bw = new BufferedWriter(new FileWriter(target));

        其中 target 为目标文本文件的路径,可填绝对路径,或相对路径(当前工作区开始)

        定义BufferedImage并加载指定图片。

BufferedImage bi = ImageIO.read(Files.newInputStream(Paths.get(ImageSource)));
int width = bi.getWidth();
int height = bi.getHeight();

        其中 ImageSource 为图片的文件路径。如:“D:\\picture\\1.jpg”。        加载完后调用BufferedImage对象的成员获取图片的宽高属性width height

对于单像素点的操作有

int pixel = bi.getRGB(i, j);
int[] rgb = new int[3];    //分别表示红绿蓝RGB。
rgb[0] = pixel >> 16 & 0xff;
rgb[1] = pixel >> 8 & 0xff;
rgb[2] = pixel & 0xff;

        调用对象的 getRGB(int x,int y) 方法可以得到一个像素点色值的十六进制数返回值。若要分离RGB3个变量,可以通过位运算的方式,每次取其中的两位。

        获取到一个像素点的RGB属性后,计算该点的灰度值。灰度值的计算一般有5种方法,如下:

1.浮点法:Gray=R*0.3+G*0.59+B*0.11

2.整数法:Gray=(R*30+G*59+B*11)/100

3.移位法:Gray =(R*77+G*151+B*28)>>8;

4.平均值法:Gray=(R+G+B)/3;

5.仅取绿色:Gray=G;

        上述5种方法均有一定程度上的误差,在本程序中为了追求运算速度,采用移位计算以加快处理。即:

        

 int Gray = (rgb[0] * 28 + rgb[1] * 151 + rgb[2] * 77) >> 8; //计算像素点的灰度
 int x = Gray * ss.length / 255 % ss.length;     //通过灰度的百分比 计算出相应密度的字符 在字符集中的位置。

     通过以上计算,即可得该像素点会被替换为字符 ss[x] ,将替换后的字符打印,即实现了画出一个像素的的功能。

便利图片的所有像素点,在矩阵中打印转换后的字符。

    for (int j = 0; j < height; j += 2) {
            for (int i = 0; i < width; i += 2) {
                int pixel = bi.getRGB(i, j);
                int[] rgb = new int[3];
                rgb[0] = pixel >> 16 & 0xff;
                rgb[1] = pixel >> 8 & 0xff;
                rgb[2] = pixel & 0xff;
                int Gray = (rgb[0] * 28 + rgb[1] * 151 + rgb[2] * 77) >> 8; //通过三原色值计算像素点的灰度
                int x = Gray * ss.length / 255 % ss.length;     //灰度值的百分比 计算出相应密度的字符表
                bw.write(ss[x]);    //输出该字符
                bw.write(ss[x]);
            }
            bw.newLine();
        }
        bw.flush();

        通过改变循环的控制变量的自增数值,可起到控制打印后文本文件长度也即“模拟像素”的大小的作用。

        由于字符集中的字符为半角字符,在显示时高度总是为宽度的二倍,因此每个像素打印两倍的字符即可平衡宽高比。

测试代码,成功输出文本文件,可正常显示字符画。

四、扩展

        由图片转换为字符画可联想到,是否可由动画转换为字符动画。

为了便于操作,这里采用的动画载体是gif文件。

        导入jar包或引入坐标:

        <dependency>
            <groupId>com.madgag</groupId>
            <artifactId>animated-gif-lib</artifactId>
            <version>1.4</version>
        </dependency>

在源代码中导入包, 定义 GifDecoder 对象,并加载指定的gif文件。

import com.madgag.gif.fmsware.GifDecoder;


    GifDecoder gd = new GifDecoder();
    gd.read(Files.newInputStream(new File(gifSource).toPath()));

       参数 gifSource 即gif资源文件的地址。 将之前描述的图片转字符画的步骤封装为方法 imageToChar 方便调用。

        通过GifDecoder对象的getFrameCount()方法获取帧数,getFrame(int x) 方法定位到第x帧图片。对每一帧图片进行转换处理。

for (int i = 0; i < gd.getFrameCount(); i++) {  //逐帧转换为字符集。
    BufferedImage frame = gd.getFrame(i);
    imageToChar(frame,"out.txt");    //参数提供缓冲图片对象,以及目标文本输出地址。
    Thread.sleep(50);
}

测试运行。

记事本打开的文本文件无法实时更新修改,查看运行效果建议使用NotePad++,或vscode。图片的大小建议在360p以内。若图片太大可通过修改像素坐标循环变量的自增值,来控制转换后的“像素”。

以下为动漫一拳超人中 主角琦玉的招式——认真一拳,所转换的字符动画展示。

本程序的代码实现思路较为简单明了,网上关于字符画的教程很多,笔者以较为擅长的java语言进行了一次修缮和总结。看到这读者可以点个赞支持下呀。

;