web-dev-qa-db-ja.com

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

74
Dundar

原因

いくつかの画像はダウンサンプリングが非常に難しく、補間など、大きなサイズから小さなサイズにしたいときに曲線を使用します。

ブラウザは通常、パフォーマンス上の理由から、バイキュービック(4x4サンプリング)ではなく、キャンバス要素でバイリニア(2x2サンプリング)補間を使用するように見えます。

ステップが大きすぎる場合、サンプルに十分なピクセルがなく、そこから結果に反映されます。

信号/ DSPの観点から見ると、これはローパスフィルターのしきい値設定が高すぎるため、信号に多くの高周波(詳細)がある場合にエイリアシングが発生する可能性があります。

解決

2018年の更新:

以下は、2Dコンテキストでfilterプロパティをサポートするブラウザーに使用できる巧妙なトリックです。これは、本質的にリサンプリングと同じイメージをプリブラーし、その後縮小します。これにより、大きなステップが可能になりますが、必要なのは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 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

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

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

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

解決策は、step-downを使用して適切な結果を取得することです。ステップダウンとは、サイズを段階的に減らして、サンプリングに十分なピクセルをカバーするために制限された補間範囲を可能にすることです。

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

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

修正されたフィドル

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

NORMAL DOWN-SCALED IMAGE

次のようにステップダウンを使用します:

DOWN-STEPPED IMAGE

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

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

12
avalanche1

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

この投稿 へのクレジット

4
Jesús Carrera
    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,/, "");
}
4
kamil

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

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

両方とも独自の長所/短所があるため、両方のソリューションを含めました。ランチョス畳み込みアプローチは、低速であるという犠牲を払ってより高品質ですが、段階的なダウンスケーリングアプローチは、適度にアンチエイリアシングされた結果を生成し、非常に高速です。

使用例:

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
    })
})
3
fisch2

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

作業デモ