001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.ansi;
018
019import java.awt.Color;
020import java.awt.color.ColorSpace;
021import java.util.Collections;
022import java.util.EnumMap;
023import java.util.Map;
024import java.util.Map.Entry;
025
026import org.springframework.util.Assert;
027
028/**
029 * Utility for working with {@link AnsiColor} in the context of {@link Color AWT Colors}.
030 *
031 * @author Craig Burke
032 * @author Ruben Dijkstra
033 * @author Phillip Webb
034 * @author Michael Simons
035 * @since 1.4.0
036 */
037public final class AnsiColors {
038
039        private static final Map<AnsiColor, LabColor> ANSI_COLOR_MAP;
040
041        static {
042                Map<AnsiColor, LabColor> colorMap = new EnumMap<>(AnsiColor.class);
043                colorMap.put(AnsiColor.BLACK, new LabColor(0x000000));
044                colorMap.put(AnsiColor.RED, new LabColor(0xAA0000));
045                colorMap.put(AnsiColor.GREEN, new LabColor(0x00AA00));
046                colorMap.put(AnsiColor.YELLOW, new LabColor(0xAA5500));
047                colorMap.put(AnsiColor.BLUE, new LabColor(0x0000AA));
048                colorMap.put(AnsiColor.MAGENTA, new LabColor(0xAA00AA));
049                colorMap.put(AnsiColor.CYAN, new LabColor(0x00AAAA));
050                colorMap.put(AnsiColor.WHITE, new LabColor(0xAAAAAA));
051                colorMap.put(AnsiColor.BRIGHT_BLACK, new LabColor(0x555555));
052                colorMap.put(AnsiColor.BRIGHT_RED, new LabColor(0xFF5555));
053                colorMap.put(AnsiColor.BRIGHT_GREEN, new LabColor(0x55FF00));
054                colorMap.put(AnsiColor.BRIGHT_YELLOW, new LabColor(0xFFFF55));
055                colorMap.put(AnsiColor.BRIGHT_BLUE, new LabColor(0x5555FF));
056                colorMap.put(AnsiColor.BRIGHT_MAGENTA, new LabColor(0xFF55FF));
057                colorMap.put(AnsiColor.BRIGHT_CYAN, new LabColor(0x55FFFF));
058                colorMap.put(AnsiColor.BRIGHT_WHITE, new LabColor(0xFFFFFF));
059                ANSI_COLOR_MAP = Collections.unmodifiableMap(colorMap);
060        }
061
062        private AnsiColors() {
063        }
064
065        public static AnsiColor getClosest(Color color) {
066                return getClosest(new LabColor(color));
067        }
068
069        private static AnsiColor getClosest(LabColor color) {
070                AnsiColor result = null;
071                double resultDistance = Float.MAX_VALUE;
072                for (Entry<AnsiColor, LabColor> entry : ANSI_COLOR_MAP.entrySet()) {
073                        double distance = color.getDistance(entry.getValue());
074                        if (result == null || distance < resultDistance) {
075                                resultDistance = distance;
076                                result = entry.getKey();
077                        }
078                }
079                return result;
080        }
081
082        /**
083         * Represents a color stored in LAB form.
084         */
085        private static final class LabColor {
086
087                private static final ColorSpace XYZ_COLOR_SPACE = ColorSpace
088                                .getInstance(ColorSpace.CS_CIEXYZ);
089
090                private final double l;
091
092                private final double a;
093
094                private final double b;
095
096                LabColor(Integer rgb) {
097                        this((rgb != null) ? new Color(rgb) : null);
098                }
099
100                LabColor(Color color) {
101                        Assert.notNull(color, "Color must not be null");
102                        float[] lab = fromXyz(color.getColorComponents(XYZ_COLOR_SPACE, null));
103                        this.l = lab[0];
104                        this.a = lab[1];
105                        this.b = lab[2];
106                }
107
108                private float[] fromXyz(float[] xyz) {
109                        return fromXyz(xyz[0], xyz[1], xyz[2]);
110                }
111
112                private float[] fromXyz(float x, float y, float z) {
113                        double l = (f(y) - 16.0) * 116.0;
114                        double a = (f(x) - f(y)) * 500.0;
115                        double b = (f(y) - f(z)) * 200.0;
116                        return new float[] { (float) l, (float) a, (float) b };
117                }
118
119                private double f(double t) {
120                        return (t > (216.0 / 24389.0)) ? Math.cbrt(t)
121                                        : (1.0 / 3.0) * Math.pow(29.0 / 6.0, 2) * t + (4.0 / 29.0);
122                }
123
124                // See http://en.wikipedia.org/wiki/Color_difference#CIE94
125                public double getDistance(LabColor other) {
126                        double c1 = Math.sqrt(this.a * this.a + this.b * this.b);
127                        double deltaC = c1 - Math.sqrt(other.a * other.a + other.b * other.b);
128                        double deltaA = this.a - other.a;
129                        double deltaB = this.b - other.b;
130                        double deltaH = Math.sqrt(
131                                        Math.max(0.0, deltaA * deltaA + deltaB * deltaB - deltaC * deltaC));
132                        return Math.sqrt(Math.max(0.0,
133                                        Math.pow((this.l - other.l) / (1.0), 2)
134                                                        + Math.pow(deltaC / (1 + 0.045 * c1), 2)
135                                                        + Math.pow(deltaH / (1 + 0.015 * c1), 2.0)));
136                }
137
138        }
139
140}