1. 程式人生 > >關於Android手機拍照預覽、剪裁介面出現照片九十度旋轉的問題

關於Android手機拍照預覽、剪裁介面出現照片九十度旋轉的問題

案場還原:

最近做的專案,測試機小米6X及本人的努比亞Z11測試拍照環節均正常,但在領導的三星手機及Oppo FindX上就出現了奇葩現象,拍照完預覽照片、剪裁照片出現了九十度的旋轉,如果這時候你用模擬器,比如Genymotion也能發現此問題,預覽及剪裁出現旋轉。

原因排查:

通過搜尋大量牆裡牆內資料,原因大概總結為以下幾點,自我理解,若有不對,還望指正:

  1. Android原生系統設定的拍照介面是Landscape也就是橫屏模式,因此即使你呼叫拍照的Activity是豎屏模式,拍照畫面也是豎屏模式,但到了預覽和剪裁就預設切換成了橫屏的畫面。
  2. 可能是某些手機廠商深度定製系統的結果,比如三星
    反其道而行,系統設定拍照旋轉,簡直是反人類,反社會!
  3. 有網友也反應可能與Activity啟動模式有關,建議設定當前啟動模式為Standard,但我不是這個原因,因為預設模式就是Standard。
    對於以上原因,可是嘗試在配置檔案中設定:
        <activity
            android:name=".activity.ProfileActivity"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:exported
="true" android:launchMode="standard" android:screenOrientation="portrait" />

But,這些對我來說都不起作用
4 .程式碼問題

解決:

強制將旋轉後的照片轉回來
最重要的也就是從ExifInterface這個多媒體相關類中讀取照片被旋轉的角度,然後使用Matrix強制轉回來。程式碼如下

 /**
     * 獲取原始圖片的角度(***解決三星手機拍照後圖片是橫著的問題***)
     * 讀取照片exif資訊中的旋轉角度
     *
     * @param
path 照片路徑 * @return角度 */
private int getPictureDegree(String path) { int degree = 0; /** * 方法一 */ // boolean hasRotation = false; // String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION}; // Cursor cursor = context.getContentResolver().query(cameraUri, orientationColumn, null, null, null); // Log.e(TAG, "cursor: " + cursor + " " ); // if (cursor.getCount() != 1) { // cursor.close(); // return -1; // } // if (cursor.moveToFirst()){ // degree = cursor.getInt(cursor.getColumnIndex(orientationColumn[0])); // hasRotation = true; // } // cursor.close(); // cursor = null; // Log.e(TAG, "getPictureDegree: " + degree ); /** * 方法二 */ try { ExifInterface exifInterface = new ExifInterface(path); int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); Log.e(TAG, "原圖被旋轉角度: ========== " + orientation ); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; } } catch (IOException e) { e.printStackTrace(); } /** * 方法三 */ // int degree = 0; // android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); // android.hardware.Camera.getCameraInfo(0, info); // degree = info.orientation; // Log.e(TAG, "CameraInfo: " + info.orientation ); return degree; }

然後轉回來:

        Matrix matrix = new Matrix();
        int degree = getPictureDegree(cameraImagePath);
        matrix.postRotate(degree); /*翻轉90度*/

這裡需要說明一下獲取照片被旋轉角度getPictureDegree這個方法,有三種獲取角度的方式

  • 使用資料庫Cursor讀取資訊context.getContentResolver().query(cameraUri, orientationColumn, null, null, null)但我讀不到String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};相關的資訊,報空指標異常,因此放棄。
  • 直接讀取硬體攝像頭CameraInfo相關資訊再進行旋轉,這個方法也有硬傷,在正常的測試機上預覽剪裁本來不會旋轉,結果你用你用攝像機豎屏拍照,得到的info.orientation是九十度,然後進行旋轉,本來正常的拍照也被顛倒了,因此同樣放棄該方式。
  • 使用ExifInterface讀取,關於該類的詳細說明請百度。國內多數開發者遇見此坑都推薦運用此方式讀取旋轉角度,但我遇見一個更奇葩的問題,通過 int orientation=exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)讀取到的角度永遠為0!!!!!!!!
    此問題搞得我焦頭爛額,欲哭無淚。經過一番Google,Stack Overflow上的開發者建議切換為第一種Cursor讀取資訊,但我剛才也講了,方法報空指標,有待商榷。

