我與照片之乾坤大挪移-瞬間旋轉你的照片

前言

公司項目中有一個祖傳的自定義相機,以前發現它在橫拍的時候沒有對圖片進行旋轉,對其進行一番修改以後在測試機上面測試成功。上線後又發現只有部分手機正確旋轉了,通過一番努力以後,終於解決。而且在文末給出一個與網上千篇一概的照片旋轉不一樣的比較少見的快速旋轉方案java

寫下此文章記錄下問題的解決通過,一步步分析裏面的問題。android

只有部分手機正確旋轉

這種作法只有部分手機得到了正確旋轉的照片, 直接來看一下代碼裏面相機拍照回調方法onPictureTaken()的處理:緩存

public void onPictureTaken(byte[] data, Camera camera) {
                camera.stopPreview();
                //是否須要旋轉
                boolean isOrientation = false;
                //用戶是否橫屏拍攝,true爲橫屏 0度左旋 90不旋 180右旋 270度旋
                if (CameraActivity.this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE
                        && (takePhotoOrientation <= 90+45)&&(takePhotoOrientation>=90-45)) {//不旋轉
                    isOrientation = false;
                } else {
                    isOrientation = true;
                }
               //旋轉太過消耗時間,是儲存的40倍時間左右,考慮後去掉旋轉。
               //---反註釋start---
                if (isOrientation) {
                    //橫屏拍攝,須要旋轉90度
                   byte[] bytes = ImageUtils.rotatePic(data,takePhotoOrientation-90);
                    if (bytes != null) {
                        data = bytes;
                    }
                }
                //---反註釋end---
                mPicPath = String.format("%s%s%s", AppConfig.getCarTradeFileDir("Camera"),
                            System.currentTimeMillis() + ".jpg", tempFileSuffex);
                FileSaveUtils.saveFile(data, mPicPath, new FileSaveUtils.SaveListener() {
                    @Override
                    public void saveComplete() {
                        finish();
                    }
                });
    }
複製代碼

其實這裏我作的就只是把做者關於旋轉的代碼反註釋了。app

分析一下里面作了些什麼:框架

  1. takePhotoOrientation這個變量是由一個重力監聽器提供的,用它判斷當前手機是否處於橫拍狀態,橫拍須要旋轉;
  2. 而後就能夠看到原代碼做者很是貼心地在這裏註釋了旋轉很是耗時,告誡咱們不要去旋轉它,而後註釋掉了旋轉代碼,我又把他的註釋解開了;
  3. ImageUtils.rotatePic()旋轉圖片,裏面走的核心代碼就是網上一搜就是的旋轉方案:
public static byte[] rotatePic(byte[] data, int degree) {
        byte[] bytes = null;
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            options.inJustDecodeBounds = false;
            options.inPurgeable = true;
            options.inInputShareable = true;
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);// data是字節數據,將其解析成位圖
            bitmap = rotateBitmap(bitmap, degree);
            bytes = bitmap2Bytes(bitmap);
            if (bitmap != null && !bitmap.isRecycled()) {
                bitmap.recycle();
            }
        } catch (Error e) {
            e.printStackTrace();
        }
        return bytes;
    }
    /** * 網上一搜就有的旋轉代碼 */
    public static Bitmap rotateBitmap(Bitmap bitmap, int degree) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        Bitmap bm = null;
        try {
            bm = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        } catch (Exception e) {
            bm = bitmap;
        }
        return bm;
    }
    
    public static byte[] bitmap2Bytes(Bitmap bm) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        return baos.toByteArray();
    }
複製代碼
  1. 保存byte[]數據。這裏的照片保存方法內部實現也有問題,可是這裏不是咱們這篇文章的重點,因此忽略它先。

看到這裏,一些明眼的同窗可能已經看出爲何做者說旋轉用了40倍時間了,是的呢~ide

ImageUtils.rotatePic()裏面出了問題: 它先把byte[]解析成bitmap,而後旋轉又create了一個bitmap,再把bitmap轉換成byte[]來保存。這個過程有沒有40倍我沒有實踐過,不過能夠想象這耗時很長。留待優化。post

獲取EXIF

優化的事情先放下,先解決業務上的問題-旋轉。學習

那怎麼才能知道照片的方向呢?小case,難不倒玩攝影的我,相機在拍照的時候都會有保存提個叫EXIF的照片信息,它裏面存放着這張照片的諸多信息,例如光圈、焦距、快門時間等等,最重要的還有咱們須要的旋轉方向-orientation。有了它咱們不就能夠輕輕鬆鬆判斷方向了!? 測試

嘿嘿~我讀:

ExifInterface exifInterface = new ExifInterface(filepath);
    int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                            ExifInterface.ORIENTATION_NORMAL);
    ...
複製代碼

emmmm... 這,這裏咱們不能立刻用這個代碼來讀,若是你用了這個項目的代碼,去讀exif,你可能就要進入死衚衕裏面出不來了!優化

先下載一個能看exif的app,我用的是photo exif editor,它打開是這樣的:

我波波可愛嗎?

如圖exif一目瞭然,然而用photo exif editor去查看項目相機拍出來的圖,全部exif數據都是空的,因此若是你用代碼來讀,永遠都只會讀出你填寫的那個默認值。

再去探究一下exif丟失的緣由吧,開始我覺得是Carema Api的問題,但在onPictureTaken()裏面一回調就直接保存圖片,exif是存在的,明顯問題出在旋轉代碼裏面,能夠猜測是轉換成bitmap的時候丟失了exif信息,通過一番資料查驗,的確如此。

什麼是Exif,爲何會丟失Exif信息?

