1 /*
2  * Copyright (C) 2014 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.camera2.params;
18 
19 import static com.android.internal.util.Preconditions.*;
20 
21 import android.graphics.PointF;
22 import android.hardware.camera2.CameraCharacteristics;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CameraMetadata;
25 import android.hardware.camera2.CaptureRequest;
26 import android.hardware.camera2.utils.HashCodeHelpers;
27 
28 import java.util.Arrays;
29 
30 /**
31  * Immutable class for describing a {@code 2 x M x 3} tonemap curve of floats.
32  *
33  * <p>This defines red, green, and blue curves that the {@link CameraDevice} will
34  * use as the tonemapping/contrast/gamma curve when {@link CaptureRequest#TONEMAP_MODE} is
35  * set to {@link CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE}.</p>
36  *
37  * <p>For a camera device with
38  * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME
39  * MONOCHROME} capability, all 3 channels will contain the same set of control points.
40  *
41  * <p>The total number of points {@code (Pin, Pout)} for each color channel can be no more than
42  * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS}.</p>
43  *
44  * <p>The coordinate system for each point is within the inclusive range
45  * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
46  *
47  * @see CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE
48  * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
49  */
50 public final class TonemapCurve {
51     /**
52      * Lower bound tonemap value corresponding to pure black for a single color channel.
53      */
54     public static final float LEVEL_BLACK = 0.0f;
55 
56     /**
57      * Upper bound tonemap value corresponding to a pure white for a single color channel.
58      */
59     public static final float LEVEL_WHITE = 1.0f;
60 
61     /**
62      * Number of elements in a {@code (Pin, Pout)} point;
63      */
64     public static final int POINT_SIZE = 2;
65 
66     /**
67      * Index of the red color channel curve.
68      */
69     public static final int CHANNEL_RED = 0;
70     /**
71      * Index of the green color channel curve.
72      */
73     public static final int CHANNEL_GREEN = 1;
74     /**
75      * Index of the blue color channel curve.
76      */
77     public static final int CHANNEL_BLUE = 2;
78 
79     /**
80      * Create a new immutable TonemapCurve instance.
81      *
82      * <p>Values are stored as a contiguous array of {@code (Pin, Pout)} points.</p>
83      *
84      * <p>All parameters may have independent length but should have at most
85      * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS} * {@value #POINT_SIZE} elements and
86      * at least 2 * {@value #POINT_SIZE} elements.</p>
87      *
88      * <p>All sub-elements must be in the inclusive range of
89      * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
90      *
91      * <p>This constructor copies the array contents and does not retain ownership of the array.</p>
92      *
93      * @param red An array of elements whose length is divisible by {@value #POINT_SIZE}
94      * @param green An array of elements whose length is divisible by {@value #POINT_SIZE}
95      * @param blue An array of elements whose length is divisible by {@value #POINT_SIZE}
96      *
97      * @throws IllegalArgumentException
98      *            if any of input array length is invalid,
99      *            or if any of the elements in the array are not in the range of
100      *            [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}]
101      * @throws NullPointerException
102      *            if any of the parameters are {@code null}
103      */
TonemapCurve(float[] red, float[] green, float[] blue)104     public TonemapCurve(float[] red, float[] green, float[] blue) {
105         // TODO: maxCurvePoints check?
106 
107         checkNotNull(red, "red must not be null");
108         checkNotNull(green, "green must not be null");
109         checkNotNull(blue, "blue must not be null");
110 
111         checkArgumentArrayLengthDivisibleBy(red, POINT_SIZE, "red");
112         checkArgumentArrayLengthDivisibleBy(green, POINT_SIZE, "green");
113         checkArgumentArrayLengthDivisibleBy(blue, POINT_SIZE, "blue");
114 
115         checkArgumentArrayLengthNoLessThan(red, MIN_CURVE_LENGTH, "red");
116         checkArgumentArrayLengthNoLessThan(green, MIN_CURVE_LENGTH, "green");
117         checkArgumentArrayLengthNoLessThan(blue, MIN_CURVE_LENGTH, "blue");
118 
119         checkArrayElementsInRange(red, LEVEL_BLACK, LEVEL_WHITE, "red");
120         checkArrayElementsInRange(green, LEVEL_BLACK, LEVEL_WHITE, "green");
121         checkArrayElementsInRange(blue, LEVEL_BLACK, LEVEL_WHITE, "blue");
122 
123         mRed = Arrays.copyOf(red, red.length);
124         mGreen = Arrays.copyOf(green, green.length);
125         mBlue = Arrays.copyOf(blue, blue.length);
126     }
127 
checkArgumentArrayLengthDivisibleBy(float[] array, int divisible, String arrayName)128     private static void checkArgumentArrayLengthDivisibleBy(float[] array,
129             int divisible, String arrayName) {
130         if (array.length % divisible != 0) {
131             throw new IllegalArgumentException(arrayName + " size must be divisible by "
132                     + divisible);
133         }
134     }
135 
checkArgumentColorChannel(int colorChannel)136     private static int checkArgumentColorChannel(int colorChannel) {
137         switch (colorChannel) {
138             case CHANNEL_RED:
139             case CHANNEL_GREEN:
140             case CHANNEL_BLUE:
141                 break;
142             default:
143                 throw new IllegalArgumentException("colorChannel out of range");
144         }
145 
146         return colorChannel;
147     }
148 
checkArgumentArrayLengthNoLessThan(float[] array, int minLength, String arrayName)149     private static void checkArgumentArrayLengthNoLessThan(float[] array, int minLength,
150             String arrayName) {
151         if (array.length < minLength) {
152             throw new IllegalArgumentException(arrayName + " size must be at least "
153                     + minLength);
154         }
155     }
156 
157     /**
158      * Get the number of points stored in this tonemap curve for the specified color channel.
159      *
160      * @param colorChannel one of {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, {@link #CHANNEL_BLUE}
161      * @return number of points stored in this tonemap for that color's curve (>= 0)
162      *
163      * @throws IllegalArgumentException if {@code colorChannel} was out of range
164      */
getPointCount(int colorChannel)165     public int getPointCount(int colorChannel) {
166         checkArgumentColorChannel(colorChannel);
167 
168         return getCurve(colorChannel).length / POINT_SIZE;
169     }
170 
171     /**
172      * Get the point for a color channel at a specified index.
173      *
174      * <p>The index must be at least 0 but no greater than {@link #getPointCount(int)} for
175      * that {@code colorChannel}.</p>
176      *
177      * <p>All returned coordinates in the point are between the range of
178      * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
179      *
180      * @param colorChannel {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, or {@link #CHANNEL_BLUE}
181      * @param index at least 0 but no greater than {@code getPointCount(colorChannel)}
182      * @return the {@code (Pin, Pout)} pair mapping the tone for that index
183      *
184      * @throws IllegalArgumentException if {@code colorChannel} or {@code index} was out of range
185      *
186      * @see #LEVEL_BLACK
187      * @see #LEVEL_WHITE
188      */
getPoint(int colorChannel, int index)189     public PointF getPoint(int colorChannel, int index) {
190         checkArgumentColorChannel(colorChannel);
191         if (index < 0 || index >= getPointCount(colorChannel)) {
192             throw new IllegalArgumentException("index out of range");
193         }
194 
195         final float[] curve = getCurve(colorChannel);
196 
197         final float pIn = curve[index * POINT_SIZE + OFFSET_POINT_IN];
198         final float pOut = curve[index * POINT_SIZE + OFFSET_POINT_OUT];
199 
200         return new PointF(pIn, pOut);
201     }
202 
203     /**
204      * Copy the color curve for a single color channel from this tonemap curve into the destination.
205      *
206      * <p>
207      * <!--The output is encoded the same as in the constructor -->
208      * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of
209      * {@link #getPointCount} points for that respective channel.</p>
210      *
211      * <p>All returned coordinates are between the range of
212      * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
213      *
214      * @param destination
215      *          an array big enough to hold at least {@link #getPointCount} {@code *}
216      *          {@link #POINT_SIZE} elements after the {@code offset}
217      * @param offset
218      *          a non-negative offset into the array
219      * @throws NullPointerException
220      *          If {@code destination} was {@code null}
221      * @throws IllegalArgumentException
222      *          If offset was negative
223      * @throws ArrayIndexOutOfBoundsException
224      *          If there's not enough room to write the elements at the specified destination and
225      *          offset.
226      *
227      * @see #LEVEL_BLACK
228      * @see #LEVEL_WHITE
229      */
copyColorCurve(int colorChannel, float[] destination, int offset)230     public void copyColorCurve(int colorChannel, float[] destination,
231             int offset) {
232         checkArgumentNonnegative(offset, "offset must not be negative");
233         checkNotNull(destination, "destination must not be null");
234 
235         if (destination.length + offset < getPointCount(colorChannel) * POINT_SIZE) {
236             throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
237         }
238 
239         float[] curve = getCurve(colorChannel);
240         System.arraycopy(curve, /*srcPos*/0, destination, offset, curve.length);
241     }
242 
243     /**
244      * Check if this TonemapCurve is equal to another TonemapCurve.
245      *
246      * <p>Two matrices are equal if and only if all of their elements are
247      * {@link Object#equals equal}.</p>
248      *
249      * @return {@code true} if the objects were equal, {@code false} otherwise
250      */
251     @Override
equals(Object obj)252     public boolean equals(Object obj) {
253         if (obj == null) {
254             return false;
255         }
256         if (this == obj) {
257             return true;
258         }
259         if (obj instanceof TonemapCurve) {
260             final TonemapCurve other = (TonemapCurve) obj;
261             return Arrays.equals(mRed, other.mRed) &&
262                     Arrays.equals(mGreen, other.mGreen) &&
263                     Arrays.equals(mBlue, other.mBlue);
264         }
265         return false;
266     }
267 
268     /**
269      * {@inheritDoc}
270      */
271     @Override
hashCode()272     public int hashCode() {
273         if (mHashCalculated) {
274             // Avoid re-calculating hash. Data is immutable so this is both legal and faster.
275             return mHashCode;
276         }
277 
278         mHashCode = HashCodeHelpers.hashCodeGeneric(mRed, mGreen, mBlue);
279         mHashCalculated = true;
280 
281         return mHashCode;
282     }
283 
284     /**
285      * Return the TonemapCurve as a string representation.
286      *
287      * <p> {@code "TonemapCurve{R:[(%f, %f), (%f, %f) ... (%f, %f)], G:[(%f, %f), (%f, %f) ...
288      * (%f, %f)], B:[(%f, %f), (%f, %f) ... (%f, %f)]}"},
289      * where each {@code (%f, %f)} respectively represents one point of the corresponding
290      * tonemap curve. </p>
291      *
292      * @return string representation of {@link TonemapCurve}
293      */
294     @Override
toString()295     public String toString() {
296         StringBuilder sb = new StringBuilder("TonemapCurve{");
297         sb.append("R:");
298         sb.append(curveToString(CHANNEL_RED));
299         sb.append(", G:");
300         sb.append(curveToString(CHANNEL_GREEN));
301         sb.append(", B:");
302         sb.append(curveToString(CHANNEL_BLUE));
303         sb.append("}");
304         return sb.toString();
305     }
306 
curveToString(int colorChannel)307     private String curveToString(int colorChannel) {
308         checkArgumentColorChannel(colorChannel);
309         StringBuilder sb = new StringBuilder("[");
310         float[] curve = getCurve(colorChannel);
311         int pointCount = curve.length / POINT_SIZE;
312         for (int i = 0, j = 0; i < pointCount; i++, j += 2) {
313             sb.append("(");
314             sb.append(curve[j]);
315             sb.append(", ");
316             sb.append(curve[j+1]);
317             sb.append("), ");
318         }
319         // trim extra ", " at the end. Guaranteed to work because pointCount >= 2
320         sb.setLength(sb.length() - 2);
321         sb.append("]");
322         return sb.toString();
323     }
324 
getCurve(int colorChannel)325     private float[] getCurve(int colorChannel) {
326         switch (colorChannel) {
327             case CHANNEL_RED:
328                 return mRed;
329             case CHANNEL_GREEN:
330                 return mGreen;
331             case CHANNEL_BLUE:
332                 return mBlue;
333             default:
334                 throw new AssertionError("colorChannel out of range");
335         }
336     }
337 
338     private final static int OFFSET_POINT_IN = 0;
339     private final static int OFFSET_POINT_OUT = 1;
340     private final static int TONEMAP_MIN_CURVE_POINTS = 2;
341     private final static int MIN_CURVE_LENGTH = TONEMAP_MIN_CURVE_POINTS * POINT_SIZE;
342 
343     private final float[] mRed;
344     private final float[] mGreen;
345     private final float[] mBlue;
346 
347     private int mHashCode;
348     private boolean mHashCalculated = false;
349 }
350