/* * 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. * *
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}.
* *The total number of points {@code (Pin, Pout)} for each color channel can be no more than * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS}.
* *The coordinate system for each point is within the inclusive range * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].
* * @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. * *Values are stored as a contiguous array of {@code (Pin, Pout)} points.
* *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.
* *All sub-elements must be in the inclusive range of * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].
* *This constructor copies the array contents and does not retain ownership of the array.
* * @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. * *The index must be at least 0 but no greater than {@link #getPointCount(int)} for * that {@code colorChannel}.
* *All returned coordinates in the point are between the range of * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].
* * @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. * ** * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of * {@link #getPointCount} points for that respective channel.
* *All returned coordinates are between the range of * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].
* * @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. * *Two matrices are equal if and only if all of their elements are * {@link Object#equals equal}.
* * @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. * *{@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.
* * @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; }