現在所有的問題都集中在一點,明明旋轉了的照片讀取到的旋轉角度卻是0度!!
好吧,一天兩天過去,終於找到了一種思路

還別說,之前我還真的是在讀取角度資訊之前做了圖片壓縮處理,然後才進行旋轉操作

    /**
     * 旋轉
     * @return
     */
    private Bitmap toturn() {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 簡單的壓縮
            options.inSampleSize = 4;           //把原圖按1/4的比例壓縮
            options.inJustDecodeBounds = false; // 壓縮完後便可以將inJustDecodeBounds設定為false
            // 把流轉化為Bitmap圖片
            Bitmap bitmap = BitmapFactory.decodeFile(cameraImagePath, options);
            Matrix matrix = new Matrix();
            int degree = getPictureDegree(cameraImagePath);
            matrix.postRotate(degree); /*翻轉90度*/
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();
            Bitmap returnBm  = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
            if (returnBm == null) {
                returnBm = bitmap;
            }
            if (bitmap != returnBm) {
                bitmap.recycle();
            }
             Log.e(TAG, "returnBm: " + returnBm );
            return returnBm;
    }

好吧,皇天不負有心人,先旋轉再壓縮之後,執行,終於解決了,在這也要感謝連結的博主。
有時候我們找bug,就跟買房一樣,大海撈針,也是看運氣。

