Html HTML5 Canvas Resize (Downscale) Image 高质量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18922880/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 13:38:36  来源:igfitidea点击:

HTML5 Canvas Resize (Downscale) Image High Quality?

javascriptcsshtmlcanvashtml5-canvas

提问by confile

I use html5 canvas elements to resize images im my browser. It turns out that the quality is very low. I found this: Disable Interpolation when Scaling a <canvas>but it does not help to increase the quality.

我使用 html5 canvas 元素在浏览器中调整图像大小。事实证明,质量非常低。我发现了这一点:缩放 <canvas> 时禁用插值,但这无助于提高质量。

Below is my css and js code as well as the image scalled with Photoshop and scaled in the canvas API.

下面是我的 css 和 js 代码以及用 Photoshop 调用并在画布 API 中缩放的图像。

What do I have to do to get optimal quality when scaling an image in the browser?

在浏览器中缩放图像时,我该怎么做才能获得最佳质量?

Note: I want to scale down a large image to a small one, modify color in a canvas and send the result from the canvas to the server.

注意:我想将大图像缩小为小图像,修改画布中的颜色并将结果从画布发送到服务器。

CSS:

CSS:

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS:

JS:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

The image resized with photoshop:

使用photoshop调整图像大小:

enter image description here

在此处输入图片说明

The image resized on canvas:

图像在画布上调整大小:

enter image description here

在此处输入图片说明

Edit:

编辑:

I tried to make downscaling in more than one steps as proposed in:

我尝试按照以下建议的多个步骤进行缩减:

Resizing an image in an HTML5 canvasand Html5 canvas drawImage: how to apply antialiasing

在 HTML5 画布Html5 画布 drawImage 中调整图像大小:如何应用抗锯齿

This is the function I have used:

这是我使用过的功能:

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

Here is the result if I use a 2 step down sizing:

如果我使用 2 步缩小尺寸,结果如下:

enter image description here

在此处输入图片说明

Here is the result if I use a 3 step down sizing:

如果我使用 3 步缩小尺寸,结果如下:

enter image description here

在此处输入图片说明

Here is the result if I use a 4 step down sizing:

如果我使用 4 步缩小尺寸,结果如下:

enter image description here

在此处输入图片说明

Here is the result if I use a 20 step down sizing:

如果我使用 20 步缩小尺寸,结果如下:

enter image description here

在此处输入图片说明

Note: It turns out that from 1 step to 2 steps there is a large improvement in image quality but the more steps you add to the process the more fuzzy the image becomes.

注意:事实证明,从 1 步到 2 步,图像质量有很大提高,但添加到过程中的步骤越多,图像变得越模糊。

Is there a way to solve the problem that the image gets more fuzzy the more steps you add?

有没有办法解决添加的步骤越多图像越模糊的问题?

Edit 2013-10-04: I tried the algorithm of GameAlchemist. Here is the result compared to Photoshop.

编辑 2013-10-04:我尝试了 GameAlchemist 的算法。这是与 Photoshop 相比的结果。

PhotoShop Image:

图片:

PhotoShop Image

照相馆图片

GameAlchemist's Algorithm:

游戏炼金术士的算法:

GameAlchemist's Algorithm

游戏炼金术士算法

回答by GameAlchemist

Since your problem is to downscale your image, there is no point in talking about interpolation -which is about creating pixel-. The issue here is downsampling.

由于您的问题是缩小图像的尺寸,因此谈论插值(即创建像素)毫无意义。这里的问题是下采样。

To downsample an image, we need to turn each square of p * p pixels in the original image into a single pixel in the destination image.

要对图像进行下采样,我们需要将原始图像中每个 p * p 像素的正方形转换为目标图像中的单个像素。

For performances reasons Browsers do a very simple downsampling : to build the smaller image, they will just pick ONE pixel in the source and use its value for the destination. which 'forgets' some details and adds noise.

出于性能原因,浏览器做了一个非常简单的下采样:为了构建更小的图像,他们将只在源中选取一个像素并将其值用于目标。它“忘记”了一些细节并增加了噪音。

