1 /* 2 * Copyright (C) 2024 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 android.hardware; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.hardware.flags.Flags; 22 import android.util.IntArray; 23 24 import java.util.ArrayList; 25 26 /** 27 * DisplayLuts provides the developers to apply Lookup Tables (Luts) to a 28 * {@link android.view.SurfaceControl}. Luts provides ways to control tonemapping 29 * for specific content. 30 * 31 * The general flow is as follows: 32 * <p> 33 * <img src="{@docRoot}reference/android/images/graphics/DisplayLuts.png" /> 34 * <figcaption style="text-align: center;">DisplayLuts flow</figcaption> 35 * </p> 36 * 37 * @see LutProperties 38 */ 39 @FlaggedApi(Flags.FLAG_LUTS_API) 40 public final class DisplayLuts { 41 private ArrayList<Entry> mEntries; 42 private IntArray mOffsets; 43 private int mTotalLength; 44 45 /** 46 * Create a {@link DisplayLuts} instance. 47 */ 48 @FlaggedApi(Flags.FLAG_LUTS_API) DisplayLuts()49 public DisplayLuts() { 50 mEntries = new ArrayList<>(); 51 mOffsets = new IntArray(); 52 mTotalLength = 0; 53 } 54 55 @FlaggedApi(Flags.FLAG_LUTS_API) 56 public static class Entry { 57 private float[] mBuffer; 58 private @LutProperties.Dimension int mDimension; 59 private int mSize; 60 private @LutProperties.SamplingKey int mSamplingKey; 61 62 private static final int LUT_LENGTH_LIMIT = 100000; 63 64 /** 65 * Create a Lut entry. 66 * 67 * <p> 68 * Noted that 1D Lut(s) are treated as gain curves. 69 * For 3D Lut(s), 3D Lut(s) are used for direct color manipulations. 70 * The values of 3D Lut(s) data should be normalized to the range {@code 0.0} 71 * to {@code 1.0}, inclusive. And 3D Lut(s) data is organized in the order of 72 * R, G, B channels. 73 * 74 * @param buffer The raw lut data 75 * @param dimension Either 1D or 3D 76 * @param samplingKey The sampling kay used for the Lut 77 */ 78 @FlaggedApi(Flags.FLAG_LUTS_API) Entry(@onNull float[] buffer, @LutProperties.Dimension int dimension, @LutProperties.SamplingKey int samplingKey)79 public Entry(@NonNull float[] buffer, 80 @LutProperties.Dimension int dimension, 81 @LutProperties.SamplingKey int samplingKey) { 82 if (buffer == null || buffer.length < 1) { 83 throw new IllegalArgumentException("The buffer cannot be empty!"); 84 } 85 86 if (buffer.length >= LUT_LENGTH_LIMIT) { 87 throw new IllegalArgumentException("The lut length is too big to handle!"); 88 } 89 90 if (dimension != LutProperties.ONE_DIMENSION 91 && dimension != LutProperties.THREE_DIMENSION) { 92 throw new IllegalArgumentException("The dimension should be either 1D or 3D!"); 93 } 94 95 if (dimension == LutProperties.THREE_DIMENSION) { 96 if (buffer.length <= 3) { 97 throw new IllegalArgumentException( 98 "The 3d lut size of each dimension should be over 1!"); 99 } 100 int lengthPerChannel = buffer.length; 101 if (lengthPerChannel % 3 != 0) { 102 throw new IllegalArgumentException( 103 "The lut buffer of 3dlut should have 3 channels!"); 104 } 105 lengthPerChannel /= 3; 106 107 double size = Math.cbrt(lengthPerChannel); 108 if (size == (int) size) { 109 mSize = (int) size; 110 } else { 111 throw new IllegalArgumentException( 112 "Cannot get the cube root of the 3d lut buffer!"); 113 } 114 } else { 115 mSize = buffer.length; 116 } 117 118 mBuffer = buffer; 119 mDimension = dimension; 120 mSamplingKey = samplingKey; 121 } 122 123 /** 124 * @return the dimension of the lut entry 125 */ 126 @FlaggedApi(Flags.FLAG_LUTS_API) getDimension()127 public int getDimension() { 128 return mDimension; 129 } 130 131 /** 132 * @return the size of the lut for each dimension 133 * @hide 134 */ getSize()135 public int getSize() { 136 return mSize; 137 } 138 139 /** 140 * @return the lut raw data of the lut 141 */ 142 @FlaggedApi(Flags.FLAG_LUTS_API) getBuffer()143 public @NonNull float[] getBuffer() { 144 return mBuffer; 145 } 146 147 /** 148 * @return the sampling key used by the lut 149 */ 150 @FlaggedApi(Flags.FLAG_LUTS_API) getSamplingKey()151 public int getSamplingKey() { 152 return mSamplingKey; 153 } 154 155 @Override toString()156 public String toString() { 157 return "Entry{" 158 + "dimension=" + DisplayLuts.Entry.dimensionToString(getDimension()) 159 + ", size(each dimension)=" + getSize() 160 + ", samplingKey=" + samplingKeyToString(getSamplingKey()) + "}"; 161 } 162 dimensionToString(int dimension)163 private static String dimensionToString(int dimension) { 164 switch(dimension) { 165 case LutProperties.ONE_DIMENSION: 166 return "ONE_DIMENSION"; 167 case LutProperties.THREE_DIMENSION: 168 return "THREE_DIMENSION"; 169 default: 170 return ""; 171 } 172 } 173 samplingKeyToString(int key)174 private static String samplingKeyToString(int key) { 175 switch(key) { 176 case LutProperties.SAMPLING_KEY_RGB: 177 return "SAMPLING_KEY_RGB"; 178 case LutProperties.SAMPLING_KEY_MAX_RGB: 179 return "SAMPLING_KEY_MAX_RGB"; 180 case LutProperties.SAMPLING_KEY_CIE_Y: 181 return "SAMPLING_KEY_CIE_Y"; 182 default: 183 return ""; 184 } 185 } 186 } 187 188 @Override toString()189 public String toString() { 190 StringBuilder sb = new StringBuilder("DisplayLuts{"); 191 sb.append("\n"); 192 for (DisplayLuts.Entry entry: mEntries) { 193 sb.append(entry.toString()); 194 sb.append("\n"); 195 } 196 sb.append("}"); 197 return sb.toString(); 198 } 199 addEntry(Entry entry)200 private void addEntry(Entry entry) { 201 mEntries.add(entry); 202 mOffsets.add(mTotalLength); 203 mTotalLength += entry.getBuffer().length; 204 } 205 clear()206 private void clear() { 207 mOffsets.clear(); 208 mTotalLength = 0; 209 mEntries.clear(); 210 } 211 212 /** 213 * Set a Lut to be applied. 214 * 215 * <p>Use either this or {@link #set(Entry, Entry)}. The function will 216 * replace any previously set lut(s).</p> 217 * 218 * @param entry Either an 1D Lut or a 3D Lut 219 */ 220 @FlaggedApi(Flags.FLAG_LUTS_API) set(@onNull Entry entry)221 public void set(@NonNull Entry entry) { 222 if (entry == null) { 223 throw new IllegalArgumentException("The entry is null!"); 224 } 225 clear(); 226 addEntry(entry); 227 } 228 229 /** 230 * Set Luts in order to be applied. 231 * 232 * <p> An 1D Lut and 3D Lut will be applied in order. Use either this or 233 * {@link #set(Entry)}. The function will replace any previously set lut(s)</p> 234 * 235 * @param first An 1D Lut 236 * @param second A 3D Lut 237 */ 238 @FlaggedApi(Flags.FLAG_LUTS_API) set(@onNull Entry first, @NonNull Entry second)239 public void set(@NonNull Entry first, @NonNull Entry second) { 240 if (first == null || second == null) { 241 throw new IllegalArgumentException("The entry is null!"); 242 } 243 if (first.getDimension() != LutProperties.ONE_DIMENSION 244 || second.getDimension() != LutProperties.THREE_DIMENSION) { 245 throw new IllegalArgumentException("The entries should be 1D and 3D in order!"); 246 } 247 clear(); 248 addEntry(first); 249 addEntry(second); 250 } 251 252 /** 253 * @hide 254 */ valid()255 public boolean valid() { 256 return mEntries.size() > 0; 257 } 258 259 /** 260 * @hide 261 */ getLutBuffers()262 public float[] getLutBuffers() { 263 float[] buffer = new float[mTotalLength]; 264 265 for (int i = 0; i < mEntries.size(); i++) { 266 float[] lutBuffer = mEntries.get(i).getBuffer(); 267 System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length); 268 } 269 return buffer; 270 } 271 272 /** 273 * @hide 274 */ getOffsets()275 public int[] getOffsets() { 276 return mOffsets.toArray(); 277 } 278 279 /** 280 * @hide 281 */ getLutSizes()282 public int[] getLutSizes() { 283 int[] sizes = new int[mEntries.size()]; 284 for (int i = 0; i < mEntries.size(); i++) { 285 sizes[i] = mEntries.get(i).getSize(); 286 } 287 return sizes; 288 } 289 290 /** 291 * @hide 292 */ getLutDimensions()293 public int[] getLutDimensions() { 294 int[] dimensions = new int[mEntries.size()]; 295 for (int i = 0; i < mEntries.size(); i++) { 296 dimensions[i] = mEntries.get(i).getDimension(); 297 } 298 return dimensions; 299 } 300 301 /** 302 * @hide 303 */ getLutSamplingKeys()304 public int[] getLutSamplingKeys() { 305 int[] samplingKeys = new int[mEntries.size()]; 306 for (int i = 0; i < mEntries.size(); i++) { 307 samplingKeys[i] = mEntries.get(i).getSamplingKey(); 308 } 309 return samplingKeys; 310 } 311 } 312