Springboot 系列(八)動態Banner與圖片轉字符圖案的手動實現


注意:本 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 圖案,那么它是怎么做的呢?我們或許可以想象出一個大概流程。

  1. 獲取圖片。
  2. 遍歷圖片像素點。
  3. 分析像素點,每個像素點根據顏色深度得出一個值,根據明暗度匹配不同的字符。
  4. 輸出圖案。

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 字符圖案。
思路如下:

  1. 圖片大小縮放,調整到合適大小。
  2. 遍歷圖片像素。
  3. 獲取圖片像素點亮度(RGB顏色通過公式可以得到亮度數值)。
  4. 匹配字符。
  5. 輸出圖案。

上面的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 轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM