如何在浏览器中通过Javascript压缩图像?

如何在浏览器中通过Javascript压缩图像?,javascript,image,cross-browser,compression,Javascript,Image,Cross Browser,Compression,TL;博士 在上传之前,有没有一种方法可以直接在浏览器端压缩图像(主要是jpeg、png和gif)?我很确定JavaScript可以做到这一点,但我找不到实现它的方法 下面是我想要实现的完整场景: 用户进入我的网站,通过输入类型=文件元素选择图像, 该图像通过JavaScript检索,我们进行了一些验证,如正确的文件格式、最大文件大小等, 如果一切正常,页面上将显示图像预览, 用户可以执行一些基本操作,例如将图像旋转90°/-90°,按照预定义的比率进行裁剪等,或者用户可以上载另一个图像并返回

TL;博士

在上传之前,有没有一种方法可以直接在浏览器端压缩图像(主要是jpeg、png和gif)?我很确定JavaScript可以做到这一点,但我找不到实现它的方法

下面是我想要实现的完整场景:

用户进入我的网站,通过输入类型=文件元素选择图像, 该图像通过JavaScript检索,我们进行了一些验证,如正确的文件格式、最大文件大小等, 如果一切正常,页面上将显示图像预览, 用户可以执行一些基本操作,例如将图像旋转90°/-90°,按照预定义的比率进行裁剪等,或者用户可以上载另一个图像并返回到步骤1, 当用户满意时,编辑的图像将被压缩并保存在本地,而不是保存到文件中,而是保存在浏览器内存/页面中- 用户在表格中填写姓名、年龄等数据, 用户单击Finish按钮,然后将包含数据+压缩图像的表单发送到服务器,而不使用AJAX, 到最后一步的整个过程应该在客户端完成,并且应该与最新的Chrome和Firefox、Safari 5+和IE 8+兼容。如果可能的话,应该只使用JavaScript,但我很确定这是不可能的

我现在没有编写任何代码,但我已经考虑过了。可以通过本地读取文件,可以使用元素进行图像预览和编辑,但我找不到一种方法来完成图像压缩部分

根据和的说法,由于IE的存在,支持这些浏览器相当困难,但可以使用和等polyfill来实现

实际上,目标是减少文件大小,因此我将图像压缩视为一种解决方案。但是,我知道上传的图像将显示在我的网站上,每次都在同一个地方,我知道这个显示区域的尺寸,例如200x400。因此,我可以调整图像大小以适应这些尺寸,从而减小文件大小。我不知道这种技术的压缩比是多少

你觉得怎么样?你有什么建议要告诉我吗?你知道用JavaScript压缩图像浏览器端的方法吗?感谢您的回复。

简而言之:

使用HTML5 FileReader API和.readAsArrayBuffer读取文件 使用文件数据创建Blob,并使用 创建新的Image元素并将其src设置为文件blob url 将图像发送到画布。画布大小设置为所需的输出大小 通过canvas.toDataURLimage/jpeg,0.7设置您自己的输出格式和质量,从画布获取缩小的数据 将新的隐藏输入附加到原始表单,并基本上像普通文本一样传输dataURI图像 在后端,读取dataURI,从Base64解码并保存它
来源:。

编辑:根据Mr-Me对这个答案的评论,压缩现在似乎可用于JPG/WebP格式,请参阅

据我所知,您不能使用画布压缩图像,相反,您可以调整其大小。使用canvas.toDataURL将不允许您选择要使用的压缩比。您可以查看canimage,它完全满足您的需求:

事实上,只要调整图像大小以减小其大小通常就足够了,但如果您想更进一步,则必须使用新引入的方法file.readAsArrayBuffer来获取包含图像数据的缓冲区

然后,只需使用DataView根据图像格式或规范读取其内容

这将很难处理图像数据压缩,但更糟糕的是一次尝试。另一方面,您可以尝试删除PNG标题或JPEG exif数据以使图像更小,这样做应该更容易

您必须在另一个缓冲区上创建另一个DataWiew,并用过滤后的图像内容填充它。然后,您只需使用window.btoa将图像内容编码为DataURI


如果您实现了类似的功能,请让我知道您是否对代码感兴趣。

