Loading api/current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -20131,8 +20131,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2 api/system-current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -21652,8 +21652,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2 api/test-current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -20201,8 +20201,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2 media/java/android/media/ExifInterface.java +158 −73 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.LinkedList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; Loading @@ -61,7 +62,7 @@ import libcore.io.Streams; /** * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file. * <p> * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF. * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW and RAF. * <p> * Attribute mutation is supported for JPEG image files. */ Loading Loading @@ -515,6 +516,12 @@ public class ExifInterface { private static final int DATA_PACK_BITS_COMPRESSED = 32773; private static final int DATA_LOSSY_JPEG = 34892; /** * Constant used for BitsPerSample tag. * See TIFF 6.0 Spec Section 6: RGB Full Color Images, Differences from Palette Color Images */ private static final int[] BITS_PER_SAMPLE_RGB = new int[] {8, 8, 8}; /** * Constants used for NewSubfileType tag. * See TIFF 6.0 Spec Section 8 Loading Loading @@ -1261,6 +1268,7 @@ public class ExifInterface { private int mThumbnailOffset; private int mThumbnailLength; private byte[] mThumbnailBytes; private int mThumbnailCompression; private int mExifOffset; private int mOrfMakerNoteOffset; private int mOrfThumbnailOffset; Loading Loading @@ -1843,11 +1851,23 @@ public class ExifInterface { } /** * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail. * The returned data is in JPEG format and can be decoded using * Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no * JPEG compressed thumbnail. * The returned data can be decoded using * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)} */ public byte[] getThumbnail() { if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return getThumbnailBytes(); } return null; } /** * Returns the thumbnail bytes inside the image file, regardless of the compression type of the * thumbnail image. */ public byte[] getThumbnailBytes() { if (!mHasThumbnail) { return null; } Loading Loading @@ -1879,6 +1899,7 @@ public class ExifInterface { if (in.read(buffer) != mThumbnailLength) { throw new IOException("Corrupted image"); } mThumbnailBytes = buffer; return buffer; } catch (IOException | ErrnoException e) { // Couldn't get a thumbnail image. Loading @@ -1888,6 +1909,52 @@ public class ExifInterface { return null; } /** * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the * thumbnail compression value, or {@code null} if the compression type is unsupported. */ public Bitmap getThumbnailBitmap() { if (!mHasThumbnail) { return null; } else if (mThumbnailBytes == null) { mThumbnailBytes = getThumbnailBytes(); } if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailLength); } else if (mThumbnailCompression == DATA_UNCOMPRESSED) { int[] rgbValues = new int[mThumbnailBytes.length / 3]; byte alpha = (byte) 0xff000000; for (int i = 0; i < rgbValues.length; i++) { rgbValues[i] = alpha + (mThumbnailBytes[3 * i] << 16) + (mThumbnailBytes[3 * i + 1] << 8) + mThumbnailBytes[3 * i + 2]; } ExifAttribute imageLengthAttribute = (ExifAttribute) mAttributes[IFD_THUMBNAIL_HINT].get(TAG_IMAGE_LENGTH); ExifAttribute imageWidthAttribute = (ExifAttribute) mAttributes[IFD_THUMBNAIL_HINT].get(TAG_IMAGE_WIDTH); if (imageLengthAttribute != null && imageWidthAttribute != null) { int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder); int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder); return Bitmap.createBitmap( rgbValues, imageWidth, imageLength, Bitmap.Config.ARGB_8888); } } return null; } /** * Returns true if thumbnail image is JPEG Compressed, or false if either thumbnail image does * not exist or thumbnail image is uncompressed. */ public boolean isThumbnailCompressed() { if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return true; } return false; } /** * Returns the offset and length of thumbnail inside the image file, or * {@code null} if there is no thumbnail. Loading Loading @@ -2944,33 +3011,27 @@ public class ExifInterface { ExifAttribute compressionAttribute = (ExifAttribute) thumbnailData.get(TAG_COMPRESSION); if (compressionAttribute != null) { int compressionValue = compressionAttribute.getIntValue(mExifByteOrder); switch (compressionValue) { case DATA_UNCOMPRESSED: { // TODO: add implementation for reading uncompressed thumbnail data (b/28156704) Log.d(TAG, "Uncompressed thumbnail data cannot be processed"); break; } mThumbnailCompression = compressionAttribute.getIntValue(mExifByteOrder); switch (mThumbnailCompression) { case DATA_JPEG: { handleThumbnailFromJfif(in, thumbnailData); break; } case DATA_UNCOMPRESSED: case DATA_JPEG_COMPRESSED: { handleThumbnailFromStrips(in, thumbnailData); break; } default: { break; } } } else { // Thumbnail data may not contain Compression tag value mThumbnailCompression = DATA_JPEG; handleThumbnailFromJfif(in, thumbnailData); } } // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values and // create a bitmap based on those values // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values // and reads the corresponding bytes if stream does not support seek function private void handleThumbnailFromJfif(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute jpegInterchangeFormatAttribute = (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT); Loading @@ -2978,41 +3039,9 @@ public class ExifInterface { (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); if (jpegInterchangeFormatAttribute != null && jpegInterchangeFormatLengthAttribute != null) { int jpegInterchangeFormat = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int jpegInterchangeFormatLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); createJpegThumbnailBitmap(in, jpegInterchangeFormat, jpegInterchangeFormatLength); } } int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values and // create a bitmap based on those values private void handleThumbnailFromStrips(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute stripOffsetsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS); ExifAttribute stripByteCountsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS); if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) { long[] stripOffsetsArray = (long[]) stripOffsetsAttribute.getValue(mExifByteOrder); long[] stripByteCountsArray = (long[]) stripByteCountsAttribute.getValue(mExifByteOrder); if (stripOffsetsArray.length == 1) { int stripOffsetsSum = (int) Arrays.stream(stripOffsetsArray).sum(); int stripByteCountsSum = (int) Arrays.stream(stripByteCountsArray).sum(); createJpegThumbnailBitmap(in, stripOffsetsSum, stripByteCountsSum); } else { // TODO: implement method to read multiple strips (b/29737797) Log.d(TAG, "Multiple strip thumbnail data cannot be processed"); } } } // Creates a bitmap data based on thumbnail offset and length for JPEG Compression private void createJpegThumbnailBitmap(InputStream in, int thumbnailOffset, int thumbnailLength) throws IOException { // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.available() - thumbnailOffset); if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF Loading @@ -3023,30 +3052,86 @@ public class ExifInterface { thumbnailOffset += mOrfMakerNoteOffset; } if (DEBUG) { Log.d(TAG, "Creating JPEG Thumbnail with offset: " + thumbnailOffset); Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset); } if (thumbnailOffset > 0 && thumbnailLength > 0) { mHasThumbnail = true; mThumbnailOffset = thumbnailOffset; mThumbnailLength = thumbnailLength; if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) { if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) { // Save the thumbnail in memory if the input doesn't support reading again. byte[] thumbnailBytes = new byte[thumbnailLength]; in.skip(thumbnailOffset); in.read(thumbnailBytes); mThumbnailBytes = thumbnailBytes; if (DEBUG) { Bitmap bitmap = BitmapFactory.decodeByteArray( thumbnailBytes, 0, thumbnailBytes.length); Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: " + mThumbnailLength + ", width: " + bitmap.getWidth() + ", height: " + bitmap.getHeight()); } } } } // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values private void handleThumbnailFromStrips(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute bitsPerSampleAttribute = (ExifAttribute) thumbnailData.get(TAG_BITS_PER_SAMPLE); if (bitsPerSampleAttribute != null) { int[] bitsPerSampleValue = (int[]) bitsPerSampleAttribute.getValue(mExifByteOrder); if (Arrays.equals(BITS_PER_SAMPLE_RGB, bitsPerSampleValue)) { ExifAttribute stripOffsetsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS); ExifAttribute stripByteCountsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS); if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) { long[] stripOffsets = (long[]) stripOffsetsAttribute.getValue(mExifByteOrder); long[] stripByteCounts = (long[]) stripByteCountsAttribute.getValue(mExifByteOrder); // Set thumbnail byte array data for non-consecutive strip bytes byte[] totalStripBytes = new byte[(int) Arrays.stream(stripByteCounts).sum()]; int bytesRead = 0; int bytesAdded = 0; for (int i = 0; i < stripOffsets.length; i++) { int stripOffset = (int) stripOffsets[i]; int stripByteCount = (int) stripByteCounts[i]; // Skip to offset int skipBytes = stripOffset - bytesRead; if (skipBytes < 0) { Log.d(TAG, "Invalid strip offset value"); } in.skip(skipBytes); bytesRead += skipBytes; // Read strip bytes byte[] stripBytes = new byte[stripByteCount]; in.read(stripBytes); bytesRead += stripByteCount; // Add bytes to array System.arraycopy(stripBytes, 0, totalStripBytes, bytesAdded, stripBytes.length); bytesAdded += stripBytes.length; } mHasThumbnail = true; mThumbnailBytes = totalStripBytes; } } } else { if (DEBUG) { Log.d(TAG, "Only Uncompressed RGB data process is supported"); } return; } } // Returns true if the image length and width values are <= 512. // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567 private boolean isThumbnail(HashMap map) throws IOException { Loading media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java +1 −2 Original line number Diff line number Diff line Loading @@ -267,8 +267,7 @@ public class ExifInterfaceTest extends AndroidTestCase { if (expectedValue.hasThumbnail) { byte[] thumbnailBytes = exifInterface.getThumbnail(); assertNotNull(thumbnailBytes); Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length); Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap(); assertNotNull(thumbnailBitmap); assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); Loading Loading
api/current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -20131,8 +20131,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
api/system-current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -21652,8 +21652,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
api/test-current.txt +3 −0 Original line number Diff line number Diff line Loading @@ -20201,8 +20201,11 @@ package android.media { method public int getAttributeInt(java.lang.String, int); method public boolean getLatLong(float[]); method public byte[] getThumbnail(); method public android.graphics.Bitmap getThumbnailBitmap(); method public byte[] getThumbnailBytes(); method public long[] getThumbnailRange(); method public boolean hasThumbnail(); method public boolean isThumbnailCompressed(); method public void saveAttributes() throws java.io.IOException; method public void setAttribute(java.lang.String, java.lang.String); field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
media/java/android/media/ExifInterface.java +158 −73 Original line number Diff line number Diff line Loading @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.LinkedList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; Loading @@ -61,7 +62,7 @@ import libcore.io.Streams; /** * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file. * <p> * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF. * Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW and RAF. * <p> * Attribute mutation is supported for JPEG image files. */ Loading Loading @@ -515,6 +516,12 @@ public class ExifInterface { private static final int DATA_PACK_BITS_COMPRESSED = 32773; private static final int DATA_LOSSY_JPEG = 34892; /** * Constant used for BitsPerSample tag. * See TIFF 6.0 Spec Section 6: RGB Full Color Images, Differences from Palette Color Images */ private static final int[] BITS_PER_SAMPLE_RGB = new int[] {8, 8, 8}; /** * Constants used for NewSubfileType tag. * See TIFF 6.0 Spec Section 8 Loading Loading @@ -1261,6 +1268,7 @@ public class ExifInterface { private int mThumbnailOffset; private int mThumbnailLength; private byte[] mThumbnailBytes; private int mThumbnailCompression; private int mExifOffset; private int mOrfMakerNoteOffset; private int mOrfThumbnailOffset; Loading Loading @@ -1843,11 +1851,23 @@ public class ExifInterface { } /** * Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail. * The returned data is in JPEG format and can be decoded using * Returns the JPEG compressed thumbnail inside the image file, or {@code null} if there is no * JPEG compressed thumbnail. * The returned data can be decoded using * {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)} */ public byte[] getThumbnail() { if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return getThumbnailBytes(); } return null; } /** * Returns the thumbnail bytes inside the image file, regardless of the compression type of the * thumbnail image. */ public byte[] getThumbnailBytes() { if (!mHasThumbnail) { return null; } Loading Loading @@ -1879,6 +1899,7 @@ public class ExifInterface { if (in.read(buffer) != mThumbnailLength) { throw new IOException("Corrupted image"); } mThumbnailBytes = buffer; return buffer; } catch (IOException | ErrnoException e) { // Couldn't get a thumbnail image. Loading @@ -1888,6 +1909,52 @@ public class ExifInterface { return null; } /** * Creates and returns a Bitmap object of the thumbnail image based on the byte array and the * thumbnail compression value, or {@code null} if the compression type is unsupported. */ public Bitmap getThumbnailBitmap() { if (!mHasThumbnail) { return null; } else if (mThumbnailBytes == null) { mThumbnailBytes = getThumbnailBytes(); } if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return BitmapFactory.decodeByteArray(mThumbnailBytes, 0, mThumbnailLength); } else if (mThumbnailCompression == DATA_UNCOMPRESSED) { int[] rgbValues = new int[mThumbnailBytes.length / 3]; byte alpha = (byte) 0xff000000; for (int i = 0; i < rgbValues.length; i++) { rgbValues[i] = alpha + (mThumbnailBytes[3 * i] << 16) + (mThumbnailBytes[3 * i + 1] << 8) + mThumbnailBytes[3 * i + 2]; } ExifAttribute imageLengthAttribute = (ExifAttribute) mAttributes[IFD_THUMBNAIL_HINT].get(TAG_IMAGE_LENGTH); ExifAttribute imageWidthAttribute = (ExifAttribute) mAttributes[IFD_THUMBNAIL_HINT].get(TAG_IMAGE_WIDTH); if (imageLengthAttribute != null && imageWidthAttribute != null) { int imageLength = imageLengthAttribute.getIntValue(mExifByteOrder); int imageWidth = imageWidthAttribute.getIntValue(mExifByteOrder); return Bitmap.createBitmap( rgbValues, imageWidth, imageLength, Bitmap.Config.ARGB_8888); } } return null; } /** * Returns true if thumbnail image is JPEG Compressed, or false if either thumbnail image does * not exist or thumbnail image is uncompressed. */ public boolean isThumbnailCompressed() { if (mThumbnailCompression == DATA_JPEG || mThumbnailCompression == DATA_JPEG_COMPRESSED) { return true; } return false; } /** * Returns the offset and length of thumbnail inside the image file, or * {@code null} if there is no thumbnail. Loading Loading @@ -2944,33 +3011,27 @@ public class ExifInterface { ExifAttribute compressionAttribute = (ExifAttribute) thumbnailData.get(TAG_COMPRESSION); if (compressionAttribute != null) { int compressionValue = compressionAttribute.getIntValue(mExifByteOrder); switch (compressionValue) { case DATA_UNCOMPRESSED: { // TODO: add implementation for reading uncompressed thumbnail data (b/28156704) Log.d(TAG, "Uncompressed thumbnail data cannot be processed"); break; } mThumbnailCompression = compressionAttribute.getIntValue(mExifByteOrder); switch (mThumbnailCompression) { case DATA_JPEG: { handleThumbnailFromJfif(in, thumbnailData); break; } case DATA_UNCOMPRESSED: case DATA_JPEG_COMPRESSED: { handleThumbnailFromStrips(in, thumbnailData); break; } default: { break; } } } else { // Thumbnail data may not contain Compression tag value mThumbnailCompression = DATA_JPEG; handleThumbnailFromJfif(in, thumbnailData); } } // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values and // create a bitmap based on those values // Check JpegInterchangeFormat(JFIF) tags to retrieve thumbnail offset & length values // and reads the corresponding bytes if stream does not support seek function private void handleThumbnailFromJfif(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute jpegInterchangeFormatAttribute = (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT); Loading @@ -2978,41 +3039,9 @@ public class ExifInterface { (ExifAttribute) thumbnailData.get(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); if (jpegInterchangeFormatAttribute != null && jpegInterchangeFormatLengthAttribute != null) { int jpegInterchangeFormat = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int jpegInterchangeFormatLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); createJpegThumbnailBitmap(in, jpegInterchangeFormat, jpegInterchangeFormatLength); } } int thumbnailOffset = jpegInterchangeFormatAttribute.getIntValue(mExifByteOrder); int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder); // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values and // create a bitmap based on those values private void handleThumbnailFromStrips(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute stripOffsetsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS); ExifAttribute stripByteCountsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS); if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) { long[] stripOffsetsArray = (long[]) stripOffsetsAttribute.getValue(mExifByteOrder); long[] stripByteCountsArray = (long[]) stripByteCountsAttribute.getValue(mExifByteOrder); if (stripOffsetsArray.length == 1) { int stripOffsetsSum = (int) Arrays.stream(stripOffsetsArray).sum(); int stripByteCountsSum = (int) Arrays.stream(stripByteCountsArray).sum(); createJpegThumbnailBitmap(in, stripOffsetsSum, stripByteCountsSum); } else { // TODO: implement method to read multiple strips (b/29737797) Log.d(TAG, "Multiple strip thumbnail data cannot be processed"); } } } // Creates a bitmap data based on thumbnail offset and length for JPEG Compression private void createJpegThumbnailBitmap(InputStream in, int thumbnailOffset, int thumbnailLength) throws IOException { // The following code limits the size of thumbnail size not to overflow EXIF data area. thumbnailLength = Math.min(thumbnailLength, in.available() - thumbnailOffset); if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF Loading @@ -3023,30 +3052,86 @@ public class ExifInterface { thumbnailOffset += mOrfMakerNoteOffset; } if (DEBUG) { Log.d(TAG, "Creating JPEG Thumbnail with offset: " + thumbnailOffset); Log.d(TAG, "Setting thumbnail attributes with offset: " + thumbnailOffset); } if (thumbnailOffset > 0 && thumbnailLength > 0) { mHasThumbnail = true; mThumbnailOffset = thumbnailOffset; mThumbnailLength = thumbnailLength; if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) { if (mFilename == null && mAssetInputStream == null && mSeekableFileDescriptor == null) { // Save the thumbnail in memory if the input doesn't support reading again. byte[] thumbnailBytes = new byte[thumbnailLength]; in.skip(thumbnailOffset); in.read(thumbnailBytes); mThumbnailBytes = thumbnailBytes; if (DEBUG) { Bitmap bitmap = BitmapFactory.decodeByteArray( thumbnailBytes, 0, thumbnailBytes.length); Log.d(TAG, "Thumbnail offset: " + mThumbnailOffset + ", length: " + mThumbnailLength + ", width: " + bitmap.getWidth() + ", height: " + bitmap.getHeight()); } } } } // Check StripOffsets & StripByteCounts tags to retrieve thumbnail offset & length values private void handleThumbnailFromStrips(InputStream in, HashMap thumbnailData) throws IOException { ExifAttribute bitsPerSampleAttribute = (ExifAttribute) thumbnailData.get(TAG_BITS_PER_SAMPLE); if (bitsPerSampleAttribute != null) { int[] bitsPerSampleValue = (int[]) bitsPerSampleAttribute.getValue(mExifByteOrder); if (Arrays.equals(BITS_PER_SAMPLE_RGB, bitsPerSampleValue)) { ExifAttribute stripOffsetsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_OFFSETS); ExifAttribute stripByteCountsAttribute = (ExifAttribute) thumbnailData.get(TAG_STRIP_BYTE_COUNTS); if (stripOffsetsAttribute != null && stripByteCountsAttribute != null) { long[] stripOffsets = (long[]) stripOffsetsAttribute.getValue(mExifByteOrder); long[] stripByteCounts = (long[]) stripByteCountsAttribute.getValue(mExifByteOrder); // Set thumbnail byte array data for non-consecutive strip bytes byte[] totalStripBytes = new byte[(int) Arrays.stream(stripByteCounts).sum()]; int bytesRead = 0; int bytesAdded = 0; for (int i = 0; i < stripOffsets.length; i++) { int stripOffset = (int) stripOffsets[i]; int stripByteCount = (int) stripByteCounts[i]; // Skip to offset int skipBytes = stripOffset - bytesRead; if (skipBytes < 0) { Log.d(TAG, "Invalid strip offset value"); } in.skip(skipBytes); bytesRead += skipBytes; // Read strip bytes byte[] stripBytes = new byte[stripByteCount]; in.read(stripBytes); bytesRead += stripByteCount; // Add bytes to array System.arraycopy(stripBytes, 0, totalStripBytes, bytesAdded, stripBytes.length); bytesAdded += stripBytes.length; } mHasThumbnail = true; mThumbnailBytes = totalStripBytes; } } } else { if (DEBUG) { Log.d(TAG, "Only Uncompressed RGB data process is supported"); } return; } } // Returns true if the image length and width values are <= 512. // See Section 4.8 of http://standardsproposals.bsigroup.com/Home/getPDF/567 private boolean isThumbnail(HashMap map) throws IOException { Loading
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java +1 −2 Original line number Diff line number Diff line Loading @@ -267,8 +267,7 @@ public class ExifInterfaceTest extends AndroidTestCase { if (expectedValue.hasThumbnail) { byte[] thumbnailBytes = exifInterface.getThumbnail(); assertNotNull(thumbnailBytes); Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0, thumbnailBytes.length); Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap(); assertNotNull(thumbnailBitmap); assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth()); assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight()); Loading