在網(wǎng)頁應(yīng)用中上傳圖片的操作是非常廣泛的粱年,比如近期我公司上線的一個小游戲“專業(yè)審核夫妻相”:
它的主要功能就是上傳兩張人像综苔,通過算法進行分析對比酣倾,最后得出一個相似度的分?jǐn)?shù)煎饼,以驗證你們是天造地設(shè)還是顏值互補讹挎。
但是校赤,當(dāng)我們把上傳的圖片轉(zhuǎn)換成base64格式吆玖,發(fā)送給后臺時,會發(fā)現(xiàn)偶爾會出現(xiàn)問題马篮,有一些圖片本來是這樣的:
處理之后卻變成了這樣:
經(jīng)過測試發(fā)現(xiàn)沾乘,只有iOS手機豎著拍的照片才會出現(xiàn)這樣的問題,而iOS手機橫著拍的照片浑测、Android手機拍的照片以及通過屏幕截圖翅阵、網(wǎng)絡(luò)下載等途徑獲得的圖片都不會產(chǎn)生這個問題。
那么迁央,這到底是為什么呢掷匠?
在開發(fā)過程中,由于時間緊迫岖圈,未求甚解讹语,使用了github上的一個開源項目 lrz.js 來解決此問題,這個工具的主要用途是在盡量保證圖片質(zhì)量的前提下壓縮圖片的大小蜂科,但同時也附帶了圖片旋轉(zhuǎn)角度糾正的功能顽决。
通過閱讀 lrz.js 的源代碼短条,我發(fā)現(xiàn)它引入了一個叫做 exif.js 的庫來實現(xiàn)旋轉(zhuǎn)角度的糾正,它提供了js讀取圖像的原始數(shù)據(jù)的功能擴展才菠,例如:拍照方向茸时、相機設(shè)備型號、拍攝時間赋访、ISO 感光度可都、GPS 地理位置等數(shù)據(jù)。而拍照方向就是關(guān)鍵所在蚓耽!
exif.js 獲取圖像的拍照方向的代碼如下:
EXIF.getData(IMG_FILE, function () { // IMG_FILE為圖像數(shù)據(jù)
var orientation = EXIF.getTag(this, "Orientation");
console.log("Orientation:" + orientation); // 拍照方向
});
獲取拍照方向的結(jié)果為1-8的數(shù)字:
注意:對于上面的八種方向中汹粤,加了*的并不常見,因為它們代表的是鏡像方向田晚,如果不做任何的處理嘱兼,不管相機以任何角度拍攝,都無法出現(xiàn)鏡像的情況贤徒。
這個表格代表什么意義芹壕?我們來看第一行,值為1時接奈,右邊兩列的值分別為:Row #0 is Top踢涌,Column #0 is Left side,其實很好理解序宦,它表示照片的第一行位于頂端睁壁,而第一列位于左側(cè),那么這張照片自然就是以正常角度拍攝的互捌。
而這8種結(jié)果潘明,就是第一行與第一列所在的位置的8種組合。
那么秕噪,我們來測試一下iOS手機橫著拍的照片钳降,來看看它的拍照方向是什么呢?
結(jié)果是1腌巾,即以正常角度拍攝的遂填,其實也就是原圖啦~
那么,我們再測試一下iOS手機豎著拍的照片澈蝙,來看看它的拍照方向是什么呢吓坚?
原來是6!即第一行位于右側(cè)灯荧,第一列位于頂端礁击,其實相當(dāng)于將照片順時針旋轉(zhuǎn)了90度!
所以,實際上iOS手機豎著拍出的照片與橫著拍出的照片其本質(zhì)上是一樣的客税,只不過豎著拍出的照片被添加了一個順時針旋轉(zhuǎn)90°的拍照方向况褪,所以顯示的時候,就變成了上下邊窄左右邊寬的狀態(tài)更耻,其實也就是橫著拍的照片順時針旋轉(zhuǎn)90°而成的~
那么明白了這些测垛,文章開頭所說的照片旋轉(zhuǎn)bug的原因,也就很簡單啦~
其實就是當(dāng)我們在前端對圖片進行像素處理或者drawInRect等操作之后秧均,照片的Orientaion信息食侮,即為拍照方向信息被刪除了,所以iOS手機豎著拍的照片又回到了橫著的狀態(tài)目胡,看起來也就是逆時針旋轉(zhuǎn)了90°锯七!
那么如何糾正這個旋轉(zhuǎn)角度呢?
其實思路也很簡單:在處理圖片之前誉己,先讀取并保存圖片的拍照方向信息眉尸,然后在處理圖片之后,再根據(jù)拍照方向巨双,對圖片進行相應(yīng)的調(diào)整噪猾,lrz.js 中的代碼如下:
switch (orientation) {
case 3:
ctx.rotate(180 * Math.PI / 180);
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height);
break;
case 6:
ctx.rotate(90 * Math.PI / 180);
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width);
break;
case 8:
ctx.rotate(270 * Math.PI / 180);
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width);
break;
case 2:
ctx.translate(resize.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(img, 0, 0, resize.width, resize.height);
break;
case 4:
ctx.translate(resize.width, 0);
ctx.scale(-1, 1);
ctx.rotate(180 * Math.PI / 180);
ctx.drawImage(img, -resize.width, -resize.height, resize.width, resize.height);
break;
case 5:
ctx.translate(resize.width, 0);
ctx.scale(-1, 1);
ctx.rotate(90 * Math.PI / 180);
ctx.drawImage(img, 0, -resize.width, resize.height, resize.width);
break;
case 7:
ctx.translate(resize.width, 0);
ctx.scale(-1, 1);
ctx.rotate(270 * Math.PI / 180);
ctx.drawImage(img, -resize.height, 0, resize.height, resize.width);
break;
default:
ctx.drawImage(img, 0, 0, resize.width,resize.height);
}
其中,translate是平移變換筑累,scale(-1,1)是向左翻轉(zhuǎn)袱蜡,rotate是順時針旋轉(zhuǎn)。
舉例說明 case 2慢宗,當(dāng)圖片的拍照方向為2時坪蚁,即第一行位于頂端,而第一列位于右側(cè)镜沽,其實相當(dāng)于把照片進行了左右的翻轉(zhuǎn)敏晤。所以,這里對圖片的操作是淘邻,先向右平移等于圖片寬度的距離茵典,再向左翻轉(zhuǎn)湘换,這相當(dāng)于以圖片水平方向的對稱軸為軸進行了左右翻轉(zhuǎn)宾舅,然后再以(0,0)為起始點繪制原寬高的圖片彩倚,即完成了對拍照方向的糾正筹我。
最后
經(jīng)過一系列的測試,發(fā)現(xiàn)確實只有iOS手機的豎拍照片與橫拍照片是通過拍照方向來區(qū)別的帆离,Android手機無論豎拍還是橫拍的照片蔬蕊,拍照方向都為1,也就是說即使丟失了拍照方向這一信息哥谷,也不會影響到圖片的旋轉(zhuǎn)角度岸夯。而手機或電腦的屏幕截圖麻献、網(wǎng)絡(luò)上的圖片、通過PS制作的圖片等也是如此猜扮。