【问题标题】:Resizing an image in an HTML5 canvas在 HTML5 画布中调整图像大小
【发布时间】:2024-05-23 10:15:02
【问题描述】:

我正在尝试使用 javascript 和画布元素在客户端创建缩略图,但是当我缩小图像时,它看起来很糟糕。它看起来好像在 Photoshop 中缩小了尺寸,将重采样设置为“最近邻”而不是双三次。我知道有可能让它看起来正确,因为this site 也可以使用画布很好地做到这一点。我尝试使用与“[Source]”链接中显示的相同的代码,但它看起来仍然很糟糕。有什么我遗漏的,需要设置的设置吗?

编辑:

我正在尝试调整 jpg 的大小。我尝试在链接网站和 Photoshop 中调整相同 jpg 的大小,缩小后看起来还不错。

以下是相关代码:

reader.onloadend = function(e)
{
    var img = new Image();
    var ctx = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");

    img.onload = function()
    {
        var ratio = 1;

        if(img.width > maxWidth)
            ratio = maxWidth / img.width;
        else if(img.height > maxHeight)
            ratio = maxHeight / img.height;

        canvasCopy.width = img.width;
        canvasCopy.height = img.height;
        copyContext.drawImage(img, 0, 0);

        canvas.width = img.width * ratio;
        canvas.height = img.height * ratio;
        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
    };

    img.src = reader.result;
}

EDIT2:

似乎我弄错了,链接的网站在缩小图像尺寸方面并没有做得更好。我尝试了建议的其他方法,但没有一个看起来更好。这就是不同方法的结果:

Photoshop:

画布:

带有图像渲染的图像:optimizeQuality 设置并按宽度/高度缩放:

带有图像渲染的图像:optimizeQuality 设置并使用 -moz-transform 缩放:

在 pixastic 上调整画布大小:

我想这意味着 firefox 没有像预期的那样使用双三次采样。我只需要等到他们真正添加它。

EDIT3:

Original Image

【问题讨论】:

  • 你能贴出你用来调整图片大小的代码吗?
  • 您是否尝试使用有限的调色板调整 GIF 图像或类似图像的大小?即使在 Photoshop 中,这些图像也不能很好地按比例缩小,除非您将它们转换为 RGB。
  • 可以发一份原图吗?
  • 使用 javascript 调整图像大小有点麻烦 - 您不仅使用客户端处理能力来调整图像大小,而且在每次加载页面时都会这样做。为什么不直接从 Photoshop 中保存一个缩小的版本,然后将其与原始图像一起提供/与原始图像一起提供?
  • 因为我正在制作一个能够在上传之前调整大小和裁剪图像的图像上传器。

标签: javascript html canvas image-resizing


【解决方案1】:

如果所有浏览器(实际上,Chrome 5 给了我一个相当不错的浏览器)都不能给你足够好的重采样质量,你会怎么做?然后你自己实现它们!哦,来吧,我们正在进入 Web 3.0 的新时代,兼容 HTML5 的浏览器,超级优化的 JIT javascript 编译器,多核(†)机器,拥有大量内存,你害怕什么?哎,javascript里面有java这个词,应该保证性能吧?看,缩略图生成代码:

// returns a function that calculates lanczos weight
function lanczosCreate(lobes) {
    return function(x) {
        if (x > lobes)
            return 0;
        x *= Math.PI;
        if (Math.abs(x) < 1e-16)
            return 1;
        var xx = x / lobes;
        return Math.sin(x) * Math.sin(xx) / x / xx;
    };
}

// elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes) {
    this.canvas = elem;
    elem.width = img.width;
    elem.height = img.height;
    elem.style.display = "none";
    this.ctx = elem.getContext("2d");
    this.ctx.drawImage(img, 0, 0);
    this.img = img;
    this.src = this.ctx.getImageData(0, 0, img.width, img.height);
    this.dest = {
        width : sx,
        height : Math.round(img.height * sx / img.width),
    };
    this.dest.data = new Array(this.dest.width * this.dest.height * 3);
    this.lanczos = lanczosCreate(lobes);
    this.ratio = img.width / sx;
    this.rcp_ratio = 2 / this.ratio;
    this.range2 = Math.ceil(this.ratio * lobes / 2);
    this.cacheLanc = {};
    this.center = {};
    this.icenter = {};
    setTimeout(this.process1, 0, this, 0);
}

thumbnailer.prototype.process1 = function(self, u) {
    self.center.x = (u + 0.5) * self.ratio;
    self.icenter.x = Math.floor(self.center.x);
    for (var v = 0; v < self.dest.height; v++) {
        self.center.y = (v + 0.5) * self.ratio;
        self.icenter.y = Math.floor(self.center.y);
        var a, r, g, b;
        a = r = g = b = 0;
        for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
            if (i < 0 || i >= self.src.width)
                continue;
            var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
            if (!self.cacheLanc[f_x])
                self.cacheLanc[f_x] = {};
            for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                if (j < 0 || j >= self.src.height)
                    continue;
                var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                if (self.cacheLanc[f_x][f_y] == undefined)
                    self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2)
                            + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                weight = self.cacheLanc[f_x][f_y];
                if (weight > 0) {
                    var idx = (j * self.src.width + i) * 4;
                    a += weight;
                    r += weight * self.src.data[idx];
                    g += weight * self.src.data[idx + 1];
                    b += weight * self.src.data[idx + 2];
                }
            }
        }
        var idx = (v * self.dest.width + u) * 3;
        self.dest.data[idx] = r / a;
        self.dest.data[idx + 1] = g / a;
        self.dest.data[idx + 2] = b / a;
    }

    if (++u < self.dest.width)
        setTimeout(self.process1, 0, self, u);
    else
        setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self) {
    self.canvas.width = self.dest.width;
    self.canvas.height = self.dest.height;
    self.ctx.drawImage(self.img, 0, 0, self.dest.width, self.dest.height);
    self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
    var idx, idx2;
    for (var i = 0; i < self.dest.width; i++) {
        for (var j = 0; j < self.dest.height; j++) {
            idx = (j * self.dest.width + i) * 3;
            idx2 = (j * self.dest.width + i) * 4;
            self.src.data[idx2] = self.dest.data[idx];
            self.src.data[idx2 + 1] = self.dest.data[idx + 1];
            self.src.data[idx2 + 2] = self.dest.data[idx + 2];
        }
    }
    self.ctx.putImageData(self.src, 0, 0);
    self.canvas.style.display = "block";
};

...你可以用它产生这样的结果!

无论如何,这是您示例的“固定”版本:

img.onload = function() {
    var canvas = document.createElement("canvas");
    new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
    // but feel free to raise it up to 8. Your client will appreciate
    // that the program makes full use of his machine.
    document.body.appendChild(canvas);
};

现在是时候将您最好的浏览器放到那里,看看哪一个最不可能增加您客户的血压!

嗯,我的讽刺标签呢?

(由于代码的很多部分都是基于Anrieff Gallery Generator 是否也包含在 GPL2 中?我不知道)

实际上由于javascript的限制,不支持多核。

