128

キャンバスでいくつかの画像のサイズを変更しようとしていますが、それらを滑らかにする方法がわかりません。Photoshop、ブラウザなどでは、使用するアルゴリズムがいくつかあります(バイキュービック、バイリニアなど)が、これらがキャンバスに組み込まれているかどうかはわかりません。

これが私のフィドルです:http://jsfiddle.net/EWupT/

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);

1 つ目は通常のサイズ変更されたイメージ タグで、2 つ目はキャンバスです。キャンバスが滑らかではないことに注意してください。どうすれば「滑らかさ」を達成できますか?

4

11 に答える 11

164

ダウンステップを使用して、より良い結果を得ることができます。ほとんどのブラウザーは、画像のサイズを変更するときにバイ 3 次ではなく線形補間を使用しているようです。

(更新imageSmoothingQuality現在 Chrome でのみ利用可能な品質プロパティが仕様に追加されました。)

スムージングまたは最近傍を選択しない限り、ブラウザは、エイリアシングを回避するためのローパス フィルターとして機能するため、ダウン スケーリング後に常に画像を補間します。

バイリニアは 2x2 ピクセルを使用して補間を行いますが、バイキュービックは 4x4 を使用するため、結果の画像に見られるようにバイリニア補間を使用しながら段階的に行うことで、バイキュービックの結果に近づけることができます。

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();

img.onload = function () {

    // set size proportional to image
    canvas.height = canvas.width * (img.height / img.width);

    // step 1 - resize to 50%
    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);

    // step 2
    octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);

    // step 3, resize to final size
    ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
    0, 0, canvas.width, canvas.height);
}
img.src = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>

サイズ変更の程度によっては、違いが少ない場合はステップ 2 をスキップできます。

デモでは、新しい結果が画像要素に非常に似ていることがわかります。

于 2013-10-09T03:08:38.603 に答える
17

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

どちらにも長所と短所があるため、このサービスには 2 つのソリューションが含まれています。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
    })
})
于 2014-09-16T23:34:42.883 に答える
12

これらのコード スニペットの一部は短くて機能していますが、理解して理解するのは簡単ではありません。

私はスタック オーバーフローからの「コピー アンド ペースト」が好きではないので、開発者にはソフトウェアにプッシュするコードを理解してもらいたいと思います。以下が役立つことを願っています。

DEMO : JS と HTML Canvas Demo fiddler を使用した画像のサイズ変更。

このサイズ変更を行う 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 dize 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
        }
    };
}}
于 2018-12-31T10:16:12.770 に答える
4

K3Nの回答に基づいて、誰でも必要なコードを一般的に書き直します

var oc = document.createElement('canvas'), octx = oc.getContext('2d');
    oc.width = img.width;
    oc.height = img.height;
    octx.drawImage(img, 0, 0);
    while (oc.width * 0.5 > width) {
       oc.width *= 0.5;
       oc.height *= 0.5;
       octx.drawImage(oc, 0, 0, oc.width, oc.height);
    }
    oc.width = width;
    oc.height = oc.width * img.height / img.width;
    octx.drawImage(img, 0, 0, oc.width, oc.height);

JSFIDDLE デモの更新

ここに私のオンラインデモがあります

于 2016-06-07T10:09:13.867 に答える
1

これが私のコードです。これが SO コミュニティの誰かに役立つことを願っています。

スクリプト呼び出しで、ターゲット イメージのサイズをパラメーターとして含めることができます。これは、画像の幅または高さのどちらか大きい方の結果の値になります。小さい方のサイズは、画像の縦横比を変更せずにサイズ変更されます。スクリプトでデフォルトのターゲット サイズをハードコーディングすることもできます。

