Html Html5 canvas drawImage:如何应用抗锯齿

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17861447/
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 11:30:36  来源:igfitidea点击:

Html5 canvas drawImage: how to apply antialiasing

htmlcanvashtml5-canvasinterpolationantialiasing

提问by Dundar

Please have a look at the following example:

请看下面的例子:

http://jsfiddle.net/MLGr4/47/

http://jsfiddle.net/MLGr4/47/

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

img = new Image();
img.onload = function(){
    canvas.width = 400;
    canvas.height = 150;
    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 400, 150);
}
img.src = "http://openwalls.com/image/1734/colored_lines_on_blue_background_1920x1200.jpg";

As you see, the image is not anti-aliased although it is said that drawImage applies anti aliasing automatically. I tried many different ways but it doesn't seem to work. Could you please tell me how I can get anti-aliased image? Thanks.

如您所见,尽管据说 drawImage 会自动应用抗锯齿,但图像并未抗锯齿。我尝试了很多不同的方法,但似乎不起作用。你能告诉我如何获得抗锯齿图像吗?谢谢。

回答by

Cause

原因

Some images are just very hard to down-sample and interpolatesuch as this one with curves when you want to go from a large size to a small.

当您想从大尺寸变为小尺寸时,有些图像很难进行下采样和插值,例如带有曲线的图像。

Browsers appear to typically use bi-linear (2x2 sampling) interpolation with the canvas element rather than bi-cubic (4x4 sampling) for (likely) performance reasons.

出于(可能的)性能原因,浏览器似乎通常对画布元素使用双线性(2x2 采样)插值,而不是双三次(4x4 采样)插值。

If the step is too huge then there are simply not enough pixels to sample from which is reflected in the result.

如果步长太大,则根本没有足够的像素进行采样,从而反映在结果中。

From a signal/DSP perspective you could see this as a low-pass filter's threshold value set too high, which may result in aliasing if there are many high frequencies (details) in the signal.

从信号/DSP 的角度来看,您可以将其视为低通滤波器的阈值设置得太高,如果信号中有许多高频(细节),这可能会导致混叠。

Solution

解决方案

Update 2018:

2018 年更新:

Here's a neat trick you can use for browsers which supports the filterproperty on the 2D context. This pre-blurs the image which is in essence the same as a resampling, then scales down. This allows for large steps but only needs two steps and two draws.

这是一个可以用于支持filter2D 上下文属性的浏览器的巧妙技巧。这与重采样本质上相同的图像预模糊,然后按比例缩小。这允许大步骤,但只需要两个步骤和两次绘制。

Pre-blur using number of steps (original size / destination size / 2) as radius (you may need to adjust this heuristically based on browser and odd/even steps - here only shown simplified):

使用步数(原始大小/目标大小/2)作为半径进行预模糊(您可能需要根据浏览器和奇数/偶数步长进行启发式调整 - 此处仅显示简化):

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

if (typeof ctx.filter === "undefined") {
 alert("Sorry, the browser doesn't support Context2D filters.")
}

const img = new Image;
img.onload = function() {

  // step 1
  const oc = document.createElement('canvas');
  const octx = oc.getContext('2d');
  oc.width = this.width;
  oc.height = this.height;

  // steo 2: pre-filter image using steps as radius
  const steps = (oc.width / canvas.width)>>1;
  octx.filter = `blur(${steps}px)`;
  octx.drawImage(this, 0, 0);

  // step 3, draw scaled
  ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height);

}
img.src = "//i.stack.imgur.com/cYfuM.jpg";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<br/><p>Original was 1600x1200, reduced to 400x300 canvas</p><br/>
<canvas id="canvas" width=400 height=250></canvas>

Support for filter as ogf Oct/2018:

支持过滤器为 ogf 2018 年 10 月:

CanvasRenderingContext2D.filter                                                   
api.CanvasRenderingContext2D.filter                                               
On Standard Track, Experimental                                                   
https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/filter        
                                                                                  
DESKTOP >        |Chrome    |Edge      |Firefox   |IE        |Opera     |Safari   
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter !         |    52    |    ?     |    49    |    -     |    -     |    -    
                                                                                  
MOBILE >         |Chrome/A  |Edge/mob  |Firefox/A |Opera/A   |Safari/iOS|Webview/A
:----------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------
filter !         |    52    |    ?     |    49    |    -     |    -     |    52   
                                                                                  
! = Experimental                                                                  
                                                                                  
Data from MDN - "npm i -g mdncomp" (c) epistemex

Update 2017:There is now a new propertydefined in the specs for setting resampling quality:

2017 年更新:现在在规范中定义了一个用于设置重采样质量的新属性

context.imageSmoothingQuality = "low|medium|high"

It's currently only supported in Chrome. The actual methods used per level is left to the vendor to decide, but it's reasonable to assume Lanczos for "high" or something equivalent in quality. This means step-down may be skipped altogether, or larger steps can be used with fewer redraws, depending on the image size and

它目前仅在 Chrome 中受支持。每个级别使用的实际方法由供应商决定,但假设 Lanczos 为“高”或质量相当的东西是合理的。这意味着可以完全跳过降压,或者可以使用更大的步长和更少的重绘,这取决于图像大小和

Support for imageSmoothingQuality:

支持imageSmoothingQuality

CanvasRenderingContext2D.imageSmoothingQuality
api.CanvasRenderingContext2D.imageSmoothingQuality
On Standard Track, Experimental
https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality

DESKTOP >              |Chrome    |Edge      |Firefox   |IE        |Opera     |Safari
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !|    54    |    ?     |    -     |    ?     |    41    |    Y

MOBILE >               |Chrome/A  |Edge/mob  |Firefox/A |Opera/A   |Safari/iOS|Webview/A
:----------------------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:
imageSmoothingQuality !|    54    |    ?     |    -     |    41    |    Y     |    54

! = Experimental

Data from MDN - "npm i -g mdncomp" (c) epistemex

browser. Until then..:
End of transmission

浏览器。在那之前..:
传输结束

The solution is to use step-downto get a proper result. Step-down means you reduce the size in steps to allow the limited interpolation range to cover enough pixels for sampling.

解决方案是使用降压来获得正确的结果。降压意味着您可以逐步减小尺寸,以允许有限的插值范围覆盖足够的像素进行采样。

This will allow good results also with bi-linear interpolation (it actually behaves much like bi-cubic when doing this) and the overhead is minimal as there are less pixels to sample in each step.

这也将允许使用双线性插值获得良好的结果(它实际上在执行此操作时的行为与双三次插值非常相似)并且开销最小,因为在每个步骤中要采样的像素较少。

The ideal step is to go to half the resolutionin each step until you would set the target size (thanks to Joe Mabel for mentioning this!).

理想的步骤是在每个步骤中将分辨率降低一半,直到您设置目标大小(感谢 Joe Mabel 提到这一点!)。

Modified fiddle

修改后的小提琴

Using direct scaling as in original question:

在原始问题中使用直接缩放:

NORMAL DOWN-SCALED IMAGE

正常缩小图像

Using step-down as shown below:

使用降压如下图:

DOWN-STEPPED IMAGE

降级图像

In this case you will need to step down in 3 steps:

在这种情况下,您需要分 3 个步骤下台:

In step 1 we reduce the image to half by using an off-screen canvas:

在步骤 1 中,我们使用屏幕外画布将图像缩小到一半:

// step 1 - create off-screen canvas
var oc   = document.createElement('canvas'),
    octx = oc.getContext('2d');

oc.width  = img.width  * 0.5;
oc.height = img.height * 0.5;

octx.drawImage(img, 0, 0, oc.width, oc.height);

Step 2 reuses the off-screen canvas and draws the image reduced to half again:

Step 2 重用离屏画布,再次绘制缩小一半的图像:

// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

And we draw once more to main canvas, again reduced to halfbut to the final size:

我们再次在主画布上绘制,再次缩小到一半但最终尺寸:

// step 3
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
                  0, 0, canvas.width,   canvas.height);

Tip:

提示:

You can calculate total number of steps needed, using this formula (it includes the final step to set target size):

您可以使用此公式计算所需的总步数(它包括设置目标大小的最后一步):

steps = Math.ceil(Math.log(sourceWidth / targetWidth) / Math.log(2))

回答by avalanche1

I highly recommend picafor such tasks. Its quality is superior to multiple downsizing and is quite speedy at the same time. Here is a demo.

我强烈推荐pica用于此类任务。它的质量优于多次缩小尺寸,同时速度相当快。这是一个演示

回答by kamil

    var getBase64Image = function(img, quality) {
    var canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    var ctx = canvas.getContext("2d");

    //----- origin draw ---
    ctx.drawImage(img, 0, 0, img.width, img.height);

    //------ reduced draw ---
    var canvas2 = document.createElement("canvas");
    canvas2.width = img.width * quality;
    canvas2.height = img.height * quality;
    var ctx2 = canvas2.getContext("2d");
    ctx2.drawImage(canvas, 0, 0, img.width * quality, img.height * quality);

    // -- back from reduced draw ---
    ctx.drawImage(canvas2, 0, 0, img.width, img.height);

    var dataURL = canvas.toDataURL("image/png");
    return dataURL;
    // return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}

回答by Jesús Carrera

As addition to Ken's answer, here another solution to perform the downsampling in halves (so the result looks good using the browser's algorithm):

除了 Ken 的回答之外,这里还有另一种对半执行下采样的解决方案(因此使用浏览器的算法结果看起来不错):

  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 fisch2

I created a reusable Angular service to handle high quality resizing of images for anyone who's interested: https://gist.github.com/fisch0920/37bac5e741eaec60e983

我创建了一个可重用的 Angular 服务来为任何感兴趣的人处理高质量的图像大小调整:https: //gist.github.com/fisch0920/37bac5e741eaec60e983

The service includes Ken's step-wise downscaling approach as well as a modified version of the lanczos convolution approach found here.

该服务包括 Ken 的逐步缩减方法以及此处找到的 lanczos 卷积方法的修改版本。

I included both solutions because they both have their own pros / cons. The lanczos 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 Munkhjargal Narmandakh

In case someone else looking for answer still, there is another way which you can use background image instead of drawImage(). You won't lose any image quality this way.

如果其他人仍在寻找答案,还有另一种方法可以使用背景图像而不是 drawImage()。这样您不会损失任何图像质量。

JS:

JS:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
   var url = "http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

    img=new Image();
    img.onload=function(){

        canvas.style.backgroundImage = "url(\'" + url + "\')"

    }
    img.src="http://openwalls.com/image/17342/colored_lines_on_blue_background_1920x1200.jpg";

working demo

工作演示