为啥使用相机意图捕获的图像会在 Android 上的某些设备上旋转?

Posted

技术标签:

【中文标题】为啥使用相机意图捕获的图像会在 Android 上的某些设备上旋转?【英文标题】:Why does an image captured using camera intent gets rotated on some devices on Android?为什么使用相机意图捕获的图像会在 Android 上的某些设备上旋转? 【发布时间】:2012-12-13 12:20:02 【问题描述】:

我正在捕捉图像并将其设置为图像视图。

public void captureImage() 

    Intent intentCamera = new Intent("android.media.action.IMAGE_CAPTURE");
    File filePhoto = new File(Environment.getExternalStorageDirectory(), "Pic.jpg");
    imageUri = Uri.fromFile(filePhoto);
    MyApplicationGlobal.imageUri = imageUri.getPath();
    intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intentCamera, TAKE_PICTURE);


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

    if (resultCode == RESULT_OK && requestCode == TAKE_PICTURE) 

        if (intentFromCamera != null) 
            Bundle extras = intentFromCamera.getExtras();
            if (extras.containsKey("data")) 
                bitmap = (Bitmap) extras.get("data");
            
            else 
                bitmap = getBitmapFromUri();
            
        
        else 
            bitmap = getBitmapFromUri();
        
        // imageView.setImageBitmap(bitmap);
        imageView.setImageURI(imageUri);
    
    else 
    


public Bitmap getBitmapFromUri() 

    getContentResolver().notifyChange(imageUri, null);
    ContentResolver cr = getContentResolver();
    Bitmap bitmap;

    try 
        bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, imageUri);
        return bitmap;
    
    catch (Exception e) 
        e.printStackTrace();
        return null;
    

但问题是,某些设备上的图像每次旋转时都会出现。例如,在三星设备上效果很好,但在 Sony Xperia 上,图像会旋转 90 度,在Toshiba Thrive(平板电脑)上会旋转 180 度。

【问题讨论】:

在你的活动清单中试试这个 android:configChanges="orientation" android:screenOrientation="portrait" 我认为当您使用内部意图处理相机应用程序时,它会旋转图像。这取决于您如何握住设备来捕获图像。因此,您可以限制用户以特定方式拍摄图像,这意味着用户将始终通过纵向或横向手持设备来拍摄图像。之后,您可以将其更改为特定角度以获取所需的图像。或其他选项,制作您自己的相机应用程序。 我相信捕获意图总是会调出默认的相机应用程序,该应用程序在每个设备上都有特定的方向,因此 - 固定的照片方向。它不依赖于用户持有设备的方式或调用意图的活动的方向。 要避免存储权限,请参阅this 或this 答案或use Glide 任何人都找到了 ORIENTATION_UNDEFINED 的任何解决方案,因为在某些设备(Android 8 模拟器)上,图像会旋转,而在某些设备(Android 9 模拟器)上,它没有相同的方向值。如何知道图片是否需要旋转? 【参考方案1】:

大多数手机摄像头都是横向的,这意味着如果您拍摄纵向照片,生成的照片将旋转 90 度。在这种情况下,相机软件应使用查看照片的方向填充Exif 数据。

请注意,以下解决方案取决于填充 Exif 数据的相机软件/设备制造商,因此它在大多数情况下都可以工作,但它不是 100% 可靠的解决方案。

ExifInterface ei = new ExifInterface(photoPath);
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                                     ExifInterface.ORIENTATION_UNDEFINED);

Bitmap rotatedBitmap = null;
switch(orientation) 

    case ExifInterface.ORIENTATION_ROTATE_90:
        rotatedBitmap = rotateImage(bitmap, 90);
        break;

    case ExifInterface.ORIENTATION_ROTATE_180:
        rotatedBitmap = rotateImage(bitmap, 180);
        break;

    case ExifInterface.ORIENTATION_ROTATE_270:
        rotatedBitmap = rotateImage(bitmap, 270);
        break;

    case ExifInterface.ORIENTATION_NORMAL:
    default:
        rotatedBitmap = bitmap;

这里是rotateImage 方法:

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

【讨论】:

从@JasonRobinson 代码中,我学习了如何获得实际方向,并通过与these code 结合,我成功地管理了方向。 这段代码是针对已经写入磁盘的图像,对吧?对于即将写入磁盘的位图,我使用此方法没有得到任何结果。 总是返回 0 值。请告诉如何获得实际方向。 总是得到 0,知道为什么吗? 注意:使用 androidx.exifinterface.media.ExifInterface 而不是 android.media.ExifInterface【参考方案2】:

通过将 Jason Robinson 的 answer 与 Felix 的 answer 结合起来并填充缺失的部分,这是最终的完整解决方案在 Android Android 4.1 (Jelly Bean)、Android 4.4 (KitKat) 和 Android 5.0 ( >棒棒糖)。