@PsychoWoods的答案很好。我想提供我自己的解决方案。此Javascript函数获取图像数据URL和宽度,将其缩放为新的宽度,并返回新的数据URL

// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
    "use strict";
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;

    // Provide default values
    imageType = imageType || "image/jpeg";
    imageArguments = imageArguments || 0.7;

    // Create a temporary image so that we can compute the height of the downscaled image.
    image = new Image();
    image.src = dataUrl;
    oldWidth = image.width;
    oldHeight = image.height;
    newHeight = Math.floor(oldHeight / oldWidth * newWidth)

    // Create a temporary canvas to draw the downscaled image on.
    canvas = document.createElement("canvas");
    canvas.width = newWidth;
    canvas.height = newHeight;

    // Draw the downscaled image on the canvas and return the new data URL.
    ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, newWidth, newHeight);
    newDataUrl = canvas.toDataURL(imageType, imageArguments);
    return newDataUrl;
}

这段代码可以在任何有数据URL的地方使用,并且需要缩小图像的数据URL。

对于JPG图像压缩,可以使用称为JIC的最佳压缩技术
Javascript图像压缩这肯定会帮助您->

我发现其他答案中缺少两件事:

canvas.toBlob(如果可用)比canvas.toDataURL性能更好,而且也是异步的。 文件->图像->画布->文件转换丢失EXIF数据;特别是,关于图像旋转的数据通常由现代手机/平板电脑设置。 以下脚本处理这两点:

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
        value: function(callback, type, quality) {

            var binStr = atob(this.toDataURL(type, quality).split(',')[1]),
                len = binStr.length,
                arr = new Uint8Array(len);

            for (var i = 0; i < len; i++) {
                arr[i] = binStr.charCodeAt(i);
            }

            callback(new Blob([arr], {type: type || 'image/png'}));
        }
    });
}

window.URL = window.URL || window.webkitURL;

// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
    if (file.slice) {
        file = file.slice(0, 131072);
    } else if (file.webkitSlice) {
        file = file.webkitSlice(0, 131072);
    }

    var reader = new FileReader();
    reader.onload = function(e) {
        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8) {
            callback(-2);
            return;
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) {
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) {
                if (view.getUint32(offset += 2, false) != 0x45786966) {
                    callback(-1);
                    return;
                }
                var little = view.getUint16(offset += 6, false) == 0x4949;
                offset += view.getUint32(offset + 4, little);
                var tags = view.getUint16(offset, little);
                offset += 2;
                for (var i = 0; i < tags; i++)
                    if (view.getUint16(offset + (i * 12), little) == 0x0112) {
                        callback(view.getUint16(offset + (i * 12) + 8, little));
                        return;
                    }
            }
            else if ((marker & 0xFF00) != 0xFF00) break;
            else offset += view.getUint16(offset, false);
        }
        callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
    var canvas = document.createElement('canvas');
    if (orientation > 4) {
        canvas.width = rawHeight;
        canvas.height = rawWidth;
    } else {
        canvas.width = rawWidth;
        canvas.height = rawHeight;
    }

    if (orientation > 1) {
        console.log("EXIF orientation = " + orientation + ", rotating picture");
    }

    var ctx = canvas.getContext('2d');
    switch (orientation) {
        case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
        case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
        case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
        case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
        case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
        case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
    }
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
    return canvas;
}

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
    if (file.size <= acceptFileSize) {
        callback(file);
        return;
    }
    var img = new Image();
    img.onerror = function() {
        URL.revokeObjectURL(this.src);
        callback(file);
    };
    img.onload = function() {
        URL.revokeObjectURL(this.src);
        getExifOrientation(file, function(orientation) {
            var w = img.width, h = img.height;
            var scale = (orientation > 4 ?
                Math.min(maxHeight / w, maxWidth / h, 1) :
                Math.min(maxWidth / w, maxHeight / h, 1));
            h = Math.round(h * scale);
            w = Math.round(w * scale);

            var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
            canvas.toBlob(function(blob) {
                console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
                callback(blob);
            }, 'image/jpeg', quality);
        });
    };
    img.src = URL.createObjectURL(file);
}
用法示例:

