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:

原始图像


当前回答

快速和简单的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调整器确实是最快的,并且提供了相当好的输出)。扩展了您需要的功能。

fork 1 worker来调整大小,这样它就不会在调整大小时冻结你的浏览器,不像其他所有的JS调整器。

其他回答

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

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

尝试将图像绘制到画布控件中,并将height和width属性设置为图像的大小,然后使用CSS将画布调整为您想要的大小,这是值得一试的。也许这将使用不同的调整大小算法。

还应该注意的是,canvas在不同浏览器(甚至不同浏览器的不同版本)中具有不同的效果。在浏览器中使用的算法和技术可能会随着时间的推移而改变(特别是Firefox 4和Chrome 6很快就会出来,这将非常强调画布渲染性能)。

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

祝你好运!

快速图像调整/重采样算法使用Hermite过滤器与JavaScript。支持透明,给予优质。预览:

更新:在GitHub上添加2.0版本(更快,web工作者+可转移对象)。终于我让它工作了!

前往: 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);
}

这是一个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();

}

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

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

它工作=)。

我想从答案中得到一些定义良好的函数,所以最后得到了这些,我希望对其他人也有用,

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

为了测试这个,在一个标签中打开的图像上运行它。