Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exif data lost when correctOrientation is true #329

Closed
ddsky opened this issue Aug 25, 2018 · 23 comments · Fixed by #331
Closed

Exif data lost when correctOrientation is true #329

ddsky opened this issue Aug 25, 2018 · 23 comments · Fixed by #331

Comments

@ddsky
Copy link

ddsky commented Aug 25, 2018

The image I retrieve in the success callback has proper exif data if I set the options to:

 let options = {
    sourceType: Camera.PictureSourceType.CAMERA,
    destinationType: Camera.DestinationType.FILE_URI
};

Once I use correctOrientation (or quality, or targetHeight or targetWidth) in the options, the exif data is lost:

 let options = {
    correctOrientation: true,
    sourceType: Camera.PictureSourceType.CAMERA,
    destinationType: Camera.DestinationType.FILE_URI
};
@janpio janpio added the bug label Aug 25, 2018
@janpio
Copy link
Member

janpio commented Aug 25, 2018

Which platform? What versions (OS, Cordova platform, plugin)?

@ddsky
Copy link
Author

ddsky commented Aug 25, 2018

Sorry for not posting this information right away!

cordova-android 7.0.0
cordova-plugin-camera: 4.0.3

I'm building this via yarn/npm latest version on Windows and Linux.

@janpio
Copy link
Member

janpio commented Aug 25, 2018

Just for completeness, have you tried upgrading to cordova-android 7.1.1, the current version? (I don't really expect any difference, but lets better check)

What Android version and device are you testing on?

@ddsky
Copy link
Author

ddsky commented Aug 25, 2018

Thanks for the info. I updated to 7.1.1 but it made no difference. I tested this on multiple devices with Android 6.0.

@janpio
Copy link
Member

janpio commented Aug 25, 2018

Thanks, if you have any handy you might also test on other Android versions to make sure it does not only affect Android 6.0.


My guess: correctOrientation reads the file as a bitmap, transforms it and saves it back - but doesn't add the EXIF data at this time (or loses it along the way). The code for this lives at

private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException {
// If no new width or height were specified, and orientation is not needed return the original bitmap
if (this.targetWidth <= 0 && this.targetHeight <= 0 && !(this.correctOrientation)) {
InputStream fileStream = null;
Bitmap image = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
image = BitmapFactory.decodeStream(fileStream);
} catch (OutOfMemoryError e) {
callbackContext.error(e.getLocalizedMessage());
} catch (Exception e){
callbackContext.error(e.getLocalizedMessage());
}
finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG, "Exception while closing file input stream.");
}
}
}
return image;
}
/* Copy the inputstream to a temporary file on the device.
We then use this temporary file to determine the width/height/orientation.
This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc)
This also ensures we create a scaled bitmap with the correct orientation
We delete the temporary file once we are done
*/
File localFile = null;
Uri galleryUri = null;
int rotate = 0;
try {
InputStream fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
if (fileStream != null) {
// Generate a temporary file
String timeStamp = new SimpleDateFormat(TIME_FORMAT).format(new Date());
String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? JPEG_EXTENSION : PNG_EXTENSION);
localFile = new File(getTempDirectoryPath() + fileName);
galleryUri = Uri.fromFile(localFile);
writeUncompressedImage(fileStream, galleryUri);
try {
String mimeType = FileHelper.getMimeType(imageUrl.toString(), cordova);
if (JPEG_MIME_TYPE.equalsIgnoreCase(mimeType)) {
// ExifInterface doesn't like the file:// prefix
String filePath = galleryUri.toString().replace("file://", "");
// read exifData of source
exifData = new ExifHelper();
exifData.createInFile(filePath);
// Use ExifInterface to pull rotation information
if (this.correctOrientation) {
ExifInterface exif = new ExifInterface(filePath);
rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED));
}
}
} catch (Exception oe) {
LOG.w(LOG_TAG,"Unable to read Exif data: "+ oe.toString());
rotate = 0;
}
}
}
catch (Exception e)
{
LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
return null;
}
try {
// figure out the original width and height of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream fileStream = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG, "Exception while closing file input stream.");
}
}
}
//CB-2292: WTF? Why is the width null?
if (options.outWidth == 0 || options.outHeight == 0) {
return null;
}
// User didn't specify output dimensions, but they need orientation
if (this.targetWidth <= 0 && this.targetHeight <= 0) {
this.targetWidth = options.outWidth;
this.targetHeight = options.outHeight;
}
// Setup target width/height based on orientation
int rotatedWidth, rotatedHeight;
boolean rotated= false;
if (rotate == 90 || rotate == 270) {
rotatedWidth = options.outHeight;
rotatedHeight = options.outWidth;
rotated = true;
} else {
rotatedWidth = options.outWidth;
rotatedHeight = options.outHeight;
}
// determine the correct aspect ratio
int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
// Load in the smallest bitmap possible that is closest to the size we want
options.inJustDecodeBounds = false;
options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight, widthHeight[0], widthHeight[1]);
Bitmap unscaledBitmap = null;
try {
fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
LOG.d(LOG_TAG, "Exception while closing file input stream.");
}
}
}
if (unscaledBitmap == null) {
return null;
}
int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1];
int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0];
Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true);
if (scaledBitmap != unscaledBitmap) {
unscaledBitmap.recycle();
unscaledBitmap = null;
}
if (this.correctOrientation && (rotate != 0)) {
Matrix matrix = new Matrix();
matrix.setRotate(rotate);
try {
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
this.orientationCorrected = true;
} catch (OutOfMemoryError oom) {
this.orientationCorrected = false;
}
}
return scaledBitmap;
}
finally {
// delete the temporary copy
if (localFile != null) {
localFile.delete();
}
}
}
as far as I could make out, but it is too much for me (I'm not an Android or Java developer) to understand.

@ddsky
Copy link
Author

ddsky commented Aug 26, 2018

@janpio that would be my guess too. Who can solve this though?

@AlvaroHerrero
Copy link
Contributor

AlvaroHerrero commented Aug 26, 2018

I think we need to call
exifData.readExifData();
just after
exifData.createInFile(filePath);
This way the original exifData data will be in the helper before re-saving them on the rotated file.

AlvaroHerrero added a commit to AlvaroHerrero/cordova-plugin-camera that referenced this issue Aug 26, 2018
@ddsky
Copy link
Author

ddsky commented Aug 27, 2018

Thanks @AlvaroHerrero that sounds like the right path. Please note that this does not only happen if correctOrientation is set to true but also if quality, targetWidth, or targetHeight is set. I guess whenever the image is manipulated and re-saved.

@AlvaroHerrero
Copy link
Contributor

Yes, it also happens when the image is selected from the gallery.

@ddsky
Copy link
Author

ddsky commented Sep 13, 2018

Is there a roadmap on when this might be fixed?

@janpio
Copy link
Member

janpio commented Sep 13, 2018

Please create a Pull Request that fixes this on this repository, then this can be reviewed, merged and finally released. @AlvaroHerrero 's branch could probably be the base for that.

@AlvaroHerrero
Copy link
Contributor

The PR is already created. I don´t know if there is something more for me to do.
#331

@janpio
Copy link
Member

janpio commented Sep 13, 2018

Linking to the PR here and to this issue in the PR would be a good thing ;) With a few hundred issues and PRs per day, this is the only way to get the connetions :)

