1 /* 2 * Copyright (C) 2020 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.car.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