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 17 package android.hardware.camera2.params; 18 19 import static com.android.internal.util.Preconditions.*; 20 21 import android.graphics.PointF; 22 import android.hardware.camera2.CameraCharacteristics; 23 import android.hardware.camera2.CameraDevice; 24 import android.hardware.camera2.CameraMetadata; 25 import android.hardware.camera2.CaptureRequest; 26 import android.hardware.camera2.utils.HashCodeHelpers; 27 28 import 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 */ 49 public 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 and 85 * at least 2 * {@value #POINT_SIZE} elements.</p> 86 * 87 * <p>All sub-elements must be in the inclusive range of 88 * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> 89 * 90 * <p>This constructor copies the array contents and does not retain ownership of the array.</p> 91 * 92 * @param red An array of elements whose length is divisible by {@value #POINT_SIZE} 93 * @param green An array of elements whose length is divisible by {@value #POINT_SIZE} 94 * @param blue An array of elements whose length is divisible by {@value #POINT_SIZE} 95 * 96 * @throws IllegalArgumentException 97 * if any of input array length is invalid, 98 * or if any of the elements in the array are not in the range of 99 * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}] 100 * @throws NullPointerException 101 * if any of the parameters are {@code null} 102 */ TonemapCurve(float[] red, float[] green, float[] blue)103 public TonemapCurve(float[] red, float[] green, float[] blue) { 104 // TODO: maxCurvePoints check? 105 106 checkNotNull(red, "red must not be null"); 107 checkNotNull(green, "green must not be null"); 108 checkNotNull(blue, "blue must not be null"); 109 110 checkArgumentArrayLengthDivisibleBy(red, POINT_SIZE, "red"); 111 checkArgumentArrayLengthDivisibleBy(green, POINT_SIZE, "green"); 112 checkArgumentArrayLengthDivisibleBy(blue, POINT_SIZE, "blue"); 113 114 checkArgumentArrayLengthNoLessThan(red, MIN_CURVE_LENGTH, "red"); 115 checkArgumentArrayLengthNoLessThan(green, MIN_CURVE_LENGTH, "green"); 116 checkArgumentArrayLengthNoLessThan(blue, MIN_CURVE_LENGTH, "blue"); 117 118 checkArrayElementsInRange(red, LEVEL_BLACK, LEVEL_WHITE, "red"); 119 checkArrayElementsInRange(green, LEVEL_BLACK, LEVEL_WHITE, "green"); 120 checkArrayElementsInRange(blue, LEVEL_BLACK, LEVEL_WHITE, "blue"); 121 122 mRed = Arrays.copyOf(red, red.length); 123 mGreen = Arrays.copyOf(green, green.length); 124 mBlue = Arrays.copyOf(blue, blue.length); 125 } 126 checkArgumentArrayLengthDivisibleBy(float[] array, int divisible, String arrayName)127 private static void checkArgumentArrayLengthDivisibleBy(float[] array, 128 int divisible, String arrayName) { 129 if (array.length % divisible != 0) { 130 throw new IllegalArgumentException(arrayName + " size must be divisible by " 131 + divisible); 132 } 133 } 134 checkArgumentColorChannel(int colorChannel)135 private static int checkArgumentColorChannel(int colorChannel) { 136 switch (colorChannel) { 137 case CHANNEL_RED: 138 case CHANNEL_GREEN: 139 case CHANNEL_BLUE: 140 break; 141 default: 142 throw new IllegalArgumentException("colorChannel out of range"); 143 } 144 145 return colorChannel; 146 } 147 checkArgumentArrayLengthNoLessThan(float[] array, int minLength, String arrayName)148 private static void checkArgumentArrayLengthNoLessThan(float[] array, int minLength, 149 String arrayName) { 150 if (array.length < minLength) { 151 throw new IllegalArgumentException(arrayName + " size must be at least " 152 + minLength); 153 } 154 } 155 156 /** 157 * Get the number of points stored in this tonemap curve for the specified color channel. 158 * 159 * @param colorChannel one of {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, {@link #CHANNEL_BLUE} 160 * @return number of points stored in this tonemap for that color's curve (>= 0) 161 * 162 * @throws IllegalArgumentException if {@code colorChannel} was out of range 163 */ getPointCount(int colorChannel)164 public int getPointCount(int colorChannel) { 165 checkArgumentColorChannel(colorChannel); 166 167 return getCurve(colorChannel).length / POINT_SIZE; 168 } 169 170 /** 171 * Get the point for a color channel at a specified index. 172 * 173 * <p>The index must be at least 0 but no greater than {@link #getPointCount(int)} for 174 * that {@code colorChannel}.</p> 175 * 176 * <p>All returned coordinates in the point are between the range of 177 * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> 178 * 179 * @param colorChannel {@link #CHANNEL_RED}, {@link #CHANNEL_GREEN}, or {@link #CHANNEL_BLUE} 180 * @param index at least 0 but no greater than {@code getPointCount(colorChannel)} 181 * @return the {@code (Pin, Pout)} pair mapping the tone for that index 182 * 183 * @throws IllegalArgumentException if {@code colorChannel} or {@code index} was out of range 184 * 185 * @see #LEVEL_BLACK 186 * @see #LEVEL_WHITE 187 */ getPoint(int colorChannel, int index)188 public PointF getPoint(int colorChannel, int index) { 189 checkArgumentColorChannel(colorChannel); 190 if (index < 0 || index >= getPointCount(colorChannel)) { 191 throw new IllegalArgumentException("index out of range"); 192 } 193 194 final float[] curve = getCurve(colorChannel); 195 196 final float pIn = curve[index * POINT_SIZE + OFFSET_POINT_IN]; 197 final float pOut = curve[index * POINT_SIZE + OFFSET_POINT_OUT]; 198 199 return new PointF(pIn, pOut); 200 } 201 202 /** 203 * Copy the color curve for a single color channel from this tonemap curve into the destination. 204 * 205 * <p> 206 * <!--The output is encoded the same as in the constructor --> 207 * Values are stored as packed {@code (Pin, Pout}) points, and there are a total of 208 * {@link #getPointCount} points for that respective channel.</p> 209 * 210 * <p>All returned coordinates are between the range of 211 * [{@value #LEVEL_BLACK}, {@value #LEVEL_WHITE}].</p> 212 * 213 * @param destination 214 * an array big enough to hold at least {@link #getPointCount} {@code *} 215 * {@link #POINT_SIZE} elements after the {@code offset} 216 * @param offset 217 * a non-negative offset into the array 218 * @throws NullPointerException 219 * If {@code destination} was {@code null} 220 * @throws IllegalArgumentException 221 * If offset was negative 222 * @throws ArrayIndexOutOfBoundsException 223 * If there's not enough room to write the elements at the specified destination and 224 * offset. 225 * 226 * @see CaptureRequest#TONEMAP_CURVE_BLUE 227 * @see CaptureRequest#TONEMAP_CURVE_RED 228 * @see CaptureRequest#TONEMAP_CURVE_GREEN 229 * @see #LEVEL_BLACK 230 * @see #LEVEL_WHITE 231 */ copyColorCurve(int colorChannel, float[] destination, int offset)232 public void copyColorCurve(int colorChannel, float[] destination, 233 int offset) { 234 checkArgumentNonnegative(offset, "offset must not be negative"); 235 checkNotNull(destination, "destination must not be null"); 236 237 if (destination.length + offset < getPointCount(colorChannel) * POINT_SIZE) { 238 throw new ArrayIndexOutOfBoundsException("destination too small to fit elements"); 239 } 240 241 float[] curve = getCurve(colorChannel); 242 System.arraycopy(curve, /*srcPos*/0, destination, offset, curve.length); 243 } 244 245 /** 246 * Check if this TonemapCurve is equal to another TonemapCurve. 247 * 248 * <p>Two matrices are equal if and only if all of their elements are 249 * {@link Object#equals equal}.</p> 250 * 251 * @return {@code true} if the objects were equal, {@code false} otherwise 252 */ 253 @Override equals(Object obj)254 public boolean equals(Object obj) { 255 if (obj == null) { 256 return false; 257 } 258 if (this == obj) { 259 return true; 260 } 261 if (obj instanceof TonemapCurve) { 262 final TonemapCurve other = (TonemapCurve) obj; 263 return Arrays.equals(mRed, other.mRed) && 264 Arrays.equals(mGreen, other.mGreen) && 265 Arrays.equals(mBlue, other.mBlue); 266 } 267 return false; 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override hashCode()274 public int hashCode() { 275 if (mHashCalculated) { 276 // Avoid re-calculating hash. Data is immutable so this is both legal and faster. 277 return mHashCode; 278 } 279 280 mHashCode = HashCodeHelpers.hashCodeGeneric(mRed, mGreen, mBlue); 281 mHashCalculated = true; 282 283 return mHashCode; 284 } 285 286 /** 287 * Return the TonemapCurve as a string representation. 288 * 289 * <p> {@code "TonemapCurve{R:[(%f, %f), (%f, %f) ... (%f, %f)], G:[(%f, %f), (%f, %f) ... 290 * (%f, %f)], B:[(%f, %f), (%f, %f) ... (%f, %f)]}"}, 291 * where each {@code (%f, %f)} respectively represents one point of the corresponding 292 * tonemap curve. </p> 293 * 294 * @return string representation of {@link TonemapCurve} 295 */ 296 @Override toString()297 public String toString() { 298 StringBuilder sb = new StringBuilder("TonemapCurve{"); 299 sb.append("R:"); 300 sb.append(curveToString(CHANNEL_RED)); 301 sb.append(", G:"); 302 sb.append(curveToString(CHANNEL_GREEN)); 303 sb.append(", B:"); 304 sb.append(curveToString(CHANNEL_BLUE)); 305 sb.append("}"); 306 return sb.toString(); 307 } 308 curveToString(int colorChannel)309 private String curveToString(int colorChannel) { 310 checkArgumentColorChannel(colorChannel); 311 StringBuilder sb = new StringBuilder("["); 312 float[] curve = getCurve(colorChannel); 313 int pointCount = curve.length / POINT_SIZE; 314 for (int i = 0, j = 0; i < pointCount; i++, j += 2) { 315 sb.append("("); 316 sb.append(curve[j]); 317 sb.append(", "); 318 sb.append(curve[j+1]); 319 sb.append("), "); 320 } 321 // trim extra ", " at the end. Guaranteed to work because pointCount >= 2 322 sb.setLength(sb.length() - 2); 323 sb.append("]"); 324 return sb.toString(); 325 } 326 getCurve(int colorChannel)327 private float[] getCurve(int colorChannel) { 328 switch (colorChannel) { 329 case CHANNEL_RED: 330 return mRed; 331 case CHANNEL_GREEN: 332 return mGreen; 333 case CHANNEL_BLUE: 334 return mBlue; 335 default: 336 throw new AssertionError("colorChannel out of range"); 337 } 338 } 339 340 private final static int OFFSET_POINT_IN = 0; 341 private final static int OFFSET_POINT_OUT = 1; 342 private final static int TONEMAP_MIN_CURVE_POINTS = 2; 343 private final static int MIN_CURVE_LENGTH = TONEMAP_MIN_CURVE_POINTS * POINT_SIZE; 344 345 private final float[] mRed; 346 private final float[] mGreen; 347 private final float[] mBlue; 348 349 private int mHashCode; 350 private boolean mHashCalculated = false; 351 } 352