Yet there's an exception to that : since the 2X image downsampling is very simple to compute (average 4 pixels to make one) and is used for retina/HiDPI pixels, this case is handled properly -the Browser does make use of 4 pixels to make one-.

然而,有一个例外:由于 2X 图像下采样非常容易计算(平均 4 个像素来制作一个)并且用于视网膜/HiDPI 像素,这种情况得到了正确处理 - 浏览器确实使用了 4 个像素来制作一-。

BUT... if you use several time a 2X downsampling, you'll face the issue that the successive rounding errors will add too much noise.
What's worse, you won't always resize by a power of two, and resizing to the nearest power + a last resizing is very noisy.

但是...如果您多次使用 2X 下采样,您将面临连续舍入误差会增加太多噪音的问题。
更糟糕的是,您不会总是以 2 的幂调整大小,并且调整到最接近的幂 + 最后一次调整大小非常嘈杂。

What you seek is a pixel-perfect downsampling, that is : a re-sampling of the image that will take all input pixels into account -whatever the scale-.
To do that we must compute, for each input pixel, its contribution to one, two, or four destination pixels depending wether the scaled projection of the input pixels is right inside a destination pixels, overlaps an X border, an Y border, or both.
( A scheme would be nice here, but i don't have one. )

您寻求的是像素完美的下采样,即:对图像进行重新采样,将所有输入像素都考虑在内 - 无论比例如何 -。
为此,我们必须为每个输入像素计算其对一个、两个或四个目标像素的贡献,具体取决于输入像素的缩放投影是否正好位于目标像素内部、与 X 边界、Y 边界重叠,或两者兼而有之.
(这里有一个计划会很好,但我没有。)

Here's an example of canvas scale vs my pixel perfect scale on a 1/3 scale of a zombat.

这是画布比例与我的像素完美比例在 zombat 的 1/3 比例上的示例。

Notice that the picture might get scaled in your Browser, and is .jpegized by S.O..
Yet we see that there's much less noise especially in the grass behind the wombat, and the branches on its right. The noise in the fur makes it more contrasted, but it looks like he's got white hairs -unlike source picture-.
Right image is less catchy but definitively nicer.

请注意,图片可能会在您的浏览器中缩放,并由 SO 进行 .jpegized。
然而,我们看到噪音要小得多,尤其是在袋熊身后的草地和它右边的树枝上。皮毛中的噪点使其对比更加鲜明,但看起来他有白发——与源图片不同——。
正确的图像不那么吸引人,但绝对更好。

enter image description here

在此处输入图片说明

Here's the code to do the pixel perfect downscaling :

这是进行像素完美缩小的代码:

fiddle result : http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
fiddle itself : http://jsfiddle.net/gamealchemist/r6aVp/

小提琴结果:http: //jsfiddle.net/gamealchemist/r6aVp/embedded/result/
小提琴本身:http: //jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel's y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 | ?tx;    // rounded : target pixel's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

It is quitememory greedy, since a float buffer is required to store the intermediate values of the destination image (-> if we count the result canvas, we use 6 times the source image's memory in this algorithm).
It is also quite expensive, since each source pixel is used whatever the destination size, and we have to pay for the getImageData / putImageDate, quite slow also.
But there's no way to be faster than process each source value in this case, and situation is not that bad : For my 740 * 556 image of a wombat, processing takes between 30 and 40 ms.

这是非常贪婪的,因为需要一个浮点缓冲区来存储目标图像的中间值(-> 如果我们计算结果画布,我们在此算法中使用源图像内存的 6 倍)。
它也非常昂贵,因为无论目标大小如何,都会使用每个源像素,而且我们必须为 getImageData / putImageDate 付费,而且速度也很慢。
但是在这种情况下,没有比处理每个源值更快的方法,而且情况也不错:对于我的 740 * 556 袋熊图像,处理需要 30 到 40 毫秒。

回答by ViliusL

Fast canvas resample with good quality: http://jsfiddle.net/9g9Nv/442/

质量好的快速画布重采样:http: //jsfiddle.net/9g9Nv/442/

Update:version 2.0 (faster, web workers + transferable objects) - https://github.com/viliusle/Hermite-resize

更新:2.0 版(更快,网络工作者 + 可转移对象) - https://github.com/viliusle/Hermite-resize

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

回答by ViliusL

Suggestion 1 - extend the process pipe-line

建议 1 - 延长工艺管线

You can use step-down as I describe in the links you refer to but you appear to use them in a wrong way.

您可以使用我在您引用的链接中描述的降压,但您似乎以错误的方式使用它们。

Step down is not needed to scale images to ratios above 1:2 (typically, but not limited to). It is where you need to do a drasticdown-scaling you need to split it up in two (and rarely, more) steps depending on content of the image (in particular where high-frequencies such as thin lines occur).

不需要降级将图像缩放到 1:2 以上的比率(通常但不限于)。这是您需要进行大幅缩小的地方,您需要根据图像的内容将其分成两个(很少,更多)步骤(特别是在出现细线等高频的情况下)。

Every time you down-sample an image you will loose details and information. You cannot expect the resulting image to be as clear as the original.

每次对图像进行下采样时,您都会丢失细节和信息。您不能期望生成的图像与原始图像一样清晰。

If you are then scaling down the images in many steps you will loose a lot of information in total and the result will be poor as you already noticed.

如果您随后在多个步骤中按比例缩小图像,您将总共丢失大量信息,并且结果将很差,正如您已经注意到的那样。

Try with just one extra step, or at tops two.

尝试只做一个额外的步骤,或者最多两步。

Convolutions

卷积

In case of Photoshop notice that it applies a convolution after the image has been re-sampled, such as sharpen. It's not just bi-cubic interpolation that takes place so in order to fully emulate Photoshop we need to also add the steps Photoshop is doing (with the default setup).

如果是 Photoshop,请注意它在图像重新采样后应用卷积,例如锐化。这不仅仅是发生双三次插值,因此为了完全模拟 Photoshop,我们还需要添加 Photoshop 正在执行的步骤(使用默认设置)。

For this example I will use my original answer that you refer to in your post, but I have added a sharpen convolution to it to improve quality as a post process (see demo at bottom).

对于此示例,我将使用您在帖子中引用的原始答案,但我已为其添加了锐化卷积以提高后期处理质量(请参阅底部的演示)。

Here is code for adding sharpen filter (it's based on a generic convolution filter - I put the weight matrix for sharpen inside it as well as a mix factor to adjust the pronunciation of the effect):

这是添加锐化过滤器的代码(它基于通用卷积过滤器 - 我将锐化的权重矩阵放在其中,以及用于调整效果发音的混合因子):

Usage:

用法:

sharpen(context, width, height, mixFactor);

The mixFactoris a value between [0.0, 1.0] and allow you do downplay the sharpen effect - rule-of-thumb: the less size the less of the effect is needed.

mixFactor是一个介于 [0.0, 1.0] 之间的值,并允许您淡化锐化效果 - 经验法则:尺寸越小,需要的效果越少。

Function(based on this snippet):

功能(基于此代码段):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;

    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

The result of using this combination will be:

使用这种组合的结果将是:

ONLINE DEMO HERE

在线演示在这里

Result downsample and sharpen convolution

结果下采样和锐化卷积

Depending on how much of the sharpening you want to add to the blend you can get result from default "blurry" to very sharp:

根据您想要添加到混合中的锐化程度,您可以获得从默认“模糊”到非常清晰的结果:

Variations of sharpen

锐化的变化

Suggestion 2 - low level algorithm implementation

建议 2 - 低级算法实现

If you want to get the best result quality-wise you'll need to go low-level and consider to implement for example this brand new algorithm to do this.

如果你想在质量方面获得最好的结果,你需要低级并考虑实现例如这个全新的算法来做到这一点。

See Interpolation-Dependent Image Downsampling(2011) from IEEE.
Here is a link to the paper in full (PDF).

请参阅IEEE 的Interpolation-Dependent Image Downsampling(2011)。
这是该论文的全文链接 (PDF)

There are no implementations of this algorithm in JavaScript AFAIK of at this time so you're in for a hand-full if you want to throw yourself at this task.

目前在 JavaScript AFAIK 中没有此算法的实现,因此如果您想全身心地投入到这项任务中,您将需要全力以赴。

The essence is (excerpts from the paper):

实质是(论文节选):

Abstract

抽象的

An interpolation oriented adaptive down-sampling algorithm is proposed for low bit-rate image coding in this paper. Given an image, the proposed algorithm is able to obtain a low resolution image, from which a high quality image with the same resolution as the input image can be interpolated. Different from the traditional down-sampling algorithms, which are independent from the interpolation process, the proposed down-sampling algorithm hinges the down-sampling to the interpolation process. Consequently, the proposed down-sampling algorithm is able to maintain the original information of the input image to the largest extent. The down-sampled image is then fed into JPEG. A total variation (TV) based post processing is then applied to the decompressed low resolution image. Ultimately, the processed image is interpolated to maintain the original resolution of the input image. Experimental results verify that utilizing the downsampled image by the proposed algorithm, an interpolated image with much higher quality can be achieved. Besides, the proposed algorithm is able to achieve superior performance than JPEG for low bit rate image coding.

该文针对低码率图像编码提出了一种面向插值的自适应下采样算法。给定图像,所提出的算法能够获得低分辨率图像,从中可以插入与输入图像具有相同分辨率的高质量图像。与独立于插值过程的传统下采样算法不同,所提出的下采样算法将下采样与插值过程结合起来。因此,所提出的下采样算法能够最大程度地保持输入图像的原始信息。然后将下采样图像输入 JPEG。然后将基于总变化 (TV) 的后处理应用于解压缩的低分辨率图像。最终,实验结果证明,利用所提出的算法下采样的图像,可以获得更高质量的插值图像。此外,所提出的算法在低比特率图像编码方面能够实现优于JPEG的性能。

Snapshot from paper

纸上的快照

(see provided link for all details, formulas etc.)

(有关所有详细信息、公式等,请参阅提供的链接)

回答by Vitaly

If you wish to use canvas only, the best result will be with multiple downsteps. But that's not good enougth yet. For better quality you need pure js implementation. We just released pica- high speed downscaler with variable quality/speed. In short, it resizes 1280*1024px in ~0.1s, and 5000*3000px image in 1s, with highest quality (lanczos filter with 3 lobes). Pica has demo, where you can play with your images, quality levels, and even try it on mobile devices.

如果您只想使用画布,最好的结果将是多个步骤。但这还不够好。为了获得更好的质量,您需要纯 js 实现。我们刚刚发布了pica- 具有可变质量/速度的高速降频器。简而言之,它在大约 0.1 秒内调整 1280*1024 像素的大小,并在 1 秒内调整 5000*3000 像素的图像,并具有最高质量(具有 3 个波瓣的 lanczos 过滤器)。Pica 有演示,您可以在其中使用您的图像、质量级别,甚至可以在移动设备上试用。

Pica does not have unsharp mask yet, but that will be added very soon. That's much more easy than implement high speed convolution filter for resize.

Pica 还没有不清晰的蒙版,但很快就会添加。这比实现高速卷积滤波器来调整大小要容易得多。

回答by Robusto

Why use the canvas to resize images? Modern browsers all use bicubic interpolation — the same process used by Photoshop (if you're doing it right) — and they do it faster than the canvas process. Just specify the image size you want (use only one dimension, height or width, to resize proportionally).

为什么要使用画布来调整图像大小?现代浏览器都使用双三次插值——与 Photoshop 使用的过程相同(如果你做得对)——而且它们比画布过程更快。只需指定您想要的图像大小(仅使用一个维度、高度或宽度,按比例调整大小)。

This is supported by most browsers, including later versions of IE. Earlier versions may require browser-specific CSS.

大多数浏览器都支持这一点,包括更高版本的 IE。早期版本可能需要特定于浏览器的 CSS

A simple function (using jQuery) to resize an image would be like this:

调整图像大小的简单函数(使用 jQuery)如下所示:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

Then just use the returned value to resize the image in one or both dimensions.

然后只需使用返回的值在一个或两个维度上调整图像大小。

Obviously there are different refinements you could make, but this gets the job done.

显然,您可以进行不同的改进,但这可以完成工作。

Paste the following code into the console of this page and watch what happens to the gravatars:

将以下代码粘贴到此页面的控制台中,然后观察 gravatars 会发生什么:

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});

回答by halfbit

Not the right answer for people who really need to resize the image itself, but just to shrink the file size.

对于真正需要调整图像大小的人来说,这不是正确的答案,而只是缩小文件大小

I had a problem with "directly from the camera" pictures, that my customers often uploaded in "uncompressed" JPEG.

我遇到了“直接来自相机”的图片问题,我的客户经常以“未压缩”的 JPEG 格式上传这些图片。

Not so well known is, that the canvas supports (in most browsers 2017) to change the quality of JPEG

不太为人所知的是,画布支持(在大多数浏览器 2017 中)更改 JPEG 的质量

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

With this trick I could reduce 4k x 3k pics with >10Mb to 1 or 2Mb, sure it depends on your needs.

使用这个技巧,我可以将大于 10Mb 的 4k x 3k 图片减少到 1 或 2Mb,当然这取决于您的需要。

look here

看这里

回答by fisch2

Here is a reusable Angular service for high quality image / canvas resizing: https://gist.github.com/fisch0920/37bac5e741eaec60e983

这是用于高质量图像/画布调整大小的可重用 Angular 服务:https: //gist.github.com/fisch0920/37bac5e741eaec60e983

The service supports lanczos convolution and step-wise downscaling. The convolution approach is higher quality at the cost of being slower, whereas the step-wise downscaling approach produces reasonably antialiased results and is significantly faster.

该服务支持 lanczos 卷积和逐步缩减。卷积方法以速度较慢为代价获得更高质量,而逐步缩小方法产生合理的抗锯齿结果并且速度明显更快。

Example usage:

用法示例:

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

回答by Calvintwr

This is the improved Hermite resize filter that utilises 1 worker so that the window doesn't freeze.

这是改进的 Hermite 调整大小过滤器,它使用 1 个工人,以便窗口不会冻结。

https://github.com/calvintwr/Hermite-resize

https://github.com/calvintwr/Hermite-resize

回答by Jesús Carrera

I found a solution that doesn't need to access directly the pixel data and loop through it to perform the downsampling. Depending on the size of the image this can be very resource intensive, and it would be better to use the browser's internal algorithms.

我找到了一个不需要直接访问像素数据并循环遍历它来执行下采样的解决方案。根据图像的大小,这可能会占用大量资源,最好使用浏览器的内部算法。

The drawImage()function is using a linear-interpolation, nearest-neighbor resampling method. That works well when you are not resizing down more than half the original size.

所述的drawImage()函数是使用线性内插,最近邻居重采样方法。当您调整大小不超过原始大小的一半时,这很有效

If you loop to only resize max one half at a time, the results would be quite good, and much faster than accessing pixel data.

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

This function downsample to half at a time until reaching the desired size:

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

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

回答by RandomYang

Maybe man you can try this, which is I always use in my project.In this way you can not only get high quality image ,but any other element on your canvas.

也许你可以试试这个,这是我在我的项目中经常使用的。这样你不仅可以获得高质量的图像,还可以获得画布上的任何其他元素。

/* 
 * @parame canvas => canvas object
 * @parame rate => the pixel quality
 */
function setCanvasSize(canvas, rate) {
    const scaleRate = rate;
    canvas.width = window.innerWidth * scaleRate;
    canvas.height = window.innerHeight * scaleRate;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    canvas.getContext('2d').scale(scaleRate, scaleRate);
}