TonemapCurve.java revision 83a9e4d86407d627f3f6fbf8757d2a389097ab6f
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.</p>
85     *
86     * <p>All sub-elements must be in the inclusive range of
87     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
88     *
89     * <p>This constructor copies the array contents and does not retain ownership of the array.</p>
90     *
91     * @param red An array of elements whose length is divisible by {@value #POINT_SIZE}
92     * @param green An array of elements whose length is divisible by {@value #POINT_SIZE}
93     * @param blue An array of elements whose length is divisible by {@value #POINT_SIZE}
94     *
95     * @throws IllegalArgumentException
96     *            if any of input array length is invalid,
97     *            or if any of the elements in the array are not in the range of
98     *            [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}]
99     * @throws NullPointerException
100     *            if any of the parameters are {@code null}
101     */
102    public TonemapCurve(float[] red, float[] green, float[] blue) {
103        // TODO: maxCurvePoints check?
104
105        checkNotNull(red, "red must not be null");
106        checkNotNull(green, "green must not be null");
107        checkNotNull(blue, "blue must not be null");
108
109        checkArgumentArrayLengthDivisibleBy(red, POINT_SIZE, "red");
110        checkArgumentArrayLengthDivisibleBy(green, POINT_SIZE, "green");
111        checkArgumentArrayLengthDivisibleBy(blue, POINT_SIZE, "blue");
112
113        checkArrayElementsInRange(red, LEVEL_BLACK, LEVEL_WHITE, "red");
114        checkArrayElementsInRange(green, LEVEL_BLACK, LEVEL_WHITE, "green");
115        checkArrayElementsInRange(blue, LEVEL_BLACK, LEVEL_WHITE, "blue");
116
117        mRed = Arrays.copyOf(red, red.length);
118        mGreen = Arrays.copyOf(green, green.length);
119        mBlue = Arrays.copyOf(blue, blue.length);
120    }
121
122    private static void checkArgumentArrayLengthDivisibleBy(float[] array,
123            int divisible, String arrayName) {
124        if (array.length % divisible != 0) {
125            throw new IllegalArgumentException(arrayName + " size must be divisible by "
126                    + divisible);
127        }
128    }
129
130    private static int checkArgumentColorChannel(int colorChannel) {
131        switch (colorChannel) {
132            case CHANNEL_RED:
133            case CHANNEL_GREEN:
134            case CHANNEL_BLUE:
135                break;
136            default:
137                throw new IllegalArgumentException("colorChannel out of range");
138        }
139
140        return colorChannel;
141    }
142
143    /**
144     * Get the number of points stored in this tonemap curve for the specified color channel.
145     *
146     * @param colorChannel one of {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, {@link #CHANNEL_BLUE}
147     * @return number of points stored in this tonemap for that color's curve (>= 0)
148     *
149     * @throws IllegalArgumentException if {@code colorChannel} was out of range
150     */
151    public int getPointCount(int colorChannel) {
152        checkArgumentColorChannel(colorChannel);
153
154        return getCurve(colorChannel).length / POINT_SIZE;
155    }
156
157    /**
158     * Get the point for a color channel at a specified index.
159     *
160     * <p>The index must be at least 0 but no greater than {@link #getPointCount(int)} for
161     * that {@code colorChannel}.</p>
162     *
163     * <p>All returned coordinates in the point are between the range of
164     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
165     *
166     * @param colorChannel {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, or {@link #CHANNEL_BLUE}
167     * @param index at least 0 but no greater than {@code getPointCount(colorChannel)}
168     * @return the {@code (Pin, Pout)} pair mapping the tone for that index
169     *
170     * @throws IllegalArgumentException if {@code colorChannel} or {@code index} was out of range
171     *
172     * @see #LEVEL_BLACK
173     * @see #LEVEL_WHITE
174     */
175    public PointF getPoint(int colorChannel, int index) {
176        checkArgumentColorChannel(colorChannel);
177        if (index < 0 || index >= getPointCount(colorChannel)) {
178            throw new IllegalArgumentException("index out of range");
179        }
180
181        final float[] curve = getCurve(colorChannel);
182
183        final float pIn = curve[index * POINT_SIZE + OFFSET_POINT_IN];
184        final float pOut = curve[index * POINT_SIZE + OFFSET_POINT_OUT];
185
186        return new PointF(pIn, pOut);
187    }
188
189    /**
190     * Copy the color curve for a single color channel from this tonemap curve into the destination.
191     *
192     * <p>
193     * <!--The output is encoded the same as in the constructor -->
194     * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of
195     * {@link #getPointCount} points for that respective channel.</p>
196     *
197     * <p>All returned coordinates are between the range of
198     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
199     *
200     * @param destination
201     *          an array big enough to hold at least {@link #getPointCount} {@code *}
202     *          {@link #POINT_SIZE} elements after the {@code offset}
203     * @param offset
204     *          a non-negative offset into the array
205     * @throws NullPointerException
206     *          If {@code destination} was {@code null}
207     * @throws IllegalArgumentException
208     *          If offset was negative
209     * @throws ArrayIndexOutOfBoundsException
210     *          If there's not enough room to write the elements at the specified destination and
211     *          offset.
212     *
213     * @see CaptureRequest#TONEMAP_CURVE_BLUE
214     * @see CaptureRequest#TONEMAP_CURVE_RED
215     * @see CaptureRequest#TONEMAP_CURVE_GREEN
216     * @see #LEVEL_BLACK
217     * @see #LEVEL_WHITE
218     */
219    public void copyColorCurve(int colorChannel, float[] destination,
220            int offset) {
221        checkArgumentNonnegative(offset, "offset must not be negative");
222        checkNotNull(destination, "destination must not be null");
223
224        if (destination.length + offset < getPointCount(colorChannel) * POINT_SIZE) {
225            throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
226        }
227
228        float[] curve = getCurve(colorChannel);
229        System.arraycopy(curve, /*srcPos*/0, destination, offset, curve.length);
230    }
231
232    /**
233     * Check if this TonemapCurve is equal to another TonemapCurve.
234     *
235     * <p>Two matrices are equal if and only if all of their elements are
236     * {@link Object#equals equal}.</p>
237     *
238     * @return {@code true} if the objects were equal, {@code false} otherwise
239     */
240    @Override
241    public boolean equals(Object obj) {
242        if (obj == null) {
243            return false;
244        }
245        if (this == obj) {
246            return true;
247        }
248        if (obj instanceof TonemapCurve) {
249            final TonemapCurve other = (TonemapCurve) obj;
250            return Arrays.equals(mRed, other.mRed) &&
251                    Arrays.equals(mGreen, other.mGreen) &&
252                    Arrays.equals(mBlue, other.mBlue);
253        }
254        return false;
255    }
256
257    /**
258     * {@inheritDoc}
259     */
260    @Override
261    public int hashCode() {
262        if (mHashCalculated) {
263            // Avoid re-calculating hash. Data is immutable so this is both legal and faster.
264            return mHashCode;
265        }
266
267        mHashCode = HashCodeHelpers.hashCode(mRed, mGreen, mBlue);
268        mHashCalculated = true;
269
270        return mHashCode;
271    }
272
273    private float[] getCurve(int colorChannel) {
274        switch (colorChannel) {
275            case CHANNEL_RED:
276                return mRed;
277            case CHANNEL_GREEN:
278                return mGreen;
279            case CHANNEL_BLUE:
280                return mBlue;
281            default:
282                throw new AssertionError("colorChannel out of range");
283        }
284    }
285
286    private final static int OFFSET_POINT_IN = 0;
287    private final static int OFFSET_POINT_OUT = 1;
288
289    private final float[] mRed;
290    private final float[] mGreen;
291    private final float[] mBlue;
292
293    private int mHashCode;
294    private boolean mHashCalculated = false;
295};
296