Skip to content

Optimize the conversion of AnsiColor #32181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2012-2022 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
*
* https://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 org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import java.util.Objects;

/**
* ANSI Color Wrapper
*
* @author TomXin
* @since 3.0.0
*/
public class AnsiColorWrapper {

private final int code;

private final AnsiColors.BitDepth bitDepth;

/**
* Create a new {@link AnsiColorWrapper} instance with the specified bit depth.
* @param bitDepth the required bit depth
* @param code Color code, when the bit depth is 4bit, the value range of code is
* [30~37], [90~97]. When the bit depth is 8bit, the code value range is [0~255]
*/
public AnsiColorWrapper(int code, AnsiColors.BitDepth bitDepth) {
if (bitDepth == AnsiColors.BitDepth.FOUR) {
Assert.isTrue((30 <= code && code <= 37) || (90 <= code && code <= 97),
"The value of 4 bit color only supported [30~37],[90~97].");
}
Assert.isTrue((0 <= code && code <= 255), "The value of 8 bit color only supported [0~255].");
this.code = code;
this.bitDepth = bitDepth;
}

/**
* Convert to {@link AnsiElement} instance
* @param foreOrBack foreground or background
* @return {@link AnsiElement} instance
*/
public AnsiElement toAnsiElement(ForeOrBack foreOrBack) {
if (bitDepth == AnsiColors.BitDepth.FOUR) {
if (foreOrBack == ForeOrBack.FORE) {
for (AnsiColor item : AnsiColor.values()) {
if (ObjectUtils.nullSafeEquals(item.toString(), String.valueOf(this.code))) {
return item;
}
}
throw new IllegalArgumentException(String.format("No matched AnsiColor instance,code= %d", this.code));
}
for (AnsiBackground item : AnsiBackground.values()) {
if (ObjectUtils.nullSafeEquals(item.toString(), String.valueOf(this.code + 10))) {
return item;
}
}
throw new IllegalArgumentException(String.format("No matched Background instance,code= %d", this.code));
}
if (foreOrBack == ForeOrBack.FORE) {
return Ansi8BitColor.foreground(this.code);
}
return Ansi8BitColor.background(this.code);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AnsiColorWrapper that = (AnsiColorWrapper) o;
return this.code == that.code && this.bitDepth == that.bitDepth;
}

@Override
public int hashCode() {
return Objects.hash(this.code, this.bitDepth);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.awt.Color;
import java.awt.color.ColorSpace;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.Map;

Expand All @@ -32,33 +31,115 @@
* @author Ruben Dijkstra
* @author Phillip Webb
* @author Michael Simons
* @author TomXin
* @since 1.4.0
*/
public final class AnsiColors {

private static final Map<AnsiElement, LabColor> ANSI_COLOR_MAP;
private static final Map<AnsiColorWrapper, LabColor> ANSI_COLOR_MAP;

/**
* @see AnsiColor#BLACK
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BLACK = 30;

/**
* @see AnsiColor#RED
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_RED = 31;

/**
* @see AnsiColor#GREEN
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_GREEN = 32;

/**
* @see AnsiColor#YELLOW
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_YELLOW = 33;

/**
* @see AnsiColor#BLUE
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BLUE = 34;

/**
* @see AnsiColor#MAGENTA
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_MAGENTA = 35;

/**
* @see AnsiColor#CYAN
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_CYAN = 36;

/**
* @see AnsiColor#WHITE
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_WHITE = 37;

/**
* @see AnsiColor#BRIGHT_BLACK
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_BLACK = 90;

/**
* @see AnsiColor#BRIGHT_RED
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_RED = 91;

/**
* @see AnsiColor#BRIGHT_GREEN
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_GREEN = 92;

/**
* @see AnsiColor#BRIGHT_YELLOW
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_YELLOW = 93;

/**
* @see AnsiColor#BRIGHT_BLUE
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_BLUE = 94;

/**
* @see AnsiColor#BRIGHT_MAGENTA
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_MAGENTA = 95;

/**
* @see AnsiColor#BRIGHT_CYAN
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_CYAN = 96;

/**
* @see AnsiColor#BRIGHT_WHITE
*/
private static final int CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_WHITE = 97;

// @formatter:off
static {
Map<AnsiColor, LabColor> colorMap = new EnumMap<>(AnsiColor.class);
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));
Map<AnsiColorWrapper, LabColor> colorMap = new LinkedHashMap<>(16);
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BLACK, BitDepth.FOUR), new LabColor(0x000000));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_RED, BitDepth.FOUR), new LabColor(0xAA0000));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_GREEN, BitDepth.FOUR), new LabColor(0x00AA00));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_YELLOW, BitDepth.FOUR), new LabColor(0xAA5500));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BLUE, BitDepth.FOUR), new LabColor(0x0000AA));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_MAGENTA, BitDepth.FOUR), new LabColor(0xAA00AA));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_CYAN, BitDepth.FOUR), new LabColor(0x00AAAA));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_WHITE, BitDepth.FOUR), new LabColor(0xAAAAAA));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_BLACK, BitDepth.FOUR), new LabColor(0x555555));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_RED, BitDepth.FOUR), new LabColor(0xFF5555));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_GREEN, BitDepth.FOUR), new LabColor(0x55FF00));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_YELLOW, BitDepth.FOUR), new LabColor(0xFFFF55));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_BLUE, BitDepth.FOUR), new LabColor(0x5555FF));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_MAGENTA, BitDepth.FOUR), new LabColor(0xFF55FF));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_CYAN, BitDepth.FOUR), new LabColor(0x55FFFF));
colorMap.put(new AnsiColorWrapper(CODE_OF_4_BIT_ANSI_COLOR_BRIGHT_WHITE, BitDepth.FOUR), new LabColor(0xFFFFFF));
ANSI_COLOR_MAP = Collections.unmodifiableMap(colorMap);
}

