55

Springboot 系列(八)自定义 Banner 与 ASCII 字符图案的收到实现

 6 years ago
source link: https://www.codingme.net/2019/02/springboot/springboot-08-banner/?amp%3Butm_medium=referral
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

bQV73mJ.png!web

使用过 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 测试。

网上随便找了一个图片。

nUNRzeM.jpg!web

再次启动观察输出。

nqQ3q2I.png!web

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
 */

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 图片作为实验对象,运行得到字符图案输出。

Fb2aQ3R.png!web

文章代码已经上传到 GitHub Spring Boot


Recommend

  • 68

    Hannah 时尚图案 长款雨衣 69元包邮(Z秒杀),来自什么值得买甄选出的亚马逊中国优惠产品,汇聚数十万什么值得买网友对该网购产品的点评。

  • 73
    • www.choupangxia.com 6 years ago
    • Cache

    SpringBoot基础之banner玩法解析

    SpringBoot项目启动时会在控制台打印一个默认的启动图案,这个图案就是我们要讲的banner。看似简单的banner,我们能够对它做些什么呢?本篇文章就带大家深入了解一下banner的使用(版本:SpringBoot2.1.4)。

  • 56

    SpringMVC提供了各种姿势的http参数解析支持,从前面的GET/POST参数解析篇也可以看到,加一个 @RequsetParam 注解就可以将方法参数与http参数绑定,看到这时自然就会好奇这是怎么做到的,我们能不能自己定义一种参数解析规则...

  • 35
    • www.zhyea.com 5 years ago
    • Cache

    springboot入门10 – 修改banner

    这个内容有点儿水了。但是将springboot启动时的banner修改一下是个蛮好玩的事情。比如,不知道什么时候,我们组的springboot应用的banner就被改成了这个样子: ///////////////////////////////////////////////////////////////...

  • 27

    新浪科技讯北京时间6月20日消息,在一项新研究中,科学家创造出了一种极其复杂,以至于不可能复制或伪造的图案,这一成果将有效地打击造假者。日本筑波大学的研究人员表示,这些图案的关键在于一个两步验证系统,将微图案与回音壁的基本原理整合在一起。在

  • 26

  • 25

    不会自动转换string与date 主要是这个意思,前端提交的JSON里,日期是一个字符串,而对应后端的实体里,它是一个Date的日期,这两个在默认情况下是不能自动转换的,我们先看一下实体 实体 public class Us...

  • 6

    这个小功能据说是Springboot2.x的小彩蛋,出来很久很久了。之前知道没试过,这次试了一下,还可以,挺好玩的。最近因为开始重新看Springboot,看到了这个部分,于是整理到了文章中。烂大街的东西,拿来丢人显眼了。什么是banner?就是这个默认...

  • 11

    前面一篇博文介绍了一个@Value的一些知识点,其中提了一个点,@Value对应的配置,除了是配置文件中之外,可以从其他的数据源中获取么,如从redis,db,http中获取配置? 了解过SpringCloud Config的可以给出确切的答案,可以...

  • 7

    【DB系列】SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定 ...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK