12

I am developing a camera application for Android API 16 to 21 which main and only purpose is to take portrait photo. I am able to take picture with several devices (Nexus 4, Nexus 5, HTC...) and have them correctly oriented (meaning that my preview equals the taken picture both in size and orientation).

However I have tested my application on several other devices and some of them are giving me alot of trouble: Samsung Galaxy S3/S4/S5.

On these three devices, the preview is correctly displayed, however the pictures returned by the method onPictureTaken(final byte[] jpeg, Camera camera) are always sideways.

This is the Bitmap created from byte[] jpeg and displayed in the ImageView to my user just before saving it to the disk:

preview

And here is the image once saved on the disk:

disk

As you can see the image is completly stretched in the preview and wrongly rotated once saved on the disk.

Here is my CameraPreview class (I obfuscated other methods since they had nothing to do with camera parameters):

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback
{
    private SurfaceHolder surfaceHolder;
    private Camera camera;

    // Removed unnecessary code

    public void surfaceCreated(SurfaceHolder holder)
    {
        camera.setPreviewDisplay(holder);
        setCameraParameters();
        camera.startPreview();
    }

    private void setCameraParameters()
    {
        Camera.Parameters parameters = camera.getParameters();
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
        int rotation = windowManager.getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation)
        {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }
        int rotate = (info.orientation - degrees + 360) % 360;
        parameters.setRotation(rotate);

        // Save Parameters
        camera.setDisplayOrientation(90);
        camera.setParameters(parameters);
    }
}

How come this exact piece of code works for other devices except Samsung's one ?

I tried to find answers on the following SO posts but nothing could help me so far: this one and this other one.

EDIT

Implementing Joey Chong's answer does not changes anything:

public void onPictureTaken(final byte[] data, Camera camera)
{
    try
    {
        File pictureFile = new File(...);
        Bitmap realImage = BitmapFactory.decodeByteArray(data, 0, data.length);
        FileOutputStream fos = new FileOutputStream(pictureFile);
        realImage.compress(Bitmap.CompressFormat.JPEG, 100, fos);

        int orientation = -1;
        ExifInterface exif = new ExifInterface(pictureFile.toString());
        int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,  ExifInterface.ORIENTATION_NORMAL);

        switch (exifOrientation)
        {
            case ExifInterface.ORIENTATION_ROTATE_270:
                orientation = 270;
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                orientation = 180;
                break;
            case ExifInterface.ORIENTATION_ROTATE_90:
                orientation = 90;
                break;
            case ExifInterface.ORIENTATION_NORMAL:
                orientation = 0;
                break;
            default:
                break;
        }

        fos.close();
}

Here are the EXIF results I get for a working device:

  • Orientation: 0

And here the results for the S4:

  • Orientation: 0
Community
  • 1
  • 1
Aymeric
  • 1,324
  • 3
  • 15
  • 33
  • possible duplicate of [setRotation(90) to take picture in portrait mode does not work on samsung devices](http://stackoverflow.com/questions/11023696/setrotation90-to-take-picture-in-portrait-mode-does-not-work-on-samsung-device) – Alex Cohn Mar 11 '15 at 14:52

4 Answers4

5

It is because the phone still save in landscape and put the meta data as 90 degree. You can try check the exif, rotate the bitmap before put in image view. To check exif, use something like below:

    int orientation = -1;

    ExifInterface exif = new ExifInterface(imagePath);

    int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 
            ExifInterface.ORIENTATION_NORMAL);

    switch (exifOrientation) {
        case ExifInterface.ORIENTATION_ROTATE_270:
            orientation = 270;

            break;
        case ExifInterface.ORIENTATION_ROTATE_180:
            orientation = 180;

            break;
        case ExifInterface.ORIENTATION_ROTATE_90:
            orientation = 90;

            break;

        case ExifInterface.ORIENTATION_NORMAL:
            orientation = 0;

            break;
        default:
            break;
    }
jkunzika
  • 164
  • 2
  • 8
Joey Chong
  • 1,470
  • 15
  • 20
  • 2
    Thanks for your answer, I implemented your solution (see edit) but the exifOrientation is always equal to 0 on working and non working devices. – Aymeric Dec 17 '14 at 16:58
  • What is the scaleType setting for the ImageView? If you set it to `center`, what is the result? – Joey Chong Dec 18 '14 at 01:25
  • The preview has nothing to do with the problem, the problem is that on some Samsung devices the image is saved as a landscape instead of a portrait as it does in the Nexus 5. – Aymeric Dec 18 '14 at 13:29
  • Sorry that my suggestion can't help, so far my projec using s3 and s4 for testing seems ok. Just a quick test, if you change the `parameters.setRotation(rotate);` to `parameters.setRotation(90);`, is there any different? – Joey Chong Dec 19 '14 at 01:19
4

I had a similar problem regarding the saved image.

I used something similar to what is described here https://github.com/googlesamples/android-vision/issues/124 by user kinghsumit (the comment from Sep 15, 2016).

