content: package com.bihe0832.android.lib.media.image;
  
  import android.text.TextUtils;
  import android.util.Log;
  import androidx.exifinterface.media.ExifInterface;
  import com.bihe0832.android.lib.file.reader.RandomAccessReader;
  import com.bihe0832.android.lib.file.reader.Reader;
  import com.bihe0832.android.lib.file.reader.StreamReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.ByteOrder;
  import java.nio.charset.Charset;
  
  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;
      private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
      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;
      }
  
      public static void copyExif(ExifInterface originalExif, int width, int height, String imageOutputPath) {
          String[] attributes = new String[]{
                  ExifInterface.TAG_F_NUMBER,
                  ExifInterface.TAG_DATETIME,
                  ExifInterface.TAG_DATETIME_DIGITIZED,
                  ExifInterface.TAG_EXPOSURE_TIME,
                  ExifInterface.TAG_FLASH,
                  ExifInterface.TAG_FOCAL_LENGTH,
                  ExifInterface.TAG_GPS_ALTITUDE,
                  ExifInterface.TAG_GPS_ALTITUDE_REF,
                  ExifInterface.TAG_GPS_DATESTAMP,
                  ExifInterface.TAG_GPS_LATITUDE,
                  ExifInterface.TAG_GPS_LATITUDE_REF,
                  ExifInterface.TAG_GPS_LONGITUDE,
                  ExifInterface.TAG_GPS_LONGITUDE_REF,
                  ExifInterface.TAG_GPS_PROCESSING_METHOD,
                  ExifInterface.TAG_GPS_TIMESTAMP,
                  ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
                  ExifInterface.TAG_MAKE,
                  ExifInterface.TAG_MODEL,
                  ExifInterface.TAG_SUBSEC_TIME,
                  ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
                  ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
                  ExifInterface.TAG_WHITE_BALANCE
          };
  
          try {
              ExifInterface newExif = new ExifInterface(imageOutputPath);
              String value;
              for (String attribute : attributes) {
                  value = originalExif.getAttribute(attribute);
                  if (!TextUtils.isEmpty(value)) {
                      newExif.setAttribute(attribute, value);
                  }
              }
              newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
              newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
              newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");
  
              newExif.saveAttributes();
  
          } catch (IOException e) {
              Log.d(TAG, e.getMessage());
          }
      }
  
  }
  
  