diff --git a/README.md b/README.md index 8ef8865c..c850b200 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,11 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt - While taking pictures, image is captured normally using the camera APIs. - While shooting videos, image is captured as a freeze frame of the `CameraView` preview (similar to SnapChat and Instagram) - Built-in tap to focus +- `CameraUtils` to help with Bitmaps and orientations - EXIF support - Automatically detected orientation tag - Plug in location tags with `CameraView.setLocation(double, double)` -- Control the camera parameters via XML or programmatically -- TODO: Built-in pinch to zoom +- Control the camera parameters via XML or programmatically ## Setup @@ -109,7 +109,8 @@ camera.setCameraListener(new CameraListener() { @Override public void onPictureTaken(byte[] picture) { // Create a bitmap or a file... - Bitmap result = BitmapFactory.decodeByteArray(picture, 0, picture.length); + // CameraUtils will read EXIF orientation for you. + CameraUtils.decodeBitmap(picture, ...); } }); @@ -376,7 +377,7 @@ The library manifest file is not strict and only asks for camera permissions. Th android:required="true"/> ``` -If you don't request this feature, you can use `CameraKit.hasCameras()` to detect if current device has cameras, and then start the camera view. +If you don't request this feature, you can use `CameraUtils.hasCameras()` to detect if current device has cameras, and then start the camera view. ## Roadmap diff --git a/camerakit/build.gradle b/camerakit/build.gradle index e67385f7..8209d6c8 100644 --- a/camerakit/build.gradle +++ b/camerakit/build.gradle @@ -32,9 +32,8 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:25.2.0' - - + compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support:exifinterface:25.3.1' compile 'android.arch.lifecycle:runtime:1.0.0-alpha1' compile 'android.arch.lifecycle:extensions:1.0.0-alpha1' annotationProcessor 'android.arch.lifecycle:compiler:1.0.0-alpha1' diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java index 79eaaf6f..650ed030 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java @@ -5,12 +5,6 @@ import android.content.pm.PackageManager; public class CameraKit { - public static boolean hasCameras(Context context) { - PackageManager manager = context.getPackageManager(); - // There's also FEATURE_CAMERA_EXTERNAL , should we support it? - return manager.hasSystemFeature(PackageManager.FEATURE_CAMERA) - || manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); - } public static class Constants { diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java new file mode 100644 index 00000000..b6d4431f --- /dev/null +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java @@ -0,0 +1,107 @@ +package com.flurgle.camerakit; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.os.Handler; +import android.support.media.ExifInterface; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Static utilities for dealing with camera I/O, orientation, etc. + */ +public class CameraUtils { + + + /** + * Determines whether the device has valid camera sensors, so the library + * can be used. + * + * @param context a valid Context + * @return whether device has cameras + */ + public static boolean hasCameras(Context context) { + PackageManager manager = context.getPackageManager(); + // There's also FEATURE_CAMERA_EXTERNAL , should we support it? + return manager.hasSystemFeature(PackageManager.FEATURE_CAMERA) + || manager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); + } + + + /** + * Decodes an input byte array and outputs a Bitmap that is ready to be displayed. + * The difference with {@link android.graphics.BitmapFactory#decodeByteArray(byte[], int, int)} + * is that this cares about orientation, reading it from the EXIF header. + * This is executed in a background thread, and returns the result to the original thread. + * + * This ignores flipping at the moment. + * TODO care about flipping using Matrix.scale() + * + * @param source a JPEG byte array + * @param callback a callback to be notified + */ + public static void decodeBitmap(final byte[] source, final BitmapCallback callback) { + final Handler ui = new Handler(); + new Thread(new Runnable() { + @Override + public void run() { + + int orientation = 0; + try { + // http://sylvana.net/jpegcrop/exif_orientation.html + ExifInterface exif = new ExifInterface(new ByteArrayInputStream(source)); + Integer exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + switch (exifOrientation) { + case ExifInterface.ORIENTATION_NORMAL: + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: + orientation = 0; break; + + case ExifInterface.ORIENTATION_ROTATE_180: + case ExifInterface.ORIENTATION_FLIP_VERTICAL: + orientation = 180; break; + + case ExifInterface.ORIENTATION_ROTATE_90: + case ExifInterface.ORIENTATION_TRANSPOSE: + orientation = 90; break; + + case ExifInterface.ORIENTATION_ROTATE_270: + case ExifInterface.ORIENTATION_TRANSVERSE: + orientation = 270; break; + + default: orientation = 0; + } + } catch (IOException e) { + e.printStackTrace(); + orientation = 0; + } + + + Bitmap bitmap = BitmapFactory.decodeByteArray(source, 0, source.length); + if (orientation != 0) { + Matrix matrix = new Matrix(); + matrix.setRotate(orientation); + Bitmap temp = bitmap; + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + temp.recycle(); + } + final Bitmap result = bitmap; + ui.post(new Runnable() { + @Override + public void run() { + callback.onBitmapReady(result); + } + }); + } + }).start(); + } + + + public interface BitmapCallback { + void onBitmapReady(Bitmap bitmap); + } +}