33import android .content .Context ;
44import android .graphics .Bitmap ;
55import android .graphics .BitmapFactory ;
6+ import android .graphics .Matrix ;
67import android .media .ExifInterface ;
78import android .util .Log ;
89
10+ import androidx .annotation .Nullable ;
11+
912import java .io .ByteArrayOutputStream ;
1013import java .io .File ;
1114import java .io .FileNotFoundException ;
2124
2225public class MethodCallHandlerImpl implements MethodChannel .MethodCallHandler {
2326 private final Context context ;
27+ private final String LOG_TAG = "FlutterNativeImage" ;
2428
2529 MethodCallHandlerImpl (Context context ) {
2630 this .context = context ;
@@ -66,7 +70,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
6670 OutputStream outputStream = new FileOutputStream (outputFileName );
6771 bos .writeTo (outputStream );
6872
69- copyExif (fileName , outputFileName );
73+ copyExif (fileName , outputFileName , null );
7074
7175 result .success (outputFileName );
7276 } catch (FileNotFoundException e ) {
@@ -95,13 +99,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
9599 properties .put ("width" , options .outWidth );
96100 properties .put ("height" , options .outHeight );
97101
98- int orientation = ExifInterface .ORIENTATION_UNDEFINED ;
99- try {
100- ExifInterface exif = new ExifInterface (fileName );
101- orientation = exif .getAttributeInt (ExifInterface .TAG_ORIENTATION , ExifInterface .ORIENTATION_UNDEFINED );
102- } catch (IOException ex ) {
103- // EXIF could not be read from the file; ignore
104- }
102+ int orientation = getOrientation (fileName );
105103 properties .put ("orientation" , orientation );
106104
107105 result .success (properties );
@@ -124,7 +122,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
124122 Bitmap .CompressFormat format = isPNG ? Bitmap .CompressFormat .PNG : Bitmap .CompressFormat .JPEG ;
125123 String extension = isPNG ? ".png" : ".jpg" ;
126124
127- Bitmap bmp = BitmapFactory . decodeFile (fileName );
125+ Bitmap bmp = decodeBitmapWithResolvedOrientation (fileName );
128126 ByteArrayOutputStream bos = new ByteArrayOutputStream ();
129127 try {
130128 bmp = Bitmap .createBitmap (bmp , originX , originY , width , height );
@@ -147,7 +145,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
147145 outputStream = new FileOutputStream (outputFileName );
148146 bos .writeTo (outputStream );
149147
150- copyExif (fileName , outputFileName );
148+ copyExif (fileName , outputFileName , ExifInterface . ORIENTATION_NORMAL );
151149
152150 result .success (outputFileName );
153151 } catch (FileNotFoundException e ) {
@@ -177,7 +175,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
177175 }
178176 }
179177
180- private void copyExif (String filePathOri , String filePathDest ) {
178+ private void copyExif (String filePathOri , String filePathDest , @ Nullable Integer overwriteOrientationAttr ) {
181179 try {
182180 ExifInterface oldExif = new ExifInterface (filePathOri );
183181 ExifInterface newExif = new ExifInterface (filePathDest );
@@ -201,16 +199,21 @@ private void copyExif(String filePathOri, String filePathDest) {
201199 "GPSLongitude" ,
202200 "GPSLongitudeRef" ,
203201 "Make" ,
204- "Model" ,
205- "Orientation" );
202+ "Model" );
206203 for (String attribute : attributes ) {
207204 setIfNotNull (oldExif , newExif , attribute );
208205 }
209206
207+ if (overwriteOrientationAttr == null ) {
208+ setIfNotNull (oldExif , newExif , "Orientation" );
209+ } else {
210+ newExif .setAttribute ("Orientation" , String .valueOf (overwriteOrientationAttr ));
211+ }
212+
210213 newExif .saveAttributes ();
211214
212215 } catch (Exception ex ) {
213- Log .e ("FlutterNativeImagePlugin" , "Error preserving Exif data on selected image: " + ex );
216+ Log .e (LOG_TAG , "Error preserving Exif data on selected image: " + ex );
214217 }
215218 }
216219
@@ -220,6 +223,72 @@ private void setIfNotNull(ExifInterface oldExif, ExifInterface newExif, String p
220223 }
221224 }
222225
226+ /**
227+ * This method resolves the orientation of an image and returns the bitmap normalised.
228+ * @param filePath Path of the image file. Used to decode the {@link Bitmap} and camera rotation
229+ * @return {@link Bitmap} object with normalised orientation.
230+ */
231+ private Bitmap decodeBitmapWithResolvedOrientation (String filePath ) {
232+ Bitmap bitmap = BitmapFactory .decodeFile (filePath );
233+ int orientation = getOrientation (filePath );
234+
235+ Matrix matrix = new Matrix ();
236+ switch (orientation ) {
237+ case ExifInterface .ORIENTATION_FLIP_HORIZONTAL :
238+ matrix .setScale (-1 , 1 );
239+ break ;
240+ case ExifInterface .ORIENTATION_ROTATE_180 :
241+ matrix .setRotate (180 );
242+ break ;
243+ case ExifInterface .ORIENTATION_FLIP_VERTICAL :
244+ matrix .setRotate (180 );
245+ matrix .postScale (-1 , 1 );
246+ break ;
247+ case ExifInterface .ORIENTATION_TRANSPOSE :
248+ matrix .setRotate (90 );
249+ matrix .postScale (-1 , 1 );
250+ break ;
251+ case ExifInterface .ORIENTATION_ROTATE_90 :
252+ matrix .setRotate (90 );
253+ break ;
254+ case ExifInterface .ORIENTATION_TRANSVERSE :
255+ matrix .setRotate (-90 );
256+ matrix .postScale (-1 , 1 );
257+ break ;
258+ case ExifInterface .ORIENTATION_ROTATE_270 :
259+ matrix .setRotate (-90 );
260+ break ;
261+ case ExifInterface .ORIENTATION_NORMAL :
262+ case ExifInterface .ORIENTATION_UNDEFINED :
263+ default :
264+ return bitmap ;
265+ }
266+
267+ try {
268+ Bitmap oriented = Bitmap .createBitmap (bitmap , 0 , 0 , bitmap .getWidth (), bitmap .getHeight (), matrix , true );
269+ bitmap .recycle ();
270+ return oriented ;
271+ } catch (OutOfMemoryError e ) {
272+ e .printStackTrace ();
273+ return bitmap ;
274+ }
275+ }
276+
277+ /**
278+ * Reads the orientation attribute from an image file.
279+ * @param filePath Path of the image file
280+ * @return orientation attribute read by {@link ExifInterface} or the default value {@link ExifInterface#ORIENTATION_NORMAL}
281+ */
282+ private int getOrientation (String filePath ) {
283+ try {
284+ ExifInterface exif = new ExifInterface (filePath );
285+ return exif .getAttributeInt (ExifInterface .TAG_ORIENTATION , ExifInterface .ORIENTATION_NORMAL );
286+ } catch (IOException ex ) {
287+ // EXIF could not be read from the file; return default
288+ return ExifInterface .ORIENTATION_NORMAL ;
289+ }
290+ }
291+
223292 private static String pathComponent (String filename ) {
224293 int i = filename .lastIndexOf (File .separator );
225294 return (i > -1 ) ? filename .substring (0 , i ) : filename ;
0 commit comments