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