步骤

    如果图像大于 1024x1024,则缩小图像。

    仅在图像旋转 90、180 或 270 度时将图像旋转到正确的方向

    回收旋转的图像以供记忆。

这里是代码部分:

使用当前的Context 和要修复的图像URI 调用以下方法

/**
 * This method is responsible for solving the rotation issue if exist. Also scale the images to
 * 1024x1024 resolution
 *
 * @param context       The current context
 * @param selectedImage The Image URI
 * @return Bitmap image results
 * @throws IOException
 */
public static Bitmap handleSamplingAndRotationBitmap(Context context, Uri selectedImage)
        throws IOException 
    int MAX_HEIGHT = 1024;
    int MAX_WIDTH = 1024;

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
    BitmapFactory.decodeStream(imageStream, null, options);
    imageStream.close();

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    imageStream = context.getContentResolver().openInputStream(selectedImage);
    Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);

    img = rotateImageIfRequired(context, img, selectedImage);
    return img;

这是前面提到的source中的CalculateInSampleSize方法:

/**
  * Calculate an inSampleSize for use in a @link BitmapFactory.Options object when decoding
  * bitmaps using the decode* methods from @link BitmapFactory. This implementation calculates
  * the closest inSampleSize that will result in the final decoded bitmap having a width and
  * height equal to or larger than the requested width and height. This implementation does not
  * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
  * results in a larger bitmap which isn't as useful for caching purposes.
  *
  * @param options   An options object with out* params already populated (run through a decode*
  *                  method with inJustDecodeBounds==true
  * @param reqWidth  The requested width of the resulting bitmap
  * @param reqHeight The requested height of the resulting bitmap
  * @return The value to be used for inSampleSize
  */
private static int calculateInSampleSize(BitmapFactory.Options options,
                                         int reqWidth, int reqHeight) 
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) 

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
        // with both dimensions larger than or equal to the requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

        // This offers some additional logic in case the image has a strange
        // aspect ratio. For example, a panorama may have a much larger
        // width than height. In these cases the total pixels might still
        // end up being too large to fit comfortably in memory, so we should
        // be more aggressive with sample down the image (=larger inSampleSize).

        final float totalPixels = width * height;

        // Anything more than 2x the requested pixels we'll sample down further
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;

        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) 
            inSampleSize++;
        
    
    return inSampleSize;

然后是检查当前图像方向以确定旋转角度的方法

 /**
 * Rotate an image if required.
 *
 * @param img           The image bitmap
 * @param selectedImage Image URI
 * @return The resulted Bitmap after manipulation
 */
private static Bitmap rotateImageIfRequired(Context context, Bitmap img, Uri selectedImage) throws IOException 

InputStream input = context.getContentResolver().openInputStream(selectedImage);
ExifInterface ei;
if (Build.VERSION.SDK_INT > 23)
    ei = new ExifInterface(input);
else
    ei = new ExifInterface(selectedImage.getPath());

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

    switch (orientation) 
        case ExifInterface.ORIENTATION_ROTATE_90:
            return rotateImage(img, 90);
        case ExifInterface.ORIENTATION_ROTATE_180:
            return rotateImage(img, 180);
        case ExifInterface.ORIENTATION_ROTATE_270:
            return rotateImage(img, 270);
        default:
            return img;
    

最后是旋转方法本身

private static Bitmap rotateImage(Bitmap img, int degree) 
    Matrix matrix = new Matrix();
    matrix.postRotate(degree);
    Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
    img.recycle();
    return rotatedImg;

-不要忘记为那些回答他们的努力的人以及提出这个有用问题的 Shirish Herwade 投票。

【讨论】:

它对我来说很完美。谢谢 rotateImageIfRequired() 方法效果很好.. 谢谢!! 对我不起作用。有时我的手机会显示纵向照片,有时会显示风景照片,但检测到的方向始终是 0 度。 @Makalele 拍照和通过 WhatsApp 附加时是否也会出现此问题? 科特林:gist.github.com/fada21/feadb8d2feb925a821b6eb233692d31d【参考方案3】:

很容易检测图像方向并使用以下方法替换位图:

 /**
 * Rotate an image if required.
 * @param img
 * @param selectedImage
 * @return
 */
private static Bitmap rotateImageIfRequired(Context context,Bitmap img, Uri selectedImage) 

    // Detect rotation
    int rotation = getRotation(context, selectedImage);
    if (rotation != 0) 
        Matrix matrix = new Matrix();
        matrix.postRotate(rotation);
        Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
        img.recycle();
        return rotatedImg;
    
    else
        return img;
    


/**
 * Get the rotation of the last image added.
 * @param context
 * @param selectedImage
 * @return
 */
private static int getRotation(Context context,Uri selectedImage) 

    int rotation = 0;
    ContentResolver content = context.getContentResolver();

    Cursor mediaCursor = content.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                                       new String[]  "orientation", "date_added" ,
                                       null, null, "date_added desc");

    if (mediaCursor != null && mediaCursor.getCount() != 0) 
        while(mediaCursor.moveToNext())
            rotation = mediaCursor.getInt(0);
            break;
        
    
    mediaCursor.close();
    return rotation;

