web-dev-qa-db-ja.com

HTML5キャンバスサイズ変更(縮小)画像高品質?

私は自分のブラウザで画像のサイズを変更するためにhtml5キャンバス要素を使用します。品質が非常に低いことがわかります。私はこれを見つけました: <canvas>をスケーリングするとき補間を無効にする しかし、それは品質を向上させるのに役立ちません。

以下は私のcssとjsのコード、およびPhotoshopで拡大縮小されキャンバスAPIで拡大縮小された画像です。

ブラウザで画像を拡大縮小するときに最適な品質を得るにはどうすればよいですか?

注:大きな画像を小さな画像に縮小し、キャンバス内の色を変更して、キャンバスからサーバーに結果を送信します。

CSS:

canvas, img {
    image-rendering: optimizeQuality;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
}

JS:

var $img = $('<img>');
var $originalCanvas = $('<canvas>');
$img.load(function() {


   var originalContext = $originalCanvas[0].getContext('2d');   
   originalContext.imageSmoothingEnabled = false;
   originalContext.webkitImageSmoothingEnabled = false;
   originalContext.mozImageSmoothingEnabled = false;
   originalContext.drawImage(this, 0, 0, 379, 500);
});

フォトショップでサイズを変更した画像:

enter image description here

キャンバス上でサイズ変更された画像:

enter image description here

編集する

以下で提案されているように、複数のステップでダウンスケーリングを試みました。

HTML 5キャンバスの画像のサイズ変更 and Html 5キャンバスのdrawImage:アンチエイリアスの適用方法

これが私が使った機能です。

function resizeCanvasImage(img, canvas, maxWidth, maxHeight) {
    var imgWidth = img.width, 
        imgHeight = img.height;

    var ratio = 1, ratio1 = 1, ratio2 = 1;
    ratio1 = maxWidth / imgWidth;
    ratio2 = maxHeight / imgHeight;

    // Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
    if (ratio1 < ratio2) {
        ratio = ratio1;
    }
    else {
        ratio = ratio2;
    }

    var canvasContext = canvas.getContext("2d");
    var canvasCopy = document.createElement("canvas");
    var copyContext = canvasCopy.getContext("2d");
    var canvasCopy2 = document.createElement("canvas");
    var copyContext2 = canvasCopy2.getContext("2d");
    canvasCopy.width = imgWidth;
    canvasCopy.height = imgHeight;  
    copyContext.drawImage(img, 0, 0);

    // init
    canvasCopy2.width = imgWidth;
    canvasCopy2.height = imgHeight;        
    copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);


    var rounds = 2;
    var roundRatio = ratio * rounds;
    for (var i = 1; i <= rounds; i++) {
        console.log("Step: "+i);

        // tmp
        canvasCopy.width = imgWidth * roundRatio / i;
        canvasCopy.height = imgHeight * roundRatio / i;

        copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);

        // copy back
        canvasCopy2.width = imgWidth * roundRatio / i;
        canvasCopy2.height = imgHeight * roundRatio / i;
        copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);

    } // end for


    // copy back to canvas
    canvas.width = imgWidth * roundRatio / rounds;
    canvas.height = imgHeight * roundRatio / rounds;
    canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);


}

2段階ダウンサイズを使用した場合の結果は次のとおりです。

enter image description here

これが3段階ダウンサイジングを使用した場合の結果です。

enter image description here

これが4段階ダウンサイズを使用した場合の結果です。

enter image description here

これが、20段階ダウンのサイズ変更を使用した場合の結果です。

enter image description here

注:1ステップから2ステップにかけて画質が大幅に向上しますが、プロセスに追加するステップが多いほど、画像がぼやけます。

追加するステップが多いほど、画像がぼやけてしまうという問題を解決する方法はありますか?

編集2013-10-04:私はGameAlchemistのアルゴリズムを試しました。これがPhotoshopと比較した結果です。

フォトショップの画像:

