今天,我们来完成一个小玩意,将图片转成ASCII,最后使用Base64转换成灰色图。如,将图
转换成下图的模样。
步骤
主要包含如下几个步骤:
- 读取图片文件到BufferedImage, 如:
BufferedImage sourceBufferedImage = ImageIO.read(new File(sourceFileFile))
- 创建画布并进行灰度处理 - 获取像素点的灰度值,将灰度值的深浅转换成自己定义的ASCII字符
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = bufferedImage.createGraphics();
代码语言:javascript复制 for (int i = miny; i < height; i = steps) {
for (int j = minx; j < width; j = steps) {
/**
* <pre>
* 像素灰度化:
*
* 灰度化,在RGB模型中,如果R=G=B时,则彩色表示一种灰度颜色,
* 其中R=G=B的值叫灰度值,
* 因此,灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),
* 灰度范围为0-255
*
* </pre>
*/
int pixel = bi.getRGB(j, i);
int red = (pixel & 0xff0000) >> 16;
int green = (pixel & 0xff00) >> 8;
int blue = (pixel & 0xff);
/**
* <pre>
* 典型处理公式:0.299f * red 0.578f * green 0.114f * blue
*
* 根据重要性及其它指标,将三个分量以不同的权值进行加权平均。
* 由于人眼对绿色的敏感最高,对蓝色敏感最低,
* 因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。
* </pre>
*/
float gray = 0.299f * red 0.578f * green 0.114f * blue;
int index = Math.round(gray * (CANDIDATE_CHARS.length() 1) / 255);
// 如果超过使用空格代替
String c = index >= CANDIDATE_CHARS.length() ? " " : String.valueOf(CANDIDATE_CHARS.charAt(index));
g.drawString(c, j, i);
System.out.print(c);
}
System.out.println();
}
- 获取输出图片的Base64字符串
private static String encodeBase64(BufferedImage targetBufferedImage) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ImageIO.write(targetBufferedImage, "jpg", out);
String base64 = org.apache.commons.codec.binary.Base64.encodeBase64String(out.toByteArray());
return base64;
} catch (IOException e) {
logger.error("ImageIO.write error", e);
}
return null;
}
- Base64字符串转图片
File fileImage = new File(targetFilePath);
byte[] decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(fileBase64String);
FileUtils.writeByteArrayToFile(fileImage, decodedBytes);
完整代码:
代码语言:javascript复制import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ImageToAsciiHandler {
private static Logger logger = LoggerFactory.getLogger(ImageToAsciiHandler.class);
// 自定义候选使用的字符串
private static final String CANDIDATE_CHARS = "MN@& @~oC!:.";
public static void generate(String sourceFileFile, String targetFilePath, int skipSteps) throws IOException {
/**
* 1、读取原图片文件
*/
BufferedImage sourceBufferedImage = ImageIO.read(new File(sourceFileFile));
/**
* 2、写入BufferedImage
*/
BufferedImage targetBufferedImage = writeInBufferImage(sourceBufferedImage, skipSteps);
/**
* 3、获取图片文件的Base64字串
*/
String fileBase64String = encodeBase64(targetBufferedImage);
/**
* 4、Base64转图片
*/
File fileImage = new File(targetFilePath);
byte[] decodedBytes = org.apache.commons.codec.binary.Base64.decodeBase64(fileBase64String);
FileUtils.writeByteArrayToFile(fileImage, decodedBytes);
}
private static String encodeBase64(BufferedImage targetBufferedImage) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ImageIO.write(targetBufferedImage, "jpg", out);
String base64 = org.apache.commons.codec.binary.Base64.encodeBase64String(out.toByteArray());
return base64;
} catch (IOException e) {
logger.error("ImageIO.write error", e);
}
return null;
}
private static BufferedImage writeInBufferImage(BufferedImage bi, int skipSteps) {
int width = bi.getWidth();
int height = bi.getHeight();
int minx = bi.getMinX();
int miny = bi.getMinY();
int steps = 1 skipSteps;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = bufferedImage.createGraphics();
g.setColor(null);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.setFont(new Font("微软雅黑", Font.PLAIN, steps));
for (int i = miny; i < height; i = steps) {
for (int j = minx; j < width; j = steps) {
/**
* <pre>
* 像素灰度化:
*
* 灰度化,在RGB模型中,如果R=G=B时,则彩色表示一种灰度颜色,
* 其中R=G=B的值叫灰度值,
* 因此,灰度图像每个像素只需一个字节存放灰度值(又称强度值、亮度值),
* 灰度范围为0-255
*
* </pre>
*/
int pixel = bi.getRGB(j, i);
int red = (pixel & 0xff0000) >> 16;
int green = (pixel & 0xff00) >> 8;
int blue = (pixel & 0xff);
/**
* <pre>
* 典型处理公式:0.299f * red 0.578f * green 0.114f * blue
*
* 根据重要性及其它指标,将三个分量以不同的权值进行加权平均。
* 由于人眼对绿色的敏感最高,对蓝色敏感最低,
* 因此,按下式对RGB三分量进行加权平均能得到较合理的灰度图像。
* </pre>
*/
float gray = 0.299f * red 0.578f * green 0.114f * blue;
int index = Math.round(gray * (CANDIDATE_CHARS.length() 1) / 255);
// 如果超过使用空格代替
String c = index >= CANDIDATE_CHARS.length() ? " " : String.valueOf(CANDIDATE_CHARS.charAt(index));
g.drawString(c, j, i);
System.out.print(c);
}
System.out.println();
}
g.dispose();
return bufferedImage;
}
}
测试一下:
代码语言:javascript复制import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
for(int skipSteps = 3; skipSteps <= 3; skipSteps ) {
ImageToAsciiHandler.generate("/Users/xxxx/Desktop/katong.png", "/Users/td/Desktop/xxx" skipSteps ".jpg", skipSteps);
}
}
}
运行就能等到转换后的图。
像素处理
如果每个像素处理,会发现图转换后,具有较多的阴影。所以,在处理的时候,增加了skipStep的参数,用于过滤掉像素处理。
大家可以通过一个for循环进行尝试。
代码语言:javascript复制import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
for(int skipSteps = 0; skipSteps <= 7; skipSteps ) {
ImageToAsciiHandler.generate("/Users/xxxx/Desktop/katong.png", "/Users/td/Desktop/xxx" skipSteps ".jpg", skipSteps);
}
}
}
发现skipStep ==3的时候,效果还行,不过也要看指定的ASCII字符串。
再对一张100元的纸币进行一次测试:
过滤不同像素点有不一样的显示,如:
有兴趣的同学可以试试。今天的例子只有一张图,后续将多张图组成的GIF处理补充上。