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

Posted

技术标签:

【中文标题】在某些 Gingerbread 设备上,使用 ACTION_IMAGE_CAPTURE 拍摄的图像对于 ExifInterface.TAG_ORIENTATION 始终返回 1【英文标题】:Images taken with ACTION_IMAGE_CAPTURE always returns 1 for ExifInterface.TAG_ORIENTATION on some Gingerbread devices 【发布时间】:2012-01-17 01:02:37 【问题描述】:

我在使用 ACTION_IMAGE_CAPTURE 活动时遇到了方向问题。我使用了TAG_ORIENTATION,以便相应地旋转图片。但是现在我们发现在一些较新的设备上这不起作用。事实上,它对所有方向都返回 1。

这是我们观察到的设备列表;

三星 Infuse 4G (2.3.3) 三星 Galaxy SII X (2.3.5) 索尼 Xperia Arc (2.3.3)

有趣的是,一旦这张图片是画廊,它就会正确显示,如果我选择它,TAG_ORIENTATION 就会正确填充。因此,OS 以某种方式正确填写了此信息,但在 ActivityResult 上却没有。

确定方向最可靠的方法是什么?另一个问题上有人建议比较高度和宽度,但是当得到这些时,它们会根据方向正确切换(另一个谜)

编辑:这似乎与另一个错误有关,其中操作系统复制了画廊中拍摄的图像(它只应该将图像保存在我们指定的 URL 中),问题是画廊中的这张图片有ORIENTATION 信息,而指定位置的则没有。

这是错误; http://code.google.com/p/android/issues/detail?id=19268

EDIT-2:我提交了一个新的 Android 错误。我很确定这是与上述错误相关的操作系统错误。 http://code.google.com/p/android/issues/detail?id=22822

【问题讨论】:

我不确定问题出在操作系统本身。相机和画廊应用程序由手机供应商开发,不保证没有错误或行为正确。这样,您会在某些设备上出错,而​​在其他设备上不会出错。 哦,我明白你的意思了,这是真的。无论如何,作为一种解决方案,我决定自己建立一个画廊(主要是通过复制粘贴 android 画廊) 摩托罗拉xoom也出现了同样的问题。 【参考方案1】:

好的,伙计们,看来这个 android 的 bug 暂时不会修复。虽然我找到了一种实现 ExifInformation 的方法,以便两个设备(具有正确 Exif 标签的设备和不正确的 exif 标签一起工作)..

所以问题出在某些(较新的)设备上,存在一个错误,它使拍摄的图片保存在您的应用文件夹中而没有正确的 exif 标签,而正确旋转的图像保存在 android 默认文件夹中(即使它不应该是)..

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

    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 方向,就像这个问题 Camera orientation issue in 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);

【讨论】:

很高兴听到这个消息,我花了很长时间才想出一个可行的解决方案。Android 操作系统需要修复这个错误 是的,没错。实际上,即使合法的错误(如您在此处确定的错误)记录在公共存储库中,也可以为开发人员节省大量的挫败感和时间。 @TolgaE "captureTime" 是 System.currenttimeinMillis() 对吗? 我无法在三星 Galaxy S4 上使用它 mediaCursor.getCount() 等于 0 它不起作用,我猜我没有得到当前时间戳。怎么办?【参考方案2】:

你也可以这样:

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

【讨论】:

【参考方案3】:

确实是一个有问题的错误!我不确定我是否喜欢建议的解决方法,所以这里有另一个 :)

关键是使用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 数据正确保存至关重要。 我在纵向拍摄照片时的方向 =6,在横向拍摄时获得 1.. 这是什么意思??? loader.loadInBackground();返回空【参考方案4】:

我最近了解到,如果您调整图像大小,它通常会丢失其 EXIF 信息。所以你想给新文件旧的 EXIF 信息。

Source.

【讨论】:

【参考方案5】:

我的解决方案。在 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();
    

【讨论】:

以上是关于在某些 Gingerbread 设备上,使用 ACTION_IMAGE_CAPTURE 拍摄的图像对于 ExifInterface.TAG_ORIENTATION 始终返回 1的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 2.3 (Gingerbread) 上增加堆大小?

如何使用适用于 Gingerbread 和 Froyo 的 ICS UI 创建 Android 应用程序?

HttpURLConnection 不解压缩 Gzip

在 Android 2.3 .X (Gingerbread) 及更低版本上修改 LayerDrawable 层

如何在 Android 中启用 A2DP 接收器功能?

在浏览器中测试姜饼Cordova