PhotoShop Image

GameAlchemistのアルゴリズム:

GameAlchemist's Algorithm

143
confile

あなたの問題はあなたのイメージを縮小することであるので、補間について話しても意味がありません - それはピクセルを作成することについてです - 。ここでの問題はダウンサンプリングです。

画像をダウンサンプリングするには、元の画像のp * pピクセルの各正方形を変換先の画像の1ピクセルに変換する必要があります。

パフォーマンス上の理由から、ブラウザは非常に単純なダウンサンプリングを行います。小さい画像を作成するために、ブラウザはソース内の1ピクセルを選択し、その値を宛先に使用します。これは細部を「忘れ」、ノイズが増えます。

それでも例外があります。2X画像のダウンサンプリングは計算がとても簡単で(平均するのに4ピクセル)、網膜/ HiDPIピクセルに使用されるので、この場合は適切に処理されます。ブラウザは4ピクセルを使用します。 1-。

しかし... 2倍のダウンサンプリングを数回使用すると、連続した丸め誤差によってノイズが増えすぎるという問題に直面するでしょう。
さらに悪いことに、あなたはいつも2のべき乗でサイズ変更するわけではありません、そして最も近いパワー+最後のサイズ変更にサイズ変更することは非常にうるさいです。

あなたが求めているのはピクセルパーフェクトダウンサンプリングです。つまり、スケールを問わず、すべての入力ピクセルを考慮に入れる画像の再サンプリングです。
これを行うには、入力ピクセルのスケーリングされた投影がデスティネーションピクセルのすぐ内側にあるかどうかに応じて、1つ、2つ、または4つのデスティネーションピクセルへの寄与を計算する必要があります。 Y境界、あるいはその両方.
(スキームはここではいいのですが、私は持っていません。)

これはキャンバススケールとゾンビートの1/3スケールでの私のピクセルパーフェクトスケールの対比の例です。

画像がブラウザで拡大縮小され、S.O..pegizedされていることに注意してください。
それでも、特にウォンバットの後ろの芝生とその右側の枝には、はるかに少ないノイズがあることがわかります。毛皮のノイズはそれをより対照的なものにしているが、それは彼が白い毛を持っているように見える - それはソース画像とは違う - .
右の画像はあまり目立ちませんが、明らかにきれいです。

enter image description here

これは、ピクセルパーフェクトダウンスケーリングを行うためのコードです。

フィドル結果: http://jsfiddle.net/gamealchemist/r6aVp/embedded/result/
自分自身をいじる: http://jsfiddle.net/gamealchemist/r6aVp/

// scales the image by (float) scale < 1
// returns a canvas containing the scaled image.
function downScaleImage(img, scale) {
    var imgCV = document.createElement('canvas');
    imgCV.width = img.width;
    imgCV.height = img.height;
    var imgCtx = imgCV.getContext('2d');
    imgCtx.drawImage(img, 0, 0);
    return downScaleCanvas(imgCV, scale);
}

