Created
June 14, 2017 15:49
-
-
Save Johnson145/0e913541ce5c40147039ddb675e7c472 to your computer and use it in GitHub Desktop.
Fix exif orientation flag of images captured with the CameraKit-Android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CameraController implements OrientationManager.OrientationListener{ | |
public static String TAG = CameraController.class.getCanonicalName(); | |
/** | |
* This holds the actual camera view object. | |
*/ | |
private CameraView cameraView; | |
/** | |
* Although the the associated activity is constrained to the portrait view, this object still allows | |
* us to check the real device orientation. | |
*/ | |
private OrientationManager orientationManager; | |
/** | |
* The activity that is managing this object. | |
*/ | |
private Activity activity; | |
public CameraController(MainActivity activity){ | |
// save given params | |
this.activity = activity; | |
// track device orientation | |
orientationManager = new OrientationManager(activity, | |
SensorManager.SENSOR_DELAY_NORMAL, | |
this); | |
orientationManager.enable(); | |
// init the camera | |
cameraView = (CameraView) activity.findViewById(R.id.camera); | |
// handle captured pictures and fix orientation | |
cameraView.setCameraListener(new CameraListener() { | |
@Override | |
public void onPictureTaken(byte[] picture) { | |
super.onPictureTaken(picture); | |
try { | |
// store current device orientation before it can change | |
int deviceOrientationExif = orientationManager.getExifOrientation( | |
chosenCamera == CameraKit.Constants.FACING_FRONT | |
); | |
File target = FileManager.getOutputMediaFile(MEDIA_TYPE_IMAGE); | |
// write current camera picture into the file | |
FileOutputStream fos = new FileOutputStream(target); | |
fos.write(picture); | |
fos.close(); | |
// get current exif orientation data | |
Uri imageUri = Uri.fromFile(target); | |
int oldExifOrientationConstant = Rotation.getExifOrientation(imageUri); | |
int oldAngleInDegrees = Rotation.exifOrientationToDegrees(oldExifOrientationConstant); | |
// use the old orientation data as an offset applied to the actual device | |
// orientation | |
int deviceOrientationInDegrees = Rotation.exifOrientationToDegrees(deviceOrientationExif); | |
int newOrientationInDegrees = (deviceOrientationInDegrees + oldAngleInDegrees) % 360; | |
// log | |
Log.i(TAG, "Old exif angle in degrees: " + oldAngleInDegrees); | |
Log.i(TAG, "device angle in degrees: " + deviceOrientationInDegrees); | |
Log.i(TAG, "new orientation in degrees: " + newOrientationInDegrees); | |
// Update orientation data if needed | |
if (newOrientationInDegrees != oldAngleInDegrees) { | |
int newExifOrientation = Rotation.degreesToExifOrientation(newOrientationInDegrees); | |
Rotation.setExifOrientation(imageUri, | |
newExifOrientation | |
); | |
} | |
// ready to use target | |
} | |
catch (FileNotFoundException e) { | |
Log.d(TAG, "File not found: " + e.getMessage()); | |
} | |
catch (IOException e) { | |
Log.d(TAG, "Error accessing file: " + e.getMessage()); | |
} | |
} | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class OrientationManager extends OrientationEventListener { | |
public enum ScreenOrientation { | |
REVERSED_LANDSCAPE, LANDSCAPE, PORTRAIT, REVERSED_PORTRAIT | |
} | |
private ScreenOrientation screenOrientation; | |
private OrientationListener listener; | |
public OrientationManager(Context context, int rate, OrientationListener listener) { | |
super(context, rate); | |
setListener(listener); | |
// always init with portrait orientation, because the main activity is actually fixed to | |
// the portrait view. | |
// (without any initialization an error will occur if the device is not moved) | |
screenOrientation = ScreenOrientation.PORTRAIT; | |
} | |
public OrientationManager(Context context, int rate) { | |
super(context, rate); | |
} | |
public OrientationManager(Context context) { | |
super(context); | |
} | |
@Override | |
public void onOrientationChanged(int orientation) { | |
if (orientation == -1){ | |
return; | |
} | |
ScreenOrientation newOrientation; | |
if (orientation >= 60 && orientation <= 140){ | |
newOrientation = ScreenOrientation.REVERSED_LANDSCAPE; | |
} else if (orientation >= 140 && orientation <= 220) { | |
newOrientation = ScreenOrientation.REVERSED_PORTRAIT; | |
} else if (orientation >= 220 && orientation <= 300) { | |
newOrientation = ScreenOrientation.LANDSCAPE; | |
} else { | |
newOrientation = ScreenOrientation.PORTRAIT; | |
} | |
if(newOrientation != screenOrientation){ | |
screenOrientation = newOrientation; | |
if(listener != null){ | |
listener.onOrientationChange(screenOrientation); | |
} | |
} | |
} | |
public void setListener(OrientationListener listener){ | |
this.listener = listener; | |
} | |
public ScreenOrientation getScreenOrientation(){ | |
return screenOrientation; | |
} | |
public interface OrientationListener { | |
public void onOrientationChange(ScreenOrientation screenOrientation); | |
} | |
/** | |
* Get the Exif version of getScreenOrientation. | |
* @return | |
*/ | |
public int getExifOrientation(boolean frontCamera) { | |
// the front camera rotates the landscape view by 180 degree | |
if(frontCamera) { | |
switch (screenOrientation) { | |
case REVERSED_LANDSCAPE: | |
return ExifInterface.ORIENTATION_ROTATE_270; | |
case REVERSED_PORTRAIT: | |
return ExifInterface.ORIENTATION_ROTATE_180; | |
case LANDSCAPE: | |
return ExifInterface.ORIENTATION_ROTATE_90; | |
case PORTRAIT: | |
return ExifInterface.ORIENTATION_NORMAL; | |
default: | |
Log.w("ExifOrientation", "Invalid input"); | |
return ExifInterface.ORIENTATION_NORMAL; | |
} | |
} | |
else { | |
switch (screenOrientation) { | |
case REVERSED_LANDSCAPE: | |
return ExifInterface.ORIENTATION_ROTATE_90; | |
case REVERSED_PORTRAIT: | |
return ExifInterface.ORIENTATION_ROTATE_180; | |
case LANDSCAPE: | |
return ExifInterface.ORIENTATION_ROTATE_270; | |
case PORTRAIT: | |
return ExifInterface.ORIENTATION_NORMAL; | |
default: | |
Log.w("ExifOrientation", "Invalid input"); | |
return ExifInterface.ORIENTATION_NORMAL; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Rotation { | |
/** | |
* Rotate an image if required. | |
* See http://stackoverflow.com/a/31720143 | |
* | |
* @param img The image bitmap | |
* @param selectedImage Image URI | |
* @return The resulted Bitmap after manipulation | |
*/ | |
public static Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) throws IOException { | |
try { | |
int exifOrientationConstant = getExifOrientation(selectedImage); | |
int angleInDegrees = exifOrientationToDegrees(exifOrientationConstant); | |
if(angleInDegrees != 0) { | |
return rotateImage(img, angleInDegrees); | |
} | |
else { | |
return img; | |
} | |
} catch (Exception e) { | |
Log.e("Rotation", "Could not check image orientation" + e); | |
e.printStackTrace(); | |
} | |
return img; | |
} | |
/** | |
* Open an Exif interface for the given file. | |
* This should work with any files. | |
* @param imageFile | |
* @return | |
*/ | |
private static ExifInterface openExifInterfaceReadable(Uri imageFile) { | |
try { | |
// don't use selectedImage.getPath() ! | |
// => see https://stackoverflow.com/a/42937272/1665966 | |
InputStream inputStream = MainActivity.getAppContext().getContentResolver().openInputStream(imageFile); | |
ExifInterface exif = new ExifInterface(inputStream); | |
return exif; | |
} | |
catch(IOException e) { | |
Log.e("ExifInterface", "Can not open readable exif interface, because the input file can not be read."); | |
return null; | |
} | |
} | |
/** | |
* Open an Exif interface for the given file. | |
* This will probably not work with Uris pointing to files chosen from another app etc. | |
* See link in openExifInterfaceReadable() | |
* @param imageFile | |
* @return | |
*/ | |
private static ExifInterface openExifInterfaceWritable(Uri imageFile) { | |
try { | |
ExifInterface exif = new ExifInterface(imageFile.getPath()); | |
return exif; | |
} | |
catch(IOException e) { | |
Log.e("ExifInterface", "Can not open writable exif interface, because the input file can not be read."); | |
return null; | |
} | |
} | |
private static Bitmap rotateImage(Bitmap img, int degree) { | |
Matrix matrix = new Matrix(); | |
matrix.postRotate(degree); | |
Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true); | |
img.recycle(); | |
return rotatedImg; | |
} | |
/** | |
* Write the orientation information as Exif data into the existing image file. | |
* @param imageFile | |
* @param exifOrientationConstant | |
*/ | |
public static void setExifOrientation(Uri imageFile, int exifOrientationConstant) { | |
// validate parameter | |
validateExifOrientationConstant(exifOrientationConstant); | |
// Although the name suggests something else, it is important to save any of the given | |
// values, including ORIENTATION_NORMAL | |
try { | |
ExifInterface exifInterface = openExifInterfaceWritable(imageFile); | |
exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, | |
String.valueOf(exifOrientationConstant)); | |
exifInterface.saveAttributes(); | |
} | |
catch(Exception e) { | |
Log.e("ExifOrientationWriter", "Can not save orientation information:" + e); | |
} | |
} | |
/** | |
* Read the exif orientation information from an image file. | |
* @param imageFile | |
* @return One of the predefined exif orientation constants. | |
*/ | |
public static int getExifOrientation(Uri imageFile) { | |
ExifInterface exif = openExifInterfaceReadable(imageFile); | |
int exifOrientationConstant = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, | |
ExifInterface.ORIENTATION_NORMAL); | |
// if exif data is not available, use best guess | |
if(exifOrientationConstant == ExifInterface.ORIENTATION_UNDEFINED) { | |
exifOrientationConstant = exifFallback(imageFile); | |
} | |
return exifOrientationConstant; | |
} | |
/** | |
* Sometimes, the exif orientation flag is not available. | |
* In these cases, we will calculate the most likely orientation based on the width and height | |
* of the image. | |
* @param imageFileUri | |
* @return | |
*/ | |
private static int exifFallback(Uri imageFileUri) { | |
BitmapFactory.Options options = new BitmapFactory.Options(); | |
options.inJustDecodeBounds = true; | |
BitmapFactory.decodeFile(new File(imageFileUri.getPath()).getAbsolutePath(), options); | |
int imageHeight = options.outHeight; | |
int imageWidth = options.outWidth; | |
int rotation; | |
if(imageHeight >= imageWidth) { | |
rotation = ExifInterface.ORIENTATION_NORMAL; | |
} | |
else { | |
rotation = ExifInterface.ORIENTATION_ROTATE_90; | |
} | |
return rotation; | |
} | |
/** | |
* Ensure that exifOrientationConstant is a valid exif orientation constant. | |
* @param exifOrientationConstant | |
*/ | |
private static void validateExifOrientationConstant(int exifOrientationConstant) { | |
if(exifOrientationConstant != ExifInterface.ORIENTATION_ROTATE_90 && | |
exifOrientationConstant != ExifInterface.ORIENTATION_ROTATE_180 && | |
exifOrientationConstant != ExifInterface.ORIENTATION_ROTATE_270 && | |
exifOrientationConstant != ExifInterface.ORIENTATION_NORMAL) { | |
throw new InvalidParameterException(exifOrientationConstant + " is not a valid exif value."); | |
} | |
} | |
/** | |
* Accepts one of the predefined exif orientation constants and converts them to the according | |
* degree value. | |
* E.g. ExifInterface.ORIENTATION_ROTATE_90 => 90 | |
* @return | |
*/ | |
public static int exifOrientationToDegrees(int exifOrientationConstant) { | |
// validate parameter | |
validateExifOrientationConstant(exifOrientationConstant); | |
// begin conversion | |
int result; | |
switch (exifOrientationConstant) { | |
case ExifInterface.ORIENTATION_ROTATE_90: | |
result = 90; | |
break; | |
case ExifInterface.ORIENTATION_ROTATE_180: | |
result = 180; | |
break; | |
case ExifInterface.ORIENTATION_ROTATE_270: | |
result = 270; | |
break; | |
default: | |
result = 0; | |
} | |
return result; | |
} | |
/** | |
* The opposite of exifOrientationToDegrees(). | |
* @param angleInDegrees | |
* @return | |
*/ | |
public static int degreesToExifOrientation(int angleInDegrees) { | |
// validate parameter | |
if(angleInDegrees != 90 && | |
angleInDegrees != 180 && | |
angleInDegrees != 270 && | |
angleInDegrees != 0) { | |
throw new InvalidParameterException(angleInDegrees + " is not a valid degree that can be used for an exif constant."); | |
} | |
// begin conversion | |
int result; | |
switch (angleInDegrees) { | |
case 90: | |
result = ExifInterface.ORIENTATION_ROTATE_90; | |
break; | |
case 180: | |
result = ExifInterface.ORIENTATION_ROTATE_180; | |
break; | |
case 270: | |
result = ExifInterface.ORIENTATION_ROTATE_270; | |
break; | |
default: | |
result = ExifInterface.ORIENTATION_NORMAL; | |
} | |
return result; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is great, but how am I able to attached it to the camera? Even passing through Mainactivity it can't find the CameraView id/Camera, also I'm getting an error here at line 42 in Rotation.java
MainActivity.getAppContext().getContentResolver().openInputStream(imageFile);