附上我所有的拍照相關程式碼

    private Uri cameraUri;
    // 裁剪後圖片的寬(X)和高(Y),480 X 480的正方形。
    private static int output_X = 480;
    private static int output_Y = 480;
    private Uri imageUriCrop;   //剪裁後的圖片uri

    /**
     * 拍照
     */
    public void fromCamera(){
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File dir = new File(ConstantManager.cameraPath);//建立資料夾
            if (!dir.exists()) dir.mkdirs();
            Intent openCameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            cameraFile = new File(dir, String.valueOf(System.currentTimeMillis()) + ".jpg");//指定照片路徑
            if (!cameraFile.exists()) {
                try {
                    cameraFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            cameraImagePath = cameraFile.getPath();

            if (Build.VERSION.SDK_INT >= 24) {
                cameraUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", cameraFile);
            } else {
                cameraUri = Uri.fromFile(cameraFile);
            }
//            Log.e(TAG, "cameraUri: " + cameraUri );
            openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraUri);
            openCameraIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
            ((Activity) context).startActivityForResult(openCameraIntent, 0x008);
        } else {
            view.showMsg("沒有儲存卡");
        }
    }


    /**
     * 圖片剪裁
     *
     * @param system
     * @param in
     */
    public void crop(boolean system, Intent in,@Nullable  File cameraFile) {

        Intent intent = new Intent("com.android.camera.action.CROP");
        //設定資料來源
        Uri uri;
        if (system) {       //從系統相簿選擇
            if (in != null && in.getData() != null) {
                uri = createSingleCropUri("crop_image.jpg");   //剪裁後的相簿圖片名
                intent.setDataAndType(in.getData(), "image/*");
            } else {
                return;
            }
        } else {        //從相機拍照選擇
            if (Build.VERSION.SDK_INT >= 24) {
                //新增這一句表示對目標應用臨時授權該Uri所代表的檔案
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                /**
                 * Android 7.0要求設定資料來源使用ContentUri,輸出使用正常的Uri
                 */
                Uri contentUri = getImageContentUri(context,cameraFile);
                intent.setDataAndType(contentUri,"image/*");        //設定剪裁後的圖片資料來源
                uri = createSingleCropUri(cameraFile.getName());    //剪裁後的照片名

            } else {
                intent.setDataAndType(Uri.fromFile(cameraFile), "image/*");    //設定剪裁後的圖片資料來源
                uri = createSingleCropUri(cameraFile.getName());    //剪裁後的照片名
            }
        }
        Log.e(TAG, "crop: " + uri );
        intent.putExtra("crop", "true");// 設定裁剪
        intent.putExtra("aspectX", 1);// aspectX , aspectY :寬高的比例,為浮點數則不會固定裁剪框比例
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", output_X);// 定義輸出圖片大小,不定義則按裁剪框大小定義,但裁剪耗時會加長
        intent.putExtra("outputY", output_Y);
        intent.putExtra("return-data", true);
        intent.putExtra("scale", true);//保留比例
        intent.putExtra("return-data", false);//是否返回data
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);//輸出路徑,剪裁後的圖片
        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());//編碼格式
        intent.putExtra("noFaceDetection", true);
        ((Activity) context).startActivityForResult(intent, 0x009);
    }

    private Uri createSingleCropUri(String fileName) {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File dir = new File(ConstantManager.cameraPath);//建立資料夾
            if (!dir.exists()) dir.mkdirs();
            File cropFile = new File(dir, fileName);//指定裁剪路徑,多圖情況下裁剪路徑不能唯一,否則檔案會被覆蓋
            imageUriCrop = Uri.fromFile(cropFile);
        }
        return imageUriCrop;
    }


    /**
     * 安卓7.0裁剪根據檔案路徑獲取uri
     */
    public static Uri getImageContentUri(Context context, File imageFile) {
        String filePath = imageFile.getAbsolutePath();
        Cursor cursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Images.Media._ID},
                MediaStore.Images.Media.DATA + "=? ",
                new String[]{filePath}, null);

        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor
                    .getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/images/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (imageFile.exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, filePath);
                return context.getContentResolver().insert(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }

    /**
     * 獲取原始圖片的角度(***解決三星手機拍照後圖片是橫著的問題***)
     * 讀取照片exif資訊中的旋轉角度
     *
     * @param path 照片路徑
     * @return角度
     */
    private int getPictureDegree(String path) {

        int degree = 0;
        /**
         * 方法一
         */
//        boolean hasRotation = false;
//        String[] orientationColumn = {MediaStore.Images.Media.ORIENTATION};
//        Cursor cursor = context.getContentResolver().query(cameraUri, orientationColumn, null, null, null);
//        Log.e(TAG, "cursor: " + cursor +  "  "  );
//        if (cursor.getCount() != 1) {
//            cursor.close();
//            return -1;
//        }
//        if (cursor.moveToFirst()){
//            degree = cursor.getInt(cursor.getColumnIndex(orientationColumn[0]));
//            hasRotation = true;
//        }
//        cursor.close();
//        cursor = null;
//        Log.e(TAG, "getPictureDegree: " + degree );

        /**
         * 方法二
         */
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            Log.e(TAG, "原圖被旋轉角度: ========== " + orientation );
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        /**
         * 方法三
         */
//        int degree = 0;
//        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
//        android.hardware.Camera.getCameraInfo(0, info);
//        degree = info.orientation;
//        Log.e(TAG, "CameraInfo: " + info.orientation );

        return degree;
    }

    /**
     * 旋轉
     * @return
     */
    private Bitmap toturn() {
        Matrix matrix = new Matrix();
        int degree = getPictureDegree(cameraImagePath);
        matrix.postRotate(degree); /*翻轉90度*/

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        // 簡單的壓縮
        options.inSampleSize = 4;           //把原圖按1/4的比例壓縮
        options.inJustDecodeBounds = false; // 壓縮完後便可以將inJustDecodeBounds設定為false
        // 把流轉化為Bitmap圖片
        Bitmap bitmap = BitmapFactory.decodeFile(cameraImagePath, options);

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Bitmap returnBm  = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
//        if (returnBm == null) {
//            returnBm = bitmap;
//        }
//        if (bitmap != returnBm) {
//            bitmap.recycle();
//        }
//           Log.e(TAG, "returnBm: " + returnBm );
        return returnBm;
    }

    /**
     * 儲存旋轉後的Bitmap圖片在SD卡中
     * 如果沒有SD卡則存在手機中
     * @return 儲存成功時返回圖片的路徑,失敗時返回null
     */
    public String savePhotoToSD() {
        Bitmap mbitmap = toturn();
        FileOutputStream outStream = null;
        String fileName = String.valueOf(System.currentTimeMillis()) + ".jpg";
        File file = new File(ConstantManager.cameraPath );
        String filePath = file + "/" +fileName;
        // 判斷檔案是否已經存在,不存在則建立
        if ( !file.exists() ) {
            file.mkdirs();
        }
        try {
            outStream = new FileOutputStream(filePath);
            // 把資料寫入檔案,100表示不壓縮
            mbitmap.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
            Log.e(TAG, "旋轉處理後的圖片: " + filePath );
            return filePath;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (outStream != null) {
                    outStream.close();
                }
                if (mbitmap != null) {
                    mbitmap.recycle();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

Activity中回撥處理:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.e(TAG, "resultCode: " + resultCode);
        if (resultCode == this.RESULT_OK) {
            Log.e(TAG, "requestcode: " + requestCode);
            switch (requestCode) {
                case 0x007:
                    presenter.crop(true, data, null);
                    break;
                case 0x008:
                    File imageFile = new File(presenter.savePhotoToSD());
                    presenter.crop(false, data, imageFile);
                    break;
                case 0x009:
                    presenter.uploadImage(userId, entity);
                    break;
                default:
                    break;
            }
        }
    }

相關推薦

關於Android手機拍照剪裁介面出現照片旋轉的問題

案場還原: 最近做的專案,測試機小米6X及本人的努比亞Z11測試拍照環節均正常,但在領導的三星手機及Oppo FindX上就出現了奇葩現象,拍照完預覽照片、剪裁照片出現了九十度的旋轉,如果這時候你用模擬器,比如Genymotion也能發現此問題,預覽及剪裁出現

Android自定義相機實現拍照顯示上傳

自定義相機拍照並存放到本地,可以預覽,用okHttp上傳到伺服器 用法 1.點選登入進入到拍照頁面 2.拍照後進入到上傳介面,需要在Constant中修改BASE_URL為自己伺服器圖片上傳地

Android多媒體技術(一)Camera實時視訊採集拍照JPEG圖片方向的處理

Camera實時視訊採集預覽、拍照、JPEG圖片方向的處理                          作者:     蔣東國    時間:  2017年1月12日 星期四  

Android 相機1 之Camera1的最簡單的使用(拍照變焦特效)

Android兩個相機的API的個人總結 API1的方法較少、命名規則等都比較簡單,如果是針對目前市面上的手機,API1是足夠而且使用起來非常方便,尤其是它的setParameter方法,相較於API2的要自己去填key和value來說,它不僅很容易能找到相機

Android Camera2教程之開啟相機開啟實現PreviewCallback拍照

Android API 21新增了Camera2,這與之前的camera架構完全不同,使用起來也比較複雜,但是功能變得很強大。

android 手機拍照相簿選擇照片並顯示

關鍵程式碼: public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn1; private Uri imageUri;

手機+PC雙屏顯示:android端即時PC端修改的程式碼

前言 如何讓手機充當第二個顯示器,用來隨時預覽PC端的程式碼?前一陣子寫程式碼時,一直在琢磨這個問題。 因為辦公室電腦配置低下,且只配備一個17寸顯示器,每當反覆除錯預覽網頁時,都要儲存,重新整理。用過 brackets即使預覽功能,總是不太習慣。於是就想

根據介面獲取拍照方向以及圖片儲存旋轉角度

//根據當前介面顯示方向設定攝像頭預覽方向 public static int cameraDisplayOrientation(Activity activity, int cameraId) { android.hardware.Camera.CameraIn

android 仿微信多圖選擇器(帶照相功能)

實現了單選、多選 、拍照 、預覽 等功能;先上圖:      程式碼結構  下面不如正題: 一、新增依賴、許可權 1)新增以下依賴 dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])

