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

时间:2018-02-05 22:22:46

标签: javascript

我是一个服务器端开发人员,从纯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 });
}

如果有必要,请参阅我使用的测试图片:

enter image description here

Here's图片的原始位置。

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

2 个答案:

答案 0 :(得分:1)

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

我不会详细介绍太多细节,也不会深入解释,但我会尽力向您解释您遇到的基本知识。

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

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

Lossy Image Format

图像格式可分为三类:

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

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

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

近似( artifacts )是有效的,因为解压缩算法知道它必须在给定区域上传播颜色信息,因此它不必保留每个像素信息。

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

将图像绘制到画布上。

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

导出画布图像

canvas 2D API有三种方法可以导出其原始数据:

  • getImageData。这将返回原始像素RGBA值
  • toDataURL。这将同步应用与您作为参数传递的MIME对应的压缩算法。
  • toBlob。与toDataURL类似,但异步。

我们感兴趣的案例是toDataURLtoBlob以及"image/jpeg" MIME。 请记住,在调用此方法时,浏览器只能看到它在画布上的当前原始像素数据。因此,它将再次应用jpeg算法,删除一些数据,并从此原始图像中生成新的近似值( artifacts )。

所以,是的,在这些方法中有一个0-1 quality参数可用于有损压缩,所以我们可以认为我们可以尝试知道用于生成原始图像的原始损失级别是什么,但即便如此,由于我们实际上在绘图到画布步骤中生成了新的图像数据,因此该算法可能无法为这些工件生成良好的扩展方案。

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

好吧,帆布不适合它。那么呢?

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

关于无损格式的特别说明

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

var ctx= c.getContext('2d');
c.width = 501;
for(var i = 0; i<500; i+=10) {
  ctx.moveTo(i+.5, 0);
  ctx.lineTo(i+.5, 150);
}
ctx.stroke();

c.toBlob(b=>console.log('original', b.size));

c2.width = 500;
c2.height = (500 / 501) * c.height;
c2.getContext('2d').drawImage(c, 0, 0, c2.width, c2.height);
c2.toBlob(b=>console.log('resized', b.size));
<canvas id="c"></canvas>
<canvas id="c2"></canvas>

答案 1 :(得分:0)

这是推荐,而不是修复(或解决方案)。

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