Проблемы с ориентацией камеры / изображения 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 для моего пользователя непосредственно перед сохранением его на диск:
И вот изображение, когда-то сохраненное на диске:
Как вы можете видеть, изображение полностью растянуто в предварительном просмотре и неправильно повернуто после сохранения на диске.
Вот мой класс 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?
Я пытался найти ответы на следующие 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
4 ответа
Это потому, что телефон все еще сохраняет в альбомной ориентации и помещает метаданные как 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;
}
У меня была похожая проблема с сохраненным изображением.
Я использовал нечто подобное тому, что описано здесь 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);
}
Класс ниже используется для получения ориентации из данных byte[].
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, но это потому, что это устройство имеет особую проблему из-за его конструкции.
Я надеюсь, это поможет вам!
Я использовал этот AndroidCameraUtil. Это очень помогло мне в этом вопросе.
Вы можете попробовать использовать параметры камеры, чтобы исправить проблему с вращением.
Camera.Parameters parameters = camera.getParameters();
parameters.set("orientation", "portrait");
parameters.setRotation(90);
camera.setParameters(parameters);