summaryrefslogtreecommitdiffstats
path: root/core/java/android/hardware/camera2/params/TonemapCurve.java
blob: 2d7bbaa21c54a05af41080ff8535c2c8aa5be2db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.hardware.camera2.params;

import static com.android.internal.util.Preconditions.*;

import android.graphics.PointF;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;

import java.util.Arrays;

/**
 * Immutable class for describing a {@code 2 x M x 3} tonemap curve of floats.
 *
 * <p>This defines red, green, and blue curves that the {@link CameraDevice} will
 * use as the tonemapping/contrast/gamma curve when {@link CaptureRequest#TONEMAP_MODE} is
 * set to {@link CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE}.</p>
 *
 * <p>The total number of points {@code (Pin, Pout)} for each color channel can be no more than
 * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS}.</p>
 *
 * <p>The coordinate system for each point is within the inclusive range
 * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
 *
 * @see CaptureRequest#TONEMAP_CURVE_BLUE
 * @see CaptureRequest#TONEMAP_CURVE_GREEN
 * @see CaptureRequest#TONEMAP_CURVE_RED
 * @see CameraMetadata#TONEMAP_MODE_CONTRAST_CURVE
 * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
 */
public final class TonemapCurve {
    /**
     * Lower bound tonemap value corresponding to pure black for a single color channel.
     */
    public static final float LEVEL_BLACK = 0.0f;

    /**
     * Upper bound tonemap value corresponding to a pure white for a single color channel.
     */
    public static final float LEVEL_WHITE = 1.0f;

    /**
     * Number of elements in a {@code (Pin, Pout)} point;
     */
    public static final int POINT_SIZE = 2;

    /**
     * Index of the red color channel curve.
     */
    public static final int CHANNEL_RED = 0;
    /**
     * Index of the green color channel curve.
     */
    public static final int CHANNEL_GREEN = 1;
    /**
     * Index of the blue color channel curve.
     */
    public static final int CHANNEL_BLUE = 2;

    /**
     * Create a new immutable TonemapCurve instance.
     *
     * <p>Values are stored as a contiguous array of {@code (Pin, Pout)} points.</p>
     *
     * <p>All parameters may have independent length but should have at most
     * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS} * {@value #POINT_SIZE} elements and
     * at least 2 * {@value #POINT_SIZE} elements.</p>
     *
     * <p>All sub-elements must be in the inclusive range of
     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
     *
     * <p>This constructor copies the array contents and does not retain ownership of the array.</p>
     *
     * @param red An array of elements whose length is divisible by {@value #POINT_SIZE}
     * @param green An array of elements whose length is divisible by {@value #POINT_SIZE}
     * @param blue An array of elements whose length is divisible by {@value #POINT_SIZE}
     *
     * @throws IllegalArgumentException
     *            if any of input array length is invalid,
     *            or if any of the elements in the array are not in the range of
     *            [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}]
     * @throws NullPointerException
     *            if any of the parameters are {@code null}
     */
    public TonemapCurve(float[] red, float[] green, float[] blue) {
        // TODO: maxCurvePoints check?

        checkNotNull(red, "red must not be null");
        checkNotNull(green, "green must not be null");
        checkNotNull(blue, "blue must not be null");

        checkArgumentArrayLengthDivisibleBy(red, POINT_SIZE, "red");
        checkArgumentArrayLengthDivisibleBy(green, POINT_SIZE, "green");
        checkArgumentArrayLengthDivisibleBy(blue, POINT_SIZE, "blue");

        checkArgumentArrayLengthNoLessThan(red, MIN_CURVE_LENGTH, "red");
        checkArgumentArrayLengthNoLessThan(green, MIN_CURVE_LENGTH, "green");
        checkArgumentArrayLengthNoLessThan(blue, MIN_CURVE_LENGTH, "blue");

        checkArrayElementsInRange(red, LEVEL_BLACK, LEVEL_WHITE, "red");
        checkArrayElementsInRange(green, LEVEL_BLACK, LEVEL_WHITE, "green");
        checkArrayElementsInRange(blue, LEVEL_BLACK, LEVEL_WHITE, "blue");

        mRed = Arrays.copyOf(red, red.length);
        mGreen = Arrays.copyOf(green, green.length);
        mBlue = Arrays.copyOf(blue, blue.length);
    }

    private static void checkArgumentArrayLengthDivisibleBy(float[] array,
            int divisible, String arrayName) {
        if (array.length % divisible != 0) {
            throw new IllegalArgumentException(arrayName + " size must be divisible by "
                    + divisible);
        }
    }

    private static int checkArgumentColorChannel(int colorChannel) {
        switch (colorChannel) {
            case CHANNEL_RED:
            case CHANNEL_GREEN:
            case CHANNEL_BLUE:
                break;
            default:
                throw new IllegalArgumentException("colorChannel out of range");
        }

        return colorChannel;
    }

    private static void checkArgumentArrayLengthNoLessThan(float[] array, int minLength,
            String arrayName) {
        if (array.length < minLength) {
            throw new IllegalArgumentException(arrayName + " size must be at least "
                    + minLength);
        }
    }

    /**
     * Get the number of points stored in this tonemap curve for the specified color channel.
     *
     * @param colorChannel one of {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, {@link #CHANNEL_BLUE}
     * @return number of points stored in this tonemap for that color's curve (>= 0)
     *
     * @throws IllegalArgumentException if {@code colorChannel} was out of range
     */
    public int getPointCount(int colorChannel) {
        checkArgumentColorChannel(colorChannel);

        return getCurve(colorChannel).length / POINT_SIZE;
    }

