From 31c2e8e1d8c6dca0c8cd5185f6a5c5e9bb279f15 Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:21:49 +1300 Subject: [PATCH 1/7] Move method channel calls into separate methods --- .../MethodCallHandlerImpl.java | 283 +++++++++--------- 1 file changed, 143 insertions(+), 140 deletions(-) diff --git a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java index 41e7234..b75246e 100644 --- a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java @@ -22,158 +22,182 @@ public class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { private final Context context; + static final List ExifAttributes = Arrays.asList( + "FNumber", + "ExposureTime", + "ISOSpeedRatings", + "GPSAltitude", + "GPSAltitudeRef", + "FocalLength", + "GPSDateStamp", + "WhiteBalance", + "GPSProcessingMethod", + "GPSTimeStamp", + "DateTime", + "Flash", + "GPSLatitude", + "GPSLatitudeRef", + "GPSLongitude", + "GPSLongitudeRef", + "Make", + "Model", + "Orientation"); + MethodCallHandlerImpl(Context context) { this.context = context; } @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { - if(call.method.equals("compressImage")) { + if (call.method.equals("compressImage")) { String fileName = call.argument("file"); int resizePercentage = call.argument("percentage"); int targetWidth = call.argument("targetWidth") == null ? 0 : (int) call.argument("targetWidth"); int targetHeight = call.argument("targetHeight") == null ? 0 : (int) call.argument("targetHeight"); int quality = call.argument("quality"); - File file = new File(fileName); + compressImage(result, fileName, resizePercentage, targetWidth, targetHeight, quality); + } else if (call.method.equals("getImageProperties")) { + String fileName = call.argument("file"); - if(!file.exists()) { - result.error("file does not exist", fileName, null); - return; - } + getImageProperties(result, fileName); + } else if (call.method.equals("cropImage")) { + String fileName = call.argument("file"); + int originX = call.argument("originX"); + int originY = call.argument("originY"); + int width = call.argument("width"); + int height = call.argument("height"); + + cropImage(result, fileName, originX, originY, width, height); + } else if (call.method.equals("getPlatformVersion")) { + result.success("Android " + android.os.Build.VERSION.RELEASE); + } else { + result.notImplemented(); + } + } - Bitmap bmp = BitmapFactory.decodeFile(fileName); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); + private void compressImage(MethodChannel.Result result, String fileName, int resizePercentage, int targetWidth, int targetHeight, int quality) { + File file = new File(fileName); - int newWidth = targetWidth == 0 ? (bmp.getWidth() / 100 * resizePercentage) : targetWidth; - int newHeight = targetHeight == 0 ? (bmp.getHeight() / 100 * resizePercentage) : targetHeight; + if (!file.exists()) { + result.error("file does not exist", fileName, null); + return; + } - bmp = Bitmap.createScaledBitmap( - bmp, newWidth, newHeight, true); + Bitmap bmp = BitmapFactory.decodeFile(fileName); - // reconfigure bitmap to use RGB_565 before compressing - // fixes https://github.com/btastic/flutter_native_image/issues/47 - Bitmap newBmp = bmp.copy(Bitmap.Config.RGB_565, false); - newBmp.compress(Bitmap.CompressFormat.JPEG, quality, bos); + int newWidth = targetWidth == 0 ? (bmp.getWidth() / 100 * resizePercentage) : targetWidth; + int newHeight = targetHeight == 0 ? (bmp.getHeight() / 100 * resizePercentage) : targetHeight; - try { - String outputFileName = File.createTempFile( - getFilenameWithoutExtension(file).concat("_compressed"), - ".jpg", - context.getExternalCacheDir() - ).getPath(); + bmp = Bitmap.createScaledBitmap(bmp, newWidth, newHeight, true); - OutputStream outputStream = new FileOutputStream(outputFileName); - bos.writeTo(outputStream); + // reconfigure bitmap to use RGB_565 before compressing + // fixes https://github.com/btastic/flutter_native_image/issues/47 + Bitmap newBmp = bmp.copy(Bitmap.Config.RGB_565, false); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + newBmp.compress(Bitmap.CompressFormat.JPEG, quality, bos); - copyExif(fileName, outputFileName); + try { + String outputFileName = File.createTempFile( + getFilenameWithoutExtension(file).concat("_compressed"), + ".jpg", + context.getExternalCacheDir() + ).getPath(); + + OutputStream outputStream = new FileOutputStream(outputFileName); + bos.writeTo(outputStream); + + copyExif (fileName, outputFileName); + + result.success(outputFileName); + } catch (FileNotFoundException e) { + e.printStackTrace(); + result.error("file does not exist", fileName, null); + } catch (IOException e) { + e.printStackTrace(); + result.error("something went wrong", fileName, null); + } + } - result.success(outputFileName); - } catch (FileNotFoundException e) { - e.printStackTrace(); - result.error("file does not exist", fileName, null); - } catch (IOException e) { - e.printStackTrace(); - result.error("something went wrong", fileName, null); - } + private void getImageProperties(MethodChannel.Result result, String fileName) { + File file = new File(fileName); + if (!file.exists()) { + result.error("file does not exist", fileName, null); return; } - if(call.method.equals("getImageProperties")) { - String fileName = call.argument("file"); - File file = new File(fileName); - if(!file.exists()) { - result.error("file does not exist", fileName, null); - return; - } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(fileName,options); + HashMap properties = new HashMap(); + properties.put("width", options.outWidth); + properties.put("height", options.outHeight); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(fileName,options); - HashMap properties = new HashMap(); - properties.put("width", options.outWidth); - properties.put("height", options.outHeight); - - int orientation = ExifInterface.ORIENTATION_UNDEFINED; - try { - ExifInterface exif = new ExifInterface(fileName); - orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); - } catch(IOException ex) { - // EXIF could not be read from the file; ignore - } - properties.put("orientation", orientation); + int orientation = ExifInterface.ORIENTATION_UNDEFINED; + try { + ExifInterface exif = new ExifInterface(fileName); + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); + } catch (IOException ex) { + // EXIF could not be read from the file; ignore + } + properties.put("orientation", orientation); + + result.success(properties); + } - result.success(properties); + private void cropImage(MethodChannel.Result result, String fileName, int originX, int originY, int width, int height) { + File file = new File(fileName); + + if (!file.exists()) { + result.error("file does not exist", fileName, null); return; } - if(call.method.equals("cropImage")) { - String fileName = call.argument("file"); - int originX = call.argument("originX"); - int originY = call.argument("originY"); - int width = call.argument("width"); - int height = call.argument("height"); - File file = new File(fileName); + Boolean isPNG = fileName.toLowerCase().endsWith(".png"); + Bitmap.CompressFormat format = isPNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + String extension = isPNG ? ".png" : ".jpg"; - if(!file.exists()) { - result.error("file does not exist", fileName, null); - return; - } - Boolean isPNG = fileName.toLowerCase().endsWith(".png"); - Bitmap.CompressFormat format = isPNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; - String extension = isPNG ? ".png" : ".jpg"; - - Bitmap bmp = BitmapFactory.decodeFile(fileName); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - try { - bmp = Bitmap.createBitmap(bmp, originX, originY, width, height); - } catch(IllegalArgumentException e) { - e.printStackTrace(); - result.error("bounds are outside of the dimensions of the source image", fileName, null); - } + Bitmap bmp = BitmapFactory.decodeFile(fileName); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + bmp = Bitmap.createBitmap(bmp, originX, originY, width, height); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + result.error("bounds are outside of the dimensions of the source image", fileName, null); + } + + bmp.compress(format, 100, bos); + bmp.recycle(); - bmp.compress(format, 100, bos); - bmp.recycle(); - OutputStream outputStream = null; - try { - String outputFileName = File.createTempFile( - getFilenameWithoutExtension(file).concat("_cropped"), - extension, - context.getExternalCacheDir() - ).getPath(); - - - outputStream = new FileOutputStream(outputFileName); - bos.writeTo(outputStream); - - copyExif(fileName, outputFileName); - - result.success(outputFileName); - } catch (FileNotFoundException e) { - e.printStackTrace(); - result.error("file does not exist", fileName, null); - } catch (IOException e) { - e.printStackTrace(); - result.error("something went wrong", fileName, null); - }finally { - if (outputStream != null) { - try { - outputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } + OutputStream outputStream = null; + try { + String outputFileName = File.createTempFile( + getFilenameWithoutExtension(file).concat("_cropped"), + extension, + context.getExternalCacheDir() + ).getPath(); + + outputStream = new FileOutputStream(outputFileName); + bos.writeTo(outputStream); + + copyExif(fileName, outputFileName); + + result.success(outputFileName); + } catch (FileNotFoundException e) { + e.printStackTrace(); + result.error("file does not exist", fileName, null); + } catch (IOException e) { + e.printStackTrace(); + result.error("something went wrong", fileName, null); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); } } - - - - return; - } - if (call.method.equals("getPlatformVersion")) { - result.success("Android " + android.os.Build.VERSION.RELEASE); - } else { - result.notImplemented(); } } @@ -182,28 +206,7 @@ private void copyExif(String filePathOri, String filePathDest) { ExifInterface oldExif = new ExifInterface(filePathOri); ExifInterface newExif = new ExifInterface(filePathDest); - List attributes = - Arrays.asList( - "FNumber", - "ExposureTime", - "ISOSpeedRatings", - "GPSAltitude", - "GPSAltitudeRef", - "FocalLength", - "GPSDateStamp", - "WhiteBalance", - "GPSProcessingMethod", - "GPSTimeStamp", - "DateTime", - "Flash", - "GPSLatitude", - "GPSLatitudeRef", - "GPSLongitude", - "GPSLongitudeRef", - "Make", - "Model", - "Orientation"); - for (String attribute : attributes) { + for (String attribute : ExifAttributes) { setIfNotNull(oldExif, newExif, attribute); } From 08ad5a34c2423afa3f0dedea3ed8c0cc5302f56e Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:23:03 +1300 Subject: [PATCH 2/7] Handle error if bitmap can't be decoded --- .../com/example/flutternativeimage/MethodCallHandlerImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java index b75246e..6f405c1 100644 --- a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java @@ -85,6 +85,10 @@ private void compressImage(MethodChannel.Result result, String fileName, int res } Bitmap bmp = BitmapFactory.decodeFile(fileName); + if (bmp == null) { + result.error("File could not be decoded by BitmapFactory", fileName, null); + return; + } int newWidth = targetWidth == 0 ? (bmp.getWidth() / 100 * resizePercentage) : targetWidth; int newHeight = targetHeight == 0 ? (bmp.getHeight() / 100 * resizePercentage) : targetHeight; From 4fff1722ce2a3d37e26953b9658bfda89cffac6e Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:23:32 +1300 Subject: [PATCH 3/7] Close OutputStream to avoid leak --- .../flutternativeimage/MethodCallHandlerImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java index 6f405c1..80d61ac 100644 --- a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java @@ -101,6 +101,7 @@ private void compressImage(MethodChannel.Result result, String fileName, int res ByteArrayOutputStream bos = new ByteArrayOutputStream(); newBmp.compress(Bitmap.CompressFormat.JPEG, quality, bos); + OutputStream outputStream = null; try { String outputFileName = File.createTempFile( getFilenameWithoutExtension(file).concat("_compressed"), @@ -108,7 +109,7 @@ private void compressImage(MethodChannel.Result result, String fileName, int res context.getExternalCacheDir() ).getPath(); - OutputStream outputStream = new FileOutputStream(outputFileName); + outputStream = new FileOutputStream(outputFileName); bos.writeTo(outputStream); copyExif (fileName, outputFileName); @@ -120,6 +121,14 @@ private void compressImage(MethodChannel.Result result, String fileName, int res } catch (IOException e) { e.printStackTrace(); result.error("something went wrong", fileName, null); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } } From 59adc2e88a574a8595b3ea91451b3ea560ee2b1b Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:24:16 +1300 Subject: [PATCH 4/7] User AndroidX ExifInterface --- android/build.gradle | 4 ++++ .../com/example/flutternativeimage/MethodCallHandlerImpl.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index ef13912..9ed117a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -33,3 +33,7 @@ android { abortOnError false } } + +dependencies { + implementation 'androidx.exifinterface:exifinterface:1.3.5' +} diff --git a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java index 80d61ac..13ca696 100644 --- a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java @@ -3,7 +3,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.media.ExifInterface; +import androidx.exifinterface.media.ExifInterface; import android.util.Log; import java.io.ByteArrayOutputStream; From bff62d8ebcae8e920efe40457053836408ef04d6 Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:25:36 +1300 Subject: [PATCH 5/7] Update android gradle build tools to 3.3.3 --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 9ed117a..6bcc1b8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.3.3' } } From ceed7758137b4b82cdfec4f05d39c4be690c3ee8 Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:26:18 +1300 Subject: [PATCH 6/7] Replace jcenter repository with maven central --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 6bcc1b8..06841c0 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ version '1.0-SNAPSHOT' buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { @@ -15,7 +15,7 @@ buildscript { rootProject.allprojects { repositories { google() - jcenter() + mavenCentral() } } From 560048c10786d9eaad216585cda14f9095689606 Mon Sep 17 00:00:00 2001 From: James McIntosh Date: Sat, 12 Nov 2022 20:28:47 +1300 Subject: [PATCH 7/7] Remove usused method --- .../example/flutternativeimage/MethodCallHandlerImpl.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java index 13ca696..010a3f2 100644 --- a/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java +++ b/android/src/main/java/com/example/flutternativeimage/MethodCallHandlerImpl.java @@ -236,11 +236,6 @@ private void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String p } } - private static String pathComponent(String filename) { - int i = filename.lastIndexOf(File.separator); - return (i > -1) ? filename.substring(0, i) : filename; - } - private static String getFilenameWithoutExtension(File file) { String fileName = file.getName();