为避免因大图像而无法记忆,我建议您使用以下方法重新缩放图像:

private static final int MAX_HEIGHT = 1024;
private static final int MAX_WIDTH = 1024;
public static Bitmap decodeSampledBitmap(Context context, Uri selectedImage)
    throws IOException 

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
    BitmapFactory.decodeStream(imageStream, null, options);
    imageStream.close();

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    imageStream = context.getContentResolver().openInputStream(selectedImage);
    Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);

    img = rotateImageIfRequired(img, selectedImage);
    return img;

由于 Android 操作系统问题,无法使用 ExifInterface 获取方向: https://code.google.com/p/android/issues/detail?id=19268

这里是calculateInSampleSize

/**
 * Calculate an inSampleSize for use in a @link BitmapFactory.Options object when decoding
 * bitmaps using the decode* methods from @link BitmapFactory. This implementation calculates
 * the closest inSampleSize that will result in the final decoded bitmap having a width and
 * height equal to or larger than the requested width and height. This implementation does not
 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
 * results in a larger bitmap which isn't as useful for caching purposes.
 *
 * @param options   An options object with out* params already populated (run through a decode*
 *                  method with inJustDecodeBounds==true
 * @param reqWidth  The requested width of the resulting bitmap
 * @param reqHeight The requested height of the resulting bitmap
 * @return The value to be used for inSampleSize
 */
public static int calculateInSampleSize(BitmapFactory.Options options,
                                        int reqWidth, int reqHeight) 

    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) 

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
        // with both dimensions larger than or equal to the requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

        // This offers some additional logic in case the image has a strange
        // aspect ratio. For example, a panorama may have a much larger
        // width than height. In these cases the total pixels might still
        // end up being too large to fit comfortably in memory, so we should
        // be more aggressive with sample down the image (=larger inSampleSize).

        final float totalPixels = width * height;

        // Anything more than 2x the requested pixels we'll sample down further
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;

        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) 
            inSampleSize++;
        
    
    return inSampleSize;

【讨论】:

这里的calculateInSampleSize方法是什么 @madhukotagiri 这里有一个 calculateInSampleSize 的实现示例:gist.github.com/anonymous/b7ea25fc2bbc54e43616 谢谢你,你绝对是那个人!我只是想知道如果只是偶尔执行该操作,调整大小会有多大用处。 Uri selectedImage 参数未在 getRotation(...) 方法中使用。我们需要如何使用它?谢谢。 参数 'selectedImage' 似乎没有在任何地方使用。有什么理由去吗?【参考方案4】:

一线解决方案:

Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);

或者

Picasso.with(context).load("file:" + photoPath).into(imageView);

这将自动检测旋转并将图像放置在正确的方向

Picasso 是一个非常强大的库,用于在您的应用中处理图像,包括: 使用最少的内存进行复杂的图像转换。

【讨论】:

有趣的解决方案 它只是将图像加载到视图中,它不会为您提供位图或您可以操作或上传到服务器的文件。 它的显示图像按原样点击。它没有按要求旋转。 @Flawyte 您可以通过将文件加载到目标而不是返回裁剪/调整大小位图的回调视图来做到这一点:Picasso.with(this).load(cropUriToLoad.resize(1080, 810).centerInside ().into(target); where target = new Target() Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) 不适合我..毕加索也存在这个问题【参考方案5】:
// Try this way,hope this will help you to solve your problem...

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:orientation="vertical" >

    <LinearLayout
        android:layout_
        android:layout_
        android:layout_weight="1"
        android:gravity="center">
        <ImageView
            android:id="@+id/imgFromCameraOrGallery"
            android:layout_
            android:layout_
            android:adjustViewBounds="true"
            android:src="@drawable/ic_launcher"/>
    </LinearLayout>

    <LinearLayout
        android:layout_
        android:layout_>
        <Button
            android:id="@+id/btnCamera"
            android:layout_
            android:layout_weight="1"
            android:layout_
            android:text="Camera"/>
        <Button
            android:id="@+id/btnGallery"
            android:layout_
            android:layout_weight="1"
            android:layout_marginLeft="5dp"
            android:layout_
            android:text="Gallery"/>

    </LinearLayout>
</LinearLayout>