Android手機拍照或從本地相簿選取圖片設定頭像。適配小米華為7.0

1,讓使用者通過選擇本地相簿之類的圖片庫中已有的影象,裁剪後作為頭像。 2,讓使用者啟動手機的相機拍照,拍完照片後裁剪,然後作為頭像。 程式碼如下 MainActivity.Java檔案: package portrait.bala.port

html5+exif.js+canvas實現手機照片上傳壓縮旋轉功能

html5+canvas進行移動端手機照片上傳時,發現ios手機上傳豎拍照片會逆時針旋轉90度,橫拍照片無此問題;Android手機沒這個問題。 因此解決這個問題的思路是:獲取到照片拍攝的方向角,對非橫拍的ios照片進行角度旋轉修正。 利用exif.js讀取照片的拍攝資訊

Vue 實現圖片裁剪並獲取被裁剪區域的base64(無元件)

前言     最近公司專案需要用到圖片裁剪技術,便著手寫了一個,可以說是用Vue實現的原生裁剪,畢竟也只是去操作dom,不過vue有個黑魔法,ref屬性,使用的方法和原生dom一模一樣但是更節省dom操作時的消耗 裁剪思路 這邊大致介紹一下裁剪圖片的思路

Android手機系統版本號IMEI手機廠商手機型號

