Image orientation fix (#475, #309) (#493)

fix
This commit is contained in:
Alexander Pantyuhov 2016-12-05 02:32:04 +03:00 committed by Nicolas Charpentier
parent fd61e506e5
commit 06dc7ac5a7
2 changed files with 103 additions and 25 deletions

View File

@ -33,4 +33,5 @@ repositories {
dependencies { dependencies {
compile "com.facebook.react:react-native:0.19.+" compile "com.facebook.react:react-native:0.19.+"
compile "com.google.zxing:core:3.2.1" compile "com.google.zxing:core:3.2.1"
compile "com.drewnoakes:metadata-extractor:2.9.1"
} }

View File

@ -10,10 +10,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.hardware.Camera; import android.hardware.Camera;
import android.media.CamcorderProfile; import android.media.*;
import android.media.MediaActionSound;
import android.media.MediaRecorder;
import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
@ -21,6 +18,11 @@ import android.util.Base64;
import android.util.Log; import android.util.Log;
import android.view.Surface; import android.view.Surface;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactApplicationContext;
@ -30,14 +32,7 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.bridge.WritableNativeMap;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@ -493,6 +488,28 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
return byteArray; return byteArray;
} }
private byte[] saveImage(InputStream is, Bitmap image) {
byte[] result = null;
try {
result = compress(image, 85);
} catch (OutOfMemoryError e) {
try {
result = compress(image, 70);
} catch (OutOfMemoryError e2) {
e.printStackTrace();
}
}
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
private byte[] mirrorImage(byte[] data) { private byte[] mirrorImage(byte[] data) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(data); ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
Bitmap photo = BitmapFactory.decodeStream(inputStream); Bitmap photo = BitmapFactory.decodeStream(inputStream);
@ -501,25 +518,80 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
m.preScale(-1, 1); m.preScale(-1, 1);
Bitmap mirroredImage = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, false); Bitmap mirroredImage = Bitmap.createBitmap(photo, 0, 0, photo.getWidth(), photo.getHeight(), m, false);
byte[] result = null; return saveImage(inputStream, mirroredImage);
}
private byte[] rotate(byte[] data, int exifOrientation) {
final Matrix bitmapMatrix = new Matrix();
switch(exifOrientation)
{
case 1:
break;
case 2:
bitmapMatrix.postScale(-1, 1);
break;
case 3:
bitmapMatrix.postRotate(180);
break;
case 4:
bitmapMatrix.postRotate(180);
bitmapMatrix.postScale(-1, 1);
break;
case 5:
bitmapMatrix.postRotate(90);
bitmapMatrix.postScale(-1, 1);
break;
case 6:
bitmapMatrix.postRotate(90);
break;
case 7:
bitmapMatrix.postRotate(270);
bitmapMatrix.postScale(-1, 1);
break;
case 8:
bitmapMatrix.postRotate(270);
break;
default:
break;
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
Bitmap decodedBitmap = BitmapFactory.decodeStream(inputStream);
final Bitmap transformedBitmap = Bitmap.createBitmap(
decodedBitmap, 0, 0, decodedBitmap.getWidth(), decodedBitmap.getHeight(), bitmapMatrix, false
);
return saveImage(inputStream, transformedBitmap);
}
private byte[] fixOrientation(byte[] data) {
final Metadata metadata;
try { try {
result = compress(mirroredImage, 85); metadata = ImageMetadataReader.readMetadata(
} catch (OutOfMemoryError e) { new BufferedInputStream(new ByteArrayInputStream(data)), data.length
try { );
result = compress(mirroredImage, 70);
} catch (OutOfMemoryError e2) { final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
return rotate(data, exifOrientation);
}
return data;
} catch (IOException | ImageProcessingException | MetadataException e) {
e.printStackTrace(); e.printStackTrace();
return data;
} }
} }
private void rewriteOrientation(String path) {
try { try {
inputStream.close(); ExifInterface exif = new ExifInterface(path);
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL));
exif.saveAttributes();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); Log.e(TAG, e.getMessage());
} }
return result;
} }
private byte[] compress(Bitmap bitmap, int quality) throws OutOfMemoryError { private byte[] compress(Bitmap bitmap, int quality) throws OutOfMemoryError {
@ -593,6 +665,8 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
} }
} }
data = fixOrientation(data);
camera.stopPreview(); camera.stopPreview();
camera.startPreview(); camera.startPreview();
WritableMap response = new WritableNativeMap(); WritableMap response = new WritableNativeMap();
@ -615,6 +689,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
return; return;
} }
rewriteOrientation(cameraRollFile.getAbsolutePath());
addToMediaStore(cameraRollFile.getAbsolutePath()); addToMediaStore(cameraRollFile.getAbsolutePath());
response.putString("path", Uri.fromFile(cameraRollFile).toString()); response.putString("path", Uri.fromFile(cameraRollFile).toString());
promise.resolve(response); promise.resolve(response);
@ -633,6 +708,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
return; return;
} }
rewriteOrientation(pictureFile.getAbsolutePath());
response.putString("path", Uri.fromFile(pictureFile).toString()); response.putString("path", Uri.fromFile(pictureFile).toString());
promise.resolve(response); promise.resolve(response);
break; break;
@ -649,6 +725,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
promise.reject(error); promise.reject(error);
} }
rewriteOrientation(tempFile.getAbsolutePath());
response.putString("path", Uri.fromFile(tempFile).toString()); response.putString("path", Uri.fromFile(tempFile).toString());
promise.resolve(response); promise.resolve(response);
break; break;