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