獲取手機IMEI號 /** * 獲取手機IMEI號 * <p> * 需要動態許可權: android.permission.READ_PHONE_STATE */ public st

android使用webviewpng,pdf,doc,xls,txt,等檔案

最近有專案有一個需求,就是線上直接預覽pdf,doc,xls,txt等檔案,ios的webview比較強大,可以直接解析地址,然後預覽。但是android的webview就比較差強人意了。當然,開啟各種型別的檔案,我麼可以使用intent來做,但是這個明顯跟我們的需求不一致啊

小程式中圖片點選長按識別圖中二維碼的問題

通過自己的測試以及各類部落格資料的查詢,總結如下: 1.小程式中的圖片不能識別除小程式碼以外的二維碼 2.並且僅在 wx.previewImage 中支援長按識別 官方文件(wx.previewImage元件) html程式碼(這裡我就簡單的添加了一張圖片做測

基於“formData批量上傳的多種實現” 的多圖片上傳的多種實現 formData批量上傳的多種實現

  前言   圖片上傳是web專案常見的需求,我基於之前的部落格的程式碼(請戳:formData批量上傳的多種實現)裡的第三種方法實現多圖片的預覽、上傳,並且支援三種方式新增圖片到上傳列表:選擇圖片、複製貼上圖片、滑鼠拖拽圖片,同時支援從上傳列表中移除圖片(點選“X”號)      效果演示   選擇

vue中圖片轉換為base64上傳刪除

<template> <div class="com-upload-img"> <div class="img_group"> <div class="img_box" v-if="al

檢查是否是圖片限制上傳畫素圖片上傳時點選圖在新頁面檢視原圖上傳圖片

在做網站後臺管理時,涉及到了圖片的上傳,經過多次改動後,上傳圖片的一系列步驟如下 1.在點選瀏覽按鈕時彈出框只出現圖片格式的檔案,可在input type=file中通過accept=".jpg,.png,.jpeg" 限制。 增加限制前: 增加限制後: 但在上圖示紅處選擇所有檔案時又會

基於Bootstrap的多圖片(檔案也可以)上傳刪除縮放進度...顯示

一、引用js、css 二、Html程式碼 <input id="myFile" type="file" name="myFile" multiple class="file-loading

android手機拍照6.0,7.0問題

因前期專案執行沒有再7.0以上手機執行,最近一個同事用的華為8.0手機執行專案進行拍照,結果閃退,問題就來了,整的是焦頭爛額啊,先是用的FilePrivider,網上好多部落格都進行了講解,我在使用過程中FileProvider.getUriForFile發現返