Which leaves me with The Problem. I'm going to skip the intermediate steps because I don't remember them. Here is what I know now:
1. my camera rotation code is wrong according to the API documentation
2. my camera rotation code is most definitely wrong according to common sense
3. my camera rotation code works perfectly on: the Nexus 7, the Acer Iconia A, the Acer Iconia B, the Digital2, and the lenovo Yoga 8.
4. my camera rotation code DOES NOT work on the Galaxy Tab 3
4b. The preview does work, but the final picture doesn't, and the preview and the final picture are using the same rotation angle.
5. the inputs and outputs of my camera rotation code are identical on the Galaxy Tab 3 and the Acer Iconia A. That is to say, the camera angle is the same on both devices, and my resulting rotation calculations are the same value on both devices, AND I'm setting the same rotation parameters on the camera, but its working on the Iconia A and not on the Galaxy Tab.
I don't know whats going on. The fucking code I wrote shouldn't work on any of the devices (I switched the front and back camera algorithms), and the behavior should be consistent, especially between the preview and the final picture. Even worse, no one on stack overflow appears to be having this problem--that, or everyone asking the right questions are just being drowned out by people asking how to use the camera.
Once thing I'm doing differently is that I am choosing not to handle orientation changes myself--the sample camera code appears to assume that people would do that. Another thing I can try is to look at the code for the camera app, if its open source.
[Edit]
Quick update: android orientation handling is fucked up. I still prefer it over windows phone though.
[Edit]
First of all, to get the source for the camera app apparently now you have to use git:
git clone https://android.googlesource.com/platform/packages/apps/Camera.git
Fixed my code...the problem is now occurring only on samsung devices.
- http://stackoverflow.com/questions/18203369/camera-capture-orientation-on-samsung-devices-in-android
- http://www.impulseadventure.com/photo/exif-orientation.html
[Edit]
The windows picture viewer ALSO ignores the EXIF orientation.
GIMP detects the orientation, and prompts you to "change" it if you want to see the image in the correct orientation.
What I'm gathering from this, is that EXIF orientation is bullshit, and also the Samsung camera device driver developers were fucking lazy.
[Edit]
Android provides an EXIF parser for you.
The EXIF parser provided by android cannot read from a Stream. Only a file. (the source code of the Camera app uses some 3d library that is not part of the SDK)
The Camera code callback gives you a stream, not a file.
The core design of Java can be accused of being bloated but it has, from the beginning, made a giant fucking deal about file vs stream abstractions.
You don't need to memorize Code Complete to avoid these types of design mistakes.
[Final Edit ?]
Yeah, so cam orientation is still messed up on this piece of shit Digital Pad 2. I'm going to write that one off, partly because my code is consistent with the official camera app and partly because the manufacturer's website did not come up in web searches.
Ladies and Gentlemen, I give you fuckers the Glorious Arrow of Orientation:
That picture was actually taken on my Samsung Galaxy S 2 with the official Camera app, which means that since I was holding the phone ostensibly right side up, that file actually has the rotation wrong--if you open it in the wrong program, the arrow is pointing to the left.
So.
Conclusions:
There are four ways to get device orientation:
1. toDegrees(getWindowManager().getDefaultDisplay().getRotation())
2. toDegrees(getResources().getConfiguration().orientation)
3. onConfigurationChanged(Configuration)
4. OrientationEventListener
1 & 2 are equivalent, so lets revise our list for impact:
1. toDegrees(getWindowManager().getDefaultDisplay().getRotation()) / toDegrees(getResources().getConfiguration().orientation)
2. onConfigurationChanged(Configuration)
3. OrientationEventListener
Each of those methods gives a DIFFERENT FUCKING ANSWER. One of them actually only seems to give the values 90 and 180 regardless of which orientation.
The one that works with the Camera is #3. Naturally, it is the most inconvenient to use. You can't pull the value (and in fact, when your activity is getting created, the value might be wrong anyway so you have to update later), you have to receive it in the listener. The value you get will be in the range [0-359], but the camera only takes {0, 90, 180, 270} so you have to:
lastReadOrientation = (orientation + 45) / 90 * 90 % 360;
Then you set the orientation on the camera. The API code turned to be right, however you'll note that for the Front Camera, the preview is mirrored while the camera is not, so you need two different algorithms.
Next, some cameras actually give you a messed up picture. That is to say the camera didn't actually rotate the image; it just put a "hey this image is rotated" note in the EXIF header.
I don't give a fuck how well the data matches the EXIF standard; the fact that the platforms own bitmap decoder can't fix the rotation automatically and probably half of the image editing programs out there ignore the EXIF orientation means the data is fucked up.
The official Camera app actually doesnt fix the orientation. The android developers and most of the posters on stack overflow appear to believe it is acceptable to just save the corrupt picture and deal with the rotation every fucking time an image is loaded. I consider that unacceptable; so I'm rotating the image in memory.
If you were paying attention, you'll note that I just decided to do something the API devs didn't specifically anticipate. As always, this means we're in for a big fucking headache. First, the stupid ass EXIF tools in the API can't read bytes in memory. So I used a third party lib:
http://javadoc.metadata-extractor.googlecode.com/git/2.6.4/index.html
public static int getExifOrientation(byte[] jpg) {
// http://stackoverflow.com/questions/12944123/reading-android-jpeg-exif-metadata-from-picture-callback
if(jpg == null) return 0;
try{
//uses metadata-extractor-2.6.4.jar and xmpcore.jar
Metadata metadata = JpegMetadataReader.readMetadata(
new BufferedInputStream(new ByteArrayInputStream(jpg)), false);
// Get the EXIF orientation.
final ExifIFD0Directory exifIFD0Directory = metadata.getDirectory(ExifIFD0Directory.class);
if(exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION))
{
return exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
}
else
{
return 0;
}
}catch(Exception e){
log("cant read exif orientation", e);
return 0;
}
}
public static Matrix getMatrixForExifOrientation(int exifOrientation){
// from http://stackoverflow.com/questions/12944123/reading-android-jpeg-exif-metadata-from-picture-callback
final Matrix bitmapMatrix = new Matrix();
switch(exifOrientation)
{
case 1: break; // top left
case 2: bitmapMatrix.postScale(-1, 1); break; // top right
case 3: bitmapMatrix.postRotate(180); break; // bottom right
case 4: bitmapMatrix.postRotate(180); bitmapMatrix.postScale(-1, 1); break; // bottom left
case 5: bitmapMatrix.postRotate(90); bitmapMatrix.postScale(-1, 1); break; // left top
case 6: bitmapMatrix.postRotate(90); break; // right top
case 7: bitmapMatrix.postRotate(270); bitmapMatrix.postScale(-1, 1); break; // right bottom
case 8: bitmapMatrix.postRotate(270); break; // left bottom
default: break; // Unknown
}
return bitmapMatrix;
}
That code will probably look like shit...I don't care.
Next, we have to deal with the fact that bitmaps can be transformed in place. You have to create a new bitmap in the process, which invariably causes an Out of Memory Error.
To mitigate this, first put this in the application tag in your manifest:
android:largeHeap="true"
Secondly, ONLY do the transform if the EXIF orientation != 1. One means no orientation change, and--of the devices I found in Best Buy--samsung appears to be the only manufacturer I found that uses the EXIF orientation, so by limiting the transformation code to only those devices we greatly reduce the chances of Out of Memory Errors.
Fun fact: The Samsung Galaxy S 2 cannot do reverse portrait.
Also, it is important to be constantly setting the camera orientation when the orientation listener fires. You can't rely on having your activity recreated, or even on onConfigurationChanged() being fired when doing orientation changes manually; the value will be wrong at those points in time, especially when you physically rotate the devices more than 135 degrees in one shot. Also, the Acer Iconia A series seems to have the completely wrong orientation when the activity starts, so you need the listener to fire to fix that.


No comments:
Post a Comment