// scales the canvas by (float) scale < 1
// returns a new canvas containing the scaled image.
function downScaleCanvas(cv, scale) {
    if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
    var sqScale = scale * scale; // square scale = area of source pixel within target
    var sw = cv.width; // source image width
    var sh = cv.height; // source image height
    var tw = Math.floor(sw * scale); // target image width
    var th = Math.floor(sh * scale); // target image height
    var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
    var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
    var tX = 0, tY = 0; // rounded tx, ty
    var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
    // weight is weight of current source point within target.
    // next weight is weight of current source point within next target's point.
    var crossX = false; // does scaled px cross its current px right border ?
    var crossY = false; // does scaled px cross its current px bottom border ?
    var sBuffer = cv.getContext('2d').
    getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
    var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
    var sR = 0, sG = 0,  sB = 0; // source's current point r,g,b
    /* untested !
    var sA = 0;  //source alpha  */    

    for (sy = 0; sy < sh; sy++) {
        ty = sy * scale; // y src position within target
        tY = 0 | ty;     // rounded : target pixel's y
        yIndex = 3 * tY * tw;  // line index within target array
        crossY = (tY != (0 | ty + scale)); 
        if (crossY) { // if pixel is crossing botton target pixel
            wy = (tY + 1 - ty); // weight of point within target pixel
            nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
        }
        for (sx = 0; sx < sw; sx++, sIndex += 4) {
            tx = sx * scale; // x src position within target
            tX = 0 |  tx;    // rounded : target pixel's x
            tIndex = yIndex + tX * 3; // target pixel index within target array
            crossX = (tX != (0 | tx + scale));
            if (crossX) { // if pixel is crossing target pixel's right
                wx = (tX + 1 - tx); // weight of point within target pixel
                nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
            }
            sR = sBuffer[sIndex    ];   // retrieving r,g,b for curr src px.
            sG = sBuffer[sIndex + 1];
            sB = sBuffer[sIndex + 2];

            /* !! untested : handling alpha !!
               sA = sBuffer[sIndex + 3];
               if (!sA) continue;
               if (sA != 0xFF) {
                   sR = (sR * sA) >> 8;  // or use /256 instead ??
                   sG = (sG * sA) >> 8;
                   sB = (sB * sA) >> 8;
               }
            */
            if (!crossX && !crossY) { // pixel does not cross
                // just add components weighted by squared scale.
                tBuffer[tIndex    ] += sR * sqScale;
                tBuffer[tIndex + 1] += sG * sqScale;
                tBuffer[tIndex + 2] += sB * sqScale;
            } else if (crossX && !crossY) { // cross on X only
                w = wx * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tX+1) px                
                nw = nwx * scale
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
            } else if (crossY && !crossX) { // cross on Y only
                w = wy * scale;
                // add weighted component for current px
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // add weighted component for next (tY+1) px                
                nw = nwy * scale
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
            } else { // crosses both x and y : four target points involved
                // add weighted component for current px
                w = wx * wy;
                tBuffer[tIndex    ] += sR * w;
                tBuffer[tIndex + 1] += sG * w;
                tBuffer[tIndex + 2] += sB * w;
                // for tX + 1; tY px
                nw = nwx * wy;
                tBuffer[tIndex + 3] += sR * nw;
                tBuffer[tIndex + 4] += sG * nw;
                tBuffer[tIndex + 5] += sB * nw;
                // for tX ; tY + 1 px
                nw = wx * nwy;
                tBuffer[tIndex + 3 * tw    ] += sR * nw;
                tBuffer[tIndex + 3 * tw + 1] += sG * nw;
                tBuffer[tIndex + 3 * tw + 2] += sB * nw;
                // for tX + 1 ; tY +1 px
                nw = nwx * nwy;
                tBuffer[tIndex + 3 * tw + 3] += sR * nw;
                tBuffer[tIndex + 3 * tw + 4] += sG * nw;
                tBuffer[tIndex + 3 * tw + 5] += sB * nw;
            }
        } // end for sx 
    } // end for sy

    // create result canvas
    var resCV = document.createElement('canvas');
    resCV.width = tw;
    resCV.height = th;
    var resCtx = resCV.getContext('2d');
    var imgRes = resCtx.getImageData(0, 0, tw, th);
    var tByteBuffer = imgRes.data;
    // convert float32 array into a UInt8Clamped Array
    var pxIndex = 0; //  
    for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
        tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
        tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
        tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
        tByteBuffer[tIndex + 3] = 255;
    }
    // writing result to canvas.
    resCtx.putImageData(imgRes, 0, 0);
    return resCV;
}

