1、緣起
事情是這樣的,上周我接到了個需求,產品經理想在微信內嵌的H5里,靜默獲取圖片的詳細資訊,比如拍照的時間、地理位置、設備指紋等,
一方面最近活兒太多影響我摸魚,另一方面是這個需求沒辦法完全實作,因為有些圖片資訊就是沒有,又懶得解釋,于是我和產品經理說:
這個需求做不了,
原以為事情到這里就結束了,萬萬沒想到,我遇到了一個會寫代碼的產品經理…
第二天早上,我的直屬技術領導直接找到了我,和我說:
“xxx(產品經理)”把獲取圖片資訊的Demo寫出來了,這個需求可以做,之后就你來吧,
當時我的內心是這樣的:

拿錯了拿錯了:

總之,最后這個需求還是落到我頭上了,

所以就有了這篇:獲取圖片的Exif資料,
2、正文開始
像這種附帶拍攝時間、GPS地理位置等資訊的圖片格式叫做Exif,英文全稱為 Exchangeable image file format,即可交換影像檔案格式 ,
之前有一陣子傳得沸沸揚揚的微信上傳原圖會泄漏隱私資料的事兒,其實就是這個Exif的鍋,,,實際上這些Exif資料是由拍照設備產生的,并且用戶可以自行修改和洗掉,換句話說,你可以隨便修改Exif資料,比如這樣:

當然,微信上傳原圖確實會有風險,因為一不小心你就告訴了別人你是在哪里拍的這張照片,但是這個事不能全賴微信,理論上所有上傳圖片的程式都有泄漏隱私的風險,你可以像上面說的那樣直接改資料,也可以點左下角的洗掉屬性和個人資訊來直接洗掉Exif資料:

因為Exif資料是可以偽造和洗掉的,所以理論上只能作為參考,而不能當作確切資訊,
那么,如何獲取Exif資料呢?
github上有個4.1k star的開源專案exif-js:https://github.com/exif-js/exif-js
用起來很絲滑,我寫了個Demo來獲取圖片的Exif資料:
http://jsrun.net/kwTKp/edit
隨便找張手機拍的照片上傳看看:

可以看到已經獲取到了圖片的Exif資料,并且通過百度地圖API反查出了具體的地址,
但是對于非設備直接拍照的圖片,或者圖片經過了壓縮處理,比如你從網上直接下載下來的圖片等,可能就拿不到Exif資料,
作為一個有追求的前端,至少不能輸給產品經理我們不僅要知其然,還要知其所以然,
所以,去看看exif-js的原始碼:
一千多行,在下告辭
呵,區區一千多行,話不多說,就是肝,
以下是原始碼分析,
分析比較長,可以不看
直接拉到最后給我點個贊 😃

首先是個匿名自執行函式,這樣寫的好處是能擁有獨立作用域,既避免污染外界代碼,也避免被外界代碼污染,

接著定義并匯出EXIF物件,這樣就可以把EXIT暴露給外部,以便外部獲取并使用EXIF物件,
這里檢查了當前環境的模塊化規范,exports是commonjs的規范,如果當前環境支持exports,則使用exports方式匯出模塊,如果不支持,則將EXIF掛到全域物件下,
EXIF上定義了很多屬性和方法,比如我們demo里用到的getData方法:
// exif-js
EXIF.getData = function(img, callback) {
// ...省略部分非核心代碼
// 判斷img物件上是否有exifdata屬性值
if (!imageHasData(img)) {
// 本地上傳一般是沒有exifdata,需要呼叫getImageData去獲取
getImageData(img, callback);
} else {
// 如果有exifdata屬性值直接回呼
if (callback) {
callback.call(img);
}
}
})
可以看到入參是img物件和callback回呼函式,如果img物件上有exifdata屬性值,則直接呼叫callback,如果沒有,則需要調一個方法去獲取:getImageData,
因為圖片有可能是我們通過src屬性定義的,也有可能是本地上傳的,而src屬性值有可能是base64格式,也可能是blob或http/https等格式,所以getImageData會對不同情況的圖片物件進行處理,最后都處理為ArrayBuffer物件,
ArrayBuffer是一個位元組陣列,用來表示通用的、固定長度的原始二進制資料緩沖區,
ArrayBuffer是一個只能讀不能寫的物件,因此原始碼中使用DataView物件對ArrayBuffer進行寫操作,
上述的幾個物件實際上都是JS提供的處理二進制資料的物件,如果想進一步了解,推薦閱讀這篇文章:《聊聊JS的二進制家族:Blob、ArrayBuffer和Buffer》:https://zhuanlan.zhihu.com/p/97768916
我們繼續看原始碼:
拿到ArrayBuffer物件后,就可以對二進制資料進行決議了,
// 決議圖片的Exif資料
function findEXIFinJPEG(file) {
var dataView = new DataView(file);
// 根據檔案頭的前2個位元組判斷是否為圖片檔案
if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
return false; // not a valid jpeg
}
var offset = 2,
length = file.byteLength,
marker;
// 遍歷查找Exif資料資訊串
while (offset < length) {
// 這里是將0xFFE1分成兩個位元組判斷,先判斷第一個位元組是否等于0xFF
if (dataView.getUint8(offset) != 0xFF) {
return false; // not a valid marker, something is wrong
}
marker = dataView.getUint8(offset + 1);
// we could implement handling for other markers here,
// but we're only looking for 0xFFE1 for EXIF data
// 再判斷第二個位元組是否等于0xE1,也就是255
if (marker == 225) {
return readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2);
} else {
offset += 2 + dataView.getUint16(offset+2);
}
}
}
這里有個前置知識點:Exif資訊以0xFFE1作為開頭標記,所以原始碼中的這段while遍歷就是為了找到Exif資訊串的開頭,然后再呼叫readEXIFData方法,
readEXIFData就是對Exif資訊串做決議,即通過遍歷二進制串,匹配出屬性及對應的值,并放到一個物件tags中,決議完成后回傳這個物件,
除了Exif資料,原始碼中還決議了IPTC資料(作者,著作權,字幕,細節描述等)、XMP資料(Extensible Metadata Platform,Adobe公司提出的關于元資料的創建、處理和交換的一套標準),

擴展資料
exif-js的API、屬性等使用說明可以參考這篇博客:http://code.ciaoca.com/javascript/exif-js/
產品經理還有一個獲取設備指紋的需求,因篇幅所限,就下次再寫吧,這里給出Demo:http://jsrun.net/2wTKp/edit,用的是fingerprintjs,
3、總結
作為一個禿頭程式員,身處和諧社會,我積極反思了一下自己,實際上不能直接和產品經理說“這個需求做不了”,如果真的做不了,應該給出具體的技術可行性分析,再得出做不了的結論,否則就應該說:“我去看下可行性”,然后去仔細了解下技術實作上的難點,再和產品經理溝通說明,
總而言之就是:
不能直接說“這個需求做不了”,

以上都是廢話,大家看看就好,
本文的重點其實還是中間那段獲取Exif資料的分析,
閑扯兩句
中秋快到啦,我的老家最近疫情又嚴重了,所以回不了家,只能中秋的時候一起云賞月云吃餅了~~希望疫情能早日結束吧,
提前祝大家中秋節快樂,團團圓圓,幸福美滿!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301448.html
標籤:其他
上一篇:作業系統第一章知識點整理
下一篇:程式員常見單詞縮寫
