1 /*
2  * Copyright (c) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.hvac;
18 
19 import android.graphics.Color;
20 
21 /**
22  * Contains the logic for mapping colors to temperatures
23  */
24 class TemperatureColorStore {
25 
26     private static class TemperatureColorValue {
27         final float mTemperature;
28         final int mColor;
29 
TemperatureColorValue(float temperature, int color)30         private TemperatureColorValue(float temperature, int color) {
31             this.mTemperature = temperature;
32             this.mColor = color;
33         }
34 
getTemperature()35         float getTemperature() {
36             return mTemperature;
37         }
38 
getColor()39         int getColor() {
40             return mColor;
41         }
42     }
43 
tempToColor(float temperature, int color)44     private static TemperatureColorValue tempToColor(float temperature, int color) {
45         return new TemperatureColorValue(temperature, color);
46     }
47 
48     private static final int COLOR_COLDEST = 0xFF406DFF;
49     private static final int COLOR_COLD = 0xFF4094FF;
50     private static final int COLOR_NEUTRAL = 0xFFF4F4F4;
51     private static final int COLOR_WARM = 0xFFFF550F;
52     private static final int COLOR_WARMEST = 0xFFFF0000;
53     // must be sorted by temperature
54     private static final TemperatureColorValue[] sTemperatureColorValues =
55             {
56                     // Celsius
57                     tempToColor(19, COLOR_COLDEST),
58                     tempToColor(21, COLOR_COLD),
59                     tempToColor(23, COLOR_NEUTRAL),
60                     tempToColor(25, COLOR_WARM),
61                     tempToColor(27, COLOR_WARMEST),
62 
63                     // Switch over
64                     tempToColor(45, COLOR_WARMEST),
65                     tempToColor(45.00001f, COLOR_COLDEST),
66 
67                     // Farenheight
68                     tempToColor(66, COLOR_COLDEST),
69                     tempToColor(70, COLOR_COLD),
70                     tempToColor(74, COLOR_NEUTRAL),
71                     tempToColor(76, COLOR_WARM),
72                     tempToColor(80, COLOR_WARMEST)
73             };
74 
75     private static final int COLOR_UNSET = Color.BLACK;
76 
77     private final float[] mTempHsv1 = new float[3];
78     private final float[] mTempHsv2 = new float[3];
79     private final float[] mTempHsv3 = new float[3];
80 
getMinColor()81     int getMinColor() {
82         return COLOR_COLDEST;
83     }
84 
getMaxColor()85     int getMaxColor() {
86         return COLOR_WARMEST;
87     }
88 
getColorForTemperature(float temperature)89     int getColorForTemperature(float temperature) {
90         if (Float.isNaN(temperature)) {
91             return COLOR_UNSET;
92         }
93         TemperatureColorValue bottomValue = sTemperatureColorValues[0];
94         if (temperature <= bottomValue.getTemperature()) {
95             return bottomValue.getColor();
96         }
97         TemperatureColorValue topValue =
98                 sTemperatureColorValues[sTemperatureColorValues.length - 1];
99         if (temperature >= topValue.getTemperature()) {
100             return topValue.getColor();
101         }
102 
103         int index = binarySearch(temperature);
104         if (index >= 0) {
105             return sTemperatureColorValues[index].getColor();
106         }
107 
108         index = -index - 1; // move to the insertion point
109 
110         TemperatureColorValue startValue = sTemperatureColorValues[index - 1];
111         TemperatureColorValue endValue = sTemperatureColorValues[index];
112         float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature()
113                 - startValue.getTemperature());
114         return lerpColor(fraction, startValue.getColor(), endValue.getColor());
115     }
116 
lerpColor(float fraction, int startColor, int endColor)117     int lerpColor(float fraction, int startColor, int endColor) {
118         float[] startHsv = mTempHsv1;
119         Color.colorToHSV(startColor, startHsv);
120         float[] endHsv = mTempHsv2;
121         Color.colorToHSV(endColor, endHsv);
122 
123         // If a target color is white/gray, it should use the same hue as the other target
124         if (startHsv[1] == 0) {
125             startHsv[0] = endHsv[0];
126         }
127         if (endHsv[1] == 0) {
128             endHsv[0] = startHsv[0];
129         }
130 
131         float[] outColor = mTempHsv3;
132         outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]);
133         outColor[1] = lerp(fraction, startHsv[1], endHsv[1]);
134         outColor[2] = lerp(fraction, startHsv[2], endHsv[2]);
135 
136         return Color.HSVToColor(outColor);
137     }
138 
hueLerp(float fraction, float start, float end)139     private float hueLerp(float fraction, float start, float end) {
140         // If in flat part of curve, no interpolation necessary
141         if (start == end) {
142             return start;
143         }
144 
145         // If the hues are more than 180 degrees apart, go the other way around the color wheel
146         // by moving the smaller value above 360
147         if (Math.abs(start - end) > 180f) {
148             if (start < end) {
149                 start += 360f;
150             } else {
151                 end += 360f;
152             }
153         }
154         // Lerp and ensure the final output is within [0, 360)
155         return lerp(fraction, start, end) % 360f;
156 
157     }
158 
lerp(float fraction, float start, float end)159     private float lerp(float fraction, float start, float end) {
160         // If in flat part of curve, no interpolation necessary
161         if (start == end) {
162             return start;
163         }
164 
165         // If outside bounds, use boundary value
166         if (fraction >= 1) {
167             return end;
168         }
169         if (fraction <= 0) {
170             return start;
171         }
172 
173         return (end - start) * fraction + start;
174     }
175 
binarySearch(float temperature)176     private int binarySearch(float temperature) {
177         int low = 0;
178         int high = sTemperatureColorValues.length;
179 
180         while (low <= high) {
181             int mid = (low + high) >>> 1;
182             float midVal = sTemperatureColorValues[mid].getTemperature();
183 
184             if (midVal < temperature) {
185                 low = mid + 1;  // Neither val is NaN, thisVal is smaller
186             } else if (midVal > temperature) {
187                 high = mid - 1; // Neither val is NaN, thisVal is larger
188             } else {
189                 int midBits = Float.floatToIntBits(midVal);
190                 int keyBits = Float.floatToIntBits(temperature);
191                 if (midBits == keyBits) {    // Values are equal
192                     return mid;             // Key found
193                 } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN)
194                     low = mid + 1;
195                 } else {                        /* (0.0, -0.0) or (NaN, !NaN)*/
196                     high = mid - 1;
197                 }
198             }
199         }
200         return -(low + 1);  // key not found.
201     }
202 }
203