MainActivity.java

    public class MainActivity extends Activity 

    private ImageView imgFromCameraOrGallery;
    private Button btnCamera;
    private Button btnGallery;

    private String imgPath;
    final private int PICK_IMAGE = 1;
    final private int CAPTURE_IMAGE = 2;
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imgFromCameraOrGallery = (ImageView) findViewById(R.id.imgFromCameraOrGallery);
        btnCamera = (Button) findViewById(R.id.btnCamera);
        btnGallery = (Button) findViewById(R.id.btnGallery);

        btnCamera.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, setImageUri());
                startActivityForResult(intent, CAPTURE_IMAGE);
            
        );

        btnGallery.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                startActivityForResult(Intent.createChooser(intent, ""), PICK_IMAGE);
            
        );

    

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) 
            if (requestCode == CAPTURE_IMAGE) 
                setCapturedImage(getImagePath());
             else if (requestCode == PICK_IMAGE) 
                imgFromCameraOrGallery.setImageBitmap(BitmapFactory.decodeFile(getAbsolutePath(data.getData())));
            
        

    

    private String getRightAngleImage(String photoPath) 

        try 
            ExifInterface ei = new ExifInterface(photoPath);
            int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            int degree = 0;

            switch (orientation) 
                case ExifInterface.ORIENTATION_NORMAL:
                    degree = 0;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
                case ExifInterface.ORIENTATION_UNDEFINED:
                    degree = 0;
                    break;
                default:
                    degree = 90;
            

            return rotateImage(degree,photoPath);

         catch (Exception e) 
            e.printStackTrace();
        

        return photoPath;
    

    private String rotateImage(int degree, String imagePath)

        if(degree<=0)
            return imagePath;
        
        try
            Bitmap b= BitmapFactory.decodeFile(imagePath);

            Matrix matrix = new Matrix();
            if(b.getWidth()>b.getHeight())
                matrix.setRotate(degree);
                b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(),
                        matrix, true);
            

            FileOutputStream fOut = new FileOutputStream(imagePath);
            String imageName = imagePath.substring(imagePath.lastIndexOf("/") + 1);
            String imageType = imageName.substring(imageName.lastIndexOf(".") + 1);

            FileOutputStream out = new FileOutputStream(imagePath);
            if (imageType.equalsIgnoreCase("png")) 
                b.compress(Bitmap.CompressFormat.PNG, 100, out);
            else if (imageType.equalsIgnoreCase("jpeg")|| imageType.equalsIgnoreCase("jpg")) 
                b.compress(Bitmap.CompressFormat.JPEG, 100, out);
            
            fOut.flush();
            fOut.close();

            b.recycle();
        catch (Exception e)
            e.printStackTrace();
        
        return imagePath;
    

    private void setCapturedImage(final String imagePath)
        new AsyncTask<Void,Void,String>()
            @Override
            protected String doInBackground(Void... params) 
                try 
                    return getRightAngleImage(imagePath);
                catch (Throwable e)
                    e.printStackTrace();
                
                return imagePath;
            

            @Override
            protected void onPostExecute(String imagePath) 
                super.onPostExecute(imagePath);
                imgFromCameraOrGallery.setImageBitmap(decodeFile(imagePath));
            
        .execute();
    

    public Bitmap decodeFile(String path) 
        try 
            // Decode deal_image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(path, o);
            // The new size we want to scale to
            final int REQUIRED_SIZE = 1024;

            // Find the correct scale value. It should be the power of 2.
            int scale = 1;
            while (o.outWidth / scale / 2 >= REQUIRED_SIZE && o.outHeight / scale / 2 >= REQUIRED_SIZE)
                scale *= 2;
            // Decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeFile(path, o2);
         catch (Throwable e) 
            e.printStackTrace();
        
        return null;
    

    public String getAbsolutePath(Uri uri) 
        if(Build.VERSION.SDK_INT >= 19)
            String id = "";
            if(uri.getLastPathSegment().split(":").length > 1)
                id = uri.getLastPathSegment().split(":")[1];
            else if(uri.getLastPathSegment().split(":").length > 0)
                id = uri.getLastPathSegment().split(":")[0];
            if(id.length() > 0)
                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
                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;
        

    

    private Uri getUri() 
        String state = Environment.getExternalStorageState();
        if(!state.equalsIgnoreCase(Environment.MEDIA_MOUNTED))
            return MediaStore.Images.Media.INTERNAL_CONTENT_URI;

        return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    

    public Uri setImageUri() 
        Uri imgUri;
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) 
            File file = new File(Environment.getExternalStorageDirectory() + "/DCIM/",getString(R.string.app_name) + Calendar.getInstance().getTimeInMillis() + ".png");
            imgUri = Uri.fromFile(file);
            imgPath = file.getAbsolutePath();
        else 
            File file = new File(getFilesDir() ,getString(R.string.app_name) + Calendar.getInstance().getTimeInMillis()+ ".png");
            imgUri = Uri.fromFile(file);
            this.imgPath = file.getAbsolutePath();
        
        return imgUri;
    

    public String getImagePath() 
        return imgPath;
    

【讨论】:

完美解决方案 Haresh Bhai【参考方案6】:

您可以像 Google 在文档中指出的那样读取相机传感器的方向:https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html

SENSOR_ORIENTATION

Added in API level 21
Key<Integer> SENSOR_ORIENTATION
Clockwise angle through which the output image needs to be rotated to be upright on the device screen in its native orientation.

