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
This commit is contained in:
Phillip Webb 2019-09-22 00:01:05 -07:00
parent 18396a3dc3
commit 303974fde9
4 changed files with 61 additions and 14 deletions

View File

@ -62,12 +62,6 @@ public class ImageBanner implements Banner {
private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d }; 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; private final Resource image;
public ImageBanner(Resource image) { public ImageBanner(Resource image) {
@ -104,12 +98,13 @@ public class ImageBanner implements Banner {
int margin = getProperty(environment, "margin", Integer.class, 2); int margin = getProperty(environment, "margin", Integer.class, 2);
boolean invert = getProperty(environment, "invert", Boolean.class, false); boolean invert = getProperty(environment, "invert", Boolean.class, false);
BitDepth bitDepth = getBitDepthProperty(environment); BitDepth bitDepth = getBitDepthProperty(environment);
PixelMode pixelMode = getPixelModeProperty(environment);
Frame[] frames = readFrames(width, height); Frame[] frames = readFrames(width, height);
for (int i = 0; i < frames.length; i++) { for (int i = 0; i < frames.length; i++) {
if (i > 0) { if (i > 0) {
resetCursor(frames[i - 1].getImage(), out); 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()); sleep(frames[i].getDelayTime());
} }
} }
@ -119,6 +114,11 @@ public class ImageBanner implements Banner {
return (bitDepth != null) ? BitDepth.of(bitDepth) : BitDepth.FOUR; 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> T getProperty(Environment environment, String name, Class<T> targetType, T defaultValue) { private <T> T getProperty(Environment environment, String name, Class<T> targetType, T defaultValue) {
return environment.getProperty(PROPERTY_PREFIX + name, targetType, defaultValue); return environment.getProperty(PROPERTY_PREFIX + name, targetType, defaultValue);
} }
@ -197,7 +197,8 @@ public class ImageBanner implements Banner {
out.print("\033[" + lines + "A\r"); 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; AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT;
out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
out.print(AnsiOutput.encode(background)); out.print(AnsiOutput.encode(background));
@ -216,7 +217,7 @@ public class ImageBanner implements Banner {
out.print(AnsiOutput.encode(ansiColor)); out.print(AnsiOutput.encode(ansiColor));
lastColor = ansiColor; lastColor = ansiColor;
} }
out.print(getAsciiPixel(color, invert)); out.print(getAsciiPixel(color, invert, pixelMode));
} }
out.println(); out.println();
} }
@ -225,14 +226,17 @@ public class ImageBanner implements Banner {
out.println(); 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); double luminance = getLuminance(color, dark);
for (int i = 0; i < PIXEL.length; i++) { for (int i = 0; i < pixels.length; i++) {
if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) { if (luminance >= (start - (i * increment))) {
return PIXEL[i]; return pixels[i];
} }
} }
return PIXEL[PIXEL.length - 1]; return pixels[pixels.length - 1];
} }
private int getLuminance(Color color, boolean inverse) { 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;
}
}
} }

View File

@ -53,6 +53,12 @@
"description": "The bit depth to use for ANSI colors. Supported values are 4 (16 color) or 8 (256 color).", "description": "The bit depth to use for ANSI colors. Supported values are 4 (16 color) or 8 (256 color).",
"defaultValue": 4 "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", "name": "debug",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",

View File

@ -179,6 +179,14 @@ class ImageBannerTests {
assertThat(banner.contains(AnsiOutput.encode(Ansi8BitColor.foreground(37)))); 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) { private int getBannerHeight(String banner) {
return banner.split(System.lineSeparator()).length - 3; return banner.split(System.lineSeparator()).length - 3;
} }

View File

@ -0,0 +1,2 @@
spring.banner.image.bitdepth=8
spring.banner.image.pixelmode=block