在某些Gingerbread设备上,使用ACTION_IMAGE_CAPTURE拍摄的图像,ExifInterface.TAG_ORIENTATION始终返回1。

43
我在使用ACTION_IMAGE_CAPTURE活动时遇到了方向问题。我已经使用TAG_ORIENTATION来相应地旋转图片。但是现在我们发现在一些新设备上这种方法不起作用。实际上,对于所有方向,它都返回1。
以下是我们观察到的设备列表:
- Samsung Infuse 4G(2.3.3) - Samsung Galaxy SII X(2.3.5) - Sony Xperia Arc(2.3.3)
有趣的是,一旦这张照片出现在画廊中,它就会正确显示,如果我选择它,TAG_ORIENTATION就会正确填充。所以,某种方式,操作系统可以正确地填充这些信息,但在ActivityResult中却不能。
如何确定方向最可靠?另一个问题上的某个人建议比较高度和宽度,但当获取这些值时,它们根据方向正确切换(另一个谜)。
编辑:看起来这可能与另一个错误相关,其中操作系统在画廊中复制了拍摄的图像(它只应该将图像保存在我们指定的URL中),问题在于画廊中的这张图片具有ORIENTATION信息,而指定位置中的图片则没有。
这是错误:http://code.google.com/p/android/issues/detail?id=19268

编辑2:我已向Android提交了一个新的错误报告。我非常确定这是与上述错误相关的操作系统错误。 http://code.google.com/p/android/issues/detail?id=22822


我不确定问题是否出在操作系统本身。相机和图库应用程序是由手机厂商开发的,并不能保证没有漏洞或正确运行。这就是为什么你在某些设备上会遇到错误,而在其他设备上则不会。 - Konstantin Pribluda
哦,我明白你的意思,那是真的。不管怎样,作为解决方案,我决定自己建立一个画廊(主要是通过复制粘贴安卓的那个)。 - Tolga E
摩托罗拉Xoom也出现了同样的问题。 - Siddhu
5个回答

56

大家好,看来 Android 设备上的这个 bug 一时半会儿不会被修复。虽然我找到了一种方法来实现 ExifInformation,使得具有正确 Exif 标签和不正确 Exif 标签的设备可以一起工作。

问题在于,某些(新)设备存在一个 bug,导致拍摄的照片保存在您的应用程序文件夹中,而没有正确的 Exif 标签,而正确旋转的图像则保存在 Android 默认文件夹中(尽管它不应该)。

现在我所做的是,在从我的应用程序启动相机应用程序时记录时间。 然后在活动结果上,我查询 Media Provider,以查看是否保存了此时间戳之后保存了任何图片。 这意味着,最可能操作系统已将正确旋转的图像保存在默认文件夹中,并当然在媒体库中放置了一个条目,我们可以使用此行中的旋转信息。 现在为了确保我们正在查看正确的图像,我比较此文件的大小与我可以访问的文件的大小(保存在我的应用程序文件夹中);

    int rotation =-1;
    long fileSize = new File(filePath).length();

    Cursor mediaCursor = content.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] {MediaStore.Images.ImageColumns.ORIENTATION, MediaStore.MediaColumns.SIZE }, MediaStore.MediaColumns.DATE_ADDED + ">=?", new String[]{String.valueOf(captureTime/1000 - 1)}, MediaStore.MediaColumns.DATE_ADDED + " desc");

    if (mediaCursor != null && captureTime != 0 && mediaCursor.getCount() !=0 ) {
        while(mediaCursor.moveToNext()){
            long size = mediaCursor.getLong(1);
            //Extra check to make sure that we are getting the orientation from the proper file
            if(size == fileSize){
                rotation = mediaCursor.getInt(0);
                break;
            }
        }
    }

现在,如果在这一点上的旋转仍然是-1,那么这意味着这是具有正确旋转信息的手机之一。此时,我们可以在返回到我们的onActivityResult的文件上使用常规的exif方向。

    else if(rotation == -1){
        rotation = getExifOrientationAttribute(filePath);
    }

你可以轻松地找到如何查找exif方向的方法,就像这个问题的答案所示:Android中的相机方向问题

此外,请注意ExifInterface仅支持Api级别5及以上。因此,如果您想要支持2.0之前的手机,则可以使用我发现的这个非常方便的Java库,由Drew Noakes提供;http://www.drewnoakes.com/code/exif/

祝您旋转图片好运!

编辑:因为有人问起,我使用的意图和开始的方式是这样的

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//mediaFile is where the image will be saved
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mediaFile));
startActivityForResult(intent, 1);

1
是的,真的。实际上,即使像您在此处识别的这样的合法错误已记录在共同存储库中,也会为开发人员节省大量的沮丧和时间。 - Abhijit
3
“captureTime”是指System.currentTimeMillis(),对吗? - hacker
1
我无法在三星Galaxy S4上使其工作,mediaCursor.getCount()等于0。 - PaperThick
1
它不起作用,我猜测我没有获取当前时间戳。怎么做? - Jesus Dimrix
如果用户拍了几张照片,然后回到您的应用程序,怎么办?您是否保存了应用程序拍摄的所有图像的这些“时间戳”? - Piotr
显示剩余7条评论

7

你也可以选择这种方式:

Matrix matrix = new Matrix();
// rotate the Bitmap (there a problem with exif so we'll query the mediaStore for orientation
Cursor cursor = getApplicationContext().getContentResolver().query(selectedImage,
      new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);
