content: /*
   * 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 android.hardware.camera2.params.RggbChannelVector.BLUE;
  import static android.hardware.camera2.params.RggbChannelVector.COUNT;
  import static android.hardware.camera2.params.RggbChannelVector.GREEN_EVEN;
  import static android.hardware.camera2.params.RggbChannelVector.GREEN_ODD;
  import static android.hardware.camera2.params.RggbChannelVector.RED;
  
  import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
  import static com.android.internal.util.Preconditions.checkArgumentPositive;
  import static com.android.internal.util.Preconditions.checkArrayElementsInRange;
  import static com.android.internal.util.Preconditions.checkNotNull;
  
  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 RggbChannelVector#RED
       * @see RggbChannelVector#GREEN_EVEN
       * @see RggbChannelVector#GREEN_ODD
       * @see RggbChannelVector#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_CORRECTION_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;
  }
  