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