原因
大きなサイズから小さなサイズに移行する場合、この画像のようにカーブを使用してダウンサンプリングおよび補間するのが非常に難しい画像もあります。
ブラウザは通常、(可能性が高い)パフォーマンス上の理由から、バイキュービック(4x4サンプリング)ではなく、キャンバス要素でバイリニア(2x2サンプリング)補間を使用しているように見えます。
ステップが大きすぎる場合は、サンプリングするのに十分なピクセルがないため、結果に反映されます。
信号/ DSPの観点からは、これはローパスフィルターのしきい値の設定が高すぎると見なすことができます。これにより、信号に高周波数(詳細)が多数ある場合にエイリアシングが発生する可能性があります。
解決
2018年の更新:
これはfilter
、2Dコンテキストのプロパティをサポートするブラウザに使用できる巧妙なトリックです。これにより、本質的にリサンプリングと同じ画像が事前にぼかしされ、縮小されます。これにより大きなステップが可能になりますが、必要なのは2つのステップと2つのドローだけです。
半径としてステップ数(元のサイズ/宛先サイズ/ 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() {
const oc = document.createElement('canvas');
const octx = oc.getContext('2d');
oc.width = this.width;
oc.height = this.height;
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";
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>
ogfとしてのフィルターのサポート2018年10月:
CanvasRenderingContext2D.filter
api.CanvasRenderingContext2D.filter
On Standard Track, Experimental
https:
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
2017年の更新:リサンプリング品質を設定するための仕様で定義された新しいプロパティがあります:
context.imageSmoothingQuality = "low|medium|high"
現在、Chromeでのみサポートされています。レベルごとに使用される実際の方法はベンダーが決定する必要がありますが、ランチョスを「高」または同等の品質であると想定するのは合理的です。これは、画像サイズと
のサポートimageSmoothingQuality
:
CanvasRenderingContext2D.imageSmoothingQuality
api.CanvasRenderingContext2D.imageSmoothingQuality
On Standard Track, Experimental
https:
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
ブラウザ。それまで..:
送信終了
解決策は、ステップダウンを使用して適切な結果を取得することです。ステップダウンとは、サイズを段階的に縮小して、限られた補間範囲がサンプリングに十分なピクセルをカバーできるようにすることを意味します。
これにより、双一次補間でも良好な結果が得られ(これを行うと、実際には双三次のように動作します)、各ステップでサンプリングするピクセルが少なくなるため、オーバーヘッドが最小限に抑えられます。
理想的なステップは、ターゲットサイズを設定するまで、各ステップの解像度を半分にすることです(これについて言及してくれたJoe Mabelに感謝します!)。
修正されたフィドル
元の質問のように直接スケーリングを使用する:
以下に示すようにステップダウンを使用します。
この場合、次の3つのステップでステップダウンする必要があります。
ステップ1では、オフスクリーンキャンバスを使用して画像を半分に縮小します。
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は、オフスクリーンキャンバスを再利用し、画像を半分に縮小して描画します。
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
そして、もう一度メインキャンバスに描画します。これも半分に縮小されますが、最終的なサイズになります。
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))