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

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

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

但问题是,某些设备上的图像每次旋转时。例如,在三星设备上它工作得很好,但在索尼Xperia上图像旋转90度,在东芝茁壮成长(平板电脑)上旋转180度。

286422 次浏览

最好试着以特定的方向照相。

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

为了获得最好的结果,在cameraview活动中给出景观方向。

大多数手机摄像头都是横拍的,也就是说如果你用竖拍,拍出来的照片会旋转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);
}

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

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

很容易检测图像方向和替换位图使用:

 /**
* 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 < / p >

这里是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;
}
// 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_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >


<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center">
<ImageView
android:id="@+id/imgFromCameraOrGallery"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/ic_launcher"/>
</LinearLayout>


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/btnCamera"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="Camera"/>
<Button
android:id="@+id/btnGallery"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:layout_height="wrap_content"
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;
}
}

如果有人在Android 4.4 (KitKat)上使用ExifInterface获取方向时遇到问题,这可能是因为从URI中获得了错误的路径。请参阅堆栈溢出问题从URI中获取真实路径,Android KitKat新的存储访问框架中提议者getPath的解决方案

通过结合杰森·罗宾逊回答费利克斯回答并填充缺失的部分,下面是这个问题的最终完整解决方案将在Android 4.1 Android  (优柔寡断的人), Android 4.4 (KitKat)和Android 5.0 (回答0)上测试后执行以下操作。

步骤

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

  2. 旋转图像到右方向如果它是旋转90度,180度或270度。

  3. 为了内存的目的,回收旋转的图像。

下面是代码部分:

用当前的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;
}

下面是前面提到的中的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

一句话解决方案:

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

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

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

毕加索是一个非常强大的库处理图像在你的应用程序包括: 复杂的图像转换与最小的内存使用

你可以像文档中的谷歌一样读取摄像头传感器的方向: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)
{
}

可悲的是,上面的@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 );

所选的答案使用回答这个问题和类似问题的最常用方法。但是,三星的前置和后置摄像头都不能使用。对于那些正在为三星和其他主要制造商寻找可以在前置和后置摄像头上工作的解决方案的人来说,nvhausid的回答非常棒:

https://stackoverflow.com/a/18915443/6080472 < a href = " https://stackoverflow.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);

完整代码在链接中。

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

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

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

我花了很多时间来寻找这个问题的解决方案。最终成功做到了。别忘了给杰森·罗宾逊的答案点赞,因为我的答案是基于他的。

所以第一件事,你应该知道,自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;
}
}

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

Jason Robinson的回答和Sami Eltamawy 回答都很棒。

只是为了完成该方法的改进,您应该使用compat ExifInterface。

com.android.support:exifinterface:${lastLibVersion}

你将能够实例化ExifInterface(pior API <24)与InputStream(从ContentResolver)而不是uri路径避免“文件未找到异常”

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

下面是Xamarin.Android版本:

来自@Jason Robinson的回答:

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的回答:

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

如果你用的是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

下面的代码与我一起工作,它从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
}
}

在没有使用ExifInterface的情况下得到了这个问题的答案。我们可以获得相机的旋转,无论是前置相机还是后置相机,无论你正在使用哪个,然后在创建位图时,我们可以使用Matrix.postRotate(程度)旋转位图

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

我希望这能有所帮助。

我根据@Jason Robinson的回答创建了一个Kotlin扩展函数,简化了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
)
}

我用了另一种方法。你所要做的就是检查宽度是否大于高度

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

使用滑翔图书馆对我有用。旋转是自动的。

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

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

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

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

通过使用滑动库,你可以得到图像与精确的方向,而不需要检查旋转

在芬兰湾的科特林

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'

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

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