Also defines the direction of rolling shutter readout, which is from top to bottom in the sensor's coordinate system.

Units: Degrees of clockwise rotation; always a multiple of 90

Range of valid values:
0, 90, 180, 270

This key is available on all devices.

示例代码:

CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
int orientation = 0;
try 
    String cameraId = manager.getCameraIdList()[0];
    CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
    orientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

catch (Exception e)


【讨论】:

这应该被标记为答案。旋转是由相机方向引起的,所以这太棒了! 你怎么知道是哪个相机拍的,手机是否还和用户拍照时的方向一致?【参考方案7】:

我花了很多时间寻找解决方案。并最终设法做到了这一点。不要忘记支持@Jason Robinson 的回答,因为我的答案是基于他的。

所以首先,你应该知道,从 Android 7.0 开始,我们必须使用 FileProvider 和名为 ContentUri 的东西,否则你会在尝试调用 Intent 时遇到烦人的错误。这是示例代码:

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, getUriFromPath(context, "[Your path to save image]"));
startActivityForResult(intent, CAPTURE_IMAGE_RESULT);

方法getUriFromPath(Context, String)根据Android用户版本创建FileUri (file://...)ContentUri (content://...)就可以了:

public Uri getUriFromPath(Context context, String destination) 
    File file =  new File(destination);

    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) 
        return FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
     else 
        return Uri.fromFile(file);
    

onActivityResult 之后,您可以捕捉到uri,其中图像由相机保存,但现在您必须检测相机旋转,这里我们将使用修改后的@Jason Robinson 答案:

首先我们需要基于Uri创建ExifInterface

@Nullable
public ExifInterface getExifInterface(Context context, Uri uri) 
    try 
        String path = uri.toString();
        if (path.startsWith("file://")) 
            return new ExifInterface(path);
        
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) 
            if (path.startsWith("content://")) 
                InputStream inputStream = context.getContentResolver().openInputStream(uri);
                return new ExifInterface(inputStream);
            
        
    
    catch (IOException e) 
        e.printStackTrace();
    
    return null;

上面的代码可以简化,但我想展示一切。所以从FileUri我们可以基于String path创建ExifInterface,但是从ContentUri我们不能,Android不支持。

在这种情况下,我们必须使用基于InputStream 的其他构造函数。记住这个构造函数默认不可用,你必须添加额外的库:

compile "com.android.support:exifinterface:XX.X.X"

现在我们可以使用getExifInterface 方法来获取我们的角度:

public float getExifAngle(Context context, Uri uri) 
    try 
        ExifInterface exifInterface = getExifInterface(context, uri);
        if(exifInterface == null) 
            return -1f;
        

        int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                ExifInterface.ORIENTATION_UNDEFINED);

        switch (orientation) 
            case ExifInterface.ORIENTATION_ROTATE_90:
                return 90f;
            case ExifInterface.ORIENTATION_ROTATE_180:
                return 180f;
            case ExifInterface.ORIENTATION_ROTATE_270:
                return 270f;
            case ExifInterface.ORIENTATION_NORMAL:
                return 0f;
            case ExifInterface.ORIENTATION_UNDEFINED:
                return -1f;
            default:
                return -1f;
        
    
    catch (Exception e) 
        e.printStackTrace();
        return -1f;
    

现在您有了正确旋转图像的角度 :)。

【讨论】:

implementation 'androidx.exifinterface:exifinterface:X.X.X' 这是为那些使用 androidx 的人准备的。感谢您的发帖【参考方案8】:

Jason Robinson 的 answer 和 Sami Eltamawy answer 非常出色。

只是完成方法的一个改进,你应该使用兼容的ExifInterface。

com.android.support:exifinterface:$lastLibVersion

您将能够使用 InputStream(来自 ContentResolver)而不是 uri 路径来实例化 ExifInterface(pior API

https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html

【讨论】:

【参考方案9】:

通常建议使用ExifInterface 解决问题,就像@Jason Robinson 建议的那样。如果这种方法不起作用,您可以尝试查找最新拍摄的图像的Orientation...

private int getImageOrientation()
    final String[] imageColumns =  MediaStore.Images.Media._ID, MediaStore.Images.ImageColumns.ORIENTATION ;
    final String imageOrderBy = MediaStore.Images.Media._ID+" DESC";
    Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            imageColumns, null, null, imageOrderBy);

    if(cursor.moveToFirst())
        int orientation = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.ImageColumns.ORIENTATION));
        cursor.close();
        return orientation;
     else 
        return 0;
    

【讨论】:

我认为这段代码只检测到旋转发生的程度。现在我可以做到这一点,但无法完成下一个任务,即旋转图像。 你是对的,但是你没有要求在这个线程中旋转,所以让我们保持干净;)这就是为什么我把我对你的旋转问题的答案放到你的另一个线程中......希望有帮助,它对我有用:***.com/questions/14123809/… MediaStore.Images.ImageColumns.ORIENTATION 仅适用于 Android 10 及更高版本。【参考方案10】:

