diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-10-10 18:21:11 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2022-10-10 18:21:11 +0000 |
commit | 3c53934294e678ce8dddf8fd4de53248e221c841 (patch) | |
tree | ded9bad01807d883c6adc9db9d75fe325e9ec5a0 | |
parent | 519a3332e6c8fdede8c97258f204198f32700112 (diff) | |
parent | cfa07d99d34b6426a82be93b2834c5120ee4154d (diff) | |
download | support-sparse-9158283-L16600000958391730.tar.gz |
Merge "Merge cherrypicks of [2234224, 2234225, 2245634] into androidx-exifinterface-release." into androidx-exifinterface-releasesparse-9158283-L16600000958391730
3 files changed, 88 insertions, 8 deletions
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt index 3e33133bb22..88828ffc52a 100644 --- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt +++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt @@ -59,7 +59,7 @@ object LibraryVersions { val DYNAMICANIMATION_KTX = Version("1.0.0-alpha04") val EMOJI = Version("1.2.0-alpha01") val ENTERPRISE = Version("1.1.0-alpha02") - val EXIFINTERFACE = Version("1.3.4") + val EXIFINTERFACE = Version("1.3.5") val FRAGMENT = Version("1.3.0-alpha08") val FUTURES = Version("1.2.0-alpha01") val GRIDLAYOUT = Version("1.1.0-alpha01") diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java index 3a0edbb055b..2b0a5e51390 100644 --- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java +++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java @@ -1240,15 +1240,19 @@ public class ExifInterfaceTest { ExpectedValue expectedValue = new ExpectedValue( getApplicationContext().getResources().obtainTypedArray(typedArrayResourceId)); - File imageFile = getFileFromExternalDir(fileName); + File srcFile = getFileFromExternalDir(fileName); + File imageFile = clone(srcFile); String verboseTag = imageFile.getName(); ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); exifInterface.saveAttributes(); exifInterface = new ExifInterface(imageFile.getAbsolutePath()); compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false); + assertBitmapsEquivalent(srcFile, imageFile); + assertSecondSaveProducesSameSizeFile(imageFile); // Test for modifying one attribute. + exifInterface = new ExifInterface(imageFile.getAbsolutePath()); String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE); exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); exifInterface.saveAttributes(); @@ -1256,7 +1260,6 @@ public class ExifInterfaceTest { if (expectedValue.hasThumbnail) { testThumbnail(expectedValue, exifInterface); } - exifInterface = new ExifInterface(imageFile.getAbsolutePath()); assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE)); // Check if thumbnail bytes can be retrieved from the new thumbnail range. if (expectedValue.hasThumbnail) { @@ -1300,15 +1303,19 @@ public class ExifInterfaceTest { } private void writeToFilesWithoutExif(String fileName) throws IOException { - File imageFile = getFileFromExternalDir(fileName); + File srcFile = getFileFromExternalDir(fileName); + File imageFile = clone(srcFile); ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath()); exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc"); exifInterface.saveAttributes(); + assertBitmapsEquivalent(srcFile, imageFile); exifInterface = new ExifInterface(imageFile.getAbsolutePath()); String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE); assertEquals("abc", make); + + assertSecondSaveProducesSameSizeFile(imageFile); } private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) { @@ -1405,4 +1412,70 @@ public class ExifInterfaceTest { return new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName); } + + /** + * Asserts that {@code expectedImageFile} and {@code actualImageFile} can be decoded by + * {@link BitmapFactory} and the results have the same width, height and MIME type. + * + * <p>This does not check the image itself for similarity/equality. + */ + private void assertBitmapsEquivalent(File expectedImageFile, File actualImageFile) { + BitmapFactory.Options expectedOptions = new BitmapFactory.Options(); + Bitmap expectedBitmap = decodeBitmap(expectedImageFile, expectedOptions); + BitmapFactory.Options actualOptions = new BitmapFactory.Options(); + Bitmap actualBitmap = decodeBitmap(actualImageFile, actualOptions); + + assertEquals(expectedOptions.outWidth, actualOptions.outWidth); + assertEquals(expectedOptions.outHeight, actualOptions.outHeight); + assertEquals(expectedOptions.outMimeType, actualOptions.outMimeType); + assertEquals(expectedBitmap.getWidth(), actualBitmap.getWidth()); + assertEquals(expectedBitmap.getHeight(), actualBitmap.getHeight()); + } + + /** + * Equivalent to {@link BitmapFactory#decodeFile(String, BitmapFactory.Options)} but uses a + * {@link BufferedInputStream} to avoid violating + * {@link StrictMode.ThreadPolicy.Builder#detectUnbufferedIo()}. + */ + private static Bitmap decodeBitmap(File file, BitmapFactory.Options options) { + try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { + return BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options); + } catch (IOException e) { + return null; + } + } + + /** + * Asserts that saving the file the second time (without modifying any attributes) produces + * exactly the same length file as the first save. The first save (with no modifications) is + * expected to (possibly) change the file length because {@link ExifInterface} may move/reformat + * the Exif block within the file, but the second save should not make further modifications. + */ + private void assertSecondSaveProducesSameSizeFile(File imageFileAfterOneSave) + throws IOException { + File imageFileAfterTwoSaves = clone(imageFileAfterOneSave); + ExifInterface exifInterface = new ExifInterface(imageFileAfterTwoSaves.getAbsolutePath()); + exifInterface.saveAttributes(); + if (imageFileAfterOneSave.getAbsolutePath().endsWith(".png") + || imageFileAfterOneSave.getAbsolutePath().endsWith(".webp")) { + // PNG and (some) WebP files are (surprisingly) modified between the first and second + // save (b/249097443), so we check the difference between second and third save instead. + File imageFileAfterThreeSaves = clone(imageFileAfterTwoSaves); + exifInterface = new ExifInterface(imageFileAfterThreeSaves.getAbsolutePath()); + exifInterface.saveAttributes(); + assertEquals(imageFileAfterTwoSaves.length(), imageFileAfterThreeSaves.length()); + } else { + assertEquals(imageFileAfterOneSave.length(), imageFileAfterTwoSaves.length()); + } + } + + private File clone(File original) throws IOException { + File cloned = + File.createTempFile("tmp_", System.nanoTime() + "_" + original.getName()); + try (FileInputStream inputStream = new FileInputStream(original); + FileOutputStream outputStream = new FileOutputStream(cloned)) { + copy(inputStream, outputStream); + } + return cloned; + } } diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java index 22c4c286460..507066850be 100644 --- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java +++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java @@ -6518,6 +6518,11 @@ public class ExifInterface { // Skip input stream to the end of the EXIF chunk totalInputStream.skipFully(WEBP_CHUNK_TYPE_BYTE_LENGTH); int exifChunkLength = totalInputStream.readInt(); + // RIFF chunks have a single padding byte at the end if the declared chunk size is + // odd. + if (exifChunkLength % 2 != 0) { + exifChunkLength++; + } totalInputStream.skipFully(exifChunkLength); // Write new EXIF chunk to output stream @@ -6589,7 +6594,7 @@ public class ExifInterface { int widthAndHeight = 0; int width = 0; int height = 0; - int alpha = 0; + boolean alpha = false; // Save VP8 frame data for later byte[] vp8Frame = new byte[3]; @@ -6624,7 +6629,7 @@ public class ExifInterface { width = ((widthAndHeight << 18) >> 18) + 1; height = ((widthAndHeight << 4) >> 18) + 1; // Retrieve alpha bit - alpha = widthAndHeight & (1 << 3); + alpha = (widthAndHeight & (1 << 3)) != 0; bytesToRead -= (1 /* VP8L signature */ + 4); } @@ -6632,10 +6637,12 @@ public class ExifInterface { nonHeaderOutputStream.write(WEBP_CHUNK_TYPE_VP8X); nonHeaderOutputStream.writeInt(WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH); byte[] data = new byte[WEBP_CHUNK_TYPE_VP8X_DEFAULT_LENGTH]; + // ALPHA flag + if (alpha) { + data[0] = (byte) (data[0] | (1 << 4)); + } // EXIF flag data[0] = (byte) (data[0] | (1 << 3)); - // ALPHA flag - data[0] = (byte) (data[0] | (alpha << 4)); // VP8X stores Width - 1 and Height - 1 values width -= 1; height -= 1; |