阅读 6998

当程序员遇到会写代码的产品经理......

1、缘起

事情是这样的,上周我接到了个需求,产品经理想在微信内嵌的H5里,静默获取图片的详细信息,比如拍照的时间、地理位置、设备指纹等。 一方面最近活儿太多影响我摸鱼,另一方面是这个需求没办法完全实现,因为有些图片信息就是没有,又懒得解释,于是我和产品经理说:

这个需求做不了。

原以为事情到这里就结束了,万万没想到,我遇到了一个会写代码的产品经理......

第二天早上,我的直属技术领导直接找到了我,和我说:

“xxx(产品经理)”把获取图片信息的Demo写出来了,这个需求可以做,之后就你来吧。

当时我的内心是这样的:

拿错了拿错了:

总之,最后这个需求还是落到我头上了

所以就有了这篇:获取图片的Exif数据

2、正文开始

像这种附带拍摄时间、GPS地理位置等信息的图片格式叫做Exif,英文全称为 Exchangeable image file format,即可交换图像文件格式 。

之前有一阵子传得沸沸扬扬的微信上传原图会泄漏隐私数据的事儿,其实就是这个Exif的锅。。。实际上这些Exif数据是由拍照设备产生的,并且用户可以自行修改和删除,换句话说,你可以随便修改Exif数据。比如这样:

随便在电脑上找一张图右键,点开详细信息,编辑选项数据

当然,微信上传原图确实会有风险,因为一不小心你就告诉了别人你是在哪里拍的这张照片,但是这个事不能全赖微信,理论上所有上传图片的程序都有泄漏隐私的风险。你可以像上面说的那样直接改数据,也可以点左下角的删除属性和个人信息来直接删除Exif数据

image.png

因为Exif数据是可以伪造和删除的,所以理论上只能作为参考,而不能当作确切信息。

那么,如何获取Exif数据呢?

github上有个4.1k star的开源项目exif-js:github.com/exif-js/exi…

用起来很丝滑,我写了个Demo来获取图片的Exif数据:

jsrun.net/kwTKp/edit

随便找张手机拍的照片上传看看:

隐私已打码

可以看到已经获取到了图片的Exif数据,并且通过百度地图API反查出了具体的地址。

但是对于非设备直接拍照的图片,或者图片经过了压缩处理,比如你从网上直接下载下来的图片等,可能就拿不到Exif数据。

作为一个有追求的前端,至少不能输给产品经理我们不仅要知其然,还要知其所以然。

所以,去看看exif-js的源码:

一千多行,在下告辞

呵,区区一千多行,话不多说,就是肝。


以下是源码分析。

分析比较长,可以不看

直接拉到最后给我点个赞 :)


首先是个匿名自执行函数,这样写的好处是能拥有独立作用域,既避免污染外界代码,也避免被外界代码污染。

接着定义并导出EXIF对象,这样就可以把EXIT暴露给外部,以便外部获取并使用EXIF对象。

这里检查了当前环境的模块化规范,exportscommonjs的规范,如果当前环境支持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、属性等使用说明可以参考这篇博客:code.ciaoca.com/javascript/…


产品经理还有一个获取设备指纹的需求,因篇幅所限,就下次再写吧,这里给出Demo:jsrun.net/2wTKp/edit,用的是fingerprintjs


3、总结

作为一个秃头程序员,身处和谐社会,我积极反思了一下自己,实际上不能直接和产品经理说“这个需求做不了”。如果真的做不了,应该给出具体的技术可行性分析,再得出做不了的结论,否则就应该说:“我去看下可行性”,然后去仔细了解下技术实现上的难点,再和产品经理沟通说明。

总而言之就是:

不能直接说“这个需求做不了”

以上都是废话,大家看看就好。

本文的重点其实还是中间那段获取Exif数据的分析。

闲扯两句

中秋快到啦,我的老家最近疫情又严重了,所以回不了家,只能中秋的时候一起云赏月云吃饼了~~希望疫情能早日结束吧。

提前祝大家中秋节快乐,团团圆圆,幸福美满!

文章分类
前端
文章标签