遗憾的是,@jason-robinson 上面的回答对我不起作用。

虽然旋转功能完美:

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

我必须执行以下操作才能获得方向,因为 Exif 方向始终为 0

protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    super.onActivityResult(requestCode,resultCode,data);
    if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && data != null) 
            Uri selectedImage = data.getData();
            String[] orientationColumn = MediaStore.Images.Media.ORIENTATION;
            Cursor cur = managedQuery(imageUri, orientationColumn, null, null, null);
            int orientation = -1;
            if (cur != null && cur.moveToFirst()) 
                    orientation = cur.getInt(cur.getColumnIndex(orientationColumn[0]));
            
            InputStream imageStream = getContentResolver().openInputStream(selectedImage);
            Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
            switch(orientation) 
                    case 90:
                            bitmap = rotateImage(chosen_image_bitmap, 90);
                            break;
                    case 180:
                            bitmap = rotateImage(chosen_image_bitmap, 180);
                            break;
                    case 270:
                            bitmap = rotateImage(chosen_image_bitmap, 270);
                            break;
                    default:
                            break;
            
            imageView.setImageBitmap(bitmap );

【讨论】:

alwasys 0,三星 7【参考方案11】:

我用不同的方法解决了这个问题。您所要做的就是检查宽度是否大于高度

Matrix rotationMatrix = new Matrix();
if(finalBitmap.getWidth() >= finalBitmap.getHeight())
    rotationMatrix.setRotate(-90);
else
    rotationMatrix.setRotate(0);


Bitmap rotatedBitmap = Bitmap.createBitmap(finalBitmap,0,0,finalBitmap.getWidth(),finalBitmap.getHeight(),rotationMatrix,true);

【讨论】:

就我而言,我需要:rotationMatrix.setRotate(90);【参考方案12】:

所选答案使用对此问题和类似问题的最常用回答方法。但是,它不适用于三星的前后摄像头。对于那些寻找适用于三星和其他主要制造商的前置和后置摄像头解决方案的人来说,nvhausid 的这个答案太棒了:

https://***.com/a/18915443/6080472

对于那些不想点击的人来说,相关的魔法是使用 CameraInfo 而不是依赖 EXIF。

Bitmap realImage = BitmapFactory.decodeByteArray(data, 0, data.length);
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(mCurrentCameraId, info);
Bitmap bitmap = rotate(realImage, info.orientation);

链接中的完整代码。

【讨论】:

不,不同角度的错误旋转 (smasung s7)。我的意思当然是画廊【参考方案13】:

我创建了一个 Kotlin 扩展函数,它根据 @Jason Robinson 的回答简化了 Kotlin 开发人员的操作。希望对你有帮助。

fun Bitmap.fixRotation(uri: Uri): Bitmap? 

    val ei = ExifInterface(uri.path)

    val orientation: Int = ei.getAttributeInt(
        ExifInterface.TAG_ORIENTATION,
        ExifInterface.ORIENTATION_UNDEFINED
    )

    return when (orientation) 
        ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage( 90f)
        ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage( 180f)
        ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage( 270f)
        ExifInterface.ORIENTATION_NORMAL -> this
        else -> this
    


fun Bitmap.rotateImage(angle: Float): Bitmap? 
    val matrix = Matrix()
    matrix.postRotate(angle)
    return Bitmap.createBitmap(
        this, 0, 0, width, height,
        matrix, true
    )

【讨论】:

棒极了,但与所有解决方案(作为扩展或功能)存在相同的问题 - 不适用于 Android 10。【参考方案14】:

如果有人在Android 4.4 (KitKat) 上遇到ExifInterface 获取方向的问题,这可能是因为从 URI 获取的 path 错误。在 Stack Overflow 问题中查看 Propoer getPath 的解决方案 Get real path from URI, Android KitKat new storage access framework

【讨论】:

这一条评论正是我所需要的。老兄,非常感谢。【参考方案15】:

在下面的链接中找到这个解决方案是最好的 https://www.samieltamawy.com/how-to-fix-the-camera-intent-rotated-image-in-android/

【讨论】:

【参考方案16】:

最好尝试以特定方向拍照。

android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"

为获得最佳效果,请在 cameraview 活动中设置横向。

【讨论】:

对不起,它不起作用。实际上在选项卡上,每次执行完 onActivityResult 后,都会奇怪地调用 onCreate。 对不起,问题是这样的【参考方案17】:

这可能不言而喻,但请始终记住,您可以在服务器上处理其中一些图像处理问题。我使用了类似这个线程中包含的响应来处理图像的立即显示。但是,我的应用程序需要将图像存储在服务器上(如果您希望图像在用户切换手机时保持不变,这可能是一个常见的要求)。