転送先イメージの中間値を格納するにはfloatバッファーが必要なのでかなりメモリ貪欲です( - >結果キャンバスを数えるなら、このアルゴリズムでは、ソースイメージのメモリの6倍を使います。
また、各ソースピクセルは目的のサイズに関係なく使用されるため、非常に高価です。また、getImageData/putImageDateを支払う必要があります。これも非常に低速です。
しかし、この場合、各ソース値を処理するよりも速い方法はありません。状況はそれほど悪くありません。ウォンバットの740 * 556イメージでは、処理に30〜40ミリ秒かかります。

162
GameAlchemist

速いキャンバスは良い品質でリサンプルします。 http://jsfiddle.net/9g9Nv/442/

更新:バージョン2.0(速い、ウェブワーカー+譲渡可能なオブジェクト) - https://github.com/viliusle/Hermite-resize

/**
 * Hermite resize - fast image resize/resample using Hermite filter. 1 cpu version!
 * 
 * @param {HtmlElement} canvas
 * @param {int} width
 * @param {int} height
 * @param {boolean} resize_canvas if true, canvas will be resized. Optional.
 */
function resample_single(canvas, width, height, resize_canvas) {
    var width_source = canvas.width;
    var height_source = canvas.height;
    width = Math.round(width);
    height = Math.round(height);

    var ratio_w = width_source / width;
    var ratio_h = height_source / height;
    var ratio_w_half = Math.ceil(ratio_w / 2);
    var ratio_h_half = Math.ceil(ratio_h / 2);

    var ctx = canvas.getContext("2d");
    var img = ctx.getImageData(0, 0, width_source, height_source);
    var img2 = ctx.createImageData(width, height);
    var data = img.data;
    var data2 = img2.data;

    for (var j = 0; j < height; j++) {
        for (var i = 0; i < width; i++) {
            var x2 = (i + j * width) * 4;
            var weight = 0;
            var weights = 0;
            var weights_alpha = 0;
            var gx_r = 0;
            var gx_g = 0;
            var gx_b = 0;
            var gx_a = 0;
            var center_y = (j + 0.5) * ratio_h;
            var yy_start = Math.floor(j * ratio_h);
            var yy_stop = Math.ceil((j + 1) * ratio_h);
            for (var yy = yy_start; yy < yy_stop; yy++) {
                var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
                var center_x = (i + 0.5) * ratio_w;
                var w0 = dy * dy; //pre-calc part of w
                var xx_start = Math.floor(i * ratio_w);
                var xx_stop = Math.ceil((i + 1) * ratio_w);
                for (var xx = xx_start; xx < xx_stop; xx++) {
                    var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
                    var w = Math.sqrt(w0 + dx * dx);
                    if (w >= 1) {
                        //pixel too far
                        continue;
                    }
                    //hermite filter
                    weight = 2 * w * w * w - 3 * w * w + 1;
                    var pos_x = 4 * (xx + yy * width_source);
                    //alpha
                    gx_a += weight * data[pos_x + 3];
                    weights_alpha += weight;
                    //colors
                    if (data[pos_x + 3] < 255)
                        weight = weight * data[pos_x + 3] / 250;
                    gx_r += weight * data[pos_x];
                    gx_g += weight * data[pos_x + 1];
                    gx_b += weight * data[pos_x + 2];
                    weights += weight;
                }
            }
            data2[x2] = gx_r / weights;
            data2[x2 + 1] = gx_g / weights;
            data2[x2 + 2] = gx_b / weights;
            data2[x2 + 3] = gx_a / weights_alpha;
        }
    }
    //clear and resize canvas
    if (resize_canvas === true) {
        canvas.width = width;
        canvas.height = height;
    } else {
        ctx.clearRect(0, 0, width_source, height_source);
    }

    //draw
    ctx.putImageData(img2, 0, 0);
}
46
ViliusL

提案1 - プロセスパイプラインを拡張する

参照しているリンクで説明しているように、ステップダウンを使用することはできますが、間違った方法で使用しているように見えます。

1:2を超える比率で画像を拡大縮小するためにステップダウンする必要はありません(通常、ただしこれだけに限りません)。ここで、画像の内容に応じて、大幅な縮小を行う必要があります。細い線などの周波数が発生します)。

