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:
parent
18396a3dc3
commit
303974fde9
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
spring.banner.image.bitdepth=8
|
||||||
|
spring.banner.image.pixelmode=block
|
Loading…
x
Reference in New Issue
Block a user