关于这个主题的许多线程中包含的解决方案没有讨论 EXIF 数据缺乏持久性,这些数据无法在位图的图像压缩中幸存下来,这意味着每次服务器加载时都需要旋转图像它。或者,您可以将 EXIF 方向数据发送到您的服务器,然后在需要时旋转那里的图像。

在服务器上创建永久解决方案对我来说更容易,因为我不必担心 Android 的秘密文件路径。

【讨论】:

您可以在图像捕获时将其旋转一次并以这种方式保存,这样就不需要再次旋转了吗? 是的,你可以,这实际上是我最终实施的过程。我在从 Android 手机上的图像中获取文件路径时遇到了麻烦,这允许我这样做。这是有帮助的答案:***.com/a/36714242/5443056 1000 多个可以在手机上旋转图像的应用程序,而不是在服务器上旋转它们。每种情况都不同,但我会逃避在您的服务器上工作的解决方案。您总是希望尽可能将计算外包给客户手机。【参考方案18】:

这个问题最简单的解决方案:

captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
                   characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION));

我将图像保存为 jpg 格式。

【讨论】:

什么是captureBuilder【参考方案19】:

下面的代码与我一起工作,它从 fileUri 中获取位图,并在需要时进行旋转修复:

    private fun getCapturedImage(selectedPhotoUri: Uri): Bitmap 
        val bitmap = when 
            Build.VERSION.SDK_INT < 28 -> MediaStore.Images.Media.getBitmap(
                this.contentResolver,
                selectedPhotoUri
            )
            else -> 
                val source = ImageDecoder.createSource(this.contentResolver, selectedPhotoUri)
                ImageDecoder.decodeBitmap(source)
            
        

        // If the image is rotated, fix it
        return when (ExifInterface(contentResolver.run  openInputStream(selectedPhotoUri) ).getAttributeInt(
            ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) 
            ExifInterface.ORIENTATION_ROTATE_90 ->
                Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply 
                    postRotate(90F) , true)
            ExifInterface.ORIENTATION_ROTATE_180 ->
                Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply 
                    postRotate(180F) , true)
            ExifInterface.ORIENTATION_ROTATE_270 ->
                Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, Matrix().apply 
                    postRotate(270F) , true)
            else -> bitmap
         
    

【讨论】:

【参考方案20】:

这里的解决方案基于上述解决方案,但只需要上下文和图像文件作为输入

public static Bitmap rectifyImage(Context context,File imageFile)
    Bitmap originalBitmap= BitmapFactory.decodeFile(imageFile.getAbsolutePath());
    try
        Uri uri=Uri.fromFile(imageFile);
        InputStream input = context.getContentResolver().openInputStream(uri);
        ExifInterface ei;
        
        if (Build.VERSION.SDK_INT > 23)
            ei = new ExifInterface(input);
        else
            ei = new ExifInterface(uri.getPath());

        int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        switch (orientation) 
            case ExifInterface.ORIENTATION_ROTATE_90:
                return rotateImage(originalBitmap, 90);
            case ExifInterface.ORIENTATION_ROTATE_180:
                return rotateImage(originalBitmap, 180);
            case ExifInterface.ORIENTATION_ROTATE_270:
                return rotateImage(originalBitmap, 270);
            default:
                return originalBitmap;
        
    catch (Exception e)
        return originalBitmap;
    


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

【讨论】:

【参考方案21】:

这里是Xamarin.Android版本:

来自@Jason Robinson 的answer:

Bitmap rotate(Bitmap bitmap, int angle)

    var matrix = new Matrix();
    matrix.PostRotate(angle);

    return Bitmap.CreateBitmap(bitmap, 0, 0, bitmap.Width, bitmap.Height, matrix, true);


Bitmap rotateIfRequired(Bitmap bitmap, string imagePath)

    var ei = new ExifInterface(imagePath);
    var orientation = ei.GetAttributeInt(ExifInterface.TagOrientation, (int)Android.Media.Orientation.Undefined);

    switch (orientation)
    
        case (int)Android.Media.Orientation.Rotate90: return rotate(bitmap, 90);
        case (int)Android.Media.Orientation.Rotate180: return rotate(bitmap, 180);
        case (int)Android.Media.Orientation.Rotate270: return rotate(bitmap, 270);
        default: return bitmap;
    

然后calculateInSampleSize方法:

int calculateInSampleSize(BitmapFactory.Options options, int reqW, int reqH)

    float h = options.OutHeight;
    float w = options.OutWidth;
    var inSampleSize = 1;

    if (h > reqH || w > reqW)
    
        if (reqH == 0) inSampleSize = (int)Math.Floor(w / reqW);
        else if (reqW == 0) inSampleSize = (int)Math.Floor(h / reqH);
        else
        
            var hRatio = (int)Math.Floor(h / reqH);
            var wRatio = (int)Math.Floor(w / reqW);
            inSampleSize = false ? Math.Max(hRatio, wRatio) : Math.Min(hRatio, wRatio);
        
    

    return inSampleSize;

