TonemapCurve.java revision 0819c75680c81a4e9c8a1ec518ac62cceccf3f56
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
17package android.hardware.camera2.params;
18
19import static com.android.internal.util.Preconditions.*;
20
21import android.graphics.PointF;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CameraDevice;
24import android.hardware.camera2.CameraMetadata;
25import android.hardware.camera2.CaptureRequest;
26import android.hardware.camera2.utils.HashCodeHelpers;
27
28import 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 */
49public 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     */
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
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
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
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     */
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     */
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     */
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
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
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
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
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
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