怎么利用toDataURL压缩图片至固定大小

只是小丑
  • 30

在使用canvastoDataURL方法压缩图片的时候如何计算quality使图片压缩至固定大小。
用size算比例好像不行。

评论
阅读 2.7k
2 个回答

做过类似的工作,在前端采用 canvas 压缩jpg图片,但是代码没有留存下来,大致的思路是采用二分法,也就是设定目标文件体积和精度,然后反复压缩直到文件体积与目标体积之差小于设定的精度。
采用二分法的原因是生成 jpeg 的算法的压缩比和图片色彩丰富度、图片频率等图片本身的性质相关,因此 quality 和压缩比的关系不太好计算(计算过程需要对压缩算法有透彻的了解,这样一来还不如自己写压缩算法) 。
精度不能设太小,不然可能导致脚本运行时间过长甚至死循环,当然还可以限制循环次数,然后选择符合条件且最接近目标体积的文件输出。
这里还有一个难点就是文件体积的计算,我采用的方法是将文件转成 base64 字符串,然后去掉占位的等号,再计算字符串长度来获得,这个过程也比较费事,可以考虑 WebWorker。


2021年1月,重新做了类似功能,搜索方案的时候看到了自己的答案……重新实现了一下,使用二分法,没有做 polyfill,但还是有参考意义的,Windows chrome 80+ 浏览器测试过了,一般图片没有问题,代码如下:

    /**
     * @method imageCompressor - 基于 canvas 的图片压缩器
     * @param {File} file - blob 文件,可以监听输入框获得
     * @param {NUmber} radio - 目标压缩比率
     * @param {Number} [maxRetryTime = 16] - 反复压缩的最大次数,次数太多可能会导致压缩时间过长
     * @param {Number} [accuracy = 0.1] - 压缩的精确度,压缩目标是按比率来的。
     *     但是基于canvas降低图片质量的“压缩”不可能精确到刚好等于目标比率,所以允许这个误差范围,
     *     建议设为 0.2,不建议小于 0.1 ,过小的时候也没有任何意义,因为部分情况下,即便超出 JS 
     *     可表示的小数的精确度范围,还是无法适应过于精准的压缩要求。
     * @return {Promise<Blob>}
     */
    const imageCompressor = (() => {
        const compressor = (canvas, quality = 1, type = 'image/jpeg') => {
            return new Promise((resolve, reject) => {
                try{
                    canvas.toBlob(resolve, type, quality);
                } catch (err) {
                    reject(err);
                }
            });
        };
        const { abs } = Math;

        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');

        return async ({
            file,
            maxRetryTime = 16,
            accuracy = .1,
            radio = 1
        }) => {
            let currentRadio = 1;
            let output = file;
            let maxQuality = 1;
            let minQuality = 0;
            let lastQuality = 1;
            let nextQuality = 1;
            let isTooLarge = true;
            let compressTime = 0;

            const { size: sizeBefore } = file;

            const image = await loadImageFromURL(await blob2Base64(file));
            const { width, height } = image;
            canvas.setAttribute('width', width);
            canvas.setAttribute('height', height);
            context.drawImage(image, 0, 0, width, height);
            let currentSize = sizeBefore;
            let bestComposition = [currentRadio, file];

            let condition = true;

            while(condition){
                isTooLarge = currentRadio > radio;
                if(isTooLarge){
                    if(maxQuality > lastQuality){
                        maxQuality = lastQuality;
                    }
                    nextQuality = (lastQuality + minQuality) / 2;
                } else {
                    if(minQuality < lastQuality) {
                        minQuality = lastQuality;
                    }
                    nextQuality = (lastQuality + maxQuality) / 2;
                }
                lastQuality = nextQuality;
                compressTime++;
                output = await compressor(canvas, nextQuality);
                currentSize = output.size;
                currentRadio = currentSize / sizeBefore;

                condition = ((currentRadio > radio) || (abs(currentRadio - radio) > accuracy))
                    && (compressTime < maxRetryTime)
                    && (maxQuality - minQuality > 1e-16);
                if(abs(currentRadio - radio) < abs(bestComposition[0] - radio)){
                    bestComposition[0] = currentRadio;
                    bestComposition[1] = output;
                }
            }

            if(
                abs(currentRadio - radio) < abs(bestComposition[0] - radio)
                || bestComposition[0] > radio
            ){
                bestComposition[0] = currentRadio;
                bestComposition[1] = output;
            }

            return bestComposition[1];
        };
    })();

canvas.toDataURL(type, encoderOptions);
type 可选
图片格式,默认为 image/png
encoderOptions 可选
在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
详细见于https://developer.mozilla.org...

撰写回答

登录后参与交流、获取后续更新提醒

宣传栏