来自@Sami Eltamawy 的answer:

Bitmap handleSamplingAndRotationBitmap(string imagePath)

    var maxHeight = 1024;
    var maxWidth = 1024;

    var options = new BitmapFactory.Options();
    options.InJustDecodeBounds = true;
    BitmapFactory.DecodeFile(imagePath, options);

    options.InSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);

    options.InJustDecodeBounds = false;

    var bitmap = BitmapFactory.DecodeFile(imagePath, options);

    bitmap = rotateIfRequired(bitmap, imagePath);

    return bitmap;

【讨论】:

嗨,我的 xamarin 有轮换问题。安卓应用。问题是当使用后置摄像头时,图像被保存在 90 度右侧。但是使用前置摄像头可以节省90度的左侧。因此,使用此代码的旋转解决方案仅适用于前置摄像头。你遇到过这个问题吗? 您好,我想这是因为前置摄像头默认翻转。如果你把它关掉,这两个相机的代码结果是相等的吗? 嗨,你能帮我解决这个问题吗?我在这里问了一个问题。但仍然在黑暗中。 ***.com/questions/64278491/…【参考方案22】:

如果你使用 Fresco,你可以使用这个 -

final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri)
.setRotationOptions(RotationOptions.autoRotate())
.build();

mSimpleDraweeView.setController(
Fresco.newDraweeControllerBuilder()
    .setImageRequest(imageRequest)
    .build());

这会根据 Exif 数据自动旋转图像。

来源:https://frescolib.org/docs/rotation.html

【讨论】:

【参考方案23】:

在不使用 ExifInterface 的情况下得到了这个问题的答案。我们可以获取相机的旋转,无论您使用的是前置摄像头还是后置摄像头,然后在创建位图时我们可以使用 Matrix.postRotate(degree)

旋转位图
public int getRotationDegree() 
    int degree = 0;

    for (int i = 0; i < Camera.getNumberOfCameras(); i++) 
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(i, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) 
            degree = info.orientation;

            return degree;
        
    

    return degree;

计算旋转后,您可以像下面这样旋转位图:

 Matrix matrix = new Matrix();

 matrix.postRotate(getRotationDegree());

 Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true);

这里 bm 应该是您的位图。

如果您想知道前置摄像头的旋转,只需将上面的 Camera.CameraInfo.CAMERA_FACING_BACK 更改为 Camera.CameraInfo.CAMERA_FACING_FRONT

我希望这会有所帮助。

【讨论】:

糟糕的答案,但我不小心投了赞成票。此代码假定您图库中的每张图片都是使用 您的 相机制作的。事实并非如此 @Zun 问的问题是图像捕捉而不是从图库中挑选图像。【参考方案24】:

Glide library 的使用对我有用。自动处理旋转。

Bitmap bitmap = Glide.with(myContext).asBitmap().load(imageFilePath).submit(SIZE_ORIGINAL, SIZE_ORIGINAL).get();

然后,您将该位图保存为 JPEG 格式的文件。

如果您只想加载到 ImageView 而不是保存到文件中:

Glide.with(myContext).load(imageFilePath).into(myImageView)

【讨论】:

【参考方案25】:

通过使用 glide 库,您可以获得具有精确方向的图像,而无需检查旋转

在科特林中

CoroutineScope(Dispatchers.IO).launch 
         var bitmap = Glide.with(context).asBitmap().load(imagePathOrUriOrLink)
                /*.apply(
                    RequestOptions()
                        .override(MAXIMUM_IMAGE_RESOLUTION)
                )*/ //uncomment it if you want original image
                /*.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)*/ //uncomment it you want to not cache image
                .submit().get()//this is synchronous approach  

使用这个依赖

api 'com.github.bumptech.glide:glide:4.12.0'
kapt 'com.github.bumptech.glide:compiler:4.12.0'

【讨论】:

【参考方案26】:

有一个更简单的命令可以修复这个错误。

只需在 yourImageView.setBitmap(bitmap); 之后添加即可this yourImageView.setRotation(90);

这解决了我的问题。希望对您有所帮助!

【讨论】:

正如 OP 所说,有些设备不旋转图像,有些旋转 90 度,有些旋转 180 度,..等等。所以在某些情况下总是将它旋转 90 度是不正确的。

以上是关于为啥使用相机意图捕获的图像会在 Android 上的某些设备上旋转?的主要内容,如果未能解决你的问题,请参考以下文章

用于捕获图像和视频的 Android 意图?

Android:使用相机意图时应用程序在 onActivityResult 上崩溃

Android从相机捕获图像返回空数据[重复]

在 android 中使用 MediaStore.ACTION_IMAGE_CAPTURE 意图捕获图像

在 Android 中使用相同的 Intent 捕获图像和视频

如何在 Android 相机上使用意图添加功能裁剪图像?