I'm trying to create a thumbnail image on the client side using javascript and a canvas element, but when I shrink the image down, it looks terrible. It looks as if it was downsized in photoshop with the resampling set to 'Nearest Neighbor' instead of Bicubic. I know its possible to get this to look right, because this site can do it just fine using a canvas as well. I've tried using the same code they do as shown in the "[Source]" link, but it still looks terrible. Is there something I'm missing, some setting that needs to be set or something?

编辑:

我正在调整一张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:

看来我错了,链接网站并没有做任何更好的工作缩小图像。我尝试了其他建议的方法,没有一个看起来更好。这就是不同方法的结果:

ps:

帆布:

使用图像渲染:optimizeQuality设置并按宽度/高度缩放:

使用-moz-transform设置和缩放:

Canvas在pixastic上调整大小:

我猜这意味着firefox没有像它应该的那样使用双三次抽样。我只能等他们真的加进去了。

EDIT3:

原始图像


当前回答

如果你只是想调整图像的大小,我建议用CSS设置图像的宽度和高度。这里有一个简单的例子:

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

注意,高度和宽度也可以使用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规则:

-ms-interpolation-mode: bicubic:在IE7中引入 图像渲染:optimizeQuality:在FireFox 3.6中引入

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

如果设置css的宽度和高度不工作,你可能想玩一个css转换:

-moz-transform: scale(sx[, sy]) -webkit-transform:规模(sx (sy))

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

请参阅这个问题了解更多细节。

其他回答

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

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] );

本文致谢

这是一个javascript函数改编自@Telanor的代码。当将图像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();

}

那么,如果所有的浏览器(实际上,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的限制,不支持多核。

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

    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, .... )

它工作=)。

如果你只是想调整图像的大小,我建议用CSS设置图像的宽度和高度。这里有一个简单的例子:

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

注意,高度和宽度也可以使用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规则:

-ms-interpolation-mode: bicubic:在IE7中引入 图像渲染:optimizeQuality:在FireFox 3.6中引入

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

如果设置css的宽度和高度不工作,你可能想玩一个css转换:

-moz-transform: scale(sx[, sy]) -webkit-transform:规模(sx (sy))

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

请参阅这个问题了解更多细节。