From 303974fde976f833e73b3fd1b552d51a09a4f085 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 22 Sep 2019 00:01:05 -0700 Subject: [PATCH] Add block pixel mode support for image banners Add support for a `spring.banner.image.pixelmode` property which can be set to `block` to use unicode block characters when rendering image banners. Closes gh-18301 --- .../org/springframework/boot/ImageBanner.java | 59 ++++++++++++++----- ...itional-spring-configuration-metadata.json | 6 ++ .../boot/ImageBannerTests.java | 8 +++ .../src/main/resources/application.properties | 2 + 4 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-animated-banner/src/main/resources/application.properties diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java index 9a60b4386e0..de4591ec583 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java @@ -62,12 +62,6 @@ public class ImageBanner implements Banner { private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d }; - private static final char[] PIXEL = { ' ', '.', '*', ':', 'o', '&', '8', '#', '@' }; - - private static final int LUMINANCE_INCREMENT = 10; - - private static final int LUMINANCE_START = LUMINANCE_INCREMENT * PIXEL.length; - private final Resource image; public ImageBanner(Resource image) { @@ -104,12 +98,13 @@ public class ImageBanner implements Banner { int margin = getProperty(environment, "margin", Integer.class, 2); boolean invert = getProperty(environment, "invert", Boolean.class, false); BitDepth bitDepth = getBitDepthProperty(environment); + PixelMode pixelMode = getPixelModeProperty(environment); Frame[] frames = readFrames(width, height); for (int i = 0; i < frames.length; i++) { if (i > 0) { resetCursor(frames[i - 1].getImage(), out); } - printBanner(frames[i].getImage(), margin, invert, bitDepth, out); + printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out); sleep(frames[i].getDelayTime()); } } @@ -119,6 +114,11 @@ public class ImageBanner implements Banner { return (bitDepth != null) ? BitDepth.of(bitDepth) : BitDepth.FOUR; } + private PixelMode getPixelModeProperty(Environment environment) { + String pixelMode = getProperty(environment, "pixelmode", String.class, null); + return (pixelMode != null) ? PixelMode.valueOf(pixelMode.trim().toUpperCase()) : PixelMode.TEXT; + } + private T getProperty(Environment environment, String name, Class targetType, T defaultValue) { return environment.getProperty(PROPERTY_PREFIX + name, targetType, defaultValue); } @@ -197,7 +197,8 @@ public class ImageBanner implements Banner { out.print("\033[" + lines + "A\r"); } - private void printBanner(BufferedImage image, int margin, boolean invert, BitDepth bitDepth, PrintStream out) { + private void printBanner(BufferedImage image, int margin, boolean invert, BitDepth bitDepth, PixelMode pixelMode, + PrintStream out) { AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT; out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); out.print(AnsiOutput.encode(background)); @@ -216,7 +217,7 @@ public class ImageBanner implements Banner { out.print(AnsiOutput.encode(ansiColor)); lastColor = ansiColor; } - out.print(getAsciiPixel(color, invert)); + out.print(getAsciiPixel(color, invert, pixelMode)); } out.println(); } @@ -225,14 +226,17 @@ public class ImageBanner implements Banner { out.println(); } - private char getAsciiPixel(Color color, boolean dark) { + private char getAsciiPixel(Color color, boolean dark, PixelMode pixelMode) { + char[] pixels = pixelMode.getPixels(); + int increment = (10 / pixels.length) * 10; + int start = increment * pixels.length; double luminance = getLuminance(color, dark); - for (int i = 0; i < PIXEL.length; i++) { - if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) { - return PIXEL[i]; + for (int i = 0; i < pixels.length; i++) { + if (luminance >= (start - (i * increment))) { + return pixels[i]; } } - return PIXEL[PIXEL.length - 1]; + return pixels[pixels.length - 1]; } private int getLuminance(Color color, boolean inverse) { @@ -277,4 +281,31 @@ public class ImageBanner implements Banner { } + /** + * Pixel modes supported by the image banner. + */ + public enum PixelMode { + + /** + * Use text chars for pixels. + */ + TEXT(' ', '.', '*', ':', 'o', '&', '8', '#', '@'), + + /** + * Use unicode block chars for pixels. + */ + BLOCK(' ', '\u2591', '\u2592', '\u2593', '\u2588'); + + private char[] pixels; + + PixelMode(char... pixels) { + this.pixels = pixels; + } + + char[] getPixels() { + return this.pixels; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 2c6dc89d541..c81b37c8bfb 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -53,6 +53,12 @@ "description": "The bit depth to use for ANSI colors. Supported values are 4 (16 color) or 8 (256 color).", "defaultValue": 4 }, + { + "name": "spring.banner.image.pixelmode", + "type": "org.springframework.boot.ImageBanner$PixelMode", + "description": "The pixel mode to use when rendering the image.", + "defaultValue": "TEXT" + }, { "name": "debug", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java index 8c314816fc9..b1d6686f4f6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java @@ -179,6 +179,14 @@ class ImageBannerTests { assertThat(banner.contains(AnsiOutput.encode(Ansi8BitColor.foreground(37)))); } + @Test + void printBannerWhenPixelModeIsBlockShouldRenderBlocks() { + AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER); + String banner = printBanner("gradient.gif", "spring.banner.image.width=6", "spring.banner.image.margin=0", + "spring.banner.image.pixelmode=block"); + assertThat(banner).contains("\u2588\u2593\u2592\u2591 "); + } + private int getBannerHeight(String banner) { return banner.split(System.lineSeparator()).length - 3; } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-animated-banner/src/main/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-animated-banner/src/main/resources/application.properties new file mode 100644 index 00000000000..39cb91f2e0b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-animated-banner/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.banner.image.bitdepth=8 +spring.banner.image.pixelmode=block