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

最近在调试一个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
2水平翻转
3180°
4垂直翻转
5顺时针90°+水平翻转
6顺时针90°
7顺时针90°+垂直翻转
8逆时针90°
其中,行的意思是未旋转时,相机的上面; 列的意思是未旋转时,相机的上面。

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值