From ea8118bdb0ddc144caa952205f8115c08913834c Mon Sep 17 00:00:00 2001 From: Robert Odrowaz Date: Fri, 18 Aug 2023 08:23:38 +0200 Subject: [PATCH 1/5] Copy exif tags in categories 2 and 3 --- .../image_picker_android/CHANGELOG.md | 4 + .../plugins/imagepicker/ExifDataCopier.java | 126 +++++++++++++++--- .../image_picker_android/pubspec.yaml | 2 +- 3 files changed, 112 insertions(+), 20 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index b237a8c63ee3..1d14021f1536 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.8 + +* Adds additional category II and III exif tags to be copied during photo resize. + ## 0.8.7+5 * Adds pub topics to package metadata. diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java index eada546f029a..5bf0fa25df7f 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java @@ -4,6 +4,7 @@ package io.flutter.plugins.imagepicker; +import static androidx.exifinterface.media.ExifInterface.*; import android.util.Log; import androidx.exifinterface.media.ExifInterface; import java.util.Arrays; @@ -17,25 +18,112 @@ void copyExif(String filePathOri, String filePathDest) { List attributes = Arrays.asList( - "FNumber", - "ExposureTime", - "ISOSpeedRatings", - "GPSAltitude", - "GPSAltitudeRef", - "FocalLength", - "GPSDateStamp", - "WhiteBalance", - "GPSProcessingMethod", - "GPSTimeStamp", - "DateTime", - "Flash", - "GPSLatitude", - "GPSLatitudeRef", - "GPSLongitude", - "GPSLongitudeRef", - "Make", - "Model", - "Orientation"); + TAG_IMAGE_DESCRIPTION, + TAG_MAKE, + TAG_MODEL, + TAG_SOFTWARE, + TAG_DATETIME, + TAG_ARTIST, + TAG_COPYRIGHT, + TAG_EXPOSURE_TIME, + TAG_F_NUMBER, + TAG_EXPOSURE_PROGRAM, + TAG_SPECTRAL_SENSITIVITY, + TAG_PHOTOGRAPHIC_SENSITIVITY, + TAG_ISO_SPEED_RATINGS, + TAG_OECF, + TAG_SENSITIVITY_TYPE, + TAG_STANDARD_OUTPUT_SENSITIVITY, + TAG_RECOMMENDED_EXPOSURE_INDEX, + TAG_ISO_SPEED, + TAG_ISO_SPEED_LATITUDE_YYY, + TAG_ISO_SPEED_LATITUDE_ZZZ, + TAG_EXIF_VERSION, + TAG_DATETIME_ORIGINAL, + TAG_DATETIME_DIGITIZED, + TAG_OFFSET_TIME, + TAG_OFFSET_TIME_ORIGINAL, + TAG_OFFSET_TIME_DIGITIZED, + TAG_SHUTTER_SPEED_VALUE, + TAG_APERTURE_VALUE, + TAG_BRIGHTNESS_VALUE, + TAG_EXPOSURE_BIAS_VALUE, + TAG_MAX_APERTURE_VALUE, + TAG_SUBJECT_DISTANCE, + TAG_METERING_MODE, + TAG_LIGHT_SOURCE, + TAG_FLASH, + TAG_FOCAL_LENGTH, + TAG_MAKER_NOTE, + TAG_USER_COMMENT, + TAG_SUBSEC_TIME, + TAG_SUBSEC_TIME_ORIGINAL, + TAG_SUBSEC_TIME_DIGITIZED, + TAG_FLASHPIX_VERSION, + TAG_FLASH_ENERGY, + TAG_SPATIAL_FREQUENCY_RESPONSE, + TAG_FOCAL_PLANE_X_RESOLUTION, + TAG_FOCAL_PLANE_Y_RESOLUTION, + TAG_FOCAL_PLANE_RESOLUTION_UNIT, + TAG_EXPOSURE_INDEX, + TAG_SENSING_METHOD, + TAG_FILE_SOURCE, + TAG_SCENE_TYPE, + TAG_CFA_PATTERN, + TAG_CUSTOM_RENDERED, + TAG_EXPOSURE_MODE, + TAG_WHITE_BALANCE, + TAG_DIGITAL_ZOOM_RATIO, + TAG_FOCAL_LENGTH_IN_35MM_FILM, + TAG_SCENE_CAPTURE_TYPE, + TAG_GAIN_CONTROL, + TAG_CONTRAST, + TAG_SATURATION, + TAG_SHARPNESS, + TAG_DEVICE_SETTING_DESCRIPTION, + TAG_SUBJECT_DISTANCE_RANGE, + TAG_IMAGE_UNIQUE_ID, + TAG_CAMERA_OWNER_NAME, + TAG_BODY_SERIAL_NUMBER, + TAG_LENS_SPECIFICATION, + TAG_LENS_MAKE, + TAG_LENS_MODEL, + TAG_LENS_SERIAL_NUMBER, + TAG_GPS_VERSION_ID, + TAG_GPS_LATITUDE_REF, + TAG_GPS_LATITUDE, + TAG_GPS_LONGITUDE_REF, + TAG_GPS_LONGITUDE, + TAG_GPS_ALTITUDE_REF, + TAG_GPS_ALTITUDE, + TAG_GPS_TIMESTAMP, + TAG_GPS_SATELLITES, + TAG_GPS_STATUS, + TAG_GPS_MEASURE_MODE, + TAG_GPS_DOP, + TAG_GPS_SPEED_REF, + TAG_GPS_SPEED, + TAG_GPS_TRACK_REF, + TAG_GPS_TRACK, + TAG_GPS_IMG_DIRECTION_REF, + TAG_GPS_IMG_DIRECTION, + TAG_GPS_MAP_DATUM, + TAG_GPS_DEST_LATITUDE_REF, + TAG_GPS_DEST_LATITUDE, + TAG_GPS_DEST_LONGITUDE_REF, + TAG_GPS_DEST_LONGITUDE, + TAG_GPS_DEST_BEARING_REF, + TAG_GPS_DEST_BEARING, + TAG_GPS_DEST_DISTANCE_REF, + TAG_GPS_DEST_DISTANCE, + TAG_GPS_PROCESSING_METHOD, + TAG_GPS_AREA_INFORMATION, + TAG_GPS_DATESTAMP, + TAG_GPS_DIFFERENTIAL, + TAG_GPS_H_POSITIONING_ERROR, + TAG_INTEROPERABILITY_INDEX, + TAG_ORIENTATION + ); for (String attribute : attributes) { setIfNotNull(oldExif, newExif, attribute); } diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index a8bfb2133a4f..434005967530 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.7+5 +version: 0.8.8 environment: sdk: ">=2.19.0 <4.0.0" From 027149a69f351bb4d3986727567709c13a67235d Mon Sep 17 00:00:00 2001 From: Robert Odrowaz Date: Fri, 25 Aug 2023 10:07:41 +0200 Subject: [PATCH 2/5] Correct formatting and imports --- .../plugins/imagepicker/ExifDataCopier.java | 212 +++++++++--------- 1 file changed, 105 insertions(+), 107 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java index 5bf0fa25df7f..88aa929da033 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java @@ -4,7 +4,6 @@ package io.flutter.plugins.imagepicker; -import static androidx.exifinterface.media.ExifInterface.*; import android.util.Log; import androidx.exifinterface.media.ExifInterface; import java.util.Arrays; @@ -18,112 +17,111 @@ void copyExif(String filePathOri, String filePathDest) { List attributes = Arrays.asList( - TAG_IMAGE_DESCRIPTION, - TAG_MAKE, - TAG_MODEL, - TAG_SOFTWARE, - TAG_DATETIME, - TAG_ARTIST, - TAG_COPYRIGHT, - TAG_EXPOSURE_TIME, - TAG_F_NUMBER, - TAG_EXPOSURE_PROGRAM, - TAG_SPECTRAL_SENSITIVITY, - TAG_PHOTOGRAPHIC_SENSITIVITY, - TAG_ISO_SPEED_RATINGS, - TAG_OECF, - TAG_SENSITIVITY_TYPE, - TAG_STANDARD_OUTPUT_SENSITIVITY, - TAG_RECOMMENDED_EXPOSURE_INDEX, - TAG_ISO_SPEED, - TAG_ISO_SPEED_LATITUDE_YYY, - TAG_ISO_SPEED_LATITUDE_ZZZ, - TAG_EXIF_VERSION, - TAG_DATETIME_ORIGINAL, - TAG_DATETIME_DIGITIZED, - TAG_OFFSET_TIME, - TAG_OFFSET_TIME_ORIGINAL, - TAG_OFFSET_TIME_DIGITIZED, - TAG_SHUTTER_SPEED_VALUE, - TAG_APERTURE_VALUE, - TAG_BRIGHTNESS_VALUE, - TAG_EXPOSURE_BIAS_VALUE, - TAG_MAX_APERTURE_VALUE, - TAG_SUBJECT_DISTANCE, - TAG_METERING_MODE, - TAG_LIGHT_SOURCE, - TAG_FLASH, - TAG_FOCAL_LENGTH, - TAG_MAKER_NOTE, - TAG_USER_COMMENT, - TAG_SUBSEC_TIME, - TAG_SUBSEC_TIME_ORIGINAL, - TAG_SUBSEC_TIME_DIGITIZED, - TAG_FLASHPIX_VERSION, - TAG_FLASH_ENERGY, - TAG_SPATIAL_FREQUENCY_RESPONSE, - TAG_FOCAL_PLANE_X_RESOLUTION, - TAG_FOCAL_PLANE_Y_RESOLUTION, - TAG_FOCAL_PLANE_RESOLUTION_UNIT, - TAG_EXPOSURE_INDEX, - TAG_SENSING_METHOD, - TAG_FILE_SOURCE, - TAG_SCENE_TYPE, - TAG_CFA_PATTERN, - TAG_CUSTOM_RENDERED, - TAG_EXPOSURE_MODE, - TAG_WHITE_BALANCE, - TAG_DIGITAL_ZOOM_RATIO, - TAG_FOCAL_LENGTH_IN_35MM_FILM, - TAG_SCENE_CAPTURE_TYPE, - TAG_GAIN_CONTROL, - TAG_CONTRAST, - TAG_SATURATION, - TAG_SHARPNESS, - TAG_DEVICE_SETTING_DESCRIPTION, - TAG_SUBJECT_DISTANCE_RANGE, - TAG_IMAGE_UNIQUE_ID, - TAG_CAMERA_OWNER_NAME, - TAG_BODY_SERIAL_NUMBER, - TAG_LENS_SPECIFICATION, - TAG_LENS_MAKE, - TAG_LENS_MODEL, - TAG_LENS_SERIAL_NUMBER, - TAG_GPS_VERSION_ID, - TAG_GPS_LATITUDE_REF, - TAG_GPS_LATITUDE, - TAG_GPS_LONGITUDE_REF, - TAG_GPS_LONGITUDE, - TAG_GPS_ALTITUDE_REF, - TAG_GPS_ALTITUDE, - TAG_GPS_TIMESTAMP, - TAG_GPS_SATELLITES, - TAG_GPS_STATUS, - TAG_GPS_MEASURE_MODE, - TAG_GPS_DOP, - TAG_GPS_SPEED_REF, - TAG_GPS_SPEED, - TAG_GPS_TRACK_REF, - TAG_GPS_TRACK, - TAG_GPS_IMG_DIRECTION_REF, - TAG_GPS_IMG_DIRECTION, - TAG_GPS_MAP_DATUM, - TAG_GPS_DEST_LATITUDE_REF, - TAG_GPS_DEST_LATITUDE, - TAG_GPS_DEST_LONGITUDE_REF, - TAG_GPS_DEST_LONGITUDE, - TAG_GPS_DEST_BEARING_REF, - TAG_GPS_DEST_BEARING, - TAG_GPS_DEST_DISTANCE_REF, - TAG_GPS_DEST_DISTANCE, - TAG_GPS_PROCESSING_METHOD, - TAG_GPS_AREA_INFORMATION, - TAG_GPS_DATESTAMP, - TAG_GPS_DIFFERENTIAL, - TAG_GPS_H_POSITIONING_ERROR, - TAG_INTEROPERABILITY_INDEX, - TAG_ORIENTATION - ); + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_SOFTWARE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_COPYRIGHT, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_ISO_SPEED_RATINGS, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_LENS_SERIAL_NUMBER, + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR, + ExifInterface.TAG_INTEROPERABILITY_INDEX, + ExifInterface.TAG_ORIENTATION); for (String attribute : attributes) { setIfNotNull(oldExif, newExif, attribute); } From 216d016044006b9c30eddf4a1f662b05ab9549f8 Mon Sep 17 00:00:00 2001 From: Robert Odrowaz Date: Mon, 28 Aug 2023 17:12:14 +0200 Subject: [PATCH 3/5] Suppress ISOSpeedRatings deprecation --- .../main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java index 88aa929da033..3f1887d98e4d 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java @@ -15,6 +15,7 @@ void copyExif(String filePathOri, String filePathDest) { ExifInterface oldExif = new ExifInterface(filePathOri); ExifInterface newExif = new ExifInterface(filePathDest); + @SuppressWarnings("deprecation") List attributes = Arrays.asList( ExifInterface.TAG_IMAGE_DESCRIPTION, From 73836394221c862e309da8bfa2d588fd23faaa74 Mon Sep 17 00:00:00 2001 From: Robert Odrowaz Date: Mon, 28 Aug 2023 17:14:24 +0200 Subject: [PATCH 4/5] Refactor exif data copier and add tests --- .../plugins/imagepicker/ExifDataCopier.java | 236 +++++++++--------- .../plugins/imagepicker/ImageResizer.java | 7 +- .../imagepicker/ExifDataCopierTest.java | 114 +++++++++ 3 files changed, 234 insertions(+), 123 deletions(-) create mode 100644 packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ExifDataCopierTest.java diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java index 3f1887d98e4d..72080a72cacf 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java @@ -4,134 +4,126 @@ package io.flutter.plugins.imagepicker; -import android.util.Log; import androidx.exifinterface.media.ExifInterface; +import java.io.IOException; import java.util.Arrays; import java.util.List; class ExifDataCopier { - void copyExif(String filePathOri, String filePathDest) { - try { - ExifInterface oldExif = new ExifInterface(filePathOri); - ExifInterface newExif = new ExifInterface(filePathDest); - - @SuppressWarnings("deprecation") - List attributes = - Arrays.asList( - ExifInterface.TAG_IMAGE_DESCRIPTION, - ExifInterface.TAG_MAKE, - ExifInterface.TAG_MODEL, - ExifInterface.TAG_SOFTWARE, - ExifInterface.TAG_DATETIME, - ExifInterface.TAG_ARTIST, - ExifInterface.TAG_COPYRIGHT, - ExifInterface.TAG_EXPOSURE_TIME, - ExifInterface.TAG_F_NUMBER, - ExifInterface.TAG_EXPOSURE_PROGRAM, - ExifInterface.TAG_SPECTRAL_SENSITIVITY, - ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, - ExifInterface.TAG_ISO_SPEED_RATINGS, - ExifInterface.TAG_OECF, - ExifInterface.TAG_SENSITIVITY_TYPE, - ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, - ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, - ExifInterface.TAG_ISO_SPEED, - ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, - ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, - ExifInterface.TAG_EXIF_VERSION, - ExifInterface.TAG_DATETIME_ORIGINAL, - ExifInterface.TAG_DATETIME_DIGITIZED, - ExifInterface.TAG_OFFSET_TIME, - ExifInterface.TAG_OFFSET_TIME_ORIGINAL, - ExifInterface.TAG_OFFSET_TIME_DIGITIZED, - ExifInterface.TAG_SHUTTER_SPEED_VALUE, - ExifInterface.TAG_APERTURE_VALUE, - ExifInterface.TAG_BRIGHTNESS_VALUE, - ExifInterface.TAG_EXPOSURE_BIAS_VALUE, - ExifInterface.TAG_MAX_APERTURE_VALUE, - ExifInterface.TAG_SUBJECT_DISTANCE, - ExifInterface.TAG_METERING_MODE, - ExifInterface.TAG_LIGHT_SOURCE, - ExifInterface.TAG_FLASH, - ExifInterface.TAG_FOCAL_LENGTH, - ExifInterface.TAG_MAKER_NOTE, - ExifInterface.TAG_USER_COMMENT, - ExifInterface.TAG_SUBSEC_TIME, - ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, - ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, - ExifInterface.TAG_FLASHPIX_VERSION, - ExifInterface.TAG_FLASH_ENERGY, - ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, - ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, - ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, - ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, - ExifInterface.TAG_EXPOSURE_INDEX, - ExifInterface.TAG_SENSING_METHOD, - ExifInterface.TAG_FILE_SOURCE, - ExifInterface.TAG_SCENE_TYPE, - ExifInterface.TAG_CFA_PATTERN, - ExifInterface.TAG_CUSTOM_RENDERED, - ExifInterface.TAG_EXPOSURE_MODE, - ExifInterface.TAG_WHITE_BALANCE, - ExifInterface.TAG_DIGITAL_ZOOM_RATIO, - ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, - ExifInterface.TAG_SCENE_CAPTURE_TYPE, - ExifInterface.TAG_GAIN_CONTROL, - ExifInterface.TAG_CONTRAST, - ExifInterface.TAG_SATURATION, - ExifInterface.TAG_SHARPNESS, - ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, - ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, - ExifInterface.TAG_IMAGE_UNIQUE_ID, - ExifInterface.TAG_CAMERA_OWNER_NAME, - ExifInterface.TAG_BODY_SERIAL_NUMBER, - ExifInterface.TAG_LENS_SPECIFICATION, - ExifInterface.TAG_LENS_MAKE, - ExifInterface.TAG_LENS_MODEL, - ExifInterface.TAG_LENS_SERIAL_NUMBER, - ExifInterface.TAG_GPS_VERSION_ID, - ExifInterface.TAG_GPS_LATITUDE_REF, - ExifInterface.TAG_GPS_LATITUDE, - ExifInterface.TAG_GPS_LONGITUDE_REF, - ExifInterface.TAG_GPS_LONGITUDE, - ExifInterface.TAG_GPS_ALTITUDE_REF, - ExifInterface.TAG_GPS_ALTITUDE, - ExifInterface.TAG_GPS_TIMESTAMP, - ExifInterface.TAG_GPS_SATELLITES, - ExifInterface.TAG_GPS_STATUS, - ExifInterface.TAG_GPS_MEASURE_MODE, - ExifInterface.TAG_GPS_DOP, - ExifInterface.TAG_GPS_SPEED_REF, - ExifInterface.TAG_GPS_SPEED, - ExifInterface.TAG_GPS_TRACK_REF, - ExifInterface.TAG_GPS_TRACK, - ExifInterface.TAG_GPS_IMG_DIRECTION_REF, - ExifInterface.TAG_GPS_IMG_DIRECTION, - ExifInterface.TAG_GPS_MAP_DATUM, - ExifInterface.TAG_GPS_DEST_LATITUDE_REF, - ExifInterface.TAG_GPS_DEST_LATITUDE, - ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, - ExifInterface.TAG_GPS_DEST_LONGITUDE, - ExifInterface.TAG_GPS_DEST_BEARING_REF, - ExifInterface.TAG_GPS_DEST_BEARING, - ExifInterface.TAG_GPS_DEST_DISTANCE_REF, - ExifInterface.TAG_GPS_DEST_DISTANCE, - ExifInterface.TAG_GPS_PROCESSING_METHOD, - ExifInterface.TAG_GPS_AREA_INFORMATION, - ExifInterface.TAG_GPS_DATESTAMP, - ExifInterface.TAG_GPS_DIFFERENTIAL, - ExifInterface.TAG_GPS_H_POSITIONING_ERROR, - ExifInterface.TAG_INTEROPERABILITY_INDEX, - ExifInterface.TAG_ORIENTATION); - for (String attribute : attributes) { - setIfNotNull(oldExif, newExif, attribute); - } - - newExif.saveAttributes(); - - } catch (Exception ex) { - Log.e("ExifDataCopier", "Error preserving Exif data on selected image: " + ex); + void copyExif(ExifInterface oldExif, ExifInterface newExif) throws IOException { + @SuppressWarnings("deprecation") + List attributes = + Arrays.asList( + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_SOFTWARE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_COPYRIGHT, + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_ISO_SPEED_RATINGS, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_LENS_SERIAL_NUMBER, + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR, + ExifInterface.TAG_INTEROPERABILITY_INDEX, + ExifInterface.TAG_ORIENTATION); + for (String attribute : attributes) { + setIfNotNull(oldExif, newExif, attribute); } + + newExif.saveAttributes(); } private static void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String property) { diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java index 6e04ed40a11a..b5024d6780c3 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.SizeFCompat; +import androidx.exifinterface.media.ExifInterface; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -137,7 +138,11 @@ private FileOutputStream createOutputStream(File imageFile) throws IOException { } private void copyExif(String filePathOri, String filePathDest) { - exifDataCopier.copyExif(filePathOri, filePathDest); + try { + exifDataCopier.copyExif(new ExifInterface(filePathOri), new ExifInterface(filePathDest)); + } catch (Exception ex) { + Log.e("ImageResizer", "Error preserving Exif data on selected image: " + ex); + } } private SizeFCompat readFileDimensions(String path) { diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ExifDataCopierTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ExifDataCopierTest.java new file mode 100644 index 000000000000..4e2521e954d8 --- /dev/null +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/ExifDataCopierTest.java @@ -0,0 +1,114 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.imagepicker; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import androidx.exifinterface.media.ExifInterface; +import java.io.IOException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ExifDataCopierTest { + @Mock ExifInterface mockOldExif; + @Mock ExifInterface mockNewExif; + + ExifDataCopier exifDataCopier = new ExifDataCopier(); + + AutoCloseable mockCloseable; + + String orientationValue = "Horizontal (normal)"; + String imageWidthValue = "4032"; + String whitePointValue = "0.96419 1 0.82489"; + String colorSpaceValue = "Uncalibrated"; + String exposureTimeValue = "1/9"; + String exposureModeValue = "Auto"; + String exifVersionValue = "0232"; + String makeValue = "Apple"; + String dateTimeOriginalValue = "2023:02:14 18:55:19"; + String offsetTimeValue = "+01:00"; + + @Before + public void setUp() { + mockCloseable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + mockCloseable.close(); + } + + @Test + public void copyExif_copiesOrientationAttribute() throws IOException { + when(mockOldExif.getAttribute(ExifInterface.TAG_ORIENTATION)).thenReturn(orientationValue); + + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif).setAttribute(ExifInterface.TAG_ORIENTATION, orientationValue); + } + + @Test + public void copyExif_doesNotCopyCategory1AttributesExceptForOrientation() throws IOException { + when(mockOldExif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH)).thenReturn(imageWidthValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_WHITE_POINT)).thenReturn(whitePointValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_COLOR_SPACE)).thenReturn(colorSpaceValue); + + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif, never()).setAttribute(eq(ExifInterface.TAG_IMAGE_WIDTH), any()); + verify(mockNewExif, never()).setAttribute(eq(ExifInterface.TAG_WHITE_POINT), any()); + verify(mockNewExif, never()).setAttribute(eq(ExifInterface.TAG_COLOR_SPACE), any()); + } + + @Test + public void copyExif_copiesCategory2Attributes() throws IOException { + when(mockOldExif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME)).thenReturn(exposureTimeValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_EXPOSURE_MODE)).thenReturn(exposureModeValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_EXIF_VERSION)).thenReturn(exifVersionValue); + + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif).setAttribute(ExifInterface.TAG_EXPOSURE_TIME, exposureTimeValue); + verify(mockNewExif).setAttribute(ExifInterface.TAG_EXPOSURE_MODE, exposureModeValue); + verify(mockNewExif).setAttribute(ExifInterface.TAG_EXIF_VERSION, exifVersionValue); + } + + @Test + public void copyExif_copiesCategory3Attributes() throws IOException { + when(mockOldExif.getAttribute(ExifInterface.TAG_MAKE)).thenReturn(makeValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL)) + .thenReturn(dateTimeOriginalValue); + when(mockOldExif.getAttribute(ExifInterface.TAG_OFFSET_TIME)).thenReturn(offsetTimeValue); + + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif).setAttribute(ExifInterface.TAG_MAKE, makeValue); + verify(mockNewExif).setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue); + verify(mockNewExif).setAttribute(ExifInterface.TAG_OFFSET_TIME, offsetTimeValue); + } + + @Test + public void copyExif_doesNotCopyUnsetAttributes() throws IOException { + when(mockOldExif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME)).thenReturn(null); + + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif, never()).setAttribute(eq(ExifInterface.TAG_EXPOSURE_TIME), any()); + } + + @Test + public void copyExif_savesAttributes() throws IOException { + exifDataCopier.copyExif(mockOldExif, mockNewExif); + + verify(mockNewExif).saveAttributes(); + } +} From 6d053967dd584cb80525283bbde630b579ed71f7 Mon Sep 17 00:00:00 2001 From: Robert Odrowaz Date: Mon, 4 Sep 2023 10:07:37 +0200 Subject: [PATCH 5/5] Add exif copying docs --- .../flutter/plugins/imagepicker/ExifDataCopier.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java index 72080a72cacf..e80e1602146e 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/ExifDataCopier.java @@ -10,6 +10,18 @@ import java.util.List; class ExifDataCopier { + /** + * Copies all exif data not related to image structure and orientation tag. Data not related to + * image structure consists of category II (Shooting condition related metadata) and category III + * (Metadata storing other information) tags. Category I tags are not copied because they may be + * invalidated as a result of resizing. The exception is the orientation tag which is known to not + * be invalidated and is crucial for proper display of the image. + * + *

The categories mentioned refer to standard "CIPA DC-008-Translation-2012 Exchangeable image + * file format for digital still cameras: Exif Version 2.3" + * https://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf. Version 2.3 has been chosen because + * {@code ExifInterface} is based on it. + */ void copyExif(ExifInterface oldExif, ExifInterface newExif) throws IOException { @SuppressWarnings("deprecation") List attributes =