web-dev-qa-db-ja.com

より良い補間でhtml5キャンバス上の画像を拡大縮小する方法は?

まず第一に:私は何をしようとしているのですか?

画像を表示するアプリケーションがあります。 canvas要素を使用して画像をレンダリングします。ズームイン、ズームアウト、およびドラッグできます。この部分は今完璧に機能します。

しかし、テキストがたくさんある画像があるとしましょう。解像度は1200x1700で、キャンバスには1200x900があります。最初は、ズームアウトすると、レンダリング解像度が約560x800になります。

私の実際の図面は次のようになります。

drawImage(src, srcOffsetX, srcOffsetY, sourceViewWidth, sourceViewHeight,
destOffsetX, destOffsetY, destWidth, destHeight);

この画像上の小さなテキストは、特に他の画像ビューアー(IrfanViewなど)、またはhtml <img>要素と比較した場合、非常に悪く見えます。

ブラウザの補間アルゴリズムがこの問題の原因であることがわかりました。さまざまなブラウザを比較すると、Chrome=はスケーリングされた画像を最適にレンダリングしますが、それでも十分ではありません。

まあ、Interwebsの隅々を4〜5時間連続して検索しましたが、必要なものが見つかりませんでした。 「imageSmoothingEnabled」オプション、キャンバスで使用できない「image-rendering」CSSスタイル、フロート位置でのレンダリング、および補間アルゴリズムの多くのJavaScript実装を見つけました(これらは私の目的のために遅くなりますfar )。

なぜ私がこれをすべて言っているのかと尋ねるかもしれません:あなたが私にすでに知っている答えを与える時間を節約するために

だから:anyより良い補間を行うための良い、速い方法はありますか?私の現在のアイデアは、画像オブジェクトを作成し、これをサイズ変更し(imgがスケーリングされたときに適切な補間が行われるためです!)、レンダリングします。残念ながら、img.widthの適用は表示される幅にのみ影響するようです...

更新:サイモンのおかげで、問題を解決できました。これが、私が使用した動的スケーリングアルゴリズムです。縦横比を維持していることに注意してください。高さパラメータは、フロートコンピューティングを回避するためのものです。現在は縮小しているだけです。

scale(destWidth, destHeight){
        var start = new Date().getTime();
        var scalingSteps = 0;
        var ctx = this._sourceImageCanvasContext;
        var curWidth = this._sourceImageWidth;
        var curHeight = this._sourceImageHeight;

        var lastWidth = this._sourceImageWidth;
        var lastHeight = this._sourceImageHeight;

        var end = false;
        var scale=0.75;
        while(end==false){
            scalingSteps +=1;
            curWidth *= scale;
            curHeight *= scale;
            if(curWidth < destWidth){
                curWidth = destWidth;
                curHeight = destHeight;
                end=true;
            }
            ctx.drawImage(this._sourceImageCanvas, 0, 0, Math.round(lastWidth), Math.round(lastHeight), 0, 0, Math.round(curWidth), Math.round(curHeight));
            lastWidth = curWidth;
            lastHeight = curHeight;
        }
        var endTime =new Date().getTime();
        console.log("execution time: "+ ( endTime - start) + "ms. scale per frame: "+scale+ " scaling step count: "+scalingSteps);
    }
37
Kumpu

何度か「ステップダウン」する必要があります。非常に大きな画像から非常に小さな画像にスケーリングする代わりに、中間サイズに再スケーリングする必要があります。

1/6スケールで描画したい画像を考えてください。これを行うことができます:

var w = 1280;
var h = 853;

ctx.drawImage(img, 0, 0, w/6, h/6);   

または、メモリ内のキャンバスに1/2スケールで描画し、次に1/2スケールで描画し、次に1/2スケールで描画することもできます。結果は1/6スケールの画像ですが、次の3つの手順を使用します。

var can2 = document.createElement('canvas');
can2.width = w/2;
can2.height = w/2;
var ctx2 = can2.getContext('2d');

ctx2.drawImage(img, 0, 0, w/2, h/2);
ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);

その後、元のコンテキストにそれを戻すことができます。

ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);

ここで違いをライブで見ることができます:

var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');

var img = new Image();
var w = 1280;
var h = 853;
img.onload = function() {
    // step it down only once to 1/6 size:
    ctx.drawImage(img, 0, 0, w/6, h/6);   
    
    // Step it down several times
    var can2 = document.createElement('canvas');
    can2.width = w/2;
    can2.height = w/2;
    var ctx2 = can2.getContext('2d');
    
    // Draw it at 1/2 size 3 times (step down three times)
    
    ctx2.drawImage(img, 0, 0, w/2, h/2);
    ctx2.drawImage(can2, 0, 0, w/2, h/2, 0, 0, w/4, h/4);
    ctx2.drawImage(can2, 0, 0, w/4, h/4, 0, 0, w/6, h/6);
    ctx.drawImage(can2, 0, 0, w/6, h/6, 0, 200, w/6, h/6);
}



img.src = 'http://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Equus_quagga_%28Namutoni%2C_2012%29.jpg/1280px-Equus_quagga_%28Namutoni%2C_2012%29.jpg'
canvas {
    border: 1px solid gray;
}
<canvas id="canvas1" width="400" height="400"></canvas>

jsfiddleで同じスニペットを表示します。

49
Simon Sarris