inputfile.onchange = function() {
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
        let body = new FormData();
        body.set('file', blob, blob.name || "file.jpg");
        fetch('/upload-image', {method: 'POST', body}).then(...);
    });
};
我对上面由@daniel allen langdon发布的downscaleImage函数有一个问题,即image.width和image.height属性无法立即使用,因为 e图像加载是异步的

请参阅下面更新的TypeScript示例,该示例考虑到这一点,使用异步函数,并根据最长维度而不仅仅是宽度调整图像大小

function getImage(dataUrl: string): Promise<HTMLImageElement> 
{
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.src = dataUrl;
        image.onload = () => {
            resolve(image);
        };
        image.onerror = (el: any, err: ErrorEvent) => {
            reject(err.error);
        };
    });
}

export async function downscaleImage(
        dataUrl: string,  
        imageType: string,  // e.g. 'image/jpeg'
        resolution: number,  // max width/height in pixels
        quality: number   // e.g. 0.9 = 90% quality
    ): Promise<string> {

    // Create a temporary image so that we can compute the height of the image.
    const image = await getImage(dataUrl);
    const oldWidth = image.naturalWidth;
    const oldHeight = image.naturalHeight;
    console.log('dims', oldWidth, oldHeight);

    const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
    const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
    console.log('longest dim', longestDimension, currentRes);

    if (currentRes > resolution) {
        console.log('need to resize...');

        // Calculate new dimensions
        const newSize = longestDimension == 'width'
            ? Math.floor(oldHeight / oldWidth * resolution)
            : Math.floor(oldWidth / oldHeight * resolution);
        const newWidth = longestDimension == 'width' ? resolution : newSize;
        const newHeight = longestDimension == 'height' ? resolution : newSize;
        console.log('new width / height', newWidth, newHeight);

        // Create a temporary canvas to draw the downscaled image on.
        const canvas = document.createElement('canvas');
        canvas.width = newWidth;
        canvas.height = newHeight;

        // Draw the downscaled image on the canvas and return the new data URL.
        const ctx = canvas.getContext('2d')!;
        ctx.drawImage(image, 0, 0, newWidth, newHeight);
        const newDataUrl = canvas.toDataURL(imageType, quality);
        return newDataUrl;
    }
    else {
        return dataUrl;
    }

}
你可以看看,在这里试试->


我将功能a head改进为:

var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7){
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
    (new Promise(function(resolve){
      image = new Image(); image.src = dataUrl;
      log(image);
      resolve('Done : ');
    })).then((d)=>{
      oldWidth = image.width; oldHeight = image.height;
      log([oldWidth,oldHeight]);
      newHeight = Math.floor(oldHeight / oldWidth * newWidth);
      log(d+' '+newHeight);

      canvas = document.createElement("canvas");
      canvas.width = newWidth; canvas.height = newHeight;
      log(canvas);
      ctx = canvas.getContext("2d");
      ctx.drawImage(image, 0, 0, newWidth, newHeight);
      //log(ctx);
      newDataUrl = canvas.toDataURL(imageType, imageArguments);
      resolve(newDataUrl);
    });
  };
信息技术的使用:

minifyImg(<--DATAURL_HERE-->,<--new width-->,<--type like image/jpeg-->,(data)=>{
   console.log(data); // the new DATAURL
});

享受

与公认的答案相比,我发现有更简单的解决方案

使用HTML5 FileReader API和.readAsArrayBuffer读取文件 使用文件数据创建Blob,并使用window.url.createObjectURLblob获取其url 创建新的Image元素并将其src设置为文件blob url 将图像发送到画布。画布大小设置为所需的输出大小 通过canvas.toDataURLimage/jpeg,0.7设置您自己的输出格式和质量,从画布获取缩小的数据 将新的隐藏输入附加到原始表单,并基本上像普通文本一样传输dataURI图像 在后端,读取dataURI,从Base64解码并保存它 根据你的问题:

有没有一种方法可以压缩图像(主要是jpeg、png和gif) 直接在浏览器端,然后再上传

我的解决方案:

使用URL.createObjectURLinputFileElement.files[0]直接创建具有文件的blob

与接受的答案相同

