/*
* 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 static android.hardware.camera2.params.RggbChannelVector.*;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.utils.HashCodeHelpers;
import java.util.Arrays;
/**
* Immutable class for describing a {@code 4 x N x M} lens shading map of floats.
*
* @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
*/
public final class LensShadingMap {
/**
* The smallest gain factor in this map.
*
* <p>All values in this map will be at least this large.</p>
*/
public static final float MINIMUM_GAIN_FACTOR = 1.0f;
/**
* Create a new immutable LensShadingMap instance.
*
* <p>The elements must be stored in a row-major order (fully packed).</p>
*
* <p>This constructor takes over the array; do not write to the array afterwards.</p>
*
* @param elements
* An array of elements whose length is
* {@code RggbChannelVector.COUNT * rows * columns}
*
* @throws IllegalArgumentException
* if the {@code elements} array length is invalid,
* if any of the subelems are not finite or less than {@value #MINIMUM_GAIN_FACTOR},
* or if rows or columns is not positive
* @throws NullPointerException
* if {@code elements} is {@code null}
*
* @hide
*/
public LensShadingMap(final float[] elements, final int rows, final int columns) {
mRows = checkArgumentPositive(rows, "rows must be positive");
mColumns = checkArgumentPositive(columns, "columns must be positive");
mElements = checkNotNull(elements, "elements must not be null");
if (elements.length != getGainFactorCount()) {
throw new IllegalArgumentException("elements must be " + getGainFactorCount() +
" length, received " + elements.length);
}
// Every element must be finite and >= 1.0f
checkArrayElementsInRange(elements, MINIMUM_GAIN_FACTOR, Float.MAX_VALUE, "elements");
}
/**
* Get the number of rows in this map.
*/
public int getRowCount() {
return mRows;
}
/**
* Get the number of columns in this map.
*/
public int getColumnCount() {
return mColumns;
}
/**
* Get the total number of gain factors in this map.
*
* <p>A single gain factor contains exactly one color channel.
* Use with {@link #copyGainFactors} to allocate a large-enough array.</p>
*/
public int getGainFactorCount() {
return mRows * mColumns * COUNT;
}
/**
* Get a single color channel gain factor from this lens shading map by its row and column.
*
* <p>The rows must be within the range [0, {@link #getRowCount}),
* the column must be within the range [0, {@link #getColumnCount}),
* and the color channel must be within the range [0, {@value RggbChannelVector#COUNT}).</p>
*
* <p>The channel order is {@code [R, Geven, Godd, B]}, where
* {@code Geven} is the green channel for the even rows of a Bayer pattern, and
* {@code Godd} is the odd rows.
* </p>
*
* @param colorChannel color channel from {@code [R, Geven, Godd, B]}
* @param column within the range [0, {@link #getColumnCount})
* @param row within the range [0, {@link #getRowCount})
*
* @return a gain factor >= {@value #MINIMUM_GAIN_FACTOR}
*
* @throws IllegalArgumentException if any of the parameters was out of range
*
* @see #RED
* @see #GREEN_EVEN
* @see #GREEN_ODD
* @see #BLUE
* @see #getRowCount
* @see #getColumnCount
*/
public float getGainFactor(final int colorChannel, final int column, final int row) {
if (colorChannel < 0 || colorChannel > COUNT) {
throw new IllegalArgumentException("colorChannel out of range");
} else if (column < 0 || column >= mColumns) {
throw new IllegalArgumentException("column out of range");
} else if (row < 0 || row >= mRows) {
throw new IllegalArgumentException("row out of range");
}
return mElements[colorChannel + (row * mColumns + column) * COUNT ];
}
/**
* Get a gain factor vector from this lens shading map by its row and column.
*
* <p>The rows must be within the range [0, {@link #getRowCount}),
* the column must be within the range [0, {@link #getColumnCount}).</p>
*
* @param column within the range [0, {@link #getColumnCount})
* @param row within the range [0, {@link #getRowCount})
*
* @return an {@link RggbChannelVector} where each gain factor >= {@value #MINIMUM_GAIN_FACTOR}
*
* @throws IllegalArgumentException if any of the parameters was out of range
*
* @see #getRowCount
* @see #getColumnCount
*/
public RggbChannelVector getGainFactorVector(final int column, final int row) {
if (column < 0 || column >= mColumns) {
throw new IllegalArgumentException("column out of range");
} else if (row < 0 || row >= mRows) {
throw new IllegalArgumentException("row out of range");
}
final int offset = (row * mColumns + column) * COUNT;
final float red =
mElements[RED + offset];
final float greenEven =
mElements[GREEN_EVEN + offset];
final float greenOdd =
mElements[GREEN_ODD + offset];
final float blue =
mElements[BLUE + offset];
return new RggbChannelVector(red, greenEven, greenOdd, blue);
}
/**
* Copy all gain factors in row-major order from this lens shading map into the destination.
*
* <p>Each gain factor will be >= {@link #MINIMUM_GAIN_FACTOR}.</p>
*
* @param destination
* an array big enough to hold at least {@link RggbChannelVector#COUNT}
* 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 CaptureResult#STATISTICS_LENS_SHADING_MAP
*/
public void copyGainFactors(final float[] destination, final int offset) {
checkArgumentNonnegative(offset, "offset must not be negative");
checkNotNull(destination, "destination must not be null");
if (destination.length + offset < getGainFactorCount()) {
throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
}
System.arraycopy(mElements, /*srcPos*/0, destination, offset, getGainFactorCount());
}
/**
* Check if this LensShadingMap is equal to another LensShadingMap.
*
* <p>Two lens shading maps are equal if and only if they have the same rows/columns,
* and all of their elements are {@link Object#equals equal}.</p>
*
* @return {@code true} if the objects were equal, {@code false} otherwise
*/
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (obj instanceof LensShadingMap) {
final LensShadingMap other = (LensShadingMap) obj;
return mRows == other.mRows
&& mColumns == other.mColumns
&& Arrays.equals(mElements, other.mElements);
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int elemsHash = HashCodeHelpers.hashCode(mElements);
return HashCodeHelpers.hashCode(mRows, mColumns, elemsHash);
}
/**
* Return the LensShadingMap as a string representation.
*
* <p> {@code "LensShadingMap{R:([%f, %f, ... %f], ... [%f, %f, ... %f]), G_even:([%f, %f, ...
* %f], ... [%f, %f, ... %f]), G_odd:([%f, %f, ... %f], ... [%f, %f, ... %f]), B:([%f, %f, ...
* %f], ... [%f, %f, ... %f])}"},
* where each {@code %f} represents one gain factor and each {@code [%f, %f, ... %f]} represents
* a row of the lens shading map</p>
*
* @return string representation of {@link LensShadingMap}
*/
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("LensShadingMap{");
final String channelPrefix[] = {"R:(", "G_even:(", "G_odd:(", "B:("};
for (int ch = 0; ch < COUNT; ch++) {
str.append(channelPrefix[ch]);
for (int r = 0; r < mRows; r++) {
str.append("[");
for (int c = 0; c < mColumns; c++) {
float gain = getGainFactor(ch, c, r);
str.append(gain);
if (c < mColumns - 1) {
str.append(", ");
}
}
str.append("]");
if (r < mRows - 1) {
str.append(", ");
}
}
str.append(")");
if (ch < COUNT - 1) {
str.append(", ");
}
}
str.append("}");
return str.toString();
}
private final int mRows;
private final int mColumns;
private final float[] mElements;
}