修正手機拍照相片方向的方法

我們可以透過來喚起手機的拍照功能,然後拍照之後再將影像進行處理,最後上傳。

然而,有一個問題就是,手機直拿之後拍照之後的影像往往是橫倒的,該怎麼修正?

首先我們先看怎麼喚起相機:

preview的用意是用來預覽拍照的結果,而喚起相機的則是依賴 input type=file capture=camera  來實現。當相機拍照結束後, if(file) 的結果是true,所以會執行 readAsDataURL(file) ,大家的問題可能是沒聽過FileReader這個東西。FileReader是比較新一點的Web API,詳細的說明可以看Mozilla的網站

讀檔需要時間,讀檔完成後會觸發load事件,所以我們接著看 reader.addEventListener() 。這函式裡面先執行的是什麼? 是 getOrientation() 。preview的影像載入動作是在getOrientation的callback裡面,等它載入才會觸發preview的load事件。

那我們來看重頭戲 getOrientation() 。

這裡的程式碼可以參考stackoverflow的強者。照片的方向代碼是這樣:

1是我們預期的結果,3、6、8是被旋轉過後,2、4、5、7是鏡像加上旋轉的結果,所以,既然有代碼,很明顯的我們知道,如果是8就逆時針轉90度,如果是3就轉180度,如果是6就順時針轉90度,是4就轉180度再鏡射。

getOrientation() 最先執行的東西是 reader.readAsArrayBuffer(file) ,讀取完成後才會觸發load事件,並由 reader.onload 來處理。在事件處理的部份,DateView是Javascript的標準API,用來處理ArrayBuffer,reader用 readAsArrayBuffer() 來讀file,這裡用DataView接收並處理。0xFFD8是JPEG圖檔EXIF格式的Start of Image (SOI):

SOI Marker APP1 Marker APP1 Data Other Marker
FFD8 FFE1 SSSS 457869660000 TTTT…… FFXX SSSS DDDD……

沒有這東西就表示這不是一個合法的JPEG圖檔,此時用callback(-2)來處理。

再來用一個while迴圈來處理資料,每次offset都加2格,去找FFE1,然後再去找45786966,如果不對就用callback(-1)去處理,表示格式有問題。0x4949那一段是TIFF Header,用來表示byte align,所以我們將這個值與0x4949做邏輯相等的判斷,結果為真,就是little endian。little endian跟big endian影響到資料讀取方向,讀錯就全錯了。

讀取到tag的地方,0x0112是表示orientation,這就是我們要找的值了,將這個值轉換為整數回傳給callback就可以結束了。

我們回到前一個函數,看一下 resetOrientation(preview.src,orientation) 。 resetOrientation() 的參考來源是這裡

在這個函數,我們首先將到的資料丟給img變數,在載入後,我們要做兩件事,第一調整大小, 第二調整方向。首先計算適當的解析度,用ratio()來算出正確結果:

ratio()的算法很簡單漂亮,當然不是我想出來的,我那麼笨,資料來源是這裡。原理是,原圖的尺寸是x跟y,不管x比較小還是y比較小,重點是放在縮小的比例,舉例,原圖是1920×1080,我們現在想縮到640×480,則640/1920=0.33,480/1080=0.44,如果我們以0.44做為縮放比例,則1920*0.44=853.33就會超過640了,所以要以小的為主。

算出來以後,我們用 [5,6,7,8].indexOf(srcOrientation) > -1來判斷srcOrientation是否落在5、6、7、8其中之一。如果是,那表示我們需要把影像從橫的改值的,所以建立的canvas寬與高要對調。

下面這個是精髓,就是利用switch來判斷原本方向,用transform來轉換:

transform這個函數的參數意義是這樣:

以2號來看,我們要做鏡射,所以水平縮放是-1,垂直縮放維持是1,水平移動是寬度大小,垂直移動是0。

3號是旋轉180度,所以我們水平與垂直都做鏡射就回來了。

然後我們用 ctx.drawImage(img, 0, 0, img.width, img.height); 來把影像畫到canvas去,就可以看到調整後的影像了,參數中的width與height是將img的尺寸改變後再繪圖。

 

Leave a Reply

你的電子郵件位址並不會被公開。 必要欄位標記為 *