• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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