出力に必要な画像タイプ (デフォルトは「image/png」) など、特定のニーズに合わせてスクリプトを簡単に変更し、より細かい結果を得るために画像のサイズをパーセント単位で何段階変更するかを決定できます (を参照)。 const percentStep コード)。

   const ResizeImage = ( _ => {

const MAX_LENGTH = 260;     // default target size of largest dimension, either witdth or height
const percentStep = .3;     // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();

const doResize = (callback, maxLength) => {

    // abort with error if image has a dimension equal to zero
    if(image.width == 0 || image.height == 0) {
        return {blob: null, error: "either image width or height was zero "};
    }

    // use caller dimension or default length if none provided
    const length = maxLength == null  ? MAX_LENGTH : maxLength;

    canvas.width = image.width;
    canvas.height = image.height;
    canvasContext.drawImage(image, 0, 0, image.width, image.height);
    // if image size already within target size, just copy and return blob
    if(image.width <= length && image.height <= length) {
        canvas.toBlob( blob => {
            callback({ blob: blob, error: null });
        }, "image/png", 1);
        return;
    }

    var startDim = Math.max(image.width, image.height);
    var startSmallerDim = Math.min(image.width, image.height);

    // gap to decrease in size until we reach the target size,
    // be it by decreasing the image width or height,
    // whichever is largest
    const gap = startDim - length;
    // step length of each resizing iteration
    const step = parseInt(percentStep*gap);
    //  no. of iterations
    var nSteps = 0;
    if(step == 0) {
        step = 1;
    } else {
        nSteps = parseInt(gap/step);
    }
    // length of last additional resizing step, if needed
    const lastStep = gap % step;
    // aspect ratio = value by which we'll  multiply the smaller dimension
    // in order to keep the aspect ratio unchanged in each iteration
    const ratio = startSmallerDim/startDim;

    var newDim;          // calculated new length for the bigger dimension of the image, be it image width or height
    var smallerDim;     // length along the smaller dimension of the image, width or height
    for(var i = 0; i < nSteps; i++) {
        // decrease longest dimension one step in pixels
        newDim = startDim - step;
        // decrease shortest dimension proportionally, so as to keep aspect ratio
        smallerDim = parseInt(ratio*newDim);
        // assign calculated vars to their corresponding canvas dimension, width or height
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [newDim, smallerDim];
        } else {
            [canvas.width, canvas.height] = [smallerDim, newDim];
        }
        // draw image one step smaller
        canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
        // cycle var startDim for new loop
        startDim = newDim;
    }

    // do last missing resizing step to finally reach target image size
    if(lastStep > 0) {
        if(image.width > image.height) {
            [canvas.width, canvas.height]  = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
        } else {
            [canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
        }
        canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
    }

    // send blob to caller
    canvas.toBlob( blob => {
        callback({blob: blob, error: null});
    }, "image/png", 1);

};

const resize = async (imgSrc, callback, maxLength) => {
    image.src = imgSrc;
    image.onload = _ => {
       doResize(callback, maxLength);
    };
};

return { resize: resize }

})();

使用法:

ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
    if(imageObject.error != null) {
      // handle errors here
      console.log(imageObject.error);
      return;
    }
    // do whatever you want with the blob, like assinging it to
    // an img element, or uploading it to a database
    // ...
    document.querySelector("#my-image").src = imageObject.blob;
    // ...
}, 300);
于 2021-06-21T03:12:50.513 に答える
0
export const resizeImage = (imageFile, size = 80) => {
    
    let resolver = ()=>{};

    let reader = new FileReader();

    reader.onload = function (e) {
        let img = document.createElement("img");
        img.onload = function (event) {
            // Dynamically create a canvas element
            let canvas = document.createElement("canvas");

            canvas.width=size;
            canvas.height=size;

            // let canvas = document.getElementById("canvas");
            let ctx = canvas.getContext("2d");

            // Actual resizing
            ctx.drawImage(img, 0, 0, size, size);

            // Show resized image in preview element
            let dataurl = canvas.toDataURL(imageFile.type);

            resolver(dataurl);
        }
        img.src = e.target.result;
    }

    reader.readAsDataURL(imageFile);

    
    return new Promise((resolve, reject) => {
        resolver = resolve;
    })
};
于 2021-12-11T15:14:56.740 に答える