通过纯JS缩小图像尺寸导致图像尺寸膨胀(以字节为单位)

IT技术 javascript
2021-01-16 12:51:04

我是一名服务器端开发人员,从纯 JS 开始学习客户端操作的技巧。

目前我正在使用纯 JS 来调整通过浏览器上传的图像的尺寸。

我遇到了这样一种情况:将 1018 x 1529.jpg文件缩小到 400 x 601.jpeg会产生更大尺寸(以字节为单位)的文件。它从70013字节到74823字节。

我的期望是应该缩小规模,而不是通胀这是怎么回事,有没有办法修补这种情况?

注意:让我特别困惑的一点是,每个图像的压缩开始时都没有任何目标先前压缩的先验知识。因此,任何低于 100 的质量级别都会进一步降低图像质量。因此,这应该始终减小文件大小。但奇怪的是,这并没有发生?


如果需要,我的相关 JS 代码是:

var max_img_width = 400;
var wranges = [max_img_width, Math.round(0.8*max_img_width), Math.round(0.6*max_img_width),Math.round(0.4*max_img_width),Math.round(0.2*max_img_width)];

function prep_image(img_src, text, img_name, target_action, callback) { 
    var img = document.createElement('img');
    var fr = new FileReader();
    fr.onload = function(){
      var dataURL = fr.result;
      img.onload = function() {
          img_width = this.width;
          img_height = this.height;
          img_to_send = resize_and_compress(this, img_width, img_height, "image/jpeg");
          callback(text, img_name, target_action, img_to_send);
        }
      img.src = dataURL;
    };
    fr.readAsDataURL(img_src);
}


function resize_and_compress(source_img, img_width, img_height, mime_type){
    var new_width;
    switch (true) {
      case img_width < wranges[4]:
         new_width = wranges[4];
         break;
      case img_width < wranges[3]:
         new_width = wranges[4];
         break;
      case img_width < wranges[2]:
         new_width = wranges[3];
         break;
      case img_width < wranges[1]:
         new_width = wranges[2];
         break;
      case img_width < wranges[0]:
         new_width = wranges[1];
         break;
      default:
         new_width = wranges[0];
         break;
    }
    var wpercent = (new_width/img_width);
    var new_height = Math.round(img_height*wpercent);
    var canvas = document.createElement('canvas');//supported
    canvas.width = new_width;
    canvas.height = new_height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(source_img, 0, 0, new_width, new_height);
    return dataURItoBlob(canvas.toDataURL(mime_type),mime_type);
}

// converting image data uri to a blob object
function dataURItoBlob(dataURI,mime_type) {
  var byteString = atob(dataURI.split(',')[1]);
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);//supported
  for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); }
  return new Blob([ab], { type: mime_type });
}

如果有必要,这是我使用的测试图像:

在此处输入图片说明

这是图像的原始位置。

请注意,对于我尝试的其他几个图像,代码确实按预期运行。它并不总是搞砸结果,但现在我不能确定它总是有效。对于这个问题的范围,让我们坚持使用纯 JS 解决方案。

2个回答

为什么 Canvas 不是缩小图像文件大小的最佳选择。

我不会详细介绍,也不会深入解释,但我会尽量向您解释您遇到的基础知识。

以下是您需要(至少部分)了解的一些概念。

  • 什么是有损图像格式(如 JPEG)
  • 将图像绘制到画布时会发生什么
  • 将画布图像导出为图像格式时会发生什么

有损图像格式

图像格式可以分为三类:

  • 原始图像格式
  • 无损图像格式(tiff、png、gif、bmp、webp ...)
  • 有损图像格式(jpeg,...)

无损图像格式通常简单地压缩表中的数据,将像素颜色映射到使用该颜色的像素位置。

另一方面,有损图像格式将丢弃信息并从原始图像中产生数据的近似值(伪影),以便使用较少的数据创建感知上相似的图像渲染。

近似(工件)之所以起作用,是因为解压缩算法知道它必须在给定区域上传播颜色信息,因此它不必保留每个像素信息。

但是一旦算法处理了原始图像并生成了新图像,就无法找回丢失的数据。


在画布上绘制图像。

