Android:从相册加载的位图在ImageView中旋转

154

在将媒体库中的图像加载到位图中时,一切都正常,但是使用手机竖直拍摄的照片会被旋转,导致即使在媒体库中它看起来是竖直的,但我得到的却是横向的图片。

为什么会这样,我应该如何正确地加载它?


我有真正的答案 https://dev59.com/7Yrda4cB1Zd3GeqPLFZH#32747566 - A.Sanchez.SD
20个回答

190

所以,举个例子...

首先,您需要创建一个ExifInterface:

ExifInterface exif = new ExifInterface(filename);

接下来,您可以获取图像的方向:

orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
这是方向值的意思: http://sylvana.net/jpegcrop/exif_orientation.html 因此,最重要的值是3、6和8。 例如,如果方向是ExifInterface.ORIENTATION_ROTATE_90(即6),则可以这样旋转图像:
Matrix matrix = new Matrix();
matrix.postRotate(90);
rotatedBitmap = Bitmap.createBitmap(sourceBitmap, 0, 0, sourceBitmap.getWidth(), sourceBitmap.getHeight(), matrix, true);

那只是一个快速的例子,我相信还有其他的方法来执行实际的旋转。但是您也可以在StackOverflow上找到这些方法。


5
以下是不同方向的旋转数值:3: 180,6: 90,8: 270。 - Display name
110
当可以使用命名常量时,不要使用魔法数字:ExifInterface.ORIENTATION_NORMAL、ExifInterface.ORIENTATION_ROTATE_90、ExifInterface.ORIENTATION_ROTATE_180、ExifInterface.ORIENTATION_ROTATE_270。 - d60402
31
在使用这种方法时要小心 OutOfMemoryError,因为它需要同时在内存中保存两个位图。请注意避免。 - Alex Bonel

70

通过参考这篇文章,我用以下代码解决了我的问题:

            Bitmap myBitmap = getBitmap(imgFile.getAbsolutePath());

            try {
                ExifInterface exif = new ExifInterface(imgFile.getAbsolutePath());
                int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
                Log.d("EXIF", "Exif: " + orientation);
                Matrix matrix = new Matrix();
                if (orientation == 6) {
                    matrix.postRotate(90);
                }
                else if (orientation == 3) {
                    matrix.postRotate(180);
                }
                else if (orientation == 8) {
                    matrix.postRotate(270);
                }
                myBitmap = Bitmap.createBitmap(myBitmap, 0, 0, myBitmap.getWidth(), myBitmap.getHeight(), matrix, true); // rotating bitmap
            }
            catch (Exception e) {

            }
            ImageView img = (ImageView) findViewById(R.id.imgTakingPic);
            img.setImageBitmap(myBitmap);

希望它能为某人节省时间!


1
编辑建议:方向6、3、8没有适当的命名常量吗?如果不需要旋转,我们不能跳过新位图吗? - Cee McSharpface
1
正如@d60402之前在评论中所说,您可以使用命名常量:ExifInterface.ORIENTATION_NORMAL、ExifInterface.ORIENTATION_ROTATE_90、ExifInterface.ORIENTATION_ROTATE_180、ExifInterface.ORIENTATION_ROTATE_270。 - Adrian
@Adrian 但是 "postRotate" 需要一个角度值,而 ExifInterface.ORIENTATION_NORMAL 是一个整数值。 - undefined

69

这是一个完整的解决方案(在Facebook SDK的Hackbook示例中找到)。它不需要访问文件本身,这是非常有用的,特别是当你从内容解析器加载图像时(例如,如果你的应用响应于共享照片意图)。

public static int getOrientation(Context context, Uri photoUri) {
    /* it's on the external media. */
    Cursor cursor = context.getContentResolver().query(photoUri,
            new String[] { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null);

    if (cursor.getCount() != 1) {
        return -1;
    }

    cursor.moveToFirst();
    return cursor.getInt(0);
}

然后,您可以按以下方式获得旋转的位图。该代码还将图像缩小(不幸的是效果很差),以MAX_IMAGE_DIMENSION为最大尺寸。否则,您可能会耗尽内存。

public static Bitmap getCorrectlyOrientedImage(Context context, Uri photoUri) throws IOException {
    InputStream is = context.getContentResolver().openInputStream(photoUri);
    BitmapFactory.Options dbo = new BitmapFactory.Options();
    dbo.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(is, null, dbo);
    is.close();

    int rotatedWidth, rotatedHeight;
    int orientation = getOrientation(context, photoUri);

    if (orientation == 90 || orientation == 270) {
        rotatedWidth = dbo.outHeight;
        rotatedHeight = dbo.outWidth;
    } else {
        rotatedWidth = dbo.outWidth;
        rotatedHeight = dbo.outHeight;
    }

    Bitmap srcBitmap;
    is = context.getContentResolver().openInputStream(photoUri);
    if (rotatedWidth > MAX_IMAGE_DIMENSION || rotatedHeight > MAX_IMAGE_DIMENSION) {
        float widthRatio = ((float) rotatedWidth) / ((float) MAX_IMAGE_DIMENSION);
        float heightRatio = ((float) rotatedHeight) / ((float) MAX_IMAGE_DIMENSION);
        float maxRatio = Math.max(widthRatio, heightRatio);

        // Create the bitmap from file
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = (int) maxRatio;
        srcBitmap = BitmapFactory.decodeStream(is, null, options);
    } else {
        srcBitmap = BitmapFactory.decodeStream(is);
    }
    is.close();

    /*
     * if the orientation is not 0 (or -1, which means we don't know), we
     * have to do a rotation.
     */
    if (orientation > 0) {
        Matrix matrix = new Matrix();
        matrix.postRotate(orientation);

        srcBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(),
                srcBitmap.getHeight(), matrix, true);
    }

    return srcBitmap;
}