Well, then we'll have to wait for more people to review the PR - that actually know about Android and Java etc. Feel free to test the changes @ddsky and leave an "Approve" review on the PR if everything works as expected.

@ddsky
Copy link
Author

ddsky commented Sep 14, 2018

@janpio, sorry I'm new to this. How do I test the PR? Wouldn't it need to be merged so I can update to the latest version of the plugin in my app to test it?

@janpio
Copy link
Member

janpio commented Sep 14, 2018

You can install the PR branch directly as your plugin: https://github.com/AlvaroHerrero/cordova-plugin-camera/tree/GH-329 (cordova plugin add https://github.com/AlvaroHerrero/cordova-plugin-camera/tree/GH-329 should work)

@ddsky
Copy link
Author

ddsky commented Sep 28, 2018

@janpio installing this plugin like so does not seem to work for me

> cordova plugin add https://github.com/AlvaroHerrero/cordova-plugin-camera/tree/GH-329
(node:27864) UnhandledPromiseRejectionWarning: CordovaError: Failed to fetch plugin https://github.com/AlvaroHerrero/cordova-plugin-camera/tree/GH-329 via registry.
Probably this is either a connection problem, or plugin spec is incorrect.

Anything I'm doing wrong?

@janpio
Copy link
Member

janpio commented Sep 29, 2018

No, I gave you the wrong command. Sorry.

cordova plugin add https://github.com/AlvaroHerrero/cordova-plugin-camera.git#GH-329

should work

@ddsky
Copy link
Author

ddsky commented Oct 3, 2018

Super, thank you @janpio, that worked.

I can hereby confirm that rescaling and keeping the exif data now works properly.

What is the process from here, when will this be in the main branch?

@janpio
Copy link
Member

janpio commented Oct 3, 2018

Leave that comment about it working on the PR #331 please.

Someone from Apache Cordova will have to decide if this is a useful change, test and review it, then merge it. Then it will be included in the next release, whenever that will happen.

@ddsky
Copy link
Author

ddsky commented Oct 3, 2018

Thanks, I have done that.

j3k0 added a commit to Fovea/cordova-plugin-camera that referenced this issue Oct 29, 2018
Tilexou added a commit to Tilexou/cordova-plugin-camera that referenced this issue Nov 6, 2018
Includes the pull request apache#331 (apacheGH-329) from the main fork
Includes the pull request apache#238 from the main fork
janpio pushed a commit that referenced this issue Feb 21, 2019
* GH-329 android: Fix Exif data lost when correctOrientation is true

* Changed variable name
@HarelM
Copy link

HarelM commented May 8, 2019

I've just tested the code using the instructions #415 and I still see an issue with exif data getting lost when getting images from the photo library on android device.
The code I'm using is the following:
navigator.camera.getPicture(() => ..., { destinationType: Camera.DestinationType.DATA_URL, sourceType: Camera.PictureSourceType.PHOTOLIBRARY, saveToPhotoAlbum: false}).
Code can be seen here:
https://github.com/IsraelHikingMap/Site/blob/d81cfdfd339060aab9b512352741b5af00c8a214/IsraelHiking.Web/sources/application/directives/image-capture.directive.ts#L41
Let me know if there's a way I can help or if I need to open a new issue for this.
Thanks for a great plugin! Keep up the good work.

@janpio
Copy link
Member

janpio commented May 8, 2019

Best open a new issue - otherwise this mgiht get lost here. (Please include a link back to this here of course for context) Thanks.

@HarelM
Copy link

HarelM commented May 8, 2019

@janpio I opened #452. Thanks for the quick response!

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

Successfully merging a pull request may close this issue.

4 participants