与接受的答案相同。值得一提的是,canvas size是必需的,使用img.width和img.height设置canvas.width和canvas.height。不是img.clientWidth

通过canvas.toBlobcallbackfunctionblob{},'image/jpeg',0.5获取缩放图像。设置“image/jpg”无效。还支持image/png。使用let compressedImageBlob=new File[blob]在callbackfunction主体内创建一个新文件对象

添加新的隐藏输入或。服务器不需要解码任何东西

检查所有信息。读完这一章后,我想出了解决办法

代码:

3.4MB.png文件压缩测试,带有image/jpeg参数集

    |0.9| 777KB |
    |0.8| 383KB |
    |0.7| 301KB |
    |0.6| 251KB |
    |0.5| 219kB |
Compressor.js

从“axios”导入axios; 从“compressorjs”导入压缩机; document.getElementById'file'.addEventListener'change',e=>{ const file=e.target.files[0]; 如果!文件{ 回来 } 新的压缩文件{ 质量:0.6, //压缩过程是异步的, //这意味着您必须访问“success”钩子函数中的“result”。 成功结果{ const formData=新的formData; //服务器需要第三个参数 追加'file',result,result.name; //使用XMLHttpRequest将压缩图像文件发送到服务器。 axios.post'/path/to/upload',formData.then=>{ 控制台。记录“上传成功”; }; }, 错误{ console.logerr.message; }, };
};@NicholasKyriakides我可以确认canvas.ToDaturlImage/jpeg,0.7有效地压缩它,它以70质量保存jpeg,而不是默认的100质量。@Nicholas Kyriakides,这不是很好的区分。大多数编解码器都不是无损的,因此它们将适合您的缩小定义,即您无法恢复到100。缩小是指在高度和宽度方面使图像尺寸更小。这真的是压缩。这是有损压缩,但肯定是压缩。它并没有缩小像素的大小,只是将一些像素移动到相同的颜色,以便压缩可以在更少的比特数内达到这些颜色。JPEG已经为像素内置了压缩功能,但是在有损模式下,它说一些颜色可以被称为相同的颜色。那还是压缩。关于图形的缩小通常是指实际大小的改变。我只想说:文件可以直接转到URL.createObjectUrl,而不会将文件变成blob;该文件算作一个blob。我为Psychood的代码示例添加了一个演示器JSFIDLE(如果有人需要):在不降低qualityplease的情况下,你能给我更多关于这个示例的细节吗?如何调用函数以及如何返回结果?这里有一个示例:一定要阅读页面的源代码,看看它是如何工作的。对于其他想阅读@DanielAllenLangdonAs建议的链接的人来说,有时image.width/height会返回0,因为它没有加载。您可能需要将其转换为一个异步函数,并侦听image.onload以获得具有和高度的正确图像。ToBlob为我完成了这项任务,创建了一个文件并在服务器上的$\u FILES数组中接收。非常感谢。看起来很棒!但这是否适用于所有浏览器,网络和手机?让我们忽略IEA也许在您发布此消息后发生了一些变化,但您提到的该函数的第二个参数是您要应用的压缩量。我将添加此函数的质量、分辨率和图像类型格式的说明。请添加一些有关链接资源的信息演示页面链接已断开。您可以在这里测试:我喜欢您的解决方案,但我们如何保存EXIF数据?
    function upload(){
        var f = fileToUpload.files[0];
        var fileName = f.name.split('.')[0];
        var img = new Image();
        img.src = URL.createObjectURL(f);
        img.onload = function(){
            var canvas = document.createElement('canvas');
            canvas.width = img.width;
            canvas.height = img.height;
            var ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0);
            canvas.toBlob(function(blob){
                    console.info(blob.size);
                    var f2 = new File([blob], fileName + ".jpeg");
                    var xhr = new XMLHttpRequest();
                    var form = new FormData();
                    form.append("fileToUpload", f2);
                    xhr.open("POST", "upload.php");
                    xhr.send(form);
            }, 'image/jpeg', 0.5);
        }
    }
    |0.9| 777KB |
    |0.8| 383KB |
    |0.7| 301KB |
    |0.6| 251KB |
    |0.5| 219kB |