Html5キャンバスdrawImage:アンチエイリアスを適用する方法


82

次の例をご覧ください。

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は自動的にアンチエイリアシングを適用すると言われていますが、画像はアンチエイリアシングされていません。私はさまざまな方法を試しましたが、うまくいかないようです。アンチエイリアス画像を取得する方法を教えてください。ありがとう。

回答:


178

原因

大きなサイズから小さなサイズに移行する場合、この画像のようにカーブを使用してダウンサンプリングおよび補間するのが非常に難しい画像もあります。

ブラウザは通常、(可能性が高い)パフォーマンス上の理由から、バイキュービック(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() {

  // 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>

ogfとしてのフィルターのサポート2018年10月:

2017年の更新:リサンプリング品質を設定するための仕様で定義された新しいプロパティがあります:

context.imageSmoothingQuality = "low|medium|high"

現在、Chromeでのみサポートされています。レベルごとに使用される実際の方法はベンダーが決定する必要がありますが、ランチョスを「高」または同等の品質であると想定するのは合理的です。これは、画像サイズと

のサポートimageSmoothingQuality

ブラウザ。それまで..:
送信終了

解決策は、ステップダウンを使用して適切な結果を取得することです。ステップダウンとは、サイズを段階的に縮小して、限られた補間範囲がサンプリングに十分なピクセルをカバーできるようにすることを意味します。

これにより、双一次補間でも良好な結果が得られ(これを行うと、実際には双三次のように動作します)、各ステップでサンプリングするピクセルが少なくなるため、オーバーヘッドが最小限に抑えられます。

理想的なステップは、ターゲットサイズを設定するまで、各ステップの解像度半分にすることです(これについて言及してくれたJoe Mabelに感謝します!)。

修正されたフィドル

元の質問のように直接スケーリングを使用する:

通常のダウンスケール画像

以下に示すようにステップダウンを使用します。

ダウンステップ画像

この場合、次の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))

4
いくつかの非常に大きな初期画像(8000 x 6000以上)で作業する場合、基本的に、目的のサイズの2倍以内になるまで手順2を繰り返すと便利です。
ジョーメイベル

チャームのように機能します!ありがとう!
Vlad Tsepelev 2014年

1
2番目と3番目のステップの違いで混乱しています...誰か説明できますか?
carinlynchin 2016

1
@Carine少し複雑ですが、canvasはできるだけ早くpngを保存しようとします。pngファイルは内部で5つの異なるフィルタータイプをサポートしており、圧縮(gzip)を改善できますが、最適な組み合わせを見つけるには、これらすべてのフィルターを画像の行ごとにテストする必要があります。これは大きな画像の場合は時間がかかり、ブラウザをブロックする可能性があるため、ほとんどのブラウザはフィルタ0を使用して、ある程度の圧縮を期待してそれをプッシュします。このプロセスは手動で行うこともできますが、明らかにもう少し作業が必要です。または、tinypng.comなどのサービスAPIを介して実行します。

1
@Kaiidoそれは忘れられず、「コピー」は非常に遅いです。透明性が必要な場合は、clearRect()を使用し、mainまたはaltを使用する方が高速です。ターゲットとしてのキャンバス。

12

そのような作業にはpicaを強くお勧めします。その品質は複数のダウンサイジングよりも優れており、同時に非常に高速です。これがデモです。


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

1
パラメータ「品質」の値の範囲は何ですか?
serup 2016

0と1 [0、1] bettween
イバン・ロドリゲス

4

ケンの答えに加えて、ここでは半分にダウンサンプリングを実行する別の解決策があります(したがって、ブラウザーのアルゴリズムを使用すると結果は良好に見えます)。

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

3

他の誰かがまだ答えを探している場合は、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";

作業デモ


2

興味のある人のために画像の高品質なサイズ変更を処理するために、再利用可能なAngularサービスを作成しました:https//gist.github.com/fisch0920/37bac5e741eaec60e983

このサービスには、Kenの段階的なダウンスケーリングアプローチと、ここにあるlanczos畳み込みアプローチの修正バージョンが含まれています

両方に独自の長所/短所があるため、両方のソリューションを含めました。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
    })
})
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.