    /**
     * Get the point for a color channel at a specified index.
     *
     * <p>The index must be at least 0 but no greater than {@link #getPointCount(int)} for
     * that {@code colorChannel}.</p>
     *
     * <p>All returned coordinates in the point are between the range of
     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
     *
     * @param colorChannel {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, or {@link #CHANNEL_BLUE}
     * @param index at least 0 but no greater than {@code getPointCount(colorChannel)}
     * @return the {@code (Pin, Pout)} pair mapping the tone for that index
     *
     * @throws IllegalArgumentException if {@code colorChannel} or {@code index} was out of range
     *
     * @see #LEVEL_BLACK
     * @see #LEVEL_WHITE
     */
    public PointF getPoint(int colorChannel, int index) {
        checkArgumentColorChannel(colorChannel);
        if (index < 0 || index >= getPointCount(colorChannel)) {
            throw new IllegalArgumentException("index out of range");
        }

        final float[] curve = getCurve(colorChannel);

        final float pIn = curve[index * POINT_SIZE + OFFSET_POINT_IN];
        final float pOut = curve[index * POINT_SIZE + OFFSET_POINT_OUT];

        return new PointF(pIn, pOut);
    }

    /**
     * Copy the color curve for a single color channel from this tonemap curve into the destination.
     *
     * <p>
     * <!--The output is encoded the same as in the constructor -->
     * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of
     * {@link #getPointCount} points for that respective channel.</p>
     *
     * <p>All returned coordinates are between the range of
     * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p>
     *
     * @param destination
     *          an array big enough to hold at least {@link #getPointCount} {@code *}
     *          {@link #POINT_SIZE} elements after the {@code offset}
     * @param offset
     *          a non-negative offset into the array
     * @throws NullPointerException
     *          If {@code destination} was {@code null}
     * @throws IllegalArgumentException
     *          If offset was negative
     * @throws ArrayIndexOutOfBoundsException
     *          If there's not enough room to write the elements at the specified destination and
     *          offset.
     *
     * @see CaptureRequest#TONEMAP_CURVE_BLUE
     * @see CaptureRequest#TONEMAP_CURVE_RED
     * @see CaptureRequest#TONEMAP_CURVE_GREEN
     * @see #LEVEL_BLACK
     * @see #LEVEL_WHITE
     */
    public void copyColorCurve(int colorChannel, float[] destination,
            int offset) {
        checkArgumentNonnegative(offset, "offset must not be negative");
        checkNotNull(destination, "destination must not be null");

        if (destination.length + offset < getPointCount(colorChannel) * POINT_SIZE) {
            throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
        }

        float[] curve = getCurve(colorChannel);
        System.arraycopy(curve, /*srcPos*/0, destination, offset, curve.length);
    }

    /**
     * Check if this TonemapCurve is equal to another TonemapCurve.
     *
     * <p>Two matrices are equal if and only if all of their elements are
     * {@link Object#equals equal}.</p>
     *
     * @return {@code true} if the objects were equal, {@code false} otherwise
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (obj instanceof TonemapCurve) {
            final TonemapCurve other = (TonemapCurve) obj;
            return Arrays.equals(mRed, other.mRed) &&
                    Arrays.equals(mGreen, other.mGreen) &&
                    Arrays.equals(mBlue, other.mBlue);
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        if (mHashCalculated) {
            // Avoid re-calculating hash. Data is immutable so this is both legal and faster.
            return mHashCode;
        }

        mHashCode = HashCodeHelpers.hashCodeGeneric(mRed, mGreen, mBlue);
        mHashCalculated = true;

        return mHashCode;
    }

    /**
     * Return the TonemapCurve as a string representation.
     *
     * <p> {@code "TonemapCurve{R:[(%f, %f), (%f, %f) ... (%f, %f)], G:[(%f, %f), (%f, %f) ...
     * (%f, %f)], B:[(%f, %f), (%f, %f) ... (%f, %f)]}"},
     * where each {@code (%f, %f)} respectively represents one point of the corresponding
     * tonemap curve. </p>
     *
     * @return string representation of {@link TonemapCurve}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("TonemapCurve{");
        sb.append("R:");
        sb.append(curveToString(CHANNEL_RED));
        sb.append(", G:");
        sb.append(curveToString(CHANNEL_GREEN));
        sb.append(", B:");
        sb.append(curveToString(CHANNEL_BLUE));
        sb.append("}");
        return sb.toString();
    }

    private String curveToString(int colorChannel) {
        checkArgumentColorChannel(colorChannel);
        StringBuilder sb = new StringBuilder("[");
        float[] curve = getCurve(colorChannel);
        int pointCount = curve.length / POINT_SIZE;
        for (int i = 0, j = 0; i < pointCount; i++, j += 2) {
            sb.append("(");
            sb.append(curve[j]);
            sb.append(", ");
            sb.append(curve[j+1]);
            sb.append("), ");
        }
        // trim extra ", " at the end. Guaranteed to work because pointCount >= 2
        sb.setLength(sb.length() - 2);
        sb.append("]");
        return sb.toString();
    }

    private float[] getCurve(int colorChannel) {
        switch (colorChannel) {
            case CHANNEL_RED:
                return mRed;
            case CHANNEL_GREEN:
                return mGreen;
            case CHANNEL_BLUE:
                return mBlue;
            default:
                throw new AssertionError("colorChannel out of range");
        }
    }

    private final static int OFFSET_POINT_IN = 0;
    private final static int OFFSET_POINT_OUT = 1;
    private final static int TONEMAP_MIN_CURVE_POINTS = 2;
    private final static int MIN_CURVE_LENGTH = TONEMAP_MIN_CURVE_POINTS * POINT_SIZE;

    private final float[] mRed;
    private final float[] mGreen;
    private final float[] mBlue;

    private int mHashCode;
    private boolean mHashCalculated = false;
}