JS对图片进行base64压缩以及图片的EXIF-Orientation信息

来源:互联网 发布:直线制职能制矩阵制 编辑:程序博客网 时间:2024/04/30 22:53

最近在调试一个bug,项目刚接手几天,刚开始还一脸懵逼的,后来对这个bug的解决思路渐渐轻车熟路,好了,废话不说。。。

项目中使用了一个叫 localResizeIMG 前端控件进行图片的选择,加载,base64压缩,再而上传到后台服务器。

在处理bug的过程中,学习了两个知识: 1.js对图片进行base64压缩; 2.关于图片中的EXIF中的Orientation(方向)信息。

下面,分别用于记录所学到的两个知识:

1.js对图片进行base64压缩

网上查阅了一些资料,基本的思路如下:

1.收到传入的文件后,创建一个 canvas 对象 和 blob 对象(其实也就是对应的文件引用,所以能被 img src直接引用)
2.创建 img 对象,标记允许跨域处理,src 设置为 blob,接下来就是开始压缩了。
3.开始压缩,获取图片旋转的方向(EXIF中的Orientation信息),计算用户设置的尺寸,设置canvas,然后压缩成base64

以下摘取localResizeIMG的一段代码进行剖析

function Lrz (file, opts) {    var that = this;    if (!file) throw new Error('没有收到图片,可能的解决方案:https://github.com/think2011/localResizeIMG/issues/7');    opts = opts || {};    that.defaults = {        width    : null,        height   : null,        fieldName: 'file',        quality  : 0.7    };    that.file = file;    for (var p in opts) {  //配置参数        if (!opts.hasOwnProperty(p)) continue;        that.defaults[p] = opts[p];    }    return this.init(); //初始化}Lrz.prototype.init = function () {    var that         = this,        file         = that.file,        fileIsString = typeof file === 'string',        fileIsBase64 = /^data:/.test(file),        img          = new Image(),//创建img对象        canvas       = document.createElement('canvas'),//创建canvas,用于后续的图片加载,旋转压缩        blob         = fileIsString ? file : URL.createObjectURL(file);//创建bolb用于存放图片二进制数据    that.img    = img;    that.blob   = blob;    that.canvas = canvas;    if (fileIsString) {        that.fileName = fileIsBase64 ? 'base64.jpg' : (file.split('/').pop());    } else {        that.fileName = file.name;    }    if (!document.createElement('canvas').getContext) {        throw new Error('浏览器不支持canvas');    }    return new Promise(function (resolve, reject) {        img.onerror = function () {            var err = new Error('加载图片文件失败');            reject(err);            throw err;        };        img.onload = function () {            that._getBase64()                .then(function (base64) {                    if (base64.length < 10) {                        var err = new Error('生成base64失败');                        reject(err);                        throw err;                    }                    return base64;                })                .then(function (base64) {                    var formData = null;                    // 压缩文件太大就采用源文件,且使用原生的FormData() @source #55                    if (typeof that.file === 'object' && base64.length > that.file.size) {                        formData = new FormData();                        file     = that.file;                    } else {                        formData = new BlobFormDataShim.FormData();                        file     = dataURItoBlob(base64);                    }                    formData.append(that.defaults.fieldName, file, (that.fileName.replace(/\..+/g, '.jpg')));                    resolve({                        formData : formData,                        fileLen : +file.size,                        base64  : base64,                        base64Len: base64.length,                        origin   : that.file,                        file   : file                    });                    // 释放内存                    for (var p in that) {                        if (!that.hasOwnProperty(p)) continue;                        that[p] = null;                    }                    URL.revokeObjectURL(that.blob);                });        };        // 如果传入的是base64在移动端会报错        !fileIsBase64 && (img.crossOrigin = "*");        img.src = blob;    });};Lrz.prototype._getBase64 = function () {    var that   = this,        img    = that.img,        file   = that.file,        canvas = that.canvas;    return new Promise(function (resolve) {        try {            // 传入blob在android4.3以下有bug            exif.getData(typeof file === 'object' ? file : img, function () {                that.orientation = exif.getTag(this, "Orientation");                that.resize = that._getResize();                that.ctx    = canvas.getContext('2d');                canvas.width  = that.resize.width;                canvas.height = that.resize.height;                // 设置为白色背景,jpg是不支持透明的,所以会被默认为canvas默认的黑色背景。                that.ctx.fillStyle = '#fff';                that.ctx.fillRect(0, 0, canvas.width, canvas.height);                // 根据设备对应处理方式                if (UA.oldIOS) {                    that._createBase64ForOldIOS().then(resolve);                }                else {                    that._createBase64().then(resolve);                }            });        } catch (err) {            // 这样能解决低内存设备闪退的问题吗?            throw new Error(err);        }    });};Lrz.prototype._createBase64ForOldIOS = function () {    var that        = this,        img         = that.img,        canvas      = that.canvas,        defaults    = that.defaults,        orientation = that.orientation;    return new Promise(function (resolve) {        require(['megapix-image'], function (MegaPixImage) {            var mpImg = new MegaPixImage(img);            if ("5678".indexOf(orientation) > -1) {                mpImg.render(canvas, {                    width      : canvas.height,                    height     : canvas.width,                    orientation: orientation                });            } else {                mpImg.render(canvas, {                    width      : canvas.width,                    height     : canvas.height,                    orientation: orientation                });            }            resolve(canvas.toDataURL('image/jpeg', defaults.quality));        });    });};Lrz.prototype._createBase64 = function () {    var that        = this,        resize      = that.resize,        img         = that.img,        canvas      = that.canvas,        ctx         = that.ctx,        defaults    = that.defaults,        orientation = that.orientation;    // 调整为正确方向    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);    }    return new Promise(function (resolve) {        if (UA.oldAndroid || UA.mQQBrowser || !navigator.userAgent) {            require(['jpeg_encoder_basic'], function (JPEGEncoder) {                var encoder = new JPEGEncoder(),                    img     = ctx.getImageData(0, 0, canvas.width, canvas.height);                resolve(encoder.encode(img, defaults.quality * 100));            })        }        else {            resolve(canvas.toDataURL('image/jpeg', defaults.quality));        }    });};Lrz.prototype._getResize = function () {    var that        = this,        img         = that.img,        defaults    = that.defaults,        width       = defaults.width,        height      = defaults.height,        orientation = that.orientation;    var ret = {        width : img.width,        height: img.height    };    if ("5678".indexOf(orientation) > -1) {        ret.width  = img.height;        ret.height = img.width;    }    // 如果原图小于设定,采用原图    if (ret.width < width || ret.height < height) {        return ret;    }    var scale = ret.width / ret.height;    if (width && height) {        if (scale >= width / height) {            if (ret.width > width) {                ret.width  = width;                ret.height = Math.ceil(width / scale);            }        } else {            if (ret.height > height) {                ret.height = height;                ret.width  = Math.ceil(height * scale);            }        }    }    else if (width) {        if (width < ret.width) {            ret.width  = width;            ret.height = Math.ceil(width / scale);        }    }    else if (height) {        if (height < ret.height) {            ret.width  = Math.ceil(height * scale);            ret.height = height;        }    }    // 超过这个值base64无法生成,在IOS上    while (ret.width >= 3264 || ret.height >= 2448) {        ret.width *= 0.8;        ret.height *= 0.8;    }    return ret;};/** * 获取当前js文件所在路径,必须得在代码顶部执行此函数 * @returns {string} */function getJsDir (src) {    var script = null;    if (src) {        script = [].filter.call(document.scripts, function (v) {            return v.src.indexOf(src) !== -1;        })[0];    } else {        script = document.scripts[document.scripts.length - 1];    }    if (!script) return null;    return script.src.substr(0, script.src.lastIndexOf('/'));}/** * 转换成formdata * @param dataURI * @returns {*} * * @source http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata */function dataURItoBlob (dataURI) {    // convert base64/URLEncoded data component to raw binary data held in a string    var byteString;    if (dataURI.split(',')[0].indexOf('base64') >= 0)        byteString = atob(dataURI.split(',')[1]);    else        byteString = unescape(dataURI.split(',')[1]);    // separate out the mime component    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];    // write the bytes of the string to a typed array    var ia = new Uint8Array(byteString.length);    for (var i = 0; i < byteString.length; i++) {        ia[i] = byteString.charCodeAt(i);    }    return new BlobFormDataShim.Blob([ia.buffer], {type: mimeString});}

2.图片的EXIF-Orientation信息

查询了一些相关的文章,简单的了解到EXIF-Orientation信息,原来在我们拍照的时候,拍照完成了,有一些相机是加入了EXIF-Orientation信息进去图片,这样一来,在我们查看相片的时候,它那个查看器会自动帮你旋转照片,而不用像以前那样,怎么照的就怎么显示,看的时候,还得自己去转动相机。

EXIF-Orientation信息的可参阅:Exif的Orientation信息说明

我从上面这篇博文摘取一段下来作为记录吧。


其中1836是我们最常用的几个旋转,第一行的红框,就是我们相机拍摄时的旋转状态,第二行,就是我们相机摆正后,图片实际存储的状态,到第三行,我们通过相机查看图片的时候,相机根据图片存入的EXIF-Orientation信息进行旋转,比如这里的8,在说明你拍照的时候,相机是逆时针旋转90度拍照,当你相机顺时针旋转90度来查看的时候,那么相机就会把图片逆时针选择90度,这样,在摆正相机的时候,就看到的是一个天在天,地在地的照片。


除了1836,还有另外的2754的EXIF-Orientation信息,分表就是通过1的镜面翻转后再进行角度旋转,看图理解起来有点困难,可以参照以下的表格辅助理解。

参数0行(未旋转上)0列(未旋转左)旋转(方法很多)1上左0°2上右水平翻转3下右180°4下左垂直翻转5左上顺时针90°+水平翻转6右上顺时针90°7右下顺时针90°+垂直翻转8左下逆时针90°其中,行的意思是未旋转时,相机的上面; 列的意思是未旋转时,相机的上面。

好了,此博客记录到此,如有错误或不解之处,望各位读者留下您宝贵的评论,thanks!

0 0
原创粉丝点击