1
MAX_IMAGE_DIMENSION是什么意思? - Sazzad Hissain Khan
3
这是你可以获得的图像的最大宽度或高度。例如,假设你只需要一张512x512的图片,如果你打开一张2400万像素的图片,预先进行子采样比打开整个图片再缩小要更有效率——否则很可能会耗尽所有内存。 - Timmmm
在我的程序中,我发现将位图变量定义为私有静态,并在函数中将其设置为null很有用。这样做会减少内存问题。 - Gunnar Bernstein
更聪明的做法是将MAX_IMAGE_DIMENDION替换为MAX_IMAGE_WIDTH和MAX_IMAGE_HEIGHT。 - fnc12
节省了我很多时间 :) 非常感谢。对于那些得到空游标的人,您可以尝试 ExifInterface exif = new ExifInterface(photoUri.getPath()); 然后使用 exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1) 来获取方向(例如 ORIENTATION_ROTATE_90ORIENTATION_ROTATE_180)。 - Atul
三星Galaxy S9中始终返回0。 - Miloš Černilovský

49

使用实用程序来完成繁重的工作。

9re创建了一个简单的实用工具,可处理EXIF数据和旋转图像以将其正确方向,轻松解决繁重的问题。

您可以在此处找到实用程序代码:https://gist.github.com/9re/1990019

只需下载它,将其添加到项目的 src 目录中,并使用 ExifUtil.rotateBitmap()获取正确的方向,如下所示:

String imagePath = photoFile.getAbsolutePath();             // photoFile is a File class.
Bitmap myBitmap  = BitmapFactory.decodeFile(imagePath);

Bitmap orientedBitmap = ExifUtil.rotateBitmap(imagePath, myBitmap);

2
没问题!在将位图传递给ExifUtil.rotateBitmap()之前,我只是将其调整为高清格式,以避免出现OutOfMemoryError,就像这样:Bitmap resized = Bitmap.createScaledBitmap(myBitmap, 720, 1280, true); photo = ExifUtil.rotateBitmap(picturePath, resized); - Phil
@Phil 很好的补充。我没有遇到过这种情况(我正在使用旧的、糟糕的安卓设备),但这真的很有用。 - Joshua Pinter
4
你是我的朋友,你是英雄! :) - Lendl Leyba
@klutch 你刚刚让我开心了。 :) 公平地说,9re编写了实用程序代码,所以他才是真正的英雄。 - Joshua Pinter
1
@SreekanthKarumanaghat 很好的问题!当我深入研究时,我可能知道为什么这是有意义的,但现在对我来说似乎也是多余的。可能是因为我花了太多时间在React Native上。 - Joshua Pinter
显示剩余2条评论

41

您是否查看过图片的EXIF数据?它可能会知道拍摄照片时相机的方向。


3
你说得对,那当然是解决方案。我会在另一个回答中发布我的代码示例,但我会将此答案标记为已接受,因为它让我找到了正确的方法。 - Manuel

11

Kotlin 代码:

if (file.exists()){
    val bitmap = BitmapFactory.decodeFile(file.absolutePath)

    val exif = ExifInterface(file.absoluteFile.toString())
    val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
    val matrix = Matrix()

    when(orientation){
        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
    }

    val rotatedBitmap = Bitmap.createBitmap(bitmap, 0,0 , bitmap.width, bitmap.height, matrix, true)
    bitmap.recycle()
    iv_capture.setImageBitmap(rotatedBitmap)
}

1
运行得非常好!我只是想知道为什么你要将结果存储在另一个位图中,而不是分配给同一个变量。这样做可以提高性能吗? - JCarlosR

8

