Commit d3f1d126 authored by Phillip Webb's avatar Phillip Webb

Merge pull request #4647 from craigburke/image-banner

* image-banner:
  Add image banner documentation
  Rework ImageBanner Support
  Add ImageBanner color distance calculation
  Support image based banners
parents 6550bb4c d058ddbe
...@@ -31,6 +31,11 @@ content into your application; rather pick only the properties that you need. ...@@ -31,6 +31,11 @@ content into your application; rather pick only the properties that you need.
# BANNER # BANNER
banner.charset=UTF-8 # Banner file encoding. banner.charset=UTF-8 # Banner file encoding.
banner.location=classpath:banner.txt # Banner file location. banner.location=classpath:banner.txt # Banner file location.
banner.image.location=classpath:banner.gif # (Banner image file location, jpg/png can also be used).
banner.image.width= # Width of the banner image in chars (default 76)
banner.image.hight= # Height of the banner image in chars (default based on image height)
banner.image.margin= # Left hand image margin in chars (default 2)
banner.image.invert= # If images should be inverted for dark terminal themes (default false)
# LOGGING # LOGGING
logging.config= # Location of the logging configuration file. For instance `classpath:logback.xml` for Logback logging.config= # Location of the logging configuration file. For instance `classpath:logback.xml` for Logback
......
...@@ -212,8 +212,8 @@ might have. ...@@ -212,8 +212,8 @@ might have.
[source,properties,indent=0,subs="verbatim,quotes,attributes"] [source,properties,indent=0,subs="verbatim,quotes,attributes"]
---- ----
spring.main.web_environment=false spring.main.web-environment=false
spring.main.banner_mode=off spring.main.banner-mode=off
---- ----
and then the Spring Boot banner will not be printed on startup, and the application will and then the Spring Boot banner will not be printed on startup, and the application will
...@@ -239,7 +239,7 @@ used with the following configuration: ...@@ -239,7 +239,7 @@ used with the following configuration:
[source,properties,indent=0,subs="verbatim,quotes,attributes"] [source,properties,indent=0,subs="verbatim,quotes,attributes"]
---- ----
spring.main.sources=com.acme.Config,com.acme.ExtraConfig spring.main.sources=com.acme.Config,com.acme.ExtraConfig
spring.main.banner_mode=console spring.main.banner-mode=console
---- ----
The actual application will _now_ show the banner (as overridden by configuration) and use The actual application will _now_ show the banner (as overridden by configuration) and use
......
...@@ -52,8 +52,11 @@ such as the user that launched the application. ...@@ -52,8 +52,11 @@ such as the user that launched the application.
The banner that is printed on start up can be changed by adding a `banner.txt` file The banner that is printed on start up can be changed by adding a `banner.txt` file
to your classpath, or by setting `banner.location` to the location of such a file. to your classpath, or by setting `banner.location` to the location of such a file.
If the file has an unusual encoding you can set `banner.charset` (default is `UTF-8`). If the file has an unusual encoding you can set `banner.charset` (default is `UTF-8`).
In addition a text file, you can also add a `banner.gif`, `banner.jpg` or `banner.png`
image file to your classpath, or set a `banner.image.location` property. Images will be
converted into an ASCII art representation and printed above any text banner.
You can use the following variables inside your `banner.txt` file: Inside your `banner.txt` file you can use any of the following placeholders:
.Banner variables .Banner variables
|=== |===
......
${Ansi.GREEN} :: Sample application build with Spring Boot${spring-boot.formatted-version} ::${Ansi.DEFAULT}
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot;
import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import javax.imageio.ImageIO;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ansi.AnsiBackground;
import org.springframework.boot.ansi.AnsiColor;
import org.springframework.boot.ansi.AnsiColors;
import org.springframework.boot.ansi.AnsiElement;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* Banner implementation that prints ASCII art generated from an image resource
* {@link Resource}.
*
* @author Craig Burke
* @author Phillip Webb
* @since 1.4.0
*/
public class ImageBanner implements Banner {
private static final Log log = LogFactory.getLog(ImageBanner.class);
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) {
Assert.notNull(image, "Image must not be null");
Assert.isTrue(image.exists(), "Image must exist");
this.image = image;
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
String headless = System.getProperty("java.awt.headless");
try {
System.setProperty("java.awt.headless", "true");
printBanner(environment, out);
}
catch (Exception ex) {
log.warn("Image banner not printable: " + this.image + " (" + ex.getClass()
+ ": '" + ex.getMessage() + "')", ex);
}
finally {
if (headless == null) {
System.clearProperty("java.awt.headless");
}
else {
System.setProperty("java.awt.headless", headless);
}
}
}
private void printBanner(Environment environment, PrintStream out)
throws IOException {
PropertyResolver properties = new RelaxedPropertyResolver(environment,
"banner.image.");
int width = properties.getProperty("width", Integer.class, 76);
int heigth = properties.getProperty("height", Integer.class, 0);
int margin = properties.getProperty("margin", Integer.class, 2);
boolean invert = properties.getProperty("invert", Boolean.class, false);
BufferedImage image = readImage(width, heigth);
printBanner(image, margin, invert, out);
}
private BufferedImage readImage(int width, int heigth) throws IOException {
InputStream inputStream = this.image.getInputStream();
try {
BufferedImage image = ImageIO.read(inputStream);
return resizeImage(image, width, heigth);
}
finally {
inputStream.close();
}
}
private BufferedImage resizeImage(BufferedImage image, int width, int height) {
if (width < 1) {
width = 1;
}
if (height <= 0) {
double aspectRatio = (double) width / image.getWidth() * 0.5;
height = (int) Math.ceil(image.getHeight() * aspectRatio);
}
BufferedImage resized = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Image scaled = image.getScaledInstance(width, height, Image.SCALE_DEFAULT);
resized.getGraphics().drawImage(scaled, 0, 0, null);
return resized;
}
private void printBanner(BufferedImage image, int margin, boolean invert,
PrintStream out) {
AnsiElement background = (invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT);
out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
out.print(AnsiOutput.encode(background));
out.println();
out.println();
AnsiColor lastColor = AnsiColor.DEFAULT;
for (int y = 0; y < image.getHeight(); y++) {
for (int i = 0; i < margin; i++) {
out.print(" ");
}
for (int x = 0; x < image.getWidth(); x++) {
Color color = new Color(image.getRGB(x, y), false);
AnsiColor ansiColor = AnsiColors.getClosest(color);
if (ansiColor != lastColor) {
out.print(AnsiOutput.encode(ansiColor));
lastColor = ansiColor;
}
out.print(getAsciiPixel(color, invert));
}
out.println();
}
out.print(AnsiOutput.encode(AnsiColor.DEFAULT));
out.print(AnsiOutput.encode(AnsiBackground.DEFAULT));
out.println();
}
private char getAsciiPixel(Color color, boolean dark) {
double luminance = getLuminance(color, dark);
for (int i = 0; i < PIXEL.length; i++) {
if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) {
return PIXEL[i];
}
}
return PIXEL[PIXEL.length - 1];
}
private int getLuminance(Color color, boolean inverse) {
double luminance = 0.0;
luminance += getLuminance(color.getRed(), inverse, RGB_WEIGHT[0]);
luminance += getLuminance(color.getGreen(), inverse, RGB_WEIGHT[1]);
luminance += getLuminance(color.getBlue(), inverse, RGB_WEIGHT[2]);
return (int) Math.ceil((luminance / 0xFF) * 100);
}
private double getLuminance(int component, boolean inverse, double weight) {
return (inverse ? 0xFF - component : component) * weight;
}
}
...@@ -38,7 +38,7 @@ import org.springframework.util.Assert; ...@@ -38,7 +38,7 @@ import org.springframework.util.Assert;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
/** /**
* Banner implementation that prints from a source {@link Resource}. * Banner implementation that prints from a source text {@link Resource}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Vedran Pavic * @author Vedran Pavic
......
...@@ -16,9 +16,6 @@ ...@@ -16,9 +16,6 @@
package org.springframework.boot; package org.springframework.boot;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -41,6 +38,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; ...@@ -41,6 +38,7 @@ import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.diagnostics.FailureAnalyzers; import org.springframework.boot.diagnostics.FailureAnalyzers;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
...@@ -140,6 +138,7 @@ import org.springframework.web.context.support.StandardServletEnvironment; ...@@ -140,6 +138,7 @@ import org.springframework.web.context.support.StandardServletEnvironment;
* @author Christian Dupuis * @author Christian Dupuis
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Jeremy Rickard * @author Jeremy Rickard
* @author Craig Burke
* @see #run(Object, String[]) * @see #run(Object, String[])
* @see #run(Object[], String[]) * @see #run(Object[], String[])
* @see #SpringApplication(Object...) * @see #SpringApplication(Object...)
...@@ -166,19 +165,17 @@ public class SpringApplication { ...@@ -166,19 +165,17 @@ public class SpringApplication {
/** /**
* Default banner location. * Default banner location.
*/ */
public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt"; public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;
/** /**
* Banner location property key. * Banner location property key.
*/ */
public static final String BANNER_LOCATION_PROPERTY = "banner.location"; public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;
private static final String CONFIGURABLE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.context.ConfigurableWebEnvironment"; private static final String CONFIGURABLE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.context.ConfigurableWebEnvironment";
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless"; private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
private static final Set<String> SERVLET_ENVIRONMENT_SOURCE_NAMES; private static final Set<String> SERVLET_ENVIRONMENT_SOURCE_NAMES;
static { static {
...@@ -542,42 +539,16 @@ public class SpringApplication { ...@@ -542,42 +539,16 @@ public class SpringApplication {
* @see #setBannerMode * @see #setBannerMode
*/ */
protected void printBanner(Environment environment) { protected void printBanner(Environment environment) {
Banner selectedBanner = selectBanner(environment);
if (this.bannerMode == Banner.Mode.LOG) {
try {
logger.info(createStringFromBanner(selectedBanner, environment));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
}
else {
selectedBanner.printBanner(environment, this.mainApplicationClass,
System.out);
}
}
private Banner selectBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
BANNER_LOCATION_PROPERTY_VALUE);
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader()); : new DefaultResourceLoader(getClassLoader());
Resource resource = resourceLoader.getResource(location); SpringApplicationBannerPrinter banner = new SpringApplicationBannerPrinter(resourceLoader,
if (resource.exists()) { this.banner);
return new ResourceBanner(resource); if (this.bannerMode == Mode.LOG) {
banner.print(environment, this.mainApplicationClass, logger);
} }
if (this.banner != null) { else {
return this.banner; banner.print(environment, this.mainApplicationClass, System.out);
} }
return DEFAULT_BANNER;
}
private String createStringFromBanner(Banner banner, Environment environment)
throws UnsupportedEncodingException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos));
String charset = environment.getProperty("banner.charset", "UTF-8");
return baos.toString(charset);
} }
/** /**
......
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
/**
* Class used by {@link SpringApplication} to print the application banner.
*
* @author Phillip Webb
*/
class SpringApplicationBannerPrinter {
static final String BANNER_LOCATION_PROPERTY = "banner.location";
static final String BANNER_IMAGE_LOCATION_PROPERTY = "banner.image.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
private final ResourceLoader resourceLoader;
private final Banner fallbackBanner;
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
this.resourceLoader = resourceLoader;
this.fallbackBanner = fallbackBanner;
}
public void print(Environment environment, Class<?> sourceClass, Log logger) {
Banner banner = getBanner(environment, this.fallbackBanner);
try {
logger.info(createStringFromBanner(banner, environment, sourceClass));
}
catch (UnsupportedEncodingException ex) {
logger.warn("Failed to create String for banner", ex);
}
}
public void print(Environment environment, Class<?> sourceClass, PrintStream out) {
Banner banner = getBanner(environment, this.fallbackBanner);
banner.printBanner(environment, sourceClass, out);
}
private Banner getBanner(Environment environment, Banner definedBanner) {
Banners banners = new Banners();
banners.addIfNotNull(getImageBanner(environment));
banners.addIfNotNull(getTextBanner(environment));
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
private Banner getTextBanner(Environment environment) {
String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
DEFAULT_BANNER_LOCATION);
Resource resource = this.resourceLoader.getResource(location);
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}
private Banner getImageBanner(Environment environment) {
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
if (StringUtils.hasLength(location)) {
Resource resource = this.resourceLoader.getResource(location);
return (resource.exists() ? new ImageBanner(resource) : null);
}
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
private String createStringFromBanner(Banner banner, Environment environment,
Class<?> mainApplicationClass) throws UnsupportedEncodingException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
banner.printBanner(environment, mainApplicationClass, new PrintStream(baos));
String charset = environment.getProperty("banner.charset", "UTF-8");
return baos.toString(charset);
}
/**
* {@link Banner} comprised of other {@link Banner Banners}.
*/
private static class Banners implements Banner {
private final List<Banner> banners = new ArrayList<Banner>();
public void addIfNotNull(Banner banner) {
if (banner != null) {
this.banners.add(banner);
}
}
public boolean hasAtLeastOneBanner() {
return !this.banners.isEmpty();
}
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
for (Banner banner : this.banners) {
banner.printBanner(environment, sourceClass, out);
}
}
}
}
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.ansi;
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.util.Assert;
/**
* Utility for working with {@link AnsiColor} in the context of {@link Color AWT Colors}.
*
* @author Craig Burke
* @author Ruben Dijkstra
* @author Phillip Webb
* @author Michael Simons
* @since 1.4.0
*/
public final class AnsiColors {
private static final Map<AnsiColor, LabColor> ANSI_COLOR_MAP;
static {
Map<AnsiColor, LabColor> colorMap = new LinkedHashMap<AnsiColor, LabColor>();
colorMap.put(AnsiColor.BLACK, new LabColor(0x000000));
colorMap.put(AnsiColor.RED, new LabColor(0xAA0000));
colorMap.put(AnsiColor.GREEN, new LabColor(0x00AA00));
colorMap.put(AnsiColor.YELLOW, new LabColor(0xAA5500));
colorMap.put(AnsiColor.BLUE, new LabColor(0x0000AA));
colorMap.put(AnsiColor.MAGENTA, new LabColor(0xAA00AA));
colorMap.put(AnsiColor.CYAN, new LabColor(0x00AAAA));
colorMap.put(AnsiColor.WHITE, new LabColor(0xAAAAAA));
colorMap.put(AnsiColor.BRIGHT_BLACK, new LabColor(0x555555));
colorMap.put(AnsiColor.BRIGHT_RED, new LabColor(0xFF5555));
colorMap.put(AnsiColor.BRIGHT_GREEN, new LabColor(0x55FF00));
colorMap.put(AnsiColor.BRIGHT_YELLOW, new LabColor(0xFFFF55));
colorMap.put(AnsiColor.BRIGHT_BLUE, new LabColor(0x5555FF));
colorMap.put(AnsiColor.BRIGHT_MAGENTA, new LabColor(0xFF55FF));
colorMap.put(AnsiColor.BRIGHT_CYAN, new LabColor(0x55FFFF));
colorMap.put(AnsiColor.BRIGHT_WHITE, new LabColor(0xFFFFFF));
ANSI_COLOR_MAP = Collections.unmodifiableMap(colorMap);
}
private AnsiColors() {
}
public static AnsiColor getClosest(Color color) {
return getClosest(new LabColor(color));
}
private static AnsiColor getClosest(LabColor color) {
AnsiColor result = null;
double resultDistance = Float.MAX_VALUE;
for (Entry<AnsiColor, LabColor> entry : ANSI_COLOR_MAP.entrySet()) {
double distance = color.getDistance(entry.getValue());
if (result == null || distance < resultDistance) {
resultDistance = distance;
result = entry.getKey();
}
}
return result;
}
/**
* Represents a color stored in LAB form.
*/
private static final class LabColor {
private static final ColorSpace XYZ_COLOR_SPACE = ColorSpace
.getInstance(ColorSpace.CS_CIEXYZ);
private final double l;
private final double a;
private final double b;
LabColor(Integer rgb) {
this(rgb == null ? (Color) null : new Color(rgb));
}
LabColor(Color color) {
Assert.notNull(color, "Color must not be null");
float[] lab = fromXyz(color.getColorComponents(XYZ_COLOR_SPACE, null));
this.l = lab[0];
this.a = lab[1];
this.b = lab[2];
}
private float[] fromXyz(float[] xyz) {
return fromXyz(xyz[0], xyz[1], xyz[2]);
}
private float[] fromXyz(float x, float y, float z) {
double l = (f(y) - 16.0) * 116.0;
double a = (f(x) - f(y)) * 500.0;
double b = (f(y) - f(z)) * 200.0;
return new float[] { (float) l, (float) a, (float) b };
}
private double f(double t) {
return (t > (216.0 / 24389.0) ? Math.cbrt(t)
: (1.0 / 3.0) * Math.pow(29.0 / 6.0, 2) * t + (4.0 / 29.0));
}
// See http://en.wikipedia.org/wiki/Color_difference#CIE94
public double getDistance(LabColor other) {
double c1 = Math.sqrt(this.a * this.a + this.b * this.b);
double deltaC = c1 - Math.sqrt(other.a * other.a + other.b * other.b);
double deltaA = this.a - other.a;
double deltaB = this.b - other.b;
double deltaH = Math.sqrt(
Math.max(0.0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
return Math.sqrt(Math.max(0.0,
Math.pow((this.l - other.l) / (1.0), 2)
+ Math.pow(deltaC / (1 + 0.045 * c1), 2)
+ Math.pow(deltaH / (1 + 0.015 * c1), 2.0)));
}
}
}
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.ansi;
import java.awt.Color;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AnsiColors}.
*
* @author Phillip Webb
*/
public class AnsiColorsTests {
@Test
public void getClosestWhenExactMatchShouldReturnAnsiColor() throws Exception {
assertThat(getClosest(0x000000)).isEqualTo(AnsiColor.BLACK);
assertThat(getClosest(0xAA0000)).isEqualTo(AnsiColor.RED);
assertThat(getClosest(0x00AA00)).isEqualTo(AnsiColor.GREEN);
assertThat(getClosest(0xAA5500)).isEqualTo(AnsiColor.YELLOW);
assertThat(getClosest(0x0000AA)).isEqualTo(AnsiColor.BLUE);
assertThat(getClosest(0xAA00AA)).isEqualTo(AnsiColor.MAGENTA);
assertThat(getClosest(0x00AAAA)).isEqualTo(AnsiColor.CYAN);
assertThat(getClosest(0xAAAAAA)).isEqualTo(AnsiColor.WHITE);
assertThat(getClosest(0x555555)).isEqualTo(AnsiColor.BRIGHT_BLACK);
assertThat(getClosest(0xFF5555)).isEqualTo(AnsiColor.BRIGHT_RED);
assertThat(getClosest(0x55FF00)).isEqualTo(AnsiColor.BRIGHT_GREEN);
assertThat(getClosest(0xFFFF55)).isEqualTo(AnsiColor.BRIGHT_YELLOW);
assertThat(getClosest(0x5555FF)).isEqualTo(AnsiColor.BRIGHT_BLUE);
assertThat(getClosest(0xFF55FF)).isEqualTo(AnsiColor.BRIGHT_MAGENTA);
assertThat(getClosest(0x55FFFF)).isEqualTo(AnsiColor.BRIGHT_CYAN);
assertThat(getClosest(0xFFFFFF)).isEqualTo(AnsiColor.BRIGHT_WHITE);
}
@Test
public void getClosestWhenCloseShouldReturnAnsiColor() throws Exception {
assertThat(getClosest(0x292424)).isEqualTo(AnsiColor.BLACK);
assertThat(getClosest(0x8C1919)).isEqualTo(AnsiColor.RED);
assertThat(getClosest(0x0BA10B)).isEqualTo(AnsiColor.GREEN);
assertThat(getClosest(0xB55F09)).isEqualTo(AnsiColor.YELLOW);
assertThat(getClosest(0x0B0BA1)).isEqualTo(AnsiColor.BLUE);
assertThat(getClosest(0xA312A3)).isEqualTo(AnsiColor.MAGENTA);
assertThat(getClosest(0x0BB5B5)).isEqualTo(AnsiColor.CYAN);
assertThat(getClosest(0xBAB6B6)).isEqualTo(AnsiColor.WHITE);
assertThat(getClosest(0x615A5A)).isEqualTo(AnsiColor.BRIGHT_BLACK);
assertThat(getClosest(0xF23333)).isEqualTo(AnsiColor.BRIGHT_RED);
assertThat(getClosest(0x55E80C)).isEqualTo(AnsiColor.BRIGHT_GREEN);
assertThat(getClosest(0xF5F54C)).isEqualTo(AnsiColor.BRIGHT_YELLOW);
assertThat(getClosest(0x5656F0)).isEqualTo(AnsiColor.BRIGHT_BLUE);
assertThat(getClosest(0xFA50FA)).isEqualTo(AnsiColor.BRIGHT_MAGENTA);
assertThat(getClosest(0x56F5F5)).isEqualTo(AnsiColor.BRIGHT_CYAN);
assertThat(getClosest(0xEDF5F5)).isEqualTo(AnsiColor.BRIGHT_WHITE);
}
private AnsiColor getClosest(int rgb) {
return AnsiColors.getClosest(new Color(rgb));
}
}
...@@ -13,9 +13,36 @@ ...@@ -13,9 +13,36 @@
{ {
"name": "banner.location", "name": "banner.location",
"type": "org.springframework.core.io.Resource", "type": "org.springframework.core.io.Resource",
"description": "Banner file location.", "description": "Banner text resource location.",
"defaultValue": "classpath:banner.txt" "defaultValue": "classpath:banner.txt"
}, },
{
"name": "banner.image.location",
"type": "org.springframework.core.io.Resource",
"description": "Banner image file location.",
"defaultValue": "banner.gif"
},
{
"name": "banner.image.width",
"type": "java.lang.Integer",
"description": "Banner image width (in chars)."
},
{
"name": "banner.image.height",
"type": "java.lang.Integer",
"description": "Banner image height (in chars)."
},
{
"name": "banner.image.margin",
"type": "java.lang.Integer",
"description": "Left hand image height (in chars)."
},
{
"name": "banner.image.invert",
"type": "java.lang.Boolean",
"description": "Invert images for dark console themes.",
"defaultValue": false
},
{ {
"name": "debug", "name": "debug",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
......
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.ansi.AnsiBackground;
import org.springframework.boot.ansi.AnsiColor;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.test.context.support.TestPropertySourceUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ImageBanner}.
*
* @author Craig Burke
* @author Phillip Webb
*/
public class ImageBannerTests {
private static final String NEW_LINE = System.getProperty("line.separator");
private static final char HIGH_LUMINANCE_CHARACTER = ' ';
private static final char LOW_LUMINANCE_CHARACTER = '@';
private static final String INVERT_TRUE = "banner.image.invert=true";
@Before
public void setup() {
AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS);
}
@After
public void cleanup() {
AnsiOutput.setEnabled(Enabled.DETECT);
}
@Test
public void printBannerShouldResetForegroundAndBackground() {
String banner = printBanner("black-and-white.gif");
String expected = AnsiOutput.encode(AnsiColor.DEFAULT)
+ AnsiOutput.encode(AnsiBackground.DEFAULT);
assertThat(banner).startsWith(expected);
}
@Test
public void printBannerWhenInvertedShouldResetForegroundAndBackground() {
String banner = printBanner("black-and-white.gif", INVERT_TRUE);
String expected = AnsiOutput.encode(AnsiColor.DEFAULT)
+ AnsiOutput.encode(AnsiBackground.BLACK);
assertThat(banner).startsWith(expected);
}
@Test
public void printBannerShouldPrintWhiteAsBrightWhiteHighLuminance() {
String banner = printBanner("black-and-white.gif");
String expected = AnsiOutput.encode(AnsiColor.BRIGHT_WHITE)
+ HIGH_LUMINANCE_CHARACTER;
assertThat(banner).contains(expected);
}
@Test
public void printBannerWhenInvertedShouldPrintWhiteAsBrightWhiteLowLuminance() {
String banner = printBanner("black-and-white.gif", INVERT_TRUE);
String expected = AnsiOutput.encode(AnsiColor.BRIGHT_WHITE)
+ LOW_LUMINANCE_CHARACTER;
assertThat(banner).contains(expected);
}
@Test
public void printBannerShouldPrintBlackAsBlackLowLuminance() {
String banner = printBanner("black-and-white.gif");
String expected = AnsiOutput.encode(AnsiColor.BLACK) + LOW_LUMINANCE_CHARACTER;
assertThat(banner).contains(expected);
}
@Test
public void printBannerWhenInvertedShouldPrintBlackAsBlackHighLuminance() {
String banner = printBanner("black-and-white.gif", INVERT_TRUE);
String expected = AnsiOutput.encode(AnsiColor.BLACK) + HIGH_LUMINANCE_CHARACTER;
assertThat(banner).contains(expected);
}
@Test
public void printBannerWhenShouldPrintAllColors() {
String banner = printBanner("colors.gif");
for (AnsiColor color : AnsiColor.values()) {
if (color != AnsiColor.DEFAULT) {
assertThat(banner).contains(AnsiOutput.encode(color));
}
}
}
@Test
public void printBannerShouldRenderGradient() throws Exception {
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
String banner = printBanner("gradient.gif", "banner.image.width=10",
"banner.image.margin=0");
System.out.println(banner);
assertThat(banner).contains("@#8&o:*. ");
}
@Test
public void printBannerShouldCalculateHeight() throws Exception {
String banner = printBanner("large.gif", "banner.image.width=20");
assertThat(getBannerHeight(banner)).isEqualTo(10);
}
@Test
public void printBannerWhenHasHeightPropertyShouldSetHeight() throws Exception {
String banner = printBanner("large.gif", "banner.image.width=20",
"banner.image.height=30");
assertThat(getBannerHeight(banner)).isEqualTo(30);
}
@Test
public void printBannerShouldCapWidthAndCalculateHeight() throws Exception {
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
String banner = printBanner("large.gif", "banner.image.margin=0");
assertThat(getBannerWidth(banner)).isEqualTo(76);
assertThat(getBannerHeight(banner)).isEqualTo(37);
}
@Test
public void printBannerShouldPrintMargin() throws Exception {
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
String banner = printBanner("large.gif");
String[] lines = banner.split(NEW_LINE);
for (int i = 2; i < lines.length - 1; i++) {
assertThat(lines[i]).startsWith(" @");
}
}
@Test
public void printBannerWhenHasMarginPropertShouldPrintSizedMargin() throws Exception {
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
String banner = printBanner("large.gif", "banner.image.margin=4");
String[] lines = banner.split(NEW_LINE);
for (int i = 2; i < lines.length - 1; i++) {
assertThat(lines[i]).startsWith(" @");
}
}
private int getBannerHeight(String banner) {
return banner.split(NEW_LINE).length - 3;
}
private int getBannerWidth(String banner) {
int width = 0;
for (String line : banner.split(NEW_LINE)) {
width = Math.max(width, line.length());
}
return width;
}
private String printBanner(String path, String... properties) {
ImageBanner banner = new ImageBanner(new ClassPathResource(path, getClass()));
ConfigurableEnvironment environment = new MockEnvironment();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment,
properties);
ByteArrayOutputStream out = new ByteArrayOutputStream();
banner.printBanner(environment, getClass(), new PrintStream(out));
return out.toString();
}
}
...@@ -19,8 +19,10 @@ package org.springframework.boot; ...@@ -19,8 +19,10 @@ package org.springframework.boot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
...@@ -65,7 +67,9 @@ import org.springframework.core.env.Environment; ...@@ -65,7 +67,9 @@ import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.test.context.support.TestPropertySourceUtils; import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -90,6 +94,7 @@ import static org.mockito.Mockito.verify; ...@@ -90,6 +94,7 @@ import static org.mockito.Mockito.verify;
* @author Christian Dupuis * @author Christian Dupuis
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Jeremy Rickard * @author Jeremy Rickard
* @author Craig Burke
*/ */
public class SpringApplicationTests { public class SpringApplicationTests {
...@@ -191,6 +196,29 @@ public class SpringApplicationTests { ...@@ -191,6 +196,29 @@ public class SpringApplicationTests {
.startsWith(String.format("Running a Test!%n%n123456")); .startsWith(String.format("Running a Test!%n%n123456"));
} }
@Test
public void imageBannerAndTextBanner() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
MockResourceLoader resourceLoader = new MockResourceLoader();
resourceLoader.addResource("banner.gif", "black-and-white.gif");
resourceLoader.addResource("banner.txt", "foobar.txt");
application.setWebEnvironment(false);
application.setResourceLoader(resourceLoader);
application.run();
assertThat(this.output.toString()).contains("@@@@").contains("Foo Bar");
}
@Test
public void imageBannerLoads() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
MockResourceLoader resourceLoader = new MockResourceLoader();
resourceLoader.addResource("banner.gif", "black-and-white.gif");
application.setWebEnvironment(false);
application.setResourceLoader(resourceLoader);
application.run();
assertThat(this.output.toString()).contains("@@@@@@");
}
@Test @Test
public void logsNoActiveProfiles() throws Exception { public void logsNoActiveProfiles() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class); SpringApplication application = new SpringApplication(ExampleConfig.class);
...@@ -1089,4 +1117,26 @@ public class SpringApplicationTests { ...@@ -1089,4 +1117,26 @@ public class SpringApplicationTests {
} }
} }
private static class MockResourceLoader implements ResourceLoader {
private final Map<String, Resource> resources = new HashMap<String, Resource>();
public void addResource(String source, String path) {
this.resources.put(source, new ClassPathResource(path, getClass()));
}
@Override
public Resource getResource(String path) {
Resource resource = this.resources.get(path);
return (resource == null ? new ClassPathResource("doesnotexit") : resource);
}
@Override
public ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment