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