这是因为图库正确显示旋转的图片,但 ImageView 却不行。请看这里:

                    myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(),optionss);
                    ExifInterface exif = new ExifInterface(selectedImagePath);
                    int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
                    int rotationInDegrees = exifToDegrees(rotation);
                    deg = rotationInDegrees;
                    Matrix matrix = new Matrix();
                    if (rotation != 0f) {
                        matrix.preRotate(rotationInDegrees);
                        myBitmap = Bitmap.createBitmap(myBitmap, 0, 0, myBitmap.getWidth(), myBitmap.getHeight(), matrix, true);
                    }

并且您需要这个:

private static int exifToDegrees(int exifOrientation) {        
    if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { return 90; } 
    else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {  return 180; } 
    else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {  return 270; }            
    return 0;    
} 

6

通过多次尝试,我终于让它工作了,感谢一篇我找不到的帖子 :-(

Exif似乎总是有效的,难点在于获取文件路径。我找到的代码根据API是否早于4.4或之后做出了区分。基本上,4.4+的图片URI包含“com.android.providers”。对于这种类型的URI,代码使用DocumentsContract获取图片ID,然后使用ContentResolver运行查询,而对于旧的SDK,则直接查询ContentResolver中的URI。

以下是代码(抱歉我不能给发帖人以荣誉):

/**
 * Handles pre V19 uri's
 * @param context
 * @param contentUri
 * @return
 */
public static String getPathForPreV19(Context context, Uri contentUri) {
    String res = null;

    String[] proj = { MediaStore.Images.Media.DATA };
    Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
    if(cursor.moveToFirst()){;
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        res = cursor.getString(column_index);
    }
    cursor.close();

    return res;
}

/**
 * Handles V19 and up uri's
 * @param context
 * @param contentUri
 * @return path
 */
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPathForV19AndUp(Context context, Uri contentUri) {
    String wholeID = DocumentsContract.getDocumentId(contentUri);

    // Split at colon, use second item in the array
    String id = wholeID.split(":")[1];
    String[] column = { MediaStore.Images.Media.DATA };

    // where id is equal to
    String sel = MediaStore.Images.Media._ID + "=?";
    Cursor cursor = context.getContentResolver().
            query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    column, sel, new String[]{ id }, null);

    String filePath = "";
    int columnIndex = cursor.getColumnIndex(column[0]);
    if (cursor.moveToFirst()) {
        filePath = cursor.getString(columnIndex);
    }

    cursor.close();
    return filePath;
}

public static String getRealPathFromURI(Context context,
        Uri contentUri) {
    String uriString = String.valueOf(contentUri);
    boolean goForKitKat= uriString.contains("com.android.providers");

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && goForKitKat) {
            Log.i("KIKAT","YES");
            return getPathForV19AndUp(context, contentUri);
        } else {

            return getPathForPreV19(context, contentUri);
        }
}

非常感谢您。在与光标和exif一起工作数小时后,这个解决方案拯救了我的一天。正如您所说,实际上exif具有真实可靠的数据,而不是光标返回。只需提供正确的路径即可运行。 - asozcan

3
你可以直接从SD卡读取路径并执行以下代码...它将在旋转后替换现有照片。
注意:大多数设备上Exif无法正常工作,它会提供错误的数据,因此最好在保存到任何你想要的角度之前硬编码旋转。你只需要更改postRotate中的角度值即可。
    String photopath = tempphoto.getPath().toString();
    Bitmap bmp = BitmapFactory.decodeFile(photopath);

    Matrix matrix = new Matrix();
    matrix.postRotate(90);
    bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), matrix, true);

    FileOutputStream fOut;
    try {
        fOut = new FileOutputStream(tempphoto);
        bmp.compress(Bitmap.CompressFormat.JPEG, 85, fOut);
        fOut.flush();
        fOut.close();

    } catch (FileNotFoundException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}

3
这是旋转操作,但我们不确定图像是否需要旋转。 - MSaudi

2
我改进了 Teo Inke 的答案。现在只有在必要时才会旋转图像。代码更容易阅读,运行速度也应该更快。
// Load Image
Bitmap bitmap = BitmapFactory.decodeFile(filePath);

// Rotate Image if Needed
try
{
    // Determine Orientation
    ExifInterface exif = new ExifInterface(filePath);
    int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);

    // Determine Rotation
    int rotation = 0;
    if      (orientation == 6)      rotation = 90;
    else if (orientation == 3)      rotation = 180;
    else if (orientation == 8)      rotation = 270;

    // Rotate Image if Necessary
    if (rotation != 0)
    {
        // Create Matrix
        Matrix matrix = new Matrix();
        matrix.postRotate(rotation);

        // Rotate Bitmap
        Bitmap rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 

        // Pretend none of this ever happened!
        bitmap.recycle();
        bitmap = rotated;
        rotated = null;
     }
}
catch (Exception e)
{
    // TODO: Log Error Messages Here
}

// TODO: Use Result Here
xxx.setBitmap(bitmap);

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