Skip to content

Instantly share code, notes, and snippets.

@Johnson145
Created June 14, 2017 15:49
Show Gist options
  • Save Johnson145/0e913541ce5c40147039ddb675e7c472 to your computer and use it in GitHub Desktop.
Save Johnson145/0e913541ce5c40147039ddb675e7c472 to your computer and use it in GitHub Desktop.
Fix exif orientation flag of images captured with the CameraKit-Android
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());
}
}
});
}
}
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;
}
}
}
}
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;
}
}
@Kwonjube
Copy link

Kwonjube commented Apr 2, 2019

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment