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 图案,那么它是怎么做的呢?我们或许可以想象出一个大概流程。
- 获取图片。
- 遍历图片像素点。
- 分析像素点,每个像素点根据颜色深度得出一个值,根据明暗度匹配不同的字符。
- 输出图案。
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 转载请注明出处
Springboot 系列(八)动态Banner与图片转字符图案的手动实现的更多相关文章
- 32. Springboot 系列(八)动态Banner与图片转字符图案的手动实现
使用过 Springboot 的对上面这个图案肯定不会陌生,Springboot 启动的同时会打印上面的图案,并带有版本号.查看官方文档可以找到关于 banner 的描述 The banner tha ...
- [转]SpringBoot系列——花里胡哨的banner.txt
Creating ASCII Text Banners from the Linux Command Line In Ubuntu, Debian, Linux Mint etc. $ sudo ap ...
- SpringBoot系列——花里胡哨的banner.txt
前言 我们注意到springboot项目启动时,控制台会打印自带的banner,然后对于部分IT骚年来说,太单调太普通太一般了:所以,是时候表演真正的技术了 项目结构 我们只需要在springboot ...
- springboot系列八、springboot整合kafka
背景: 当业务在同一时间出现高并发的时候,这个时候我们不想无限的增加服务器,但是又想提高吞吐量.这时可以考虑使用消息异步处理,进行消峰填谷:同时还可以降低耦合度.常见的消息中间件有kafka,rabb ...
- SpringBoot系列八:SpringBoot整合消息服务(SpringBoot 整合 ActiveMQ、SpringBoot 整合 RabbitMQ、SpringBoot 整合 Kafka)
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合消息服务 2.具体内容 对于异步消息组件在实际的应用之中会有两类: · JMS:代表作就是 ...
- katalon系列八:Katalon Studio图片识别
Katalon Studio自带集成了图片识别功能,有2个比较有用的图片识别相关的命令:Wait For Image Present和Click Image.这里重点讲下Click Image命令: ...
- SpringBoot系列(八)分分钟学会Springboot多种解决跨域方式
SpringBoot系列(八) 分分钟学会SpringBoot多种跨域解决方式 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 s ...
- SpringBoot系列——动态定时任务
前言 定时器是我们项目中经常会用到的,SpringBoot使用@Scheduled注解可以快速启用一个简单的定时器(详情请看我们之前的博客<SpringBoot系列--定时器>),然而这种 ...
- SpringBoot系列(九)单,多文件上传的正确姿势
SpringBoot系列(九)分分钟解决文件上传 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配 ...
随机推荐
- BZOJ_[JSOI2010]Group 部落划分 Group_kruskal
BZOJ_[JSOI2010]Group 部落划分 Group_kruskal Description 聪聪研究发现,荒岛野人总是过着群居的生活,但是,并不是整个荒岛上的所有野人都属于同一个部落,野人 ...
- 我的微服务观,surging 2.0将会带来多大的改变
Surging 自2017年6月16日开源以来,已收到不少公司的关注或者使用,其中既有以海克斯康超大型等外企的关注,也不乏深圳泓达康.重庆金翅膀等传统行业的正式使用,自2019年年初,surging2 ...
- python接口自动化(二十六)--批量执行用例 discover(详解)
简介 我们在写用例的时候,单个脚本的用例好执行,那么多个脚本的时候,如何批量执行呢?这时候就需要用到 unittest 里面的 discover 方法来加载用例了.加载用例后,用 unittest 里 ...
- Python爬虫实践 -- 记录我的第二只爬虫
1.爬虫基本原理 我们爬取中国电影最受欢迎的影片<红海行动>的相关信息.其实,爬虫获取网页信息和人工获取信息,原理基本是一致的. 人工操作步骤: 1. 获取电影信息的页面 2. 定位(找到 ...
- 从壹开始微服务 [ DDD ] 之九 ║从军事故事中,明白领域命令验证(上)
烽烟 哈喽大家周二好呀,咱们又见面了,上周末掐指一算,距离 圣诞节 只有 5 周的时间了(如果你还不知道为啥我要提圣诞节这个时间点,可以看看我的第二系列开篇<之一 ║ D3模式设计初探 与 我的 ...
- jdk源码阅读笔记-AbstractStringBuilder
AbstractStringBuilder 在java.lang 包中,是一个抽象类,实现 Appendable 接口和 CharSequence 接口,这个类的诞生是为了解决 String 类在创建 ...
- 游戏AI之感知(1)
目录 感知 视觉感知 听力感知 其它感知 实现 感知 视觉感知 视觉感知是一种常见的感知. 在许多即时战略游戏或者类DOTA游戏里,一个单位的视觉感知往往是圆形范围的. 当然在其他大部分俯视角游戏里, ...
- 解决Configuration 'compile' is obsolete and has been replaced with implementation
项目中Gradle版本升级到4.4后,项目构建时,每次出现红色的警告信息: WARNING: Configuration 'compile' is obsolete and has been repl ...
- 工程文件csproj使用编译条件指定属性
csproj工程文件中有很多xml格式的属性,比如PropertyGroup.ItemGroup,某些属性操作默认是全部的或者是当前编译条件的而已,当我们想指定某些属性只在某个编译条件下发生时就可以通 ...
- ArcGIS API for JavaScript 4.9 & 3.26 发布与新特性
应该是中文首发?我只想说:更新太TMD快了 QAQ Part I -- JsAPI 4.9 主题1:在2D中默认启用WebGL渲染 在要素图层.CSV图层.流图层中是使用WebGL渲染的,这个任务由M ...