從Exif2.2規範裏面能夠找到關於壓縮圖像文件數據的描述,其中有這麼一幅圖:

它描述了這麼一個 事實:壓縮圖像文件由標記碼和壓縮圖像數據組成,其中標記碼數據中包括Exif。很顯然,bitmap做爲一個圖像類只包含了解壓出的圖像數據。

總結

照片轉換成bitmap會丟失exif,因此編輯圖片的時候須要把exif保存起來再修改後從新保存到照片中去。

想要深刻了解Exif的話這裏有一個2.2版本的規範文檔 Exif2.2傳送門

既然瞭解好了,讀Exif吧!

ExifInterface exifInterface = new ExifInterface(filepath);
    int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                            ExifInterface.ORIENTATION_NORMAL);
    ...
複製代碼

我讀~~~~↓

小米:照片方向與手機方向一致,沒有正確旋轉,Exif 90°;

VIVO:照片方向與手機方向不一致,正確旋轉,Exif 90°。

驚了!竟然和預想的不一致。。出現這樣的結果證實這又是手機系統內部出現的差別,安卓碎片化!

工做陷入了停頓。。只能搬出大牛魚哥~

一番詢問以後,魚哥show his code解決了個人問題~再次感謝魚哥

配置相機參數

Camera#Parameters這個類相信對於熟悉相機的同窗不會陌生,它可以用來獲取和配置相機參數:獲取預覽尺寸,設置閃光燈,對焦模式等等,都在這個經過這個類進行調節配置,咱們要的修正方法居然藏在這裏面!

public class IOrientationEventListener extends OrientationEventListener {
        
        public IOrientationEventListener(Context context) {
            super(context);
        }
        
        @Override
        public void onOrientationChanged(int orientation) {
            if (ORIENTATION_UNKNOWN == orientation) {
                return;
            }
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(defaultCameraId, info);
            orientation = (orientation + 45) / 90 * 90;
            int rotation = 0;
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                rotation = (info.orientation - orientation + 360) % 360;
            } else {
                rotation = (info.orientation + orientation) % 360;
            }
            
            if (null != mCamera) {
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setRotation(rotation);//關鍵代碼
                mCamera.setParameters(parameters);
            }
        }
    }
複製代碼

獲取實例以後分別在SurfaceHolder#Callback#surfaceCreated()和SurfaceHolder#Callback#surfaceDestroyed()調用一下實例的enable()和disable()便可統一拍照旋轉問題

因爲onOrientationChanged()回調頻繁,更加優化的作法能夠在按下拍攝按鈕以後纔對相機進行設置。

經過修改rotation的值能夠發現,只要rotation的值必定,全部手機的拍照方向都是統一的,意味着廠商對相機設置的方向默認值碎片化。

至此解決自定義相機拍照旋轉問題。


快速旋轉照片

經過上面的一大輪介紹,聰明的同窗可能已經猜測出這個快速旋轉的方案了--就是利用Exif。

由於照片自己是有設備生成的Exif的,裏面含有標記照片方向的orientation,圖片加載框架會經過讀取orientation來對照片進行顯示,也就是說,經過修改Exif裏orientation的值,便可以達到旋轉照片的效果!並且只須要操做一個小參數就能完成,瞬間完成,速度槓槓的!不再怕旋轉圖片慢啦!

看一下Glide的處理: 對Exif解析確認圖像方向

//默認的圖片頭解析器
    public final class DefaultImageHeaderParser implements ImageHeaderParser {
        ...
        private int getOrientation(Reader reader, ArrayPool byteArrayPool) throws IOException {
            ...
            int exifSegmentLength = moveToExifSegmentAndGetLength(reader);
            if (exifSegmentLength == -1) {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Failed to parse exif segment length, or exif segment not found");
                }
                return UNKNOWN_ORIENTATION;
            }

            byte[] exifData = byteArrayPool.get(exifSegmentLength, byte[].class);
            try {
                return parseExifSegment(reader, exifData, exifSegmentLength);
            } finally {
                byteArrayPool.put(exifData);
            }
            ...
        }
        ...
    }
複製代碼

逆時針方向旋轉的代碼:

ExifInterface exifInterface = new ExifInterface(currentPath);
                    // 獲取圖片的旋轉信息
                    int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                            ExifInterface.ORIENTATION_NORMAL);
                    LogUtils.v("exif orientation:" + orientation);
                    //根據當前圖片方向設置想要的圖片方向
                    switch (orientation) {
                        case ExifInterface.ORIENTATION_ROTATE_90://正常角度
                            exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL+"");
                            break;
                        case ExifInterface.ORIENTATION_ROTATE_180:
                            exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_90+"");
                            break;
                        case ExifInterface.ORIENTATION_ROTATE_270:
                            exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_180+"");
                            break;
                        case ExifInterface.ORIENTATION_NORMAL:
                            exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_ROTATE_270+"");
                            break;
                    }
                    exifInterface.saveAttributes();
複製代碼

使用注意: 這種方案只能用於具備Exif的照片,旋轉以後的照片顯示須要注意緩存問題,每次旋轉以後要更新緩存以正確顯示照片。

Glide在加載圖片的時候直接經過signature()方法new ObjectKey傳入文件的修改時間做緩存標記便可:

Glide.with(context)
     .load(filePath)
     .signature(new ObjectKey(file.lastModified())
     .into(imageView);
複製代碼

其餘框架的話請自行查找啦~

結尾

終於寫完了,要是以爲有用或者讓你拓展了知識就點個贊以示支持唄~

謝謝你的觀看和學習!下回再見~

相關文章
相關標籤/搜索