blob: 9a7b0b9819c5b6f53ebe4c898f7eb6cbf06797dc [file] [log] [blame]
Brad Stenning034a049372018-06-15 15:29:19 -07001/*
Heemin Seog51a86332020-04-19 01:14:12 -07002 * Copyright (C) 2020 The Android Open Source Project
Brad Stenning034a049372018-06-15 15:29:19 -07003 *
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
Heemin Seog51a86332020-04-19 01:14:12 -070014 * limitations under the License.
Brad Stenning034a049372018-06-15 15:29:19 -070015 */
16
Heemin Seog51a86332020-04-19 01:14:12 -070017package com.android.systemui.car.hvac;
Brad Stenning034a049372018-06-15 15:29:19 -070018
19import android.graphics.Color;
20
21/**
22 * Contains the logic for mapping colors to temperatures
23 */
24class TemperatureColorStore {
25
26 private static class TemperatureColorValue {
27 final float mTemperature;
28 final int mColor;
29
30 private TemperatureColorValue(float temperature, int color) {
31 this.mTemperature = temperature;
32 this.mColor = color;
33 }
34
35 float getTemperature() {
36 return mTemperature;
37 }
38
39 int getColor() {
40 return mColor;
41 }
42 }
43
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
81 int getMinColor() {
82 return COLOR_COLDEST;
83 }
84
85 int getMaxColor() {
86 return COLOR_WARMEST;
87 }
88
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
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
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
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
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}