// @formatter:on
private static final int[] ANSI_8BIT_COLOR_CODE_LOOKUP = new int[] { 0x000000, 0x800000, 0x008000, 0x808000,
0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff,
0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
Expand Down Expand Up @@ -87,7 +168,7 @@ public final class AnsiColors {
0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada,
0xe4e4e4, 0xeeeeee };

private final Map<AnsiElement, LabColor> lookup;
private final Map<AnsiColorWrapper, LabColor> lookup;

/**
* Create a new {@link AnsiColors} instance with the specified bit depth.
Expand All @@ -97,11 +178,11 @@ public AnsiColors(BitDepth bitDepth) {
this.lookup = getLookup(bitDepth);
}

private Map<AnsiElement, LabColor> getLookup(BitDepth bitDepth) {
private Map<AnsiColorWrapper, LabColor> getLookup(BitDepth bitDepth) {
if (bitDepth == BitDepth.EIGHT) {
Map<Ansi8BitColor, LabColor> lookup = new LinkedHashMap<>();
Map<AnsiColorWrapper, LabColor> lookup = new LinkedHashMap<>(256);
for (int i = 0; i < ANSI_8BIT_COLOR_CODE_LOOKUP.length; i++) {
lookup.put(Ansi8BitColor.foreground(i), new LabColor(ANSI_8BIT_COLOR_CODE_LOOKUP[i]));
lookup.put(new AnsiColorWrapper(i, BitDepth.EIGHT), new LabColor(ANSI_8BIT_COLOR_CODE_LOOKUP[i]));
}
return Collections.unmodifiableMap(lookup);
}
Expand All @@ -113,14 +194,14 @@ private Map<AnsiElement, LabColor> getLookup(BitDepth bitDepth) {
* @param color the AWT color
* @return the closest ANSI color
*/
public AnsiElement findClosest(Color color) {
public AnsiColorWrapper findClosest(Color color) {
return findClosest(new LabColor(color));
}

private AnsiElement findClosest(LabColor color) {
AnsiElement closest = null;
private AnsiColorWrapper findClosest(LabColor color) {
AnsiColorWrapper closest = null;
double closestDistance = Float.MAX_VALUE;
for (Map.Entry<AnsiElement, LabColor> entry : this.lookup.entrySet()) {
for (Map.Entry<AnsiColorWrapper, LabColor> entry : this.lookup.entrySet()) {
double candidateDistance = color.getDistance(entry.getValue());
if (closest == null || candidateDistance < closestDistance) {
closestDistance = candidateDistance;
Expand Down Expand Up @@ -190,12 +271,14 @@ public enum BitDepth {

/**
* 4 bits (16 color).
*
* @see AnsiColor
*/
FOUR(4),

/**
* 8 bits (256 color).
*
* @see Ansi8BitColor
*/
EIGHT(8);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2012-2022 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
*
* https://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;

/**
* Foreground or Background
*
* @author TomXin
* @since 3.0.0
*/
public enum ForeOrBack {

/**
* Foreground
*/
FORE,
/**
* Background
*/
BACK,

}
Loading