I have an app that captures photos using the native Camera and then uploads them to a server. My problem is that all the photos have an EXIF orientation value of 0, and this messes up the display elsewhere.
How can I change the EXIF orientation? I'm not looking for a way to correct it for every circumstance, just change it to a different value.
I'm using a Samsung Galaxy Note 4
I tried this solution that sets the camera orientation before taking photos: Setting Android Photo EXIF Orientation
Camera c = Camera.open();
c.setDisplayOrientation(90);
Camera.Parameters params = mCamera.getParameters();
params.setRotation(0); // tried 0, 90, 180
c.setParameters(params);
but it doesn't influence the resulting EXIF data, its still always 0
I also tried these solutions where the image is rotated after it is taken: EXIF orientation tag value always 0 for image taken with portrait camera app android
and while this rotates the photo, the EXIF orientation is still always 0.
I also tried setting the EXIF data directly: How to save Exif data after bitmap compression in Android
private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
final File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE, "");
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
ExifInterface exif = new ExifInterface(pictureFile.toString());
exif.setAttribute(ExifInterface.TAG_ORIENTATION, "3");
exif.saveAttributes();
fos.write(data);
fos.close();
//upload photo..
}
}
}
but EXIF Orientation is still 0 after uploading.
I have also looked at these solutions:
Exif data TAG_ORIENTATION always 0
How to write exif data to image in Android?
How to get the Correct orientation of the image selected from the Default Image gallery
how to set camera Image orientation?
but they all involve correcting the orientation by rotating, which doesn't influence the EXIF data, or setting the EXIF data directly which doesn't seem to work.
How can I change the file's EXIF orientation data from 0 to 3?
UPDATE:
here is my upload code:
Bitmap sBitmap = null;
final File sResizedFile = getOutputMediaFile(MEDIA_TYPE_IMAGE, "_2");
try {
sBitmap = BitmapFactory.decodeStream(new FileInputStream(pictureFile), null, options);
} catch (FileNotFoundException e) {
Log.e("App", "[MainActivity] unable to convert pictureFile to bitmap");
e.printStackTrace();
return;
}
// ... compute sw and sh int values
Bitmap sOut = Bitmap.createScaledBitmap(sBitmap, sw, sh, false);
Bitmap rotatedBitmap = rotateBitmap(sOut, 3);
FileOutputStream sfOut;
try {
sfOut = new FileOutputStream(sResizedFile);
rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, sfOut);
sfOut.flush();
sfOut.close();
sBitmap.recycle();
sOut.recycle();
rotatedBitmap.recycle();
} catch (Exception e) {
Log.e("App", "[MainActivity] unable to save thumbnail");
e.printStackTrace();
return;
}
// upload small thumbnail
TransferObserver sObserver = transferUtility.upload(
"stills/small", /* The bucket to upload to */
filename + ".jpg", /* The key for the uploaded object */
sResizedFile /* The file where the data to upload exists */
);
As you can see, the The EXIF information is not reliable on Android (especially Samsung devices).
However the phone SQL database holding the references to Media object is reliable. I would propose going this way.
Getting the orientation from the Uri:
private static int getOrientation(Context context, Uri photoUri) {
Cursor cursor = context.getContentResolver().query(photoUri,
new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
if (cursor.getCount() != 1) {
cursor.close();
return -1;
}
cursor.moveToFirst();
int orientation = cursor.getInt(0);
cursor.close();
cursor = null;
return orientation;
}
Then initialize rotated Bitmap:
public static Bitmap rotateBitmap(Context context, Uri photoUri, Bitmap bitmap) {
int orientation = getOrientation(context, photoUri);
if (orientation <= 0) {
return bitmap;
}
Matrix matrix = new Matrix();
matrix.postRotate(orientation);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
return bitmap;
}
If you want to change the orientation of the image, try the following snippet:
public static boolean setOrientation(Context context, Uri fileUri, int orientation) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.ORIENTATION, orientation);
int rowsUpdated = context.getContentResolver().update(fileUri, values, null, null);
return rowsUpdated > 0;
}
If you set the orientation of the image, later it will be constantly set at the correct orientation. There is need to make use of ExifInterface later, because the image is already rotated in proper way.
If this method is not satisfactory, then you could try this method
You have accepted your own answer as solution. My rant is just useful side-info, anyways...
The "However..." in your Answer suggests while you now know the cause, you don't have a fix.
Turns out my code was able to set the EXIF data, but there is a
discrepancy between how Android interprets this data and how iOS...
interprets it.
This could be an endianness issue. You can try manually changing the endianness setting of Exif by opening your jpeg in a hex editor and finding...
The bytes 45 78 69 66 (makes "Exif" text) followed by two zero bytes 00 00.
Then it should be 49 49 (makes "II" text) which means read data as little endian format.
If you replace it with 4D 4D (or "MM" text) then the reading side
will consider data as big endian.
Test this in iOS to see if numbers are now correct.
and regarding this...
However, setting 3 shows up as0 on iOS and the image is sideways in
Chrome.
Setting 6 shows up as 3 on iOS and the image looks right in Chrome.
Only thing I can add is that iOS Mac is Big Endian* and Android/PC is Little Endian. Essentially Little Endian reads/writes bytes as right-to-left whilst Big Endian is opposite.
In binary : 011 means 3 and 110 means 6. The difference between 3 and 6 is simply the reading order of those bits of ones & zeroes. So a system that reads as zero-one-one gets a result of 3 but the other Endian system will read a byte with same bits as one-one-zero and tell you result is a 6. I can't explain why "3 shows up as 0" without a test file to analyse bytes but it's a strange result to me.
</end rant>
<sleep>
*note: While Macs are Big Endian, double-checking says iOS uses Little Endian system after all. Your numbers still suggest a Big vs Little Endian issue though.
Turns out my code was able to set the EXIF data, but there is a discrepancy between how Android interprets this data and how iOS and Chrome on a Mac (where I was checking the resulting file) interprets it.
This is the only code needed to set EXIF orientation:
ExifInterface exif = new ExifInterface(pictureFile.toString());
exif.setAttribute(ExifInterface.TAG_ORIENTATION, "3");
exif.saveAttributes();
However, setting 3 shows up as 0 on iOS and the image is sideways in Chrome.
setting 6 shows up as 3 on iOS and the image looks right in Chrome.
Refer this GitHub project https://github.com/pandiaraj44/Camera. It has the custom camera activity where EXIF TAG_ORIENTATION was handled correctly. You can clone the project and check. For code details please refer https://github.com/pandiaraj44/Camera/blob/master/app/src/main/java/com/pansapp/cameraview/CameraFragment.java
There is one class to read and update these information of images.
To update the attributes you can use like this
ExifInterface ef = new ExifInterface(filePath);
ef.setAttribute(MAKE_TAG, MAKE_TAG);
ef.setAttribute(ExifInterface.TAG_ORIENTATION, orientation+"");
ef.saveAttributes();
and for reading you can use like this
ExifInterface exif = null;
try {
exif = new ExifInterface(absolutePath+path);
} catch (IOException e) {
e.printStackTrace();
}
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
I hope it will help you
Related
I've never been struggling this much with images as I am today and I'd appreciate some help :D
So, thing is, I'm using the built-in camera to capture a picture and then send it to the back end to save it but the orientation is messed up for Kit kat and specially Samsung devices. I tried to use the exif interface everyone suggests, but I just can't get the photo orientation.
A few minutes ago I found an answer somewhat related to this, saying that maybe a good solution is to save the device orientation when the picture is taken, which sounds pretty nice, however, I don't know how to do that with the built in camera, since I don't have full control when opening the camera with an Intent, like this:
mPathToTakenImage = ImageProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider",
newFile);
openCamera.putExtra(MediaStore.EXTRA_OUTPUT, mPathToTakenImage);
openCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(openCamera, AlgebraNationConstants.TAKE_PHOTO_REQUEST_CODE);
So, how can I get the device orientation while the image was being taken in order to rotate the image correctly?
This is the code to rotate the image, however, I'm getting always zero:
final Bitmap finalImg;
final StringBuilder base64Image = new StringBuilder("data:image/jpeg;base64,");
final ExifInterface exifInterface;
try {
final String imagePath = params[0].getPath();
exifInterface = new ExifInterface(imagePath);
final int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
final Bitmap takenPhoto = MediaStore.Images.Media.getBitmap(mRelatedContext.getContentResolver(),
params[0]);
if (null == takenPhoto) {
base64Image.setLength(0);
} else {
finalImg = rotateBitmap(takenPhoto, orientation);
if (null == finalImg) {
base64Image.setLength(0);
} else {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
finalImg.compress(Bitmap.CompressFormat.JPEG, 75, byteArrayOutputStream);
final byte[] byteArray = byteArrayOutputStream.toByteArray();
base64Image.append(Base64.encodeToString(byteArray, Base64.DEFAULT));
}
}
} catch (IOException e) {
e.printStackTrace();
}
return base64Image.length() == 0 ? null : base64Image.toString();
I'm going crazy with this, any help will be deeply appreciated.
EDIT:
A Uri is not a file. Unless the scheme of the Uri is file, getPath() is meaningless. In your case, the scheme is mostly going to be content, not file. As it stands, you are not getting EXIF headers, because ExifInterface cannot find that file.
Use a ContentResolver and openInputStream() to open an InputStream on the content identified by the Uri. Pass that InputStream to the android.support.media.ExifInterface constructor.
Also, bear in mind that you will crash much of the time with an OutOfMemoryError, as you will not have heap space to hold a base64-encoded photo.
So, I finally resolved my problem making use of these two answers:
https://stackoverflow.com/a/17426328
https://stackoverflow.com/a/40865982
In general, the issue was due to the image allocation in disk since I don't know why Android didn't like the fact that I was giving my own path to save the taken image. At the end, the Intent to open the camera is looking like this:
final Intent openCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(openCamera, AlgebraNationConstants.TAKE_PHOTO_REQUEST_CODE);
And Android itself is resolving where to put the image. Nevertheless, #CommonsWare thanks for the enlightenment and answers.
I'm recording the photos taken by my application in the sqlite database, I know it's not very recommended anymore I need to transfer these photos to a central database that unifies all data from all applications.
The way I'm reducing and writing to sqlite bank is this way:
Android.Graphics.Drawables.BitmapDrawable bd = (Android.Graphics.Drawables.BitmapDrawable)imageView.Drawable;
Bitmap bitmap = bd.Bitmap;
MemoryStream stream = new MemoryStream();
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
byte[] ba = stream.ToArray();
string bal = Base64.EncodeToString(ba, Base64.Default);
Personally, I am editing my question because the problem is not in the select as I had thought it could be, but at the time of writing to the database. The code above works great when searching for a photo or image on the device. The problem is when it takes the photo for the application, then I get this error that is below.
I think I need to compress App.bitmap, which gets the shot taken right away. I just do not know how to do it yet. As I said the above code works great with the pictures taken by the device, but when I shoot through my application I get this error.
I'm going to post my OnActivityResult method so that you can help me figure out what this error is.
Error:
Java.Lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it.
Searching on the internet this error is returning because of the size of the cursor that have a maximum of 2MB.
OnActivityResult Method:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
// Make it available in the gallery
try
{
Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
Uri contentUri = Uri.FromFile(App._file);
mediaScanIntent.SetData(contentUri);
SendBroadcast(mediaScanIntent);
// Display in ImageView. We will resize the bitmap to fit the display.
// Loading the full sized image will consume to much memory
// and cause the application to crash.
int height = Resources.DisplayMetrics.HeightPixels;
int width = imageView.Height;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
if (App.bitmap != null)
{
imageView.SetImageBitmap(App.bitmap);
// App.bitmap = null;
}
// Dispose of the Java side bitmap.
GC.Collect();
}
catch
{
}
try
{
if (resultCode == Result.Ok)
{
var imageView =
FindViewById<ImageView>(Resource.Id.imageView1);
imageView.SetImageURI(data.Data);
}
}
catch
{
}
}
Without fetching the photo, the select does not return the error quoted above. I'm counting on your help.
Use EncoderParameter to specify the level of compression for an image.
see https://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoderparameter(v=vs.110).aspx
and https://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoder.quality(v=vs.110).aspx
This is the details of one sample picture I try to get orientation of:
Using EXIF I always get ORIENTATION_UNDEFINED (=0). Here is the code:
ExifInterface exif = new ExifInterface(imageUri.getPath());
int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
So, I searched and found bunch of similar questions. The suggestions were using the details of image in MediaStore. I do it like this:
String[] projection = {MediaStore.Images.ImageColumns.ORIENTATION};
try {
Cursor cursor = context.getContentResolver().query(imageUri, projection, null, null, null);
if (cursor.moveToFirst()) {
orientation = cursor.getInt(0);
}
cursor.close();
} catch (Exception e) {}
The above code doesn't also give any result as, "cursor" becomes null!
Having the orientation I will do image rotation and stuff. What should I do to get a photo orientation?
Your first piece of code will only work if imageUri has a file scheme. Many times, it will not.
Your second piece of code will only work if imageUri is a Uri from MediaStore.
Instead, use an ExifInterface implementation that works with streams, so you can use ContentResolver and openInputStream() to get the orientation. This should work for all Uri values, though whether or not the JPEG contains an orientation header will vary by the image.
This sample app demonstrates the approach. In my case, the images are in assets/, and so my InputStream comes from AssetManager, not ContentResolver, but otherwise it should work the same:
ExifInterface exif=new ExifInterface();
exif.readExif(is, ExifInterface.Options.OPTION_ALL);
ExifTag tag=exif.getTag(ExifInterface.TAG_ORIENTATION);
int orientation=(tag==null ? -1 : tag.getValueAsInt(-1));
(where is is the InputStream)
I did my own camera app on Android.
1) Configuring camera and preview :
Camera.Parameters parameters = camera.getParameters();
// My camera takes landscape picture by befault (Samsung GT-9300).
// But my app is only in portrait mode.
camera.setDisplayOrientation(90);
// Here to rotate final pict
parameters.set("rotation", 90);
// Some code to define best preview resolution and best picture resolution
... some code ...
// Apply
camera.setParameters(parameters);
2) StartPreview
// Here I see what i want to see... Is there no problem here.
camera.startPreview();
3) GetOutputMediaFile()
// private function to create empty file which will receive data
private static File getOutputMediaFile(){
String NewFolder = "/TEST";
String StorageDirectory;
StorageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File mediaStorageDir = new File(StorageDirectory + NewFolder);
if (!mediaStorageDir.exists()){
if (!mediaStorageDir.mkdirs()){
Log.d("myApp", "failed to create directory");
return null;
} else {
mediaStorageDir.mkdir();
}
}
String date = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss", Locale.FRANCE).format(new Date());
File photo = new File(StorageDirectory + NewFolder, "photo_" + date + ".jpg");
return photo;
}
4) Here my problem
// camera.takePicture(null, null, mPicture) called on onCreate function
// Here this callback
private PictureCallback mPicture = new PictureCallback() {
#Override
public void onPictureTaken(byte[] data, Camera camera) {
pictureFile = getOutputMediaFile();
if (pictureFile == null){
Log.d(TAG, "Error creating media file, check storage permissions: " + e.getMessage());
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
};
On my phone, if i go on my gallery, or original my files app, i have thumbnail and photo correctly oriented.
Now if I go, with Root File Manager, on this picture folder, thumbnail is oriented by default (real camera orientation), and same as if I look my picture with my computer.
Then I think my data var (byte[] data) on my onPictureTaken function is not good.
I think data is like that :
How I think my var data is
But I would like to have that as my var data :
How I would like to have
I know my var data is only byte[] but these cat pictures is to shows how I see my data var.
Now my questions :
A) Have I right on how my data variable is ?
B) If yes, can you say me how to do 90° rotation on this "array" ?
You should not change the image bits to account for rotation.
Rotation is stored in the JPG EXIF image metadata orientation value, so you just need to set the metadata value to match the camera orientation. Read more about this EXIF value and others here.
To manage the EXIF data you can use framework class ExifInterface.
If the picture shows correctly in your phone's gallery application, it is likely correctly oriented. You can experiment with different values for the Parameters.setRotation and see if they affect what you see in the gallery app.
Some picture viewing programs do not correctly apply the EXIF orientation field, which can lead to them drawing the image incorrectly rotated. If that's the case, changing the orientation field does nothing, since those programs will not work with any orientation value. You'll have to actually rotate the JPEG image, instead of just setting the metadata field, to get them to look correct there.
If you want to do this inside Android, you'll have to decode the byte[] you receive from the camera to a Bitmap (with a BitmapFactory.decodeByteArray, rotate the bitmap, and save the result as a JPEG. You'll lose all the other EXIF metadata (date/time taken, etc) unless you use ExifInterface to write them back. It's also possible to losslessly rotate a JPEG, but I don't know if there are Android libraries for that.
First I use this code to save the photo to the Android's SD Card:
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera camera) {
FileOutputStream outStream = null;
currentFilename = String.format("%d", System.currentTimeMillis());
outStream = new FileOutputStream("/sdcard/" + currentFilename + ".jpg");
outStream.write(data);
outStream.close();
}
};
Then I am using this code to upload photos on Android devices:
public void uploadPhoto() {
try {
// Create HttpPost
HttpPost post = new HttpPost("http://www.example.com/upload.php");
HttpClient client = new DefaultHttpClient();
client.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
MultipartEntity entity = new MultipartEntity( HttpMultipartMode.BROWSER_COMPATIBLE );
// Add the Picture as "userfile"
entity.addPart( "userfile", new FileBody(
new File( "/sdcard/", currentFilename + ".jpg" ),
"image/jpeg")
);
// Send the data to the server
post.setEntity( entity );
client.execute( post );
} catch (Exception e) {
// catch
} finally {
// finally
}
}
This works on my LG Optimus V running Android 2.2.2 and Droid X running Android 2.3.4
However - a user who has an HTC Evo running 2.3.4 (Same as my Droid X) is experiencing that all of her uploaded photos are scrambled when they are saved to her phone (and when they get to the server). The image looks like an array of jagged colored lines.
Any ideas about what might be going on here and what could be done to remedy the problem?
Update:
I was able to access this phone and when I pull the jpg files off of the sdcard, it says that the files are 3.5 - 4.0 MB in size, but they cannot be opened and may be corrupted... but the files on the other phones work normally.
It looks like problem with picture sizes. Decoding data from array to picture when width and heigh are wrong results with scrambled image.
Pictures might be taken with supported screen sizes:
Parameters params = camera.getParameters();
List<Camera.Size> sizes = params.getSupportedPictureSizes();
try to set one of them, for example the biggest, as picture size:
List<Size> sizes = params.getSupportedPictureSizes();
Size theBiggest=null;
int maxWidth = 0;
for (Size s : sizes) {
if (s.width > maxWidth) {
maxWidth = s.width;
theBiggest = s;
}
}
params.setPictureSize(theBiggest.width, theBiggest.height);
camera.setParameters(params);
Images need to be cached to sdcard in order to achieve full (non-compressed) size. Steps are easy
capture image
save captured image to sdcard
load saved image from sdcard
convert it to byte array and add as a ByteArrayBody, or as you o you might skip step 3 and create a file body with cached file.
Note: Currently i am away from my pc. I will post an detailed answer if there is no detailed post. Good luck.
Compress the bitmap in JPEG format into the output stream.. Assuming data is a Bitmap type
currentFilename = String.format("%d", System.currentTimeMillis());
outStream = new FileOutputStream("/sdcard/" + currentFilename + ".jpg");
data.compress(Bitmap.CompressFormat.JPEG, 90, outStream );
outStream.close();
You can change the compression ratio parameter to the compress api.. for example, pass 80 to get 80% compression.
This post may have some relation to your problem.