if (cursor.getCount() == 1) {
cursor.moveToFirst();
    int orientation =  cursor.getInt(0);
    matrix.preRotate(orientation);
    }

7

这是一个令人头痛的bug!我不确定我喜欢建议的解决方法,所以这里有另一种方法 :)

关键是使用EXTRA_OUTPUT并在图像被捕获后查询它!显然,只有在允许自己指定文件名时才能使用此方法。

protected void takePictureSequence() {      
    try {
        ContentValues values = new ContentValues();  
        values.put(MediaStore.Images.Media.TITLE, UUID.randomUUID().toString() + ".jpg");  
        newPhotoUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

        Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, newPhotoUri);

        startActivityForResult(intent, ActivityResults.TAKE_NEW_PICTURE_RESULT);
    } catch (Exception e) {
        Toast.makeText(this, R.string.could_not_initalize_camera, Toast.LENGTH_LONG).show();
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == ActivityResults.TAKE_NEW_PICTURE_RESULT) {
        if (resultCode == RESULT_OK) {
            try {
                String[] projection = { MediaStore.Images.Media.DATA }; 
                CursorLoader loader = new CursorLoader(this, newPhotoUri, projection, null, null, null);
                Cursor cursor = loader.loadInBackground();

                int column_index_data = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                cursor.moveToFirst();

                // Rotation is stored in an EXIF tag, and this tag seems to return 0 for URIs.
                // Hence, we retrieve it using an absolute path instead!
                int rotation = 0;
                String realPath = cursor.getString(column_index_data);
                if (realPath != null) {
                    rotation = ImageHelper.getRotationForImage(realPath);
                }

                // Now we can load the bitmap from the Uri, using the correct rotation.
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public int getRotationForImage(String path) {
    int rotation = 0;

    try {
        ExifInterface exif = new ExifInterface(path);
        rotation = (int)exifOrientationToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL));
    } catch (IOException e) {
        e.printStackTrace();
    }

    return rotation;
}

我在我的应用程序上尝试了这个,它有效。使用ContentResolver将临时文件插入媒体存储是至关重要的,以确保EXIF数据被正确保存。 - lazypig
在拍摄肖像照时,我的方向为6,在拍摄风景照时,方向为1。这是什么意思? - Ahmad Arslan
loader.loadInBackground(); 返回 null - Wackaloon

2
我最近学到的是,如果你调整图片大小,通常会丢失其EXIF信息。所以你需要将新文件赋予旧的EXIF信息。
来源:原文链接

0

我的解决方案。在LG G2手机上测试过。我注意到,当我使用照相机并拍摄新照片时,一切都正常。 ExifInterface返回正确的方向。所以问题肯定是路径引起的,因为在代码的这一行中我的路径为空:

exif = new ExifInterface(path);

但是当我使用绝对路径时,我的应用程序崩溃了。但是下面的方法是解决方案,因为它取决于您的SDK版本。还有一点要提到的是,我仅在选择图库图片时使用绝对路径,因为如果我将其用于相机,我的应用程序会崩溃。我是编程新手,花了2天时间才解决这个问题。希望它能帮助到某些人。

   public String getRealPathFromURI(Uri uri) {
        if(Build.VERSION.SDK_INT >= 19){
            String id = uri.getLastPathSegment().split(":")[1];
            final String[] imageColumns = {MediaStore.Images.Media.DATA };
            final String imageOrderBy = null;
            Uri tempUri = getUri();
            Cursor imageCursor = getContentResolver().query(tempUri, imageColumns,
                    MediaStore.Images.Media._ID + "="+id, null, imageOrderBy);
            if (imageCursor.moveToFirst()) {
                return imageCursor.getString(imageCursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }else{
                return null;
            }
        }else{
            String[] projection = { MediaStore.MediaColumns.DATA };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            if (cursor != null) {
                int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
                cursor.moveToFirst();
                return cursor.getString(column_index);
            } else
                return null;
        }
    }

所以我在onActivityResult方法中获取我的ExifInterface

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == GALLERY_IMAGE_REQUEST && resultCode == RESULT_OK && data != null) {
        try {
            exif = new ExifInterface(getRealPathFromURI(data.getData()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        showImage(data.getData());
    } else if (requestCode == CAMERA_IMAGE_REQUEST && resultCode == RESULT_OK) {
        try {
            exif = new ExifInterface(Uri.fromFile(getCameraFile()).getPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        showImage(Uri.fromFile(getCameraFile()));
    }
}

而我的显示图像方法看起来像这样

public void showImage(Uri uri) {
    if (uri != null) {
        try {

            Bitmap bitmap = scaleBitmapDown(MediaStore.Images.Media.getBitmap(getContentResolver(), uri), IMAGE_SIZE);

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

            bitmap = rotateBitmap(bitmap, orientation);



            if (whatPlayer.equals("Player1")) {
                mImagePlayer1.setImageBitmap(bitmap);

                bitmapPlayer1 = bitmap; //*save picture in static variable so other activity can use this
            }
            if (whatPlayer.equals("Player2")) {
                mImagePlayer2.setImageBitmap(bitmap);

                bitmapPlayer2 = bitmap;
            }

        } catch (IOException e) {
            Log.d(TAG, "Image picking failed because " + e.getMessage());
            Toast.makeText(this, R.string.image_picker_error, Toast.LENGTH_LONG).show();
        }
    } else {
        Log.d(TAG, "Image picker gave us a null image.");
        Toast.makeText(this, R.string.image_picker_error, Toast.LENGTH_LONG).show();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接