content: /*
   * Copyright 2015 Google, Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification, are
   * permitted provided that the following conditions are met:
   *
   * 1. Redistributions of source code must retain the above copyright notice, this list of
   * conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright notice, this list
   * of conditions and the following disclaimer in the documentation and/or other materials
   * provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   * The views and conclusions contained in the software and documentation are those of the
   * authors and should not be interpreted as representing official policies, either expressed
   * or implied, of Google, Inc.
   *
   * Adapted for the uCrop library.
   */
  
  package com.yalantis.ucrop.util;
  
  import android.util.Log;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.charset.Charset;
  
  /**
   * A class for parsing the exif orientation from an image header.
   */
  public class ImageHeaderParser {
      private static final String TAG = "ImageHeaderParser";
      /**
       * A constant indicating we were unable to parse the orientation from the image either because
       * no exif segment containing orientation data existed, or because of an I/O error attempting to
       * read the exif segment.
       */
      public static final int UNKNOWN_ORIENTATION = -1;
  
      private static final int EXIF_MAGIC_NUMBER = 0xFFD8;
      // "MM".
      private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
      // "II".
      private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;
      private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
      private static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES =
              JPEG_EXIF_SEGMENT_PREAMBLE.getBytes(Charset.forName("UTF-8"));
      private static final int SEGMENT_SOS = 0xDA;
      private static final int MARKER_EOI = 0xD9;
      private static final int SEGMENT_START_ID = 0xFF;
      private static final int EXIF_SEGMENT_TYPE = 0xE1;
      private static final int ORIENTATION_TAG_TYPE = 0x0112;
      private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
  
      private final Reader reader;
  
      public ImageHeaderParser(InputStream is) {
          reader = new StreamReader(is);
      }
  
      /**
       * Parse the orientation from the image header. If it doesn't handle this image type (or this is
       * not an image) it will return a default value rather than throwing an exception.
       *
       * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't
       * contain an orientation
       * @throws IOException
       */
      public int getOrientation() throws IOException {
          final int magicNumber = reader.getUInt16();
  
          if (!handles(magicNumber)) {
              if (Log.isLoggable(TAG, Log.DEBUG)) {
                  Log.d(TAG, "Parser doesn't handle magic number: " + magicNumber);
              }
              return UNKNOWN_ORIENTATION;
          } else {
              int exifSegmentLength = moveToExifSegmentAndGetLength();
              if (exifSegmentLength == -1) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Failed to parse exif segment length, or exif segment not found");
                  }
                  return UNKNOWN_ORIENTATION;
              }
  
              byte[] exifData = new byte[exifSegmentLength];
              return parseExifSegment(exifData, exifSegmentLength);
          }
      }
  
      private int parseExifSegment(byte[] tempArray, int exifSegmentLength) throws IOException {
          int read = reader.read(tempArray, exifSegmentLength);
          if (read != exifSegmentLength) {
              if (Log.isLoggable(TAG, Log.DEBUG)) {
                  Log.d(TAG, "Unable to read exif segment data"
                          + ", length: " + exifSegmentLength
                          + ", actually read: " + read);
              }
              return UNKNOWN_ORIENTATION;
          }
  
          boolean hasJpegExifPreamble = hasJpegExifPreamble(tempArray, exifSegmentLength);
          if (hasJpegExifPreamble) {
              return parseExifSegment(new RandomAccessReader(tempArray, exifSegmentLength));
          } else {
              if (Log.isLoggable(TAG, Log.DEBUG)) {
                  Log.d(TAG, "Missing jpeg exif preamble");
              }
              return UNKNOWN_ORIENTATION;
          }
      }
  
      private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) {
          boolean result =
                  exifData != null && exifSegmentLength > JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
          if (result) {
              for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
                  if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
                      result = false;
                      break;
                  }
              }
          }
          return result;
      }
  
      /**
       * Moves reader to the start of the exif segment and returns the length of the exif segment or
       * {@code -1} if no exif segment is found.
       */
      private int moveToExifSegmentAndGetLength() throws IOException {
          short segmentId, segmentType;
          int segmentLength;
          while (true) {
              segmentId = reader.getUInt8();
              if (segmentId != SEGMENT_START_ID) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Unknown segmentId=" + segmentId);
                  }
                  return -1;
              }
  
              segmentType = reader.getUInt8();
  
              if (segmentType == SEGMENT_SOS) {
                  return -1;
              } else if (segmentType == MARKER_EOI) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Found MARKER_EOI in exif segment");
                  }
                  return -1;
              }
  
              // Segment length includes bytes for segment length.
              segmentLength = reader.getUInt16() - 2;
  
              if (segmentType != EXIF_SEGMENT_TYPE) {
                  long skipped = reader.skip(segmentLength);
                  if (skipped != segmentLength) {
                      if (Log.isLoggable(TAG, Log.DEBUG)) {
                          Log.d(TAG, "Unable to skip enough data"
                                  + ", type: " + segmentType
                                  + ", wanted to skip: " + segmentLength
                                  + ", but actually skipped: " + skipped);
                      }
                      return -1;
                  }
              } else {
                  return segmentLength;
              }
          }
      }
  
      private static int parseExifSegment(RandomAccessReader segmentData) {
          final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();
  
          short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);
          final ByteOrder byteOrder;
          if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) {
              byteOrder = ByteOrder.BIG_ENDIAN;
          } else if (byteOrderIdentifier == INTEL_TIFF_MAGIC_NUMBER) {
              byteOrder = ByteOrder.LITTLE_ENDIAN;
          } else {
              if (Log.isLoggable(TAG, Log.DEBUG)) {
                  Log.d(TAG, "Unknown endianness = " + byteOrderIdentifier);
              }
              byteOrder = ByteOrder.BIG_ENDIAN;
          }
  
          segmentData.order(byteOrder);
  
          int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize;
          int tagCount = segmentData.getInt16(firstIfdOffset);
  
          int tagOffset, tagType, formatCode, componentCount;
          for (int i = 0; i < tagCount; i++) {
              tagOffset = calcTagOffset(firstIfdOffset, i);
              tagType = segmentData.getInt16(tagOffset);
  
              // We only want orientation.
              if (tagType != ORIENTATION_TAG_TYPE) {
                  continue;
              }
  
              formatCode = segmentData.getInt16(tagOffset + 2);
  
              // 12 is max format code.
              if (formatCode < 1 || formatCode > 12) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Got invalid format code = " + formatCode);
                  }
                  continue;
              }
  
              componentCount = segmentData.getInt32(tagOffset + 4);
  
              if (componentCount < 0) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Negative tiff component count");
                  }
                  continue;
              }
  
              if (Log.isLoggable(TAG, Log.DEBUG)) {
                  Log.d(TAG, "Got tagIndex=" + i + " tagType=" + tagType + " formatCode=" + formatCode
                          + " componentCount=" + componentCount);
              }
  
              final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode];
  
              if (byteCount > 4) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Got byte count > 4, not orientation, continuing, formatCode=" + formatCode);
                  }
                  continue;
              }
  
              final int tagValueOffset = tagOffset + 8;
  
              if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Illegal tagValueOffset=" + tagValueOffset + " tagType=" + tagType);
                  }
                  continue;
              }
  
              if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) {
                  if (Log.isLoggable(TAG, Log.DEBUG)) {
                      Log.d(TAG, "Illegal number of bytes for TI tag data tagType=" + tagType);
                  }
                  continue;
              }
  
              //assume componentCount == 1 && fmtCode == 3
              return segmentData.getInt16(tagValueOffset);
          }
  
          return -1;
      }
  
      private static int calcTagOffset(int ifdOffset, int tagIndex) {
          return ifdOffset + 2 + 12 * tagIndex;
      }
  
      private static boolean handles(int imageMagicNumber) {
          return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER
                  || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER
                  || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
      }
  
      private static class RandomAccessReader {
          private final ByteBuffer data;
  
          public RandomAccessReader(byte[] data, int length) {
              this.data = (ByteBuffer) ByteBuffer.wrap(data)
                      .order(ByteOrder.BIG_ENDIAN)
                      .limit(length);
          }
  
          public void order(ByteOrder byteOrder) {
              this.data.order(byteOrder);
          }
  
          public int length() {
              return data.remaining();
          }
  
          public int getInt32(int offset) {
              return data.getInt(offset);
          }
  
          public short getInt16(int offset) {
              return data.getShort(offset);
          }
      }
  
      private interface Reader {
          int getUInt16() throws IOException;
  
          short getUInt8() throws IOException;
  
          long skip(long total) throws IOException;
  
          int read(byte[] buffer, int byteCount) throws IOException;
      }
  
      private static class StreamReader implements Reader {
          private final InputStream is;
  
          // Motorola / big endian byte order.
          public StreamReader(InputStream is) {
              this.is = is;
          }
  
          @Override
          public int getUInt16() throws IOException {
              return (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);
          }
  
          @Override
          public short getUInt8() throws IOException {
              return (short) (is.read() & 0xFF);
          }
  
          @Override
          public long skip(long total) throws IOException {
              if (total < 0) {
                  return 0;
              }
  
              long toSkip = total;
              while (toSkip > 0) {
                  long skipped = is.skip(toSkip);
                  if (skipped > 0) {
                      toSkip -= skipped;
                  } else {
                      // Skip has no specific contract as to what happens when you reach the end of
                      // the stream. To differentiate between temporarily not having more data and
                      // having finished the stream, we read a single byte when we fail to skip any
                      // amount of data.
                      int testEofByte = is.read();
                      if (testEofByte == -1) {
                          break;
                      } else {
                          toSkip--;
                      }
                  }
              }
              return total - toSkip;
          }
  
          @Override
          public int read(byte[] buffer, int byteCount) throws IOException {
              int toRead = byteCount;
              int read;
              while (toRead > 0 && ((read = is.read(buffer, byteCount - toRead, toRead)) != -1)) {
                  toRead -= read;
              }
              return byteCount - toRead;
          }
      }
  }
  
  