ChatGPT解决这个技术问题 Extra ChatGPT

Html5画布drawImage:如何应用抗锯齿

请看下面的例子:

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";

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


佚名

原因

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

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

如果步长太大,则根本没有足够的像素来采样,这会反映在结果中。

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

解决方案

2018 年更新:

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

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

const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); if (typeof ctx.filter === "undefined") { alert("抱歉,浏览器不支持 Context2D 过滤器。") } const img = new Image; img.onload = function() { // 步骤 1 const oc = document.createElement('canvas');常量 octx = oc.getContext('2d'); oc.width = this.width; oc.height = this.height; // steo 2: 使用半径作为半径的步长预过滤图像 const steps = (oc.width / canvas.width)>>1; octx.filter = `blur(${steps}px)`; octx.drawImage(this, 0, 0); // 第三步,绘制缩放后的 ctx.drawImage(oc, 0, 0, oc.width, oc.height, 0, 0, canvas.width, canvas.height); } img.src = "//i.stack.imgur.com/cYfuM.jpg";身体{背景颜色:象牙色; } canvas{border:1px solid red;}

原来是 1600x1200,缩小到 400x300 画布


支持过滤器为 ogf Oct/2018:

CanvasRenderingContext2D.filter api.CanvasRenderingContext2D.filter 在标准轨道上,实验性 https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/filter DESKTOP > |Chrome |Edge |Firefox |IE |Opera |Safari :--- -------------|:--------:|:--------:|:--------:|:-- ------:|:--------:|:-------- 过滤器! | 52 | ? | 49 | - | - | - 移动 > |Chrome/A |Edge/mob |Firefox/A |Opera/A |Safari/iOS|Webview/A :----------------|:---- ----:|:--------:|:--------:|:--------:|:--------:| : - - - - 筛选 ! | 52 | ? | 49 | - | - | 52! = 来自 MDN 的实验数据 - “npm i -g mdncomp”(c)epistemex

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

context.imageSmoothingQuality = "low|medium|high"

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

支持 imageSmoothingQuality

CanvasRenderingContext2D.imageSmoothingQuality api.CanvasRenderingContext2D.imageSmoothingQuality 在标准轨道上,实验性 https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality 桌面 > |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 |是 | 54! = 来自 MDN 的实验数据 - “npm i -g mdncomp”(c)epistemex

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

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

这也将允许使用双线性插值获得良好的结果(这样做时它实际上的行为很像双三次),并且开销最小,因为在每个步骤中要采样的像素更少。

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

Modified fiddle

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

https://i.stack.imgur.com/WuSiu.png

使用降压如下图:

https://i.stack.imgur.com/0zt7z.png

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

在第 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);

第 2 步重用离屏画布,将图像再次缩小到一半:

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

我们再次绘制到主画布,再次缩小到一半,但最终大小:

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

小费:

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

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

使用一些非常大的初始图像(8000 x 6000 及以上),我发现基本上迭代第 2 步直到达到所需尺寸的 2 倍是很有用的。
奇迹般有效!谢谢!
我对第二步和第三步之间的区别感到困惑......谁能解释一下?
@Carine 这有点复杂,但画布会尽可能快地保存一个 png。 png 文件在内部支持 5 种不同的过滤器类型,可以改进压缩 (gzip),但为了找到最佳组合,所有这些过滤器必须在图像的每一行进行测试。这对于大图像来说会很耗时并且可能会阻塞浏览器,因此大多数浏览器只是使用过滤器 0 并将其推出以希望获得一些压缩。您可以手动执行此过程,但显然需要做更多的工作。或者通过 tinypng.com 等服务 API 运行它。
@Kaiido 它没有被遗忘,“复制”非常慢。如果您需要透明度,则使用 clearRect() 并使用 main 或 alt 会更快。画布作为目标。
a
avalanche1

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


J
Jean-François Fabre

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

l
laaposto
    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,/, "");
}

参数“质量”的取值范围是多少?
在零和一之间 [0, 1]
M
Munkhjargal Narmandakh

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

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


C
Community

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

该服务包括 Ken 的逐步缩减方法以及发现的 lanczos 卷积方法的修改版本 here

我包括了这两种解决方案,因为它们都有自己的优点/缺点。 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
    })
})