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 com.android.internal.util.Preconditions.*;
  
  import android.hardware.camera2.CameraCharacteristics;
  import android.hardware.camera2.utils.HashCodeHelpers;
  
  import java.util.Arrays;
  
  /**
   * Immutable class to store the input to output formats
   * {@link CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP map} to be used for with
   * camera image reprocessing.
   *
   * <p>
   * The mapping of image formats that are supported by this camera device for input streams,
   * to their corresponding output formats.</p>
   *
   * <p>
   * Attempting to configure an input stream with output streams not listed as available in this map
   * is not valid.
   * </p>
   *
   * @see CameraCharacteristics#SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP
   * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS
   *
   * <!-- hide this until we expose input streams through public API -->
   * @hide
   */
  public final class ReprocessFormatsMap {
      /**
       * Create a new {@link ReprocessFormatsMap}
       *
       * <p>This value is encoded as a variable-size array-of-arrays.
       * The inner array always contains {@code [format, length, ...]} where ... has length elements.
       * An inner array is followed by another inner array if the total metadata entry size hasn't
       * yet been exceeded.</p>
       *
       * <p>Entry must not be {@code null}. Empty array is acceptable.</p>
       *
       * <p>The entry array ownership is passed to this instance after construction; do not
       * write to it afterwards.</p>
       *
       * @param entry Array of ints, not yet deserialized (not-null)
       *
       * @throws IllegalArgumentException
       *              if the data was poorly formatted
       *              (missing output format length or too few output formats)
       *              or if any of the input/formats were not valid
       * @throws NullPointerException
       *              if entry was null
       *
       * @see StreamConfigurationMap#checkArgumentFormatInternal
       *
       * @hide
       */
      public ReprocessFormatsMap(final int[] entry) {
          checkNotNull(entry, "entry must not be null");
  
          int numInputs = 0;
          int left = entry.length;
          for (int i = 0; i < entry.length; ) {
              int inputFormat = StreamConfigurationMap.checkArgumentFormatInternal(entry[i]);
  
              left--;
              i++;
  
              if (left < 1) {
                  throw new IllegalArgumentException(
                          String.format("Input %x had no output format length listed", inputFormat));
              }
  
              final int length = entry[i];
              left--;
              i++;
  
              for (int j = 0; j < length; ++j) {
                  int outputFormat = entry[i + j];
                  StreamConfigurationMap.checkArgumentFormatInternal(outputFormat);
              }
  
              if (length > 0) {
                  if (left < length) {
                      throw new IllegalArgumentException(
                              String.format(
                                      "Input %x had too few output formats listed (actual: %d, " +
                                      "expected: %d)", inputFormat, left, length));
                  }
  
                  i += length;
                  left -= length;
              }
  
              numInputs++;
          }
  
          mEntry = entry;
          mInputCount = numInputs;
      }
  
      /**
       * Get a list of all input image formats that can be used to reprocess an input
       * stream into an output stream.
       *
       * <p>Use this input format to look up the available output formats with {@link #getOutputs}.
       * </p>
       *
       * @return an array of inputs (possibly empty, but never {@code null})
       *
       * @see ImageFormat
       * @see #getOutputs
       */
      public int[] getInputs() {
          final int[] inputs = new int[mInputCount];
  
          int left = mEntry.length;
          for (int i = 0, j = 0; i < mEntry.length; j++) {
              final int format = mEntry[i];
  
              left--;
              i++;
  
              if (left < 1) {
                  throw new AssertionError(
                          String.format("Input %x had no output format length listed", format));
              }
  
              final int length = mEntry[i];
              left--;
              i++;
  
              if (length > 0) {
                  if (left < length) {
                      throw new AssertionError(
                              String.format(
                                      "Input %x had too few output formats listed (actual: %d, " +
                                      "expected: %d)", format, left, length));
                  }
  
                  i += length;
                  left -= length;
              }
  
              inputs[j] = format;
          }
  
          return StreamConfigurationMap.imageFormatToPublic(inputs);
      }
  
      /**
       * Get the list of output formats that can be reprocessed into from the input {@code format}.
       *
       * <p>The input {@code format} must be one of the formats returned by {@link #getInputs}.</p>
       *
       * @param format an input format
       *
       * @return list of output image formats
       *
       * @see ImageFormat
       * @see #getInputs
       */
      public int[] getOutputs(final int format) {
  
          int left = mEntry.length;
          for (int i = 0; i < mEntry.length; ) {
              final int inputFormat = mEntry[i];
  
              left--;
              i++;
  
              if (left < 1) {
                  throw new AssertionError(
                          String.format("Input %x had no output format length listed", format));
              }
  
              final int length = mEntry[i];
              left--;
              i++;
  
              if (length > 0) {
                  if (left < length) {
                      throw new AssertionError(
                              String.format(
                                      "Input %x had too few output formats listed (actual: %d, " +
                                      "expected: %d)", format, left, length));
                  }
              }
  
              if (inputFormat == format) {
                  int[] outputs = new int[length];
  
                  // Copying manually faster than System.arraycopy for small arrays
                  for (int k = 0; k < length; ++k) {
                      outputs[k] = mEntry[i + k];
                  }
  
                  return StreamConfigurationMap.imageFormatToPublic(outputs);
              }
  
              i += length;
              left -= length;
  
          }
  
          throw new IllegalArgumentException(
                  String.format("Input format %x was not one in #getInputs", format));
      }
  
      /**
       * Check if this {@link ReprocessFormatsMap} is equal to another
       * {@link ReprocessFormatsMap}.
       *
       * <p>These two objects are only equal if and only if each of the respective elements is 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 ReprocessFormatsMap) {
              final ReprocessFormatsMap other = (ReprocessFormatsMap) obj;
              // Do not compare anything besides mEntry, since the rest of the values are derived
              return Arrays.equals(mEntry, other.mEntry);
          }
          return false;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public int hashCode() {
          // Do not hash anything besides mEntry since the rest of the values are derived
          return HashCodeHelpers.hashCode(mEntry);
      }
  
      private final int[] mEntry;
      /*
       * Dependent fields: values are derived from mEntry
       */
      private final int mInputCount;
  }
  