【讨论】:

  • 我实际上尝试过自己实现它,像你一样,从开源图像编辑器复制代码。由于我无法找到有关该算法的任何可靠文档,因此我很难对其进行优化。最后,我的速度有点慢(花了几秒钟来调整图像大小)。当我有机会时,我会试试你的,看看它是否更快。而且我认为网络工作者现在使多核 javascript 成为可能。我打算尝试使用它们来加快速度,但我无法弄清楚如何将其变成多线程算法
  • 抱歉,忘记了!我已经编辑了回复。无论如何它不会很快,双三次应该更快。更不用说我使用的算法不是通常的 2 向调整大小(逐行,水平然后垂直),所以它的速度要慢一些。
  • 你太棒了,应该得到无数的赞誉。
  • 这会产生不错的结果,但在最新版本的 Chrome 中,1.8 MP 图像需要 7.4 秒...
  • 这样的方法是如何达到如此高分的?显示的解决方案完全无法考虑用于存储颜色信息的对数刻度。 RGB 127,127,127 是 255、255、255 亮度的四分之一,而不是一半。解决方案中的下采样导致图像变暗。遗憾的是,这已关闭,因为有一种非常简单快捷的缩小尺寸的方法,可以产生比 Photoshop 更好的结果(OP 必须设置错误的首选项)示例
【解决方案2】:

使用 Hermite 过滤器和 JavaScript 快速调整图像大小/重新采样算法。支持透明度,提供良好的质量。预习:


更新:在 GitHub 上添加了 2.0 版(更快,网络工作者 + 可转移对象)。最后我让它工作了!

Git:https://github.com/viliusle/Hermite-resize
演示:http://viliusle.github.io/miniPaint/

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}

【讨论】:

  • 也许您可以包含指向您的 miniPaint 演示和 Github 存储库的链接?
  • 你也会分享 webworkers 版本吗?可能是由于设置开销的原因,对于小图像来说速度较慢,但​​对于较大的源图像可能很有用。
  • 添加了demo,git链接,还有多核版本。顺便说一句,我没有花太多时间优化多核版本……我相信单版本优化得很好。
  • 巨大的差异和不错的表现。非常感谢! beforeafter
  • @ViliusL 啊,现在我想起了为什么网络工作者不能很好地工作。他们以前没有共享内存,现在仍然没有!也许有一天当他们设法解决它时,你的代码会开始使用(或者人们使用 PNaCl 代替)
【解决方案3】:

试试pica - 这是一个高度优化的大小调整器,具有可选的算法。见demo

例如,使用 Lanczos 过滤器和 3px 窗口或 60ms 使用 Box 过滤器和 0.5px 窗口调整第一篇文章的原始图像的大小。对于 17mb 的巨大图像,5000x3000px 调整大小在台式机上大约需要 1 秒,在移动设备上需要 3 秒。

所有调整大小的原则在这个帖子中都被很好地描述了,而且 pica 没有添加火箭科学。但它针对现代 JIT-s 进行了很好的优化,并且可以开箱即用(通过 npm 或 bower)。此外,它在可用时使用网络工作者以避免界面冻结。

我还计划尽快添加反锐化蒙版支持,因为它在缩小后非常有用。

