Проблемы с камерой и фотографией для Android с Samsung Galaxy S3, S4, S5

Я разрабатываю приложение для камеры для Android API 16-21, главной и единственной целью которого является фотография портрет. Я могу снимать изображения с нескольких устройств (Nexus 4, Nexus 5, HTC...) и правильно их ориентировать (это означает, что мой предварительный просмотр равен принятому изображению как по размеру, так и по ориентации).

Однако я тестировал свое приложение на нескольких других устройствах, и некоторые из них дают мне много проблем: Samsung Galaxy S3/S4/S5.

На этих трех устройствах предварительный просмотр отображается правильно, однако изображения, возвращаемые методом onPictureTaken(final byte[] jpeg, Camera camera), всегда боком.

Это растровое изображение, созданное с помощью byte[] jpeg и отображаемое в ImageView моему пользователю непосредственно перед сохранением на диске:

preview

И вот изображение, однажды сохраненное на диске:

disk

Как вы можете видеть, изображение полностью растянуто в предварительном просмотре и неправильно повернуто после сохранения на диске.

Вот мой класс CameraPreview (я запутывал другие методы, так как они не имели никакого отношения к параметрам камеры):

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

Как этот точный фрагмент кода работает для других устройств, кроме Samsung one?

Я попытался найти ответы на следующие сообщения SO, но до сих пор мне ничего не помогло: этот и этот другой.

ИЗМЕНИТЬ

Реализация ответа Джои Чонга ничего не меняет:

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

Вот результаты EXIF, которые я получаю для рабочего устройства:

  • Ориентация: 0

И вот результаты для S4:

  • Ориентация: 0

Ответ 1

Это связано с тем, что телефон по-прежнему сохраняет пейзаж и помещает метаданные в 90 градусов. Вы можете попробовать проверить exif, повернуть растровое изображение перед помещением в изображение. Чтобы проверить exif, используйте что-то вроде:

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

Ответ 2

У меня была аналогичная проблема с сохраненным изображением.

Я использовал нечто похожее на то, что описано здесь https://github.com/googlesamples/android-vision/issues/124 пользователем kinghsumit (комментарий от 15 сентября 2016 года).

Я скопирую его здесь, на всякий случай.

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

Ниже класс используется для получения ориентации из данных байта [].

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

Это работало для меня, кроме Nexus 5x, но это потому, что у этого устройства есть своеобразная проблема из-за его конструкции.

Надеюсь, это поможет вам!

Ответ 3

Я использовал этот AndroidCameraUtil. Это очень помогло мне в этом вопросе.

Ответ 4

Вы можете попытаться использовать параметры камеры для устранения проблемы с ротацией.

Camera.Parameters parameters = camera.getParameters();
parameters.set("orientation", "portrait");
parameters.setRotation(90);
camera.setParameters(parameters);