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