美文网首页
js压缩图片 到固定像素以内,500k为例

js压缩图片 到固定像素以内,500k为例

作者: 景阳冈大虫在此 | 来源:发表于2019-12-18 20:48 被阅读0次

    本文旨在探究js压缩图片的两种方式:改变图片长宽改变图片质量,和结合了以上两者的最终方案

    首先,阅读本文需要知道canvas的两个方法

    drawImage(image, x, y, width, height)
    这个方法多了2个参数:width 和 height,这两个参数用来控制 当向canvas画入时应该缩放的大小

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

    这两个方法具体的说明可以在MDN上查看,关于图片压缩,也有很多现成的博客可以直接用。但是那些博客都有个问题,并没有关心之后图片的压缩质量。
    我试着用一个现成的例子去跑了一下,一个1.7M的图片压缩到了23k,堪称像素级毁灭性破坏。

    改变图片长宽实现压缩目的

    假如一张大图可能包含着很多文字等关键信息,必须上传之后使用方能清晰辨认。所以要压缩之后质量尽可能接近500k的。500k像素以内,就是若一张图宽度为1024,则高度不能超过500。因为图片有其他的信息,也是要占大小的。即不得大于1024*500

    1024*576在不控制图片质量的情况下并不是576kb

    所以,根据需求,上传图片不能超过500k的情况下尽可能保留图片的清晰度。当然如果可以的情况下用上面提到的canvas.toDataURL设置压缩程度为0.9,0.8试试看,图片质量可以接受,大小会有大幅度的缩小。

    设为0.9之后的图片大幅缩小了

    如果不压缩,靠调整图片长宽去控制上传大小呢?
    原理很简单,就是靠不断地缩小限定的最大宽高,直到最终长宽的积小于规定的大小。
    这种方法有可能最后得出的图片的大小会略大于规定大小,原因上文也提到过了,如果想使用这种方法,可自行再调整一下。

    // 实际的压缩在imgResize里
    export default function getUploadImgInfo(e) {
        /**
         * 获取最终图片上传的所需入参:{ blob:图片文件, fileExtension:图片扩展名, fileType:图片类型, imgBase64:图片前端展示的base64 }
         * { blob, fileExtension, fileType, imgBase64}
         */
        let fileInfo = getFileInfo(e);
        if (!fileInfo) return Promise.reject();
        let { fileExtension, fileType, file } = fileInfo;
        return imgResize({ file, fileType }).then(res => {
            return convertBlob({ imgBase64: res, fileType, fileExtension });
        });
    }
    // 对图片进行压缩
    function imgResize({ file, maxSizeNum = 500, fileType }) {
        let fileReader = new FileReader();
        return new Promise((resolve, reject) => {
            let maxSize = 1 * 1024 * 1024 * (maxSizeNum / 1024); // 最大默认500k 即若规定最大宽度为1024,则高度不能超过500
            let maxLength = 1024;
            if (file.size > maxSize) {
                fileReader.onload = function(e) {
                    let IMG = new Image();
                    IMG.onload = function() {
                        let reMaxLength = maxLength;
                        let originW = this.naturalWidth;
                        let originH = this.naturalHeight;
                        let canvas = document.createElement('canvas');
                        let ctx = canvas.getContext('2d');
                        let level = 1.0; // level是图片质量
                        let { resizeW, resizeH } = getImgResize(
                            originW,
                            originH,
                            maxLength
                        );
                        let count = 0;
                        while (resizeH * resizeW >= maxSize) {
                            reMaxLength = reMaxLength - 100;
                            let obj = getImgResize(originW, originH, reMaxLength);
                            resizeH = obj.resizeH;
                            resizeW = obj.resizeW;
                            console.log('rr:', resizeH, resizeW, count++);
                        }
    
                        if (window.navigator.userAgent.indexOf('iPhone') > 0) {
                            canvas.width = resizeH;
                            canvas.height = resizeW;
                            ctx.rotate((90 * Math.PI) / 180);
                            ctx.drawImage(IMG, 0, -resizeH, resizeW, resizeH);
                        } else {
                            canvas.width = resizeW;
                            canvas.height = resizeH;
                            ctx.drawImage(IMG, 0, 0, resizeW, resizeH);
                        }
                        let base64 = canvas.toDataURL('image/' + fileType, level);
                        resolve(base64);
                    };
    
                    IMG.src = e.target.result;
                };
            } else {
                fileReader.onload = function() {
                    resolve(fileReader.result);
                };
            }
            fileReader.readAsDataURL(file);
        });
    }
    function getImgResize(originW, originH, maxLength = 1024) {
        // 获得图片调整大小之后的长宽;maxLength为长宽最大的长度
        let resizeH = 0;
        let resizeW = 0;
        if (originW > maxLength || originH > maxLength) {
            let multiple = Math.max(originW / maxLength, originH / maxLength);
            resizeW = originW / multiple;
            resizeH = originH / multiple;
        } else {
            resizeW = originW;
            resizeH = originH;
        }
        return {
            resizeH,
            resizeW
        };
    }
    function getFileInfo(e) {
        if (e.target.value == '') {
            return false;
        }
        let files = e.target.files || e.dataTransfer.files;
        if (!files.length) return;
        let file = files[0];
        return {
            fileExtension:
                file.name.match(/[\w]*$/) && file.name.match(/[\w]*$/)[0],
            fileType: file.type.split('/')[1],
            file: files[0]
        };
    }
    function convertBlob({ imgBase64, fileType, fileExtension }) {
        // 获得blob,用于最终上传的图片入参
        if (!imgBase64) {
            this.$toast('请选择图片');
            return;
        }
        let base64 = window.atob(imgBase64.split(',')[1]);
        let buffer = new ArrayBuffer(base64.length);
        let ubuffer = new Uint8Array(buffer);
        for (let i = 0; i < base64.length; i++) {
            ubuffer[i] = base64.charCodeAt(i);
        }
        let blob;
        try {
            blob = new Blob([buffer], { type: 'image/' + fileType });
        } catch (e) {
            window.BlobBuilder =
                window.BlobBuilder ||
                window.WebKitBlobBuilder ||
                window.MozBlobBuilder ||
                window.MSBlobBuilder;
            if (e.name === 'TypeError' && window.BlobBuilder) {
                let blobBuilder = new BlobBuilder();
                blobBuilder.append(buffer);
                blob = blobBuilder.getBlob('image/' + fileType);
            }
        }
        return Promise.resolve({ blob, fileExtension, fileType, imgBase64 });
    }
    

    改变图片清晰度实现压缩目的

    上面的方法有个问题,就是改变了图片的原始长宽。如果一个图的长宽足够大,压缩图片质量,糊一点但是内容看得清也是ok的嘛。所以,跟上面同理,我们可以不断调整图片的质量设定直到大小合适,那么,如何在图片上传之前知道图片的大小呢?
    首先,需要知道的一点是,压缩之后拿到的base64字符串会转成blob对象,然后传给服务端。
    可以查阅文档,blob对象有个属性是size

    返回一个File对象所指代的文件的大小,单位为字节。

    这个size就是上传之后实际的文件大小。
    参照上面的思路,可以每次改变canvas.toDataURL('image/' + fileType, level);level的值,去调整压缩图片质量,然后用blob对象的size去验证是否满足500k以内的需求。
    关于 canvas.toDataURL的level到底是怎么计算的,MDN文档里也没说,写了个循环一次减少0.1的level压缩了几个图片

    726625字节 1738594字节 1615259 byte

    用加减乘除算了一下,没找到规律,数学不好放弃了(这个东西好像也不是能观察出来的,看结果跟初始大小没啥关系)。
    这里要注意的是,有可能遇到超大图片,0.1的level可能不足以压缩到500k,所以小于0.1的时候,改变level递减的差值继续压缩下去

    if (level <= cutNum) {
        cutNum = cutNum / 10;
    }
    level = (level * 100 - cutNum * 100) / 100;
    

    在开始接收到图片的时候给一个loading增加用户的耐心好了,loading万岁~

    let fileSize = null;
    export default async function getUploadImgInfo(e) {
        /**
         * 获取最终图片上传的所需入参:{ blob:图片文件, fileExtension:图片扩展名, fileType:图片类型, imgBase64:图片前端展示的base64 }
         * { blob, fileExtension, fileType, imgBase64}
         */
        let fileInfo = getFileInfo(e);
        if (!fileInfo) return Promise.reject();
        let { fileExtension, fileType, file } = fileInfo;
        let level = 0.9; // 压缩的文件质量,如果文件大小不合适,质量则会取这个值
        let cutNum = 0.1;
        let maxSize = 500 * 1024; // 字节,最大500k
        let fileRes = null;
        let finalRes = null;
        let task = async () => {
            console.log('level', level);
            fileRes = await imgResize({ file, fileType, level, maxSize });
            finalRes = await convertBlob({
                imgBase64: fileRes,
                fileType,
                fileExtension
            });
        };
        await task();
        while (finalRes.blob.size > maxSize) {
            // 如果文件大小不合适,则一直压缩到500k以内
            if (level <= cutNum) {
                cutNum = cutNum / 10;
            }
            level = (level * 100 - cutNum * 100) / 100;
            await task();
        }
        return finalRes;
    }
    function getFileInfo(e) {
        if (e.target.value == '') {
            return false;
        }
        let files = e.target.files || e.dataTransfer.files;
        if (!files.length) return;
        let file = files[0];
        return {
            fileExtension:
                file.name.match(/[\w]*$/) && file.name.match(/[\w]*$/)[0],
            fileType: file.type.split('/')[1],
            file: files[0]
        };
    }
    // 对图片进行压缩
    function imgResize({ file, maxSize = 500 * 1024, fileType, level = 1.0 }) {
        let fileReader = new FileReader();
        return new Promise((resolve, reject) => {
            fileSize = file.size;
            if (file.size > maxSize) {
                fileReader.onload = function(e) {
                    let IMG = new Image();
                    IMG.onload = function() {
                        let originW = this.naturalWidth;
                        let originH = this.naturalHeight;
                        let resizeH = originH;
                        let resizeW = originW;
                        let canvas = document.createElement('canvas');
                        let ctx = canvas.getContext('2d');
    
                        if (window.navigator.userAgent.indexOf('iPhone') > 0) {
                            canvas.width = resizeH;
                            canvas.height = resizeW;
                            ctx.rotate((90 * Math.PI) / 180);
                            ctx.drawImage(IMG, 0, -resizeH, resizeW, resizeH);
                        } else {
                            canvas.width = resizeW;
                            canvas.height = resizeH;
                            ctx.drawImage(IMG, 0, 0, resizeW, resizeH);
                        }
                        let base64 = canvas.toDataURL('image/' + fileType, level);
                        resolve(base64);
                    };
    
                    IMG.src = e.target.result;
                };
            } else {
                fileReader.onload = function() {
                    resolve(fileReader.result);
                };
            }
            fileReader.readAsDataURL(file);
        });
    }
    function convertBlob({ imgBase64, fileType, fileExtension }) {
        // 获得blob,用于最终上传的图片入参
        if (!imgBase64) {
            this.$toast('请选择图片');
            return;
        }
        let base64 = window.atob(imgBase64.split(',')[1]);
        let buffer = new ArrayBuffer(base64.length);
        let ubuffer = new Uint8Array(buffer);
        for (let i = 0; i < base64.length; i++) {
            ubuffer[i] = base64.charCodeAt(i);
        }
        let blob;
        try {
            blob = new Blob([buffer], { type: 'image/' + fileType });
        } catch (e) {
            window.BlobBuilder =
                window.BlobBuilder ||
                window.WebKitBlobBuilder ||
                window.MozBlobBuilder ||
                window.MSBlobBuilder;
            if (e.name === 'TypeError' && window.BlobBuilder) {
                let blobBuilder = new BlobBuilder();
                blobBuilder.append(buffer);
                blob = blobBuilder.getBlob('image/' + fileType);
            }
        }
        console.log(
            'count:',
            count++,
            '占比:',
            blob.size / fileSize,
            'final:',
            blob.size,
            blob.size / 1024,
            'begin',
            fileSize,
            fileSize / 1024
        );
        return Promise.resolve({ blob, fileExtension, fileType, imgBase64 });
    }
    let count = 1;
    

    综合方案

    其实单纯的压缩质量遇到稍大的图片,会导致页面高频计算,然后页面基本就用不了了- -。有尝试过用iphone的一个屏幕截图(10M左右),压的时候稍过一会,整个手机都在发烫,只能杀进程。

    所以,若对长度没有特殊的限制,可以做一个缩放,去加快压缩的进度,提高能压缩的图片大小上限。


    综合方案压缩10.8M图片

    页面到了ios上还是不行- -,可以看到最后图片level为0.001,最长边为764。
    问题还是循环次数还是过多,计算频率太高。从图中可看出,对于大图来说,初始设定的level和图片尺寸过于宽松,可以优化一下初始level和尺寸。

      if (finalRes.blob.size > maxSize) {
            // 设定一个合适的初始level
            level = orginLevel(finalRes.blob.size, maxSize);
            if (level <= 0.4) maxLength = 800;
      }
    function orginLevel(size, maxSize) {
        // 根据文件大小得到一开始的level
        // rate为与最终限定大小的倍数
        let rate = Number((size / maxSize).toFixed());
        let finalLevel = (1 * 100000 - (rate * 100000) / 3) / 100000;
        if (rate <= 1) {
            return 1;
        } else if (finalLevel > 0) {
            return Number(finalLevel.toFixed(1));
        } else {
            return 0.1;
        }
    }
    
    优化大图的初始level和最长边之后

    有的时候还会遇到一张图片无论如何也压不到500k,就是上一次和这次的压缩后大小没有变化,这种情况需要抛错,不让循环继续。

    if (finalSize === blob.size) {
        return Promise.reject(
            new Error('压缩该文件最近两次的大小相同,压缩无效!')
        );
    } 
    

    大图片的等待时间稍长,可以给用户先预览一个base64的图片增加等待耐心,方法名为getImgBase64,这里都一并给出了

    /* eslint-disable no-undef */
    let fileSize = 0;
    let count = 1;
    let finalSize = 0; // 压缩之后的大小,若两次比对这个值是相同的,则判定压缩失败,抛错
    export default async function getUploadImgInfo(e) {
        /**
         * 获取最终图片上传的所需入参:{ blob:图片文件, fileExtension:图片扩展名, fileType:图片类型, imgBase64:图片前端展示的base64 }
         * { blob, fileExtension, fileType, imgBase64}
         */
        let fileInfo = getFileInfo(e);
        if (!fileInfo) return Promise.reject();
        let { fileExtension, fileType, file } = fileInfo;
        let level = 1; // 压缩的文件质量,如果文件大小不合适,质量则会取这个值
        let cutNum = 0.1;
        let maxSize = 500 * 1024; // 字节,最大500k
        let fileRes = null;
        let finalRes = null;
        let maxLength = 1024; // 能接受的最大图片长度,作为压缩图片的入参
        const minLength = 754; // 能接受的最小图片长度
        finalSize = 0; // 重置这个值
        let task = async () => {
            console.log('test||   ', 'level:', level, 'maxLength:', maxLength);
            fileRes = await imgResize({
                file,
                fileType,
                level,
                maxSize,
                maxLength
            });
            finalRes = await convertBlob({
                imgBase64: fileRes,
                fileType,
                fileExtension
            });
        };
        await task();
        if (finalRes.blob.size > maxSize) {
            // 设定一个合适的level,避免压缩的时候太多
            level = orginLevel(finalRes.blob.size, maxSize);
            if (level <= 0.4) maxLength = 800;
            console.log('test||   ', 'originLevel', level);
        }
        while (finalRes.blob.size > maxSize) {
            // 如果文件大小不合适,则一直压缩到500k以内
            if (maxLength > minLength) {
                // 控制图片最大边的长度
                maxLength = maxLength - 10;
            }
            if (level <= cutNum) {
                // 控制level递减的差值
                cutNum = cutNum / 10;
            }
            level = (level * 1000000 - cutNum * 1000000) / 1000000;
            await task();
        }
        return finalRes;
    }
    function orginLevel(size, maxSize) {
        // 根据文件大小得到一开始的level
        // rate为与最终限定大小的倍数
        let rate = Number((size / maxSize).toFixed());
        let finalLevel = (1 * 100000 - (rate * 100000) / 3) / 100000;
        if (rate <= 1) {
            return 1;
        } else if (finalLevel > 0) {
            return Number(finalLevel.toFixed(1));
        } else {
            return 0.1;
        }
    }
    function getImgResize(originW, originH, maxLength = 1024) {
        // 获得图片调整大小之后的长宽;maxLength为长宽最大的长度
        let resizeH = 0;
        let resizeW = 0;
        if (originW > maxLength || originH > maxLength) {
            let multiple = Math.max(originW / maxLength, originH / maxLength);
            resizeW = originW / multiple;
            resizeH = originH / multiple;
        } else {
            resizeW = originW;
            resizeH = originH;
        }
        return {
            resizeH,
            resizeW
        };
    }
    export function getImgBase64(e) {
        // 把文件转成base64--预览目的
        let fileInfo = getFileInfo(e);
        let { fileType, file } = fileInfo;
        return imgResize({
            file,
            maxSize: 500 * 1024,
            fileType,
            level: 0.6,
            maxLength: 500
        });
    }
    function getFileInfo(e) {
        if (e.target.value == '') {
            return false;
        }
        let files = e.target.files || e.dataTransfer.files;
        if (!files.length) return;
        let file = files[0];
        return {
            fileExtension:
                file.name.match(/[\w]*$/) && file.name.match(/[\w]*$/)[0],
            fileType: file.type.split('/')[1],
            file: files[0]
        };
    }
    // 对图片进行压缩
    function imgResize({
        file,
        maxSize = 500 * 1024,
        fileType,
        level = 1.0,
        maxLength = 1024
    }) {
        let fileReader = new FileReader();
        return new Promise((resolve, reject) => {
            fileSize = file.size;
            if (file.size > maxSize) {
                fileReader.onload = function(e) {
                    let IMG = new Image();
                    IMG.onload = function() {
                        let originW = this.naturalWidth;
                        let originH = this.naturalHeight;
                        let canvas = document.createElement('canvas');
                        let ctx = canvas.getContext('2d');
                        let { resizeW, resizeH } = getImgResize(
                            originW,
                            originH,
                            maxLength
                        );
                        if (window.navigator.userAgent.indexOf('iPhone') > 0) {
                            canvas.width = resizeH;
                            canvas.height = resizeW;
                            ctx.rotate((90 * Math.PI) / 180);
                            ctx.drawImage(IMG, 0, -resizeH, resizeW, resizeH);
                        } else {
                            canvas.width = resizeW;
                            canvas.height = resizeH;
                            ctx.drawImage(IMG, 0, 0, resizeW, resizeH);
                        }
                        let base64 = canvas.toDataURL('image/' + fileType, level);
                        resolve(base64);
                    };
    
                    IMG.src = e.target.result;
                };
            } else {
                fileReader.onload = function() {
                    resolve(fileReader.result);
                };
            }
            fileReader.readAsDataURL(file);
        });
    }
    function convertBlob({ imgBase64, fileType, fileExtension }) {
        // 获得blob,用于最终上传的图片入参
        if (!imgBase64) {
            this.$toast('请选择图片');
            return;
        }
        let base64 = window.atob(imgBase64.split(',')[1]);
        let buffer = new ArrayBuffer(base64.length);
        let ubuffer = new Uint8Array(buffer);
        for (let i = 0; i < base64.length; i++) {
            ubuffer[i] = base64.charCodeAt(i);
        }
        let blob;
        try {
            blob = new Blob([buffer], { type: 'image/' + fileType });
        } catch (e) {
            window.BlobBuilder =
                window.BlobBuilder ||
                window.WebKitBlobBuilder ||
                window.MozBlobBuilder ||
                window.MSBlobBuilder;
            if (e.name === 'TypeError' && window.BlobBuilder) {
                let blobBuilder = new BlobBuilder();
                blobBuilder.append(buffer);
                blob = blobBuilder.getBlob('image/' + fileType);
            }
        }
        if (finalSize === blob.size) {
            return Promise.reject(
                new Error('压缩该文件两次的结果相同,压缩无效!')
            );
        } else {
            finalSize = blob.size;
        }
        console.log(
            'test||   ',
            'count:',
            count++,
            '占比:',
            blob.size / fileSize,
            'final:',
            blob.size,
            blob.size / 1024,
            'begin',
            fileSize,
            fileSize / 1024
        );
        return Promise.resolve({ blob, fileExtension, fileType, imgBase64 });
    }
    
    

    优化方案

    解决的隐患:上面这个方案会出现我需要一个500k的照片,压到了520k之后,再压了一次。有时候这最后的一次会特别夸张,直接将图片弄到了几十k。
    参考了:https://github.com/WangYuLue/image-conversion
    这个库里面有个方法compressAccurately,这个方法可以比较精准地压缩。偷偷翻了一下源码。

    async function compressAccurately(file: Blob, config: compressAccuratelyConfig = {}): Promise<Blob> {
      if (!(file instanceof Blob)) {
        throw new Error('compressAccurately(): First arg must be a Blob object or a File object.');
      }
      if (typeof config !== 'object') {
        config = Object.assign({
          size: config,
        });
      }
      // 如果指定体积不是数字或者数字字符串,则不做处理
      config.size = Number(config.size);
      if (Number.isNaN(config.size)) {
        return file;
      }
      // 如果指定体积大于原文件体积,则不做处理;
      if (config.size * 1024 > file.size) {
        return file;
      }
      config.accuracy = Number(config.accuracy);
      if (!config.accuracy
        || config.accuracy < 0.8
        || config.accuracy > 0.99) {
        config.accuracy = 0.95; // 默认精度0.95
      }
      const resultSize = {
        max: config.size * (2 - config.accuracy) * 1024,
        accurate: config.size * 1024,
        min: config.size * config.accuracy * 1024,
      };
      const dataURL = await filetoDataURL(file);
      let originalMime = dataURL.split(',')[0].match(/:(.*?);/)[1] as EImageType; // 原始图像图片类型
      let mime = EImageType.JPEG;
      if (checkImageType(config.type)) {
        mime = config.type;
        originalMime = config.type;
      }
      const image = await dataURLtoImage(dataURL);
      const canvas = await imagetoCanvas(image, Object.assign({}, config));
      /**
       * 经过测试发现,blob.size与dataURL.length的比值约等于0.75
       * 这个比值可以同过dataURLtoFile这个方法来测试验证
       * 这里为了提高性能,直接通过这个比值来计算出blob.size
       */
      const proportion = 0.75;
      let imageQuality = 0.5;
      let compressDataURL;
      const tempDataURLs: string[] = [null, null];
      /**
       * HTMLCanvasElement.toBlob()以及HTMLCanvasElement.toDataURL()压缩参数
       * 的最小细粒度为0.01,而2的7次方为128,即只要循环7次,则会覆盖所有可能性
       */
      for (let x = 1; x <= 7; x++) {
        compressDataURL = await canvastoDataURL(canvas, imageQuality, mime);
        const CalculationSize = compressDataURL.length * proportion;
        // 如果到循环第七次还没有达到精确度的值,那说明该图片不能达到到此精确度要求
        // 这时候最后一次循环出来的dataURL可能不是最精确的,需要取其周边两个dataURL三者比较来选出最精确的;
        if (x === 7) {
          if (resultSize.max < CalculationSize || resultSize.min > CalculationSize) {
            compressDataURL = [compressDataURL, ...tempDataURLs]
              .filter(i => i) // 去除null
              .sort((a, b) => Math.abs(a.length * proportion - resultSize.accurate)
                - Math.abs(b.length * proportion - resultSize.accurate))[0];
          }
          break;
        }
        if (resultSize.max < CalculationSize) {
          tempDataURLs[1] = compressDataURL;
          imageQuality -= 0.5 ** (x + 1);
        } else if (resultSize.min > CalculationSize) {
          tempDataURLs[0] = compressDataURL;
          imageQuality += 0.5 ** (x + 1);
        } else {
          break;
        }
      }
      const compressFile = await dataURLtoFile(compressDataURL, originalMime);
      // 如果压缩后体积大于原文件体积,则返回源文件;
      if (compressFile.size > file.size) {
        return file;
      }
      return compressFile;
    };
    

    其实上一个方案的痛点就在于,如何在每一个压缩循环里处理尺寸和压缩比例。

    • 尺寸
      在imagetoCanvas里,修改了图片的宽和高如下
    width = myConfig.width || myConfig.height * image.width / image.height || image.width;
    height = myConfig.height || myConfig.width * image.height / image.width || image.height;
    
    • 压缩比例
      然后看这个循环
    for (let x = 1; x <= 7; x++) {
        compressDataURL = await canvastoDataURL(canvas, imageQuality, mime);
        const CalculationSize = compressDataURL.length * proportion;
        // 如果到循环第七次还没有达到精确度的值,那说明该图片不能达到到此精确度要求
        // 这时候最后一次循环出来的dataURL可能不是最精确的,需要取其周边两个dataURL三者比较来选出最精确的;
        if (x === 7) {
          if (resultSize.max < CalculationSize || resultSize.min > CalculationSize) {
            compressDataURL = [compressDataURL, ...tempDataURLs]
              .filter(i => i) // 去除null
              .sort((a, b) => Math.abs(a.length * proportion - resultSize.accurate)
                - Math.abs(b.length * proportion - resultSize.accurate))[0];
          }
          break;
        }
        if (resultSize.max < CalculationSize) {
          tempDataURLs[1] = compressDataURL;
          imageQuality -= 0.5 ** (x + 1);
        } else if (resultSize.min > CalculationSize) {
          tempDataURLs[0] = compressDataURL;
          imageQuality += 0.5 ** (x + 1);
        } else {
          break;
        }
      }
    

    总结

    1. 根据所要求的最终图片大小,得出误差范围;
    2. 在误差范围内比对压缩过后的图片大小,调整下一次压缩的质量;
    3. 初始化压缩比例为0.5,单次调整的质量尺度为±(1/2) ^ (x + 1) 。总循环次数为7次;
      pass:总循环为7次的原因是压缩比例最小细粒度为0.01,因为1/(2^8) 约等于0.0078,当压缩比例从1/2开始对(1/2^2) 0.25 这种值进行相加减将会趋近于最接近的压缩比例。
    4. 若循环的确到了第七次,则不一定是最准的,可以找前面的最接近的结果来作为最终计算结果。

    如有纰漏,欢迎指正

    相关文章

      网友评论

          本文标题:js压缩图片 到固定像素以内,500k为例

          本文链接:https://www.haomeiwen.com/subject/scxngctx.html