I'll copy it here, just in case.

private CameraSource.PictureCallback mPicture = new CameraSource.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] bytes) {
       int orientation = Exif.getOrientation(bytes);
       Bitmap   bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
       switch(orientation) {
           case 90:
               bitmapPicture= rotateImage(bitmap, 90);
               break;
           case 180:
               bitmapPicture= rotateImage(bitmap, 180);
               break;
           case 270:
               bitmapPicture= rotateImage(bitmap, 270);
               break;
           case 0:
               // if orientation is zero we don't need to rotate this
           default:
               break;
       }
       //write your code here to save bitmap 
   }
}

public static Bitmap rotateImage(Bitmap source, float angle) {
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true);
}

Below class is used to get orientation from byte[] data.

public class Exif {
    private static final String TAG = "CameraExif";

    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
    public static int getOrientation(byte[] jpeg) {
        if (jpeg == null) {
            return 0;
        }

        int offset = 0;
        int length = 0;

        // ISO/IEC 10918-1:1993(E)
        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
            int marker = jpeg[offset] & 0xFF;

            // Check if the marker is a padding.
            if (marker == 0xFF) {
                continue;
            }
            offset++;

            // Check if the marker is SOI or TEM.
            if (marker == 0xD8 || marker == 0x01) {
                continue;
            }
            // Check if the marker is EOI or SOS.
            if (marker == 0xD9 || marker == 0xDA) {
                break;
            }

            // Get the length and check if it is reasonable.
            length = pack(jpeg, offset, 2, false);
            if (length < 2 || offset + length > jpeg.length) {
                Log.e(TAG, "Invalid length");
                return 0;
            }

            // Break if the marker is EXIF in APP1.
            if (marker == 0xE1 && length >= 8 &&
                    pack(jpeg, offset + 2, 4, false) == 0x45786966 &&
                    pack(jpeg, offset + 6, 2, false) == 0) {
                offset += 8;
                length -= 8;
                break;
            }

            // Skip other markers.
            offset += length;
            length = 0;
        }

        // JEITA CP-3451 Exif Version 2.2
        if (length > 8) {
            // Identify the byte order.
            int tag = pack(jpeg, offset, 4, false);
            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
                Log.e(TAG, "Invalid byte order");
                return 0;
            }
            boolean littleEndian = (tag == 0x49492A00);

            // Get the offset and check if it is reasonable.
            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
            if (count < 10 || count > length) {
                Log.e(TAG, "Invalid offset");
                return 0;
            }
            offset += count;
            length -= count;

            // Get the count and go through all the elements.
            count = pack(jpeg, offset - 2, 2, littleEndian);
            while (count-- > 0 && length >= 12) {
                // Get the tag and check if it is orientation.
                tag = pack(jpeg, offset, 2, littleEndian);
                if (tag == 0x0112) {
                    // We do not really care about type and count, do we?
                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
                    switch (orientation) {
                        case 1:
                            return 0;
                        case 3:
                            return 180;
                        case 6:
                            return 90;
                        case 8:
                            return 270;
                    }
                    Log.i(TAG, "Unsupported orientation");
                    return 0;
                }
                offset += 12;
                length -= 12;
            }
        }

        Log.i(TAG, "Orientation not found");
        return 0;
    }

    private static int pack(byte[] bytes, int offset, int length, boolean littleEndian) {
        int step = 1;
        if (littleEndian) {
            offset += length - 1;
            step = -1;
        }

        int value = 0;
        while (length-- > 0) {
            value = (value << 8) | (bytes[offset] & 0xFF);
            offset += step;
        }
        return value;
    }
}

It worked for me, except for the Nexus 5x, but that's because that device has a peculiar issue due to its construction.

I hope this helps you!

Mel
  • 41
  • 1
  • 4
  • Actually, you don't need to read the EXIF byte from JPEG - you can just as well keep track of the device orientation while the picture is taken. Yes, a malicious user could quickly rotate the device to landscape and back, and you will report wrong orientation. But the camera sets EXIF flag (if it does) based on exactly same logic, and can be fooled as easily. – Alex Cohn Dec 28 '17 at 17:19
  • isn't it more efficient to just check it once (when the picture is taken) ? – Mel Dec 29 '17 at 19:37
  • What kind of efficiency are you worried about? If the user changes the device orientation quickly after pressing the *capture* button, you can get a wrong result. Double checking can make your choice slightly more reliable. Anyways, if the reported angle is 45°, will you choose landscape or portrait? Actually, the TS purpose was to take a *portrait* picture. In this case, he can simply choose 90. – Alex Cohn Dec 30 '17 at 07:10
1

I used this AndroidCameraUtil. It helped me a lot on this issue.

David
  • 37,109
  • 32
  • 120
  • 141
0

You can try to use Camera parameters to fix rotation issue.

Camera.Parameters parameters = camera.getParameters();
parameters.set("orientation", "portrait");
parameters.setRotation(90);
camera.setParameters(parameters);
Vlad
  • 110
  • 1
  • 5