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