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 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> T getProperty(Environment environment, String name, Class<T> 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;
}
}
}

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).",
"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",

View File

@ -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;
}

View File

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