【讨论】:

    【解决方案4】:

    我知道这是一个旧线程,但它可能对某些人(例如我自己)在几个月后第一次遇到此问题后有用。

    这里有一些代码可以在您每次重新加载图像时调整图像大小。我知道这根本不是最优的,但我提供它作为概念证明。

    另外,很抱歉将 jQuery 用于简单的选择器,但我觉得语法太舒服了。

    $(document).on('ready', createImage);
    $(window).on('resize', createImage);
    
    var createImage = function(){
      var canvas = document.getElementById('myCanvas');
      canvas.width = window.innerWidth || $(window).width();
      canvas.height = window.innerHeight || $(window).height();
      var ctx = canvas.getContext('2d');
      img = new Image();
      img.addEventListener('load', function () {
        ctx.drawImage(this, 0, 0, w, h);
      });
      img.src = 'http://www.ruinvalor.com/Telanor/images/original.jpg';
    };
    html, body{
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
      background: #000;
    }
    canvas{
      position: absolute;
      left: 0;
      top: 0;
      z-index: 0;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>Canvas Resize</title>
      </head>
      <body>
        <canvas id="myCanvas"></canvas>
      </body>
    </html>

    我的 createImage 函数在加载文档时调用一次,之后每次窗口接收到调整大小事件时都会调用它。

    我在 Mac 上的 Chrome 6 和 Firefox 3.6 中对其进行了测试。这种“技术”就像夏天吃冰淇淋一样吃处理器,但它确实成功了。

    【讨论】:

      【解决方案5】:

      我已经提出了一些算法来对 html 画布像素数组进行图像插值,这可能在这里有用:

      https://web.archive.org/web/20170104190425/http://jsperf.com:80/pixel-interpolation/2

      这些可以复制/粘贴,并且可以在网络工作者内部使用来调整图像大小(或任何其他需要插值的操作 - 我现在正在使用它们来去除图像)。

      我没有添加上面的 lanczos 的东西,所以如果你愿意,可以随意添加它作为比较。

      【讨论】:

        【解决方案6】:

        这是一个改编自@Telanor 代码的javascript 函数。将图像 base64 作为第一个参数传递给函数时,它返回调整大小图像的 base64。 maxWidth 和 maxHeight 是可选的。

        function thumbnail(base64, maxWidth, maxHeight) {
        
          // Max size for thumbnail
          if(typeof(maxWidth) === 'undefined') var maxWidth = 500;
          if(typeof(maxHeight) === 'undefined') var maxHeight = 500;
        
          // Create and initialize two canvas
          var canvas = document.createElement("canvas");
          var ctx = canvas.getContext("2d");
          var canvasCopy = document.createElement("canvas");
          var copyContext = canvasCopy.getContext("2d");
        
          // Create original image
          var img = new Image();
          img.src = base64;
        
          // Determine new ratio based on max size
          var ratio = 1;
          if(img.width > maxWidth)
            ratio = maxWidth / img.width;
          else if(img.height > maxHeight)
            ratio = maxHeight / img.height;
        
          // Draw original image in second canvas
          canvasCopy.width = img.width;
          canvasCopy.height = img.height;
          copyContext.drawImage(img, 0, 0);
        
          // Copy and resize second canvas to first canvas
          canvas.width = img.width * ratio;
          canvas.height = img.height * ratio;
          ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
        
          return canvas.toDataURL();
        
        }
        

        【讨论】:

        【解决方案7】:

        我强烈建议您查看 this link 并确保将其设置为 true。

        控制图像缩放行为

        在 Gecko 1.9.2 (Firefox 3.6 / 雷鸟 3.1 / Fennec 1.0)

        Gecko 1.9.2 引入了 mozImageSmoothingEnabled 属性 画布元素;如果这个布尔值 值为假,图像不会 缩放时平滑。该物业是 默认情况下为真。查看普通版?

        1. cx.mozImageSmoothingEnabled = false;

        【讨论】:

          【解决方案8】:

          我通过右键单击 Firefox 中的画布元素并另存为来获得此图像。

          var img = new Image();
          img.onload = function () {
              console.debug(this.width,this.height);
              var canvas = document.createElement('canvas'), ctx;
              canvas.width = 188;
              canvas.height = 150;
              document.body.appendChild(canvas);
              ctx = canvas.getContext('2d');
              ctx.drawImage(img,0,0,188,150);
          };
          img.src = 'original.jpg';
          

          无论如何,这是您示例的“固定”版本:

          var img = new Image();
          // added cause it wasnt defined
          var canvas = document.createElement("canvas");
          document.body.appendChild(canvas);
          
          var ctx = canvas.getContext("2d");
          var canvasCopy = document.createElement("canvas");
          // adding it to the body
          
          document.body.appendChild(canvasCopy);
          
          var copyContext = canvasCopy.getContext("2d");
          
          img.onload = function()
          {
                  var ratio = 1;
          
                  // defining cause it wasnt
                  var maxWidth = 188,
                      maxHeight = 150;
          
                  if(img.width > maxWidth)
                          ratio = maxWidth / img.width;
                  else if(img.height > maxHeight)
                          ratio = maxHeight / img.height;
          
                  canvasCopy.width = img.width;
                  canvasCopy.height = img.height;
                  copyContext.drawImage(img, 0, 0);
          
                  canvas.width = img.width * ratio;
                  canvas.height = img.height * ratio;
                  // the line to change
                  // ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
                  // the method signature you are using is for slicing
                  ctx.drawImage(canvasCopy, 0, 0, canvas.width, canvas.height);
          };
          
          // changed for example
          img.src = 'original.jpg';
          

          【讨论】:

          • 我已经尝试过你所做的事情,但结果并不像你的那样好。除非我遗漏了什么,否则您所做的唯一更改是使用缩放方法签名而不是切片方法签名,对吗?出于某种原因,它对我不起作用。
          【解决方案9】:

          如果您只是想调整图片大小,我建议您使用 CSS 设置图片的 widthheight。这是一个简单的例子:

          .small-image {
              width: 100px;
              height: 100px;
          }
          

          请注意,heightwidth 也可以使用 JavaScript 设置。这是快速代码示例:

          var img = document.getElement("my-image");
          img.style.width = 100 + "px";  // Make sure you add the "px" to the end,
          img.style.height = 100 + "px"; // otherwise you'll confuse IE
          

          另外,为确保调整后的图片看起来不错,请在图片选择器中添加以下 css 规则:

          据我所知,除了 IE 之外的所有浏览器都默认使用双三次算法来调整图像大小,因此您调整大小的图像在 Firefox 和 Chrome 中应该看起来不错。

          如果设置 css widthheight 不起作用,您可能需要使用 css transform

          如果您出于某种原因需要使用画布,请注意有两种方法可以调整图像的大小:使用 css 调整画布的大小或以较小的尺寸绘制图像。

          更多详情请参阅this question

          【讨论】:

          • 遗憾的是,无论是调整画布的大小还是以较小的尺寸绘制图像都不能解决问题(在 Chrome 中)。
          • Chrome 27 生成了很好的调整大小的图像,但您无法将结果复制到画布;尝试这样做将改为复制原始图像。
          【解决方案10】:

          为了调整宽度小于原始图像的大小,我使用:

              function resize2(i) {
                var cc = document.createElement("canvas");
                cc.width = i.width / 2;
                cc.height = i.height / 2;
                var ctx = cc.getContext("2d");
                ctx.drawImage(i, 0, 0, cc.width, cc.height);
                return cc;
              }
              var cc = img;
              while (cc.width > 64 * 2) {
                cc = resize2(cc);
              }
              // .. than drawImage(cc, .... )
          

          它有效 =)。

          【讨论】:

            【解决方案11】:

            我感觉我编写的模块会产生与 Photoshop 相似的结果,因为它通过对颜色数据进行平均而不是应用算法来保留颜色数据。这有点慢,但对我来说是最好的,因为它保留了所有颜色数据。

            https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

            它不会取最近的邻居并丢弃其他像素,也不会采样一组并取随机平均值。它采用每个源像素应输出到目标像素的确切比例。源中的平均像素颜色将是目标中的平均像素颜色,我认为这些其他公式不会。

            如何使用的示例在底部 https://github.com/danschumann/limby-resize

            2018 年 10 月更新:这些天我的例子比其他任何东西都更具学术性。 Webgl 几乎是 100%,所以你最好调整大小以产生类似的结果,但速度更快。我相信 PICA.js 可以做到这一点。 ——

            【讨论】:

              【解决方案12】:

              其中一些解决方案的问题在于它们直接访问像素数据并循环通过它来执行下采样。根据图像的大小,这可能会占用大量资源,最好使用浏览器的内部算法。

              drawImage() 函数使用线性插值、最近邻重采样方法。 当您不将大小调整到原始大小的一半以上时效果很好

              如果您循环一次只调整最大一半的大小,结果会非常好,并且比访问像素数据快得多。

              此函数一次将采样减少一半,直到达到所需的大小:

                function resize_image( src, dst, type, quality ) {
                   var tmp = new Image(),
                       canvas, context, cW, cH;
              
                   type = type || 'image/jpeg';
                   quality = quality || 0.92;
              
                   cW = src.naturalWidth;
                   cH = src.naturalHeight;
              
                   tmp.src = src.src;
                   tmp.onload = function() {
              
                      canvas = document.createElement( 'canvas' );
              
                      cW /= 2;
                      cH /= 2;
              
                      if ( cW < src.width ) cW = src.width;
                      if ( cH < src.height ) cH = src.height;
              
                      canvas.width = cW;
                      canvas.height = cH;
                      context = canvas.getContext( '2d' );
                      context.drawImage( tmp, 0, 0, cW, cH );
              
                      dst.src = canvas.toDataURL( type, quality );
              
                      if ( cW <= src.width || cH <= src.height )
                         return;
              
                      tmp.src = dst.src;
                   }
              
                }
                // The images sent as parameters can be in the DOM or be image objects
                resize_image( $( '#original' )[0], $( '#smaller' )[0] );
              

              感谢this post

              【讨论】:

                【解决方案13】:

                我不久前在使用画布时发现了一些有趣的东西,可能会有所帮助:

                要自行调整画布控件的大小,您需要使用height=""width="" 属性(或canvas.width/canvas.height 元素)。如果您使用 CSS 调整画布大小,它实际上会拉伸(即:调整大小)画布的内容以适应整个画布(而不是简单地增加或减少画布的面积。

                值得尝试将图像绘制到画布控件中,并将高度和宽度属性设置为图像的大小,然后使用 CSS 将画布大小调整为所需的大小。也许这会使用不同的调整大小算法。

                还需要注意的是,canvas在不同的浏览器(甚至不同浏览器的不同版本)下的效果是不一样的。浏览器中使用的算法和技术可能会随着时间而改变(尤其是 Firefox 4 和 Chrome 6 即将推出,这将非常重视画布渲染性能)。

                此外,您可能还想试一试 SVG,因为它也可能使用不同的算法。

                祝你好运!

                【讨论】:

                • 通过 HTML 属性设置画布的宽度或高度会导致画布被清除,因此无法使用该方法调整大小。此外,SVG 用于处理数学图像。我需要能够绘制 PNG 等,所以这对我没有帮助。
                • 设置画布的高度和宽度并使用 CSS 调整大小并没有帮助,我发现(在 Chrome 中)。即使使用 -webkit-transform 而不是 CSS 宽度/高度来调整大小也无法进行插值。
                【解决方案14】:

                快速简单的 Javascript 图像缩放器:

                https://github.com/calvintwr/blitz-hermite-resize

                const blitz = Blitz.create()
                
                /* Promise */
                blitz({
                    source: DOM Image/DOM Canvas/jQuery/DataURL/File,
                    width: 400,
                    height: 600
                }).then(output => {
                    // handle output
                })catch(error => {
                    // handle error
                })
                
                /* Await */
                let resized = await blizt({...})
                
                /* Old school callback */
                const blitz = Blitz.create('callback')
                blitz({...}, function(output) {
                    // run your callback.
                })
                

                历史

                这真的是经过多轮研究、阅读和尝试。

                调整大小算法使用@ViliusL 的 Hermite 脚本(Hermite 调整大小确实是最快的,并且输出相当不错)。扩展了您需要的功能。

                派出 1 个工作人员来调整大小,这样它就不会在调整大小时冻结您的浏览器,这与所有其他 JS 调整大小不同。

                【讨论】:

                  【解决方案15】:

                  我将@syockit 的答案以及降级方法转换为可重用的 Angular 服务,供任何感兴趣的人使用:https://gist.github.com/fisch0920/37bac5e741eaec60e983

                  我将这两种解决方案都包括在内,因为它们都有各自的优缺点。 lanczos 卷积方法以较慢为代价获得更高质量,而逐步缩小方法可产生合理的抗锯齿结果,并且速度明显更快。

                  示例用法:

                  angular.module('demo').controller('ExampleCtrl', function (imageService) {
                    // EXAMPLE USAGE
                    // NOTE: it's bad practice to access the DOM inside a controller, 
                    // but this is just to show the example usage.
                  
                    // resize by lanczos-sinc filter
                    imageService.resize($('#myimg')[0], 256, 256)
                      .then(function (resizedImage) {
                        // do something with resized image
                      })
                  
                    // resize by stepping down image size in increments of 2x
                    imageService.resizeStep($('#myimg')[0], 256, 256)
                      .then(function (resizedImage) {
                        // do something with resized image
                      })
                  })
                  

                  【讨论】:

                    【解决方案16】:

                    感谢@syockit 的精彩回答。但是,我必须按如下方式重新格式化以使其正常工作。可能是由于 DOM 扫描问题:

                    $(document).ready(function () {
                    
                    $('img').on("load", clickA);
                    function clickA() {
                        var img = this;
                        var canvas = document.createElement("canvas");
                        new thumbnailer(canvas, img, 50, 3);
                        document.body.appendChild(canvas);
                    }
                    
                    function thumbnailer(elem, img, sx, lobes) {
                        this.canvas = elem;
                        elem.width = img.width;
                        elem.height = img.height;
                        elem.style.display = "none";
                        this.ctx = elem.getContext("2d");
                        this.ctx.drawImage(img, 0, 0);
                        this.img = img;
                        this.src = this.ctx.getImageData(0, 0, img.width, img.height);
                        this.dest = {
                            width: sx,
                            height: Math.round(img.height * sx / img.width)
                        };
                        this.dest.data = new Array(this.dest.width * this.dest.height * 3);
                        this.lanczos = lanczosCreate(lobes);
                        this.ratio = img.width / sx;
                        this.rcp_ratio = 2 / this.ratio;
                        this.range2 = Math.ceil(this.ratio * lobes / 2);
                        this.cacheLanc = {};
                        this.center = {};
                        this.icenter = {};
                        setTimeout(process1, 0, this, 0);
                    }
                    
                    //returns a function that calculates lanczos weight
                    function lanczosCreate(lobes) {
                        return function (x) {
                            if (x > lobes)
                                return 0;
                            x *= Math.PI;
                            if (Math.abs(x) < 1e-16)
                                return 1
                            var xx = x / lobes;
                            return Math.sin(x) * Math.sin(xx) / x / xx;
                        }
                    }
                    
                    process1 = function (self, u) {
                        self.center.x = (u + 0.5) * self.ratio;
                        self.icenter.x = Math.floor(self.center.x);
                        for (var v = 0; v < self.dest.height; v++) {
                            self.center.y = (v + 0.5) * self.ratio;
                            self.icenter.y = Math.floor(self.center.y);
                            var a, r, g, b;
                            a = r = g = b = 0;
                            for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
                                if (i < 0 || i >= self.src.width)
                                    continue;
                                var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
                                if (!self.cacheLanc[f_x])
                                    self.cacheLanc[f_x] = {};
                                for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
                                    if (j < 0 || j >= self.src.height)
                                        continue;
                                    var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
                                    if (self.cacheLanc[f_x][f_y] == undefined)
                                        self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
                                    weight = self.cacheLanc[f_x][f_y];
                                    if (weight > 0) {
                                        var idx = (j * self.src.width + i) * 4;
                                        a += weight;
                                        r += weight * self.src.data[idx];
                                        g += weight * self.src.data[idx + 1];
                                        b += weight * self.src.data[idx + 2];
                                    }
                                }
                            }
                            var idx = (v * self.dest.width + u) * 3;
                            self.dest.data[idx] = r / a;
                            self.dest.data[idx + 1] = g / a;
                            self.dest.data[idx + 2] = b / a;
                        }
                    
                        if (++u < self.dest.width)
                            setTimeout(process1, 0, self, u);
                        else
                            setTimeout(process2, 0, self);
                    };
                    
                    process2 = function (self) {
                        self.canvas.width = self.dest.width;
                        self.canvas.height = self.dest.height;
                        self.ctx.drawImage(self.img, 0, 0);
                        self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
                        var idx, idx2;
                        for (var i = 0; i < self.dest.width; i++) {
                            for (var j = 0; j < self.dest.height; j++) {
                                idx = (j * self.dest.width + i) * 3;
                                idx2 = (j * self.dest.width + i) * 4;
                                self.src.data[idx2] = self.dest.data[idx];
                                self.src.data[idx2 + 1] = self.dest.data[idx + 1];
                                self.src.data[idx2 + 2] = self.dest.data[idx + 2];
                            }
                        }
                        self.ctx.putImageData(self.src, 0, 0);
                        self.canvas.style.display = "block";
                    }
                    });
                    

                    【讨论】:

                      【解决方案17】:

                      我希望这里的答案中有一些定义明确的函数,所以最终得到了这些希望对其他人也有用的函数,

                      function getImageFromLink(link) {
                          return new Promise(function (resolve) {
                              var image = new Image();
                              image.onload = function () { resolve(image); };
                              image.src = link;
                          });
                      }
                      
                      function resizeImageToBlob(image, width, height, mime) {
                          return new Promise(function (resolve) {
                              var canvas = document.createElement('canvas');
                              canvas.width = width;
                              canvas.height = height;
                              canvas.getContext('2d').drawImage(image, 0, 0, width, height);
                              return canvas.toBlob(resolve, mime);
                          });
                      }
                      
                      getImageFromLink(location.href).then(function (image) {
                          // calculate these based on the original size
                          var width = image.width / 4;
                          var height = image.height / 4;
                          return resizeImageToBlob(image, width, height, 'image/jpeg');
                      }).then(function (blob) {
                          // Do something with the result Blob object
                          document.querySelector('img').src = URL.createObjectURL(blob);
                      });
                      

                      只是为了测试它在标签中打开的图像上运行它。

                      【讨论】:

                        【解决方案18】:

                        我刚刚运行了一页并排比较,除非最近发生了一些变化,否则我看不出使用画布与简单的 css 相比没有更好的缩小(缩放)。我在 FF6 Mac OSX 10.7 中进行了测试。与原版相比仍然略显柔和。

                        然而,我确实偶然发现了一些确实产生巨大影响的东西,那就是在支持画布的浏览器中使用图像过滤器。实际上,您可以像在 Photoshop 中一样使用模糊、锐化、饱和度、波纹、灰度等操作图像。

                        然后我发现了一个很棒的 jQuery 插件,它使这些过滤器的应用变得轻而易举: http://codecanyon.net/item/jsmanipulate-jquery-image-manipulation-plugin/428234

                        我只是在调整图像大小后立即应用锐化滤镜,这应该会给你想要的效果。我什至不必使用画布元素。

                        【讨论】:

                          【解决方案19】:

                          正在寻找另一个很棒的简单解决方案?

                          var img=document.createElement('img');
                          img.src=canvas.toDataURL();
                          $(img).css("background", backgroundColor);
                          $(img).width(settings.width);
                          $(img).height(settings.height);
                          

                          此方案将使用浏览器的resize算法! :)

                          【讨论】:

                          • 问题在于对图像进行下采样,而不仅仅是调整大小。
                          • [...] 我正在尝试调整 jpg 的大小。我尝试在链接网站和 Photoshop 中调整相同 jpg 的大小,缩小后看起来还不错。[...] 为什么不能使用 Jesus Carrera?
                          最近更新 更多