注意:本 Spring Boot 系列文章基於 Spring Boot 版本 v2.1.1.RELEASE 進行學習分析,版本不同可能會有細微差別。
使用過 Springboot 的對上面這個圖案肯定不會陌生,Springboot 啟動的同時會打印上面的圖案,並帶有版本號。查看官方文檔可以找到關於 banner 的描述
就不翻譯了,直接有道翻譯貼過來看個大概意思。The banner that is printed on start up can be changed by adding a banner.txt file to your classpath or by setting the spring.banner.location property to the location of such a file. If the file has an encoding other than UTF-8, you can set spring.banner.charset. In addition to a text file, you can also add a banner.gif, banner.jpg, or banner.png image file to your classpath or set the spring.banner.image.location property. Images are converted into an ASCII art representation and printed above any text banner.
可以通過向類路徑中添加一個banner.txt文件或設置spring.banner來更改在start up上打印的banner。屬性指向此類文件的位置。如果文件的編碼不是UTF-8,那么可以設置spring.banner.charset。除了文本文件,還可以添加橫幅。將gif、banner.jpg或banner.png圖像文件保存到類路徑或設置spring.banner.image。位置屬性。圖像被轉換成ASCII藝術形式,並打印在任何文本橫幅上面。
1. 自定義 banner
根據官方的描述,可以在類路徑中自定義 banner 圖案,我們進行嘗試在放 resouce 目錄下新建文件 banner.txt 並寫入內容(在線字符生成)。
(_)
_ __ _ _ _ _ __ ___ ___ ___
| '_ \| | | | | '_ ` _ \ / _ \ / _ \
| | | | | |_| | | | | | | (_) | (_) |
|_| |_|_|\__,_|_| |_| |_|\___/ \___/ 版本:${spring-boot.formatted-version}
啟動 Springboot 在控制台看到下面的輸出。
(_)
_ __ _ _ _ _ __ ___ ___ ___
| '_ \| | | | | '_ ` _ \ / _ \ / _ \
| | | | | |_| | | | | | | (_) | (_) |
|_| |_|_|\__,_|_| |_| |_|\___/ \___/ 版本:(v2.1.3.RELEASE)
2019-02-25 14:00:31.289 INFO 12312 --- [ main] net.codingme.banner.BannerApplication : Starting BannerApplication on LAPTOP-L1S5MKTA with PID 12312 (D:\IdeaProjectMy\springboot-git\springboot-banner\target\classes started by Niu in D:\IdeaProjectMy\springboot-git\springboot-banner)
2019-02-25 14:00:31.291 INFO 12312 --- [ main] net.codingme.banner.BannerApplication : No active profile set, falling back to default profiles: default
2019-02-25 14:00:32.087 INFO 12312 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
發現自定義 banner 已經生效了,官方文檔的介紹里說還可以放置圖片,下面放置圖片 banner.jpg 測試。
網上隨便找了一個圖片。
再次啟動觀察輸出。
Springboot 把圖案轉成了 ASCII 圖案。
2. ASCII 圖案生成原理
看了上面的例子,發現 Springboot 可以把圖片轉換成 ASCII 圖案,那么它是怎么做的呢?我們或許可以想象出一個大概流程。
- 獲取圖片。
- 遍歷圖片像素點。
- 分析像素點,每個像素點根據顏色深度得出一個值,根據明暗度匹配不同的字符。
- 輸出圖案。
Springboot 對圖片 banner 的處理到底是不是我們上面想想的那樣呢?直接去源碼中尋找答案。
/** 位置:org.springframework.boot.SpringApplicationBannerPrinter */
//方法1:
public Banner print(Environment environment, Class<?> sourceClass, Log logger) {
// 獲取 banner 調用方法記為2
Banner banner = getBanner(environment);
try {
logger.info(createStringFromBanner(banner, environment, sourceClass));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
// 打印 banner
return new PrintedBanner(banner, sourceClass);
}
// 方法2
private Banner getBanner(Environment environment) {
Banners banners = new Banners();
// 獲取圖片banner,我們只關注這個,調用方法記為3
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
// 方法3
/** 獲取自定義banner文件信息 */
private Banner getImageBanner(Environment environment) {
// BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return resource.exists() ? new ImageBanner(resource) : null;
}
// IMAGE_EXTENSION = { "gif", "jpg", "png" };
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
上面是尋找自定義圖片 banner 文件源碼,如果把圖片轉換成 ASCII 圖案繼續跟進,追蹤方法1中的PrintedBanner(banner, sourceClass)
方法。最終查找輸出圖案的主要方法。
// 位置:org.springframework.boot.ImageBanner#printBanner
private void printBanner(BufferedImage image, int margin, boolean invert,
PrintStream out) {
AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT;
out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
out.print(AnsiOutput.encode(background));
out.println();
out.println();
AnsiColor lastColor = AnsiColor.DEFAULT;
// 圖片高度遍歷
for (int y = 0; y < image.getHeight(); y++) {
for (int i = 0; i < margin; i++) {
out.print(" ");
}
// 圖片寬度遍歷
for (int x = 0; x < image.getWidth(); x++) {
// 獲取每一個像素點
Color color = new Color(image.getRGB(x, y), false);
AnsiColor ansiColor = AnsiColors.getClosest(color);
if (ansiColor != lastColor) {
out.print(AnsiOutput.encode(ansiColor));
lastColor = ansiColor;
}
// 像素點轉換成字符輸出,調用方法記為2
out.print(getAsciiPixel(color, invert));
}
out.println();
}
out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
out.print(AnsiOutput.encode(AnsiBackground.DEFAULT));
out.println();
}
// 方法2,像素點轉換成字符
private char getAsciiPixel(Color color, boolean dark) {
// 根據 color 算出一個亮度值
double luminance = getLuminance(color, dark);
for (int i = 0; i < PIXEL.length; i++) {
// 尋找亮度值匹配的字符
if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) {
// PIXEL = { ' ', '.', '*', ':', 'o', '&', '8', '#', '@' };
return PIXEL[i];
}
}
return PIXEL[PIXEL.length - 1];
}
通過查看源碼,發現 Springboot 的圖片 banner 的轉換和我們預想的大致一致,這么有趣的功能我們能不能自己寫一個呢?
3.自己實現圖片轉 ASCII字符
根據上面的分析,總結一下思路,我們也可以手動寫一個圖片轉 ASCII 字符圖案。
思路如下:
- 圖片大小縮放,調整到合適大小。
- 遍歷圖片像素。
- 獲取圖片像素點亮度(RGB顏色通過公式可以得到亮度數值)。
- 匹配字符。
- 輸出圖案。
上面的5個步驟直接使用 Java 代碼就可以完整實現,下面是編寫的源碼。
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* <p>
* 根據圖片生成字符圖案
* 1.圖片大小縮放
* 2.遍歷圖片像素點
* 3.獲取圖片像素點亮度
* 4.匹配字符
* 5.輸出圖案
*
* @author niujinpeng
* @website www.codingme.net
* @date 2019-02-25 23:03:01
*/
public class GeneratorTextImage {
private static final char[] PIXEL = {'@', '#', '8', '&', 'o', ':', '*', '.', ' '};
public static void main(String[] args) throws Exception {
// 圖片縮放
BufferedImage bufferedImage = makeSmallImage("src/main/resources/banner.jpg");
// 輸出
printImage(bufferedImage);
}
public static void printImage(BufferedImage image) throws IOException {
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 luminace = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
double index = luminace / (Math.ceil(255 / PIXEL.length) + 0.5);
System.out.print(PIXEL[(int)(Math.floor(index))]);
}
System.out.println();
}
}
public static BufferedImage makeSmallImage(String srcImageName) throws Exception {
File srcImageFile = new File(srcImageName);
if (srcImageFile == null) {
System.out.println("文件不存在");
return null;
}
FileOutputStream fileOutputStream = null;
BufferedImage tagImage = null;
Image srcImage = null;
try {
srcImage = ImageIO.read(srcImageFile);
int srcWidth = srcImage.getWidth(null);// 原圖片寬度
int srcHeight = srcImage.getHeight(null);// 原圖片高度
int dstMaxSize = 90;// 目標縮略圖的最大寬度/高度,寬度與高度將按比例縮寫
int dstWidth = srcWidth;// 縮略圖寬度
int dstHeight = srcHeight;// 縮略圖高度
float scale = 0;
// 計算縮略圖的寬和高
if (srcWidth > dstMaxSize) {
dstWidth = dstMaxSize;
scale = (float)srcWidth / (float)dstMaxSize;
dstHeight = Math.round((float)srcHeight / scale);
}
srcHeight = dstHeight;
if (srcHeight > dstMaxSize) {
dstHeight = dstMaxSize;
scale = (float)srcHeight / (float)dstMaxSize;
dstWidth = Math.round((float)dstWidth / scale);
}
// 生成縮略圖
tagImage = new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_RGB);
tagImage.getGraphics().drawImage(srcImage, 0, 0, dstWidth, dstHeight, null);
return tagImage;
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (Exception e) {
}
fileOutputStream = null;
}
tagImage = null;
srcImage = null;
System.gc();
}
}
}
還是拿上面的 Google log 圖片作為實驗對象,運行得到字符圖案輸出。
文章代碼已經上傳到 GitHub Spring Boot。
<完>
歡迎點贊關注!
本文原發於個人博客:https://www.codingme.net 轉載請注明出處