Skip to content

Commit 303974f

Browse files
committed
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
1 parent 18396a3 commit 303974f

File tree

4 files changed

+61
-14
lines changed

4 files changed

+61
-14
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/ImageBanner.java

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,6 @@ public class ImageBanner implements Banner {
6262

6363
private static final double[] RGB_WEIGHT = { 0.2126d, 0.7152d, 0.0722d };
6464

65-
private static final char[] PIXEL = { ' ', '.', '*', ':', 'o', '&', '8', '#', '@' };
66-
67-
private static final int LUMINANCE_INCREMENT = 10;
68-
69-
private static final int LUMINANCE_START = LUMINANCE_INCREMENT * PIXEL.length;
70-
7165
private final Resource image;
7266

7367
public ImageBanner(Resource image) {
@@ -104,12 +98,13 @@ private void printBanner(Environment environment, PrintStream out) throws IOExce
10498
int margin = getProperty(environment, "margin", Integer.class, 2);
10599
boolean invert = getProperty(environment, "invert", Boolean.class, false);
106100
BitDepth bitDepth = getBitDepthProperty(environment);
101+
PixelMode pixelMode = getPixelModeProperty(environment);
107102
Frame[] frames = readFrames(width, height);
108103
for (int i = 0; i < frames.length; i++) {
109104
if (i > 0) {
110105
resetCursor(frames[i - 1].getImage(), out);
111106
}
112-
printBanner(frames[i].getImage(), margin, invert, bitDepth, out);
107+
printBanner(frames[i].getImage(), margin, invert, bitDepth, pixelMode, out);
113108
sleep(frames[i].getDelayTime());
114109
}
115110
}
@@ -119,6 +114,11 @@ private BitDepth getBitDepthProperty(Environment environment) {
119114
return (bitDepth != null) ? BitDepth.of(bitDepth) : BitDepth.FOUR;
120115
}
121116

117+
private PixelMode getPixelModeProperty(Environment environment) {
118+
String pixelMode = getProperty(environment, "pixelmode", String.class, null);
119+
return (pixelMode != null) ? PixelMode.valueOf(pixelMode.trim().toUpperCase()) : PixelMode.TEXT;
120+
}
121+
122122
private <T> T getProperty(Environment environment, String name, Class<T> targetType, T defaultValue) {
123123
return environment.getProperty(PROPERTY_PREFIX + name, targetType, defaultValue);
124124
}
@@ -197,7 +197,8 @@ private void resetCursor(BufferedImage image, PrintStream out) {
197197
out.print("\033[" + lines + "A\r");
198198
}
199199

200-
private void printBanner(BufferedImage image, int margin, boolean invert, BitDepth bitDepth, PrintStream out) {
200+
private void printBanner(BufferedImage image, int margin, boolean invert, BitDepth bitDepth, PixelMode pixelMode,
201+
PrintStream out) {
201202
AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT;
202203
out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
203204
out.print(AnsiOutput.encode(background));
@@ -216,7 +217,7 @@ private void printBanner(BufferedImage image, int margin, boolean invert, BitDep
216217
out.print(AnsiOutput.encode(ansiColor));
217218
lastColor = ansiColor;
218219
}
219-
out.print(getAsciiPixel(color, invert));
220+
out.print(getAsciiPixel(color, invert, pixelMode));
220221
}
221222
out.println();
222223
}
@@ -225,14 +226,17 @@ private void printBanner(BufferedImage image, int margin, boolean invert, BitDep
225226
out.println();
226227
}
227228

228-
private char getAsciiPixel(Color color, boolean dark) {
229+
private char getAsciiPixel(Color color, boolean dark, PixelMode pixelMode) {
230+
char[] pixels = pixelMode.getPixels();
231+
int increment = (10 / pixels.length) * 10;
232+
int start = increment * pixels.length;
229233
double luminance = getLuminance(color, dark);
230-
for (int i = 0; i < PIXEL.length; i++) {
231-
if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) {
232-
return PIXEL[i];
234+
for (int i = 0; i < pixels.length; i++) {
235+
if (luminance >= (start - (i * increment))) {
236+
return pixels[i];
233237
}
234238
}
235-
return PIXEL[PIXEL.length - 1];
239+
return pixels[pixels.length - 1];
236240
}
237241

238242
private int getLuminance(Color color, boolean inverse) {
@@ -277,4 +281,31 @@ int getDelayTime() {
277281

278282
}
279283

284+
/**
285+
* Pixel modes supported by the image banner.
286+
*/
287+
public enum PixelMode {
288+
289+
/**
290+
* Use text chars for pixels.
291+
*/
292+
TEXT(' ', '.', '*', ':', 'o', '&', '8', '#', '@'),
293+
294+
/**
295+
* Use unicode block chars for pixels.
296+
*/
297+
BLOCK(' ', '\u2591', '\u2592', '\u2593', '\u2588');
298+
299+
private char[] pixels;
300+
301+
PixelMode(char... pixels) {
302+
this.pixels = pixels;
303+
}
304+
305+
char[] getPixels() {
306+
return this.pixels;
307+
}
308+
309+
}
310+
280311
}

spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@
5353
"description": "The bit depth to use for ANSI colors. Supported values are 4 (16 color) or 8 (256 color).",
5454
"defaultValue": 4
5555
},
56+
{
57+
"name": "spring.banner.image.pixelmode",
58+
"type": "org.springframework.boot.ImageBanner$PixelMode",
59+
"description": "The pixel mode to use when rendering the image.",
60+
"defaultValue": "TEXT"
61+
},
5662
{
5763
"name": "debug",
5864
"type": "java.lang.Boolean",

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/ImageBannerTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ void printBannerWhenBitDepthIs8ShouldUseColors() {
179179
assertThat(banner.contains(AnsiOutput.encode(Ansi8BitColor.foreground(37))));
180180
}
181181

182+
@Test
183+
void printBannerWhenPixelModeIsBlockShouldRenderBlocks() {
184+
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
185+
String banner = printBanner("gradient.gif", "spring.banner.image.width=6", "spring.banner.image.margin=0",
186+
"spring.banner.image.pixelmode=block");
187+
assertThat(banner).contains("\u2588\u2593\u2592\u2591 ");
188+
}
189+
182190
private int getBannerHeight(String banner) {
183191
return banner.split(System.lineSeparator()).length - 3;
184192
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
spring.banner.image.bitdepth=8
2+
spring.banner.image.pixelmode=block

0 commit comments

Comments
 (0)