当您在画布上绘制图像时,浏览器会将图像信息转换为原始图像格式
它不会存储有关传递给它的图像格式的任何信息,并且在有损图像的情况下,工件中包含的每个像素都将像其他像素一样成为一等公民。


导出画布图像

画布 2D API 具有三种导出其原始数据的方法:

  • 获取图像数据这将返回原始像素 RGBA 值
  • 到数据网址这将同步应用与您作为参数传递的 MIME 相对应的压缩算法。
  • toBlob类似于toDataURL,但异步。

我们感兴趣的情况下的一个toDataURL,并toBlob与一起"image/jpeg"MIME。
请记住,在调用此方法时,浏览器只会看到它在画布上的当前原始像素数据。所以它将再次应用 jpeg 算法,删除一些数据,并从这个原始图像中产生新的近似值(伪影)。

所以,是的,quality在这些方法中有一个 0-1参数可用于有损压缩,所以人们可能会认为我们可以尝试知道用于生成原始图像的原始损失级别是多少,但即便如此,因为我们实际上产生了画布步骤中的新图像数据,算法可能无法为这些伪影生成良好的传播方案。

另一件需要考虑的事情,主要是对于toDataURL,浏览器在执行这些操作时必须尽可能快,因此他们通常更喜欢速度而不是压缩质量。


好吧,画布不适合它。然后怎样呢?

jpeg 图像不是那么容易...... jpegtran声称它可以对你的 jpeg 图像进行无损缩放,所以我想也应该可以制作一个 js 端口,但我不知道任何......



关于无损格式的特别说明

请注意,您的调整大小算法也可以生成更大的 png 文件,这是一个示例,但我会让读者猜测为什么会发生这种情况:

@Basj 请不要再把我没说过的话放在我嘴里。我从来没有说过他们确实有糟糕的实现,也没有说它会比 MS Paint 产生更多的人工制品,我说他们会为了更快的结果而牺牲压缩质量。请记住 toDataURL 是一种同步方法。他们不能做一个会卡住 UI 5 秒的多通道。其他软件,如 Photoshop、Gimp,可能还有 ImageMagic 甚至 MS Paint 不会那么在意,通常会提供“更好的压缩,更慢”的选项。画布 API 没有这样的选项。
2021-03-22 12:51:04
您从哪里知道著名的浏览器(例如 FF 的 Chrome)在内部对 JPEG 压缩算法的实现很差,toDataURL从而导致比“普通”编辑器所做的更多的人工制品?(假设toDataURLJPG 压缩与 MS Paint JPG 压缩)我可以相信这一点,但是看到 Chromium 的基准/或源代码显示 JPEG 算法实施不当的地方会很酷。
2021-03-26 12:51:04
TL;DR 是“不要将已经是 JPG 的内容重新编码为 JPG(有损压缩),因为它会添加更多伪像”我不太同意这个答案:有时没有其他方法可以使 JPG 比重新编码更小。所以是的,当您有 2MB JPG 时,将其重新导出为质量 = 60% 的 JPG 似乎完全可以得到 400KB JPG :)
2021-03-29 12:51:04
@Basj 我认为这个答案中没有这样的 TL;DR。TL; DR 如果应该有一个,宁愿是,不要在画布上做,因为它更喜欢速度而不是压缩的 wuality。
2021-04-03 12:51:04
1/2 我没有在@Kaiido 说你的话,我只是想理解你的意思是“它更喜欢速度而不是压缩质量”更喜欢“速度胜过质量”意味着质量低于其他软件的预期。再一次,我相信你Other softs [...] won't care as much, and will generally offer an option for "better compression, slower",但我希望看到一个基准或来源,确认它var fullQuality = canvas.toDataURL('image/jpeg', 1.0);比其他软件的压缩更糟糕。
2021-04-08 12:51:04

这是一个建议,而不是真正的修复(或解决方案)。

如果遇到此问题,请确保在完成调整大小操作后比较两个图像的文件大小。如果新文件较大,则只需回退到源图像。

在评估图像以调整大小时,这是一个有趣的(但未经证实的)经验法则:stackoverflow.com/a/26509546/4936905
2021-04-01 12:51:04