画像をダウンサンプリングするたびに、詳細と情報が失われます。結果の画像が元の画像ほど鮮明になることは期待できません。

あなたがそれから多くのステップで画像を縮小しているならば、あなたは全部で多くの情報を失うでしょう、そしてあなたがすでに気づいたように結果は悪いでしょう。

たった1ステップ、またはトップ2で試してください。

たたみ込み

Photoshopの場合、鮮明化など、画像が再サンプリングされた後に畳み込みを適用することに注意してください。 Photoshopを完全にエミュレートするには、Photoshopが実行している手順を追加する必要があります(デフォルト設定)。

この例では、あなたがあなたの投稿で参照している私の最初の答えを使いますが、私はポストプロセスとしての品質を改善するためにそれに鋭い畳み込みを加えました(下のデモを見てください)。

これはシャープ化フィルタを追加するためのコードです(これは一般的な畳み込みフィルタに基づいています - シャープ化のための重み行列と効果の発音を調整するためのミックスファクタを入れています)。

使用法:

sharpen(context, width, height, mixFactor);

mixFactorは[0.0、1.0]の間の値であり、シャープ効果を軽視することを可能にします - 経験則:サイズが小さいほど効果は少なくて済みます。

関数このスニペットに基づく ):

function sharpen(ctx, w, h, mix) {

    var weights =  [0, -1, 0,  -1, 5, -1,  0, -1, 0],
        katet = Math.round(Math.sqrt(weights.length)),
        half = (katet * 0.5) |0,
        dstData = ctx.createImageData(w, h),
        dstBuff = dstData.data,
        srcBuff = ctx.getImageData(0, 0, w, h).data,
        y = h;

    while(y--) {

        x = w;

        while(x--) {

            var sy = y,
                sx = x,
                dstOff = (y * w + x) * 4,
                r = 0, g = 0, b = 0, a = 0;

            for (var cy = 0; cy < katet; cy++) {
                for (var cx = 0; cx < katet; cx++) {

                    var scy = sy + cy - half;
                    var scx = sx + cx - half;

                    if (scy >= 0 && scy < h && scx >= 0 && scx < w) {

                        var srcOff = (scy * w + scx) * 4;
                        var wt = weights[cy * katet + cx];

                        r += srcBuff[srcOff] * wt;
                        g += srcBuff[srcOff + 1] * wt;
                        b += srcBuff[srcOff + 2] * wt;
                        a += srcBuff[srcOff + 3] * wt;
                    }
                }
            }

            dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
            dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
            dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix)
            dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
        }
    }

    ctx.putImageData(dstData, 0, 0);
}

この組み合わせを使用した結果は次のようになります。

オンラインデモはこちら

Result downsample and sharpen convolution

シャープネスのどれだけをブレンドに追加したいかに応じて、デフォルトの "blurry"から非常にシャープにすることができます。

Variations of sharpen

提案2 - 低レベルのアルゴリズム実装

あなたが品質的に最高の結果を得たいならば、あなたは低レベルに行き、これをするために例えばこの真新しいアルゴリズムを実行することを考慮する必要があるでしょう。

IEEEの 補間依存型画像ダウンサンプリング (2011)を参照してください。
これは論文へのリンクです(PDF)

現時点でのJavaScript AFAIKにはこのアルゴリズムの実装はありませんので、このタスクに自分自身を投げたいのであれば、あなたは一杯になっています。

本質は(紙からの抜粋)です。

要約

本論文では、補間指向適応ダウンサンプリングアルゴリズムを低ビットレート画像符号化のために提案した。画像が与えられると、提案されたアルゴリズムは低解像度画像を得ることができ、そこから入力画像と同じ解像度を有する高品質画像を補間することができる。補間プロセスから独立している従来のダウンサンプリングアルゴリズムとは異なり、提案されたダウンサンプリングアルゴリズムは、ダウンサンプリングを補間プロセスに任せる。その結果、提案されたダウンサンプリングアルゴリズムは、入力画像の元の情報を最大限に維持することができる。ダウンサンプリングされた画像はその後JPEGに送られます。次に、全変動(TV)ベースの後処理が、解凍された低解像度画像に適用される。最終的に、処理された画像は入力画像の元の解像度を維持するために補間されます。 実験結果は提案したアルゴリズムによるダウンサンプリング画像を利用して、はるかに高品質の補間画像を達成できることを証明した。その上、提案されたアルゴリズムは、低ビットレート画像符号化のためにJPEGよりも優れた性能を達成することができる。

Snapshot from paper

(詳細、公式などについては、提供されているリンクを参照してください)

28
user1693593

キャンバスのみを使用したい場合は、複数のダウンステップを使用することをお勧めします。しかし、それはまだ十分ではありません。より良い品質のためには純粋なjsの実装が必要です。 pica - 可変品質/速度の高速ダウンスケーラをリリースしました。要するに、それは最高品質(3ローブのlanczosフィルタ)で、0.1秒で1280 * 1024ピクセル、1秒で5000 * 3000ピクセルの画像をリサイズします。 Picaには demo があります。ここでは、画像、品質レベル、そしてモバイル機器でも試すことができます。

Picaはまだアンシャープマスクを持っていませんが、それはすぐに追加されるでしょう。サイズ変更のための高速畳み込みフィルタを実装するよりもはるかに簡単です。

20
Vitaly

キャンバスを使用して画像のサイズを変更するのはなぜですか?最近のブラウザはすべて、バイキュービック補間(Photoshopで使用されているのと同じプロセス)を使用します(キャンバスプロセスよりも高速です)。必要な画像サイズを指定するだけです(縦横比を変更するには、高さまたは幅の寸法を1つだけ使用します)。

これは、それ以降のバージョンのIEを含む、ほとんどのブラウザでサポートされています。以前のバージョン ブラウザ固有のCSSが必要な場合があります

画像のサイズを変更するための簡単な関数(jQueryを使用)は、次のようになります。

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

その後、戻り値を使用して、画像のサイズを一方または両方の方向に変更します。

明らかにあなたが作ることができるさまざまな改良があるが、これは仕事を成し遂げる。

このページのコンソールに次のコードを貼り付けて、グラバターがどうなるかを見てください。

function resizeImage(img, percentage) {
    var coeff = percentage/100,
        width = $(img).width(),
        height = $(img).height();

    return {"width": width*coeff, "height": height*coeff}           
}

$('.user-gravatar32 img').each(function(){
  var newDimensions = resizeImage( this, 150);
  this.style.width = newDimensions.width + "px";
  this.style.height = newDimensions.height + "px";
});
17
Robusto

本当に画像自体のサイズを変更する必要がある人にとって正しい答えではありませんが、ファイルサイズを縮小するだけです

「直接カメラから」の写真に問題があり、私の顧客はしばしば「非圧縮」JPEGでアップロードしました。

あまり知られていませんが、キャンバスがJPEGの品質を変更するために(ほとんどのブラウザ2017で)サポートしているということです。

data=canvas.toDataURL('image/jpeg', .85) # [1..0] default 0.92

このトリックを使えば、> 10Mbの4k x 3kの写真を1または2Mbに減らすことができます。それはあなたのニーズ次第です。

ここを見てください

7
halfbit

これはウィンドウがフリーズしないように1人の作業員を利用する改良されたHermiteサイズ変更フィルタです。

https://github.com/calvintwr/Hermite-resize

4
Calvintwr

これは、高品質の画像/キャンバスのサイズ変更のための再利用可能なAngularサービスです。 https://Gist.github.com/fisch0920/37bac5e741eaec60e98

このサービスは、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
    })
})
4
fisch2

ピクセルデータに直接アクセスしてそれをループしてダウンサンプリングを実行する必要がないソリューションを見つけました。画像のサイズによっては、これはリソースを大量に消費する可能性があるため、ブラウザの内部アルゴリズムを使用することをお勧めします。

drawImage()関数は、線形補間、最近傍リサンプリング法を使用しています。元のサイズの半分を超えてサイズ変更していない場合は、うまく機能します

一度に最大半分のサイズだけを変更するようにループすると、結果はかなり良くなり、ピクセルデータにアクセスするよりもはるかに速くなります。

この関数は、目的のサイズに達するまで、一度に半分にダウンサンプリングします。

  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
Jesús Carrera

たぶんあなたはこれを試すことができます、それは私が私のプロジェクトでいつも使うものです。このようにしてあなたは高品質の画像だけでなくあなたのキャンバスの他のどの要素も得ることができます。

/* 
 * @parame canvas => canvas object
 * @parame rate => the pixel quality
 */
function setCanvasSize(canvas, rate) {
    const scaleRate = rate;
    canvas.width = window.innerWidth * scaleRate;
    canvas.height = window.innerHeight * scaleRate;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    canvas.getContext('2d').scale(scaleRate, scaleRate);
}
1
DeCoder

DEMO:JSとHTMLのキャンバスデモフィドラーを使って画像のサイズを変更する。

あなたはこのリサイズをするための3つの異なる方法を見つけるかもしれません、それはあなたがコードがどのように働いているか、そしてなぜ理解するのを助けるでしょう。

https://jsfiddle.net/1b68eLdr/93089/

デモと、コードで使用したいTypeScriptメソッドの両方の完全なコードは、GitHubプロジェクトにあります。

https://github.com/eyalc4/ts-image-resizer

これが最終的なコードです。

export class ImageTools {
base64ResizedImage: string = null;

constructor() {
}

ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
    let img = new Image();
    img.src = base64image;

    img.onload = () => {

        // Check if the image require resize at all
        if(img.height <= height && img.width <= width) {
            this.base64ResizedImage = base64image;

            // TODO: Call method to do something with the resize image
        }
        else {
            // Make sure the width and height preserve the original aspect ratio and adjust if needed
            if(img.height > img.width) {
                width = Math.floor(height * (img.width / img.height));
            }
            else {
                height = Math.floor(width * (img.height / img.width));
            }

            let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
            let resizingCanvasContext = resizingCanvas.getContext("2d");

            // Start with original image size
            resizingCanvas.width = img.width;
            resizingCanvas.height = img.height;


            // Draw the original image on the (temp) resizing canvas
            resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);

            let curImageDimensions = {
                width: Math.floor(img.width),
                height: Math.floor(img.height)
            };

            let halfImageDimensions = {
                width: null,
                height: null
            };

            // Quickly reduce the size by 50% each time in few iterations until the size is less then
            // 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
            // created with direct reduction of very big image to small image
            while (curImageDimensions.width * 0.5 > width) {
                // Reduce the resizing canvas by half and refresh the image
                halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
                halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);

                resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                    0, 0, halfImageDimensions.width, halfImageDimensions.height);

                curImageDimensions.width = halfImageDimensions.width;
                curImageDimensions.height = halfImageDimensions.height;
            }

            // Now do final resize for the resizingCanvas to meet the dimension requirments
            // directly to the output canvas, that will output the final image
            let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
            let outputCanvasContext = outputCanvas.getContext("2d");

            outputCanvas.width = width;
            outputCanvas.height = height;

            outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
                0, 0, width, height);

            // output the canvas pixels as an image. params: format, quality
            this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);

            // TODO: Call method to do something with the resize image
        }
    };
}}
0
Eyal c

。85の代わりに、1.を追加した場合。正確な答えが得られます。

data=canvas.toDataURL('image/jpeg', 1.0);

鮮明で明るい画像を得ることができます。チェックしてください

0
Phoenix