致因
有些图像很难下移-样本和内插比如这条曲线,当你想从大到小的时候。
浏览器似乎通常使用带画布元素的双线性(2x2采样)插值,而不是双三次(4x4采样),这是出于(可能的)性能原因。
如果步长太大,那么就没有足够的像素来进行采样,而这些像素将反映在结果中。
从信号/dsp的角度来看,您可以看到这是一个低通滤波器的阈值设置太高,这可能导致混叠,如果有许多高频(细节)在信号。
解
这里有一个很好的技巧,您可以在浏览器中使用它来支持filter属性在2D上下文中。这就模糊了图像,这在本质上和重采样一样,然后缩小。这允许较大的步骤,但只需要两个步骤和两个绘制。
使用步骤的数目(初始大小/目标大小/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;}
Original was 1600x1200, reduced to 400x300 canvas
对OGF OCT/2018过滤器的支持:
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
现在有一个新属性在规范中定义,用于设置重采样质量:context.imageSmoothingQuality = "low|medium|high"
它目前只支持Chrome。每个级别使用的实际方法由供应商来决定,但假设Lanczos的“高”或类似的质量是合理的。这意味着可以跳过所有步骤,或者可以使用更大的步骤来减少重绘,这取决于图像大小和
支持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
浏览器。在那之前.:
传输端
解决办法是使用下台才能得到正确的结果。逐步下降意味着您缩小了大小的步骤,以允许有限的插值范围,以涵盖足够的像素采样。
这将允许良好的结果,也与双线性插值(实际上它的行为非常类似于双立方,当这样做)和开销是最小的,因为有更少的像素在每一步采样。
理想的一步就是去决议的一半在每个步骤中,直到设置目标大小为止(感谢JoeMabel提到这一点!)
在这种情况下,您需要分三个步骤下台:
在步骤1中,我们通过使用屏幕外画布将图像减少到一半:// step 1 - create off-screen canvasvar 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 2octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
我们再一次画到主画布,再一次缩小到一半但最终的尺寸:// step 3ctx.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))