javascriptキャンバスで画像のサイズを変更します(スムーズに)


90

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

これが私のフィドルです: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つ目はキャンバスです。キャンバスがそれほど滑らかではないことに注意してください。どうすれば「滑らかさ」を実現できますか?

回答:


136

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

更新仕様に品質プロパティが追加されました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をスキップできます。

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


1
@steve heh、時々これらのことが起こります:)画像の場合、通常、cssBTWを設定することでこれをオーバーライドできます。

ケン、最初の結果はうまくいきましたが、画像を変更すると、ぼやけすぎていることがわかりますjsfiddle.net/kcHLGこの場合や他の場合に何ができるでしょうか?
スティーブ2013年

@steveでは、ステップ数を1つにするか、まったく減らすことができます(一部の画像ではこれで問題ありません)。これに似たこの回答も参照してください。ただし、ここでは、シャープな畳み込みを追加して、縮小後に結果の画像をシャープにできるようにしました。

1
@steveは、追加の1つのステップのみを使用したBillの修正されたフィドルです:jsfiddle.net/AbdiasSoftware/kcHLG/1

1
@neaumusicコードは、OPコードの続きです。フィドルを開くと、ctxが定義されているのがわかります。誤解を避けるために、ここにインライン化しました。

27

以来チュンル・グエンニャッのフィドルがすべてではないが正しいです(それはちょうど最後のステップで、元の画像を使用しています)
私は性能比較で自分の一般的なバイオリンを書きました:

フィドル

基本的には:

img.onload = function() {
   var canvas = document.createElement('canvas'),
       ctx = canvas.getContext("2d"),
       oc = document.createElement('canvas'),
       octx = oc.getContext('2d');

   canvas.width = width; // destination canvas size
   canvas.height = canvas.width * img.height / img.width;

   var cur = {
     width: Math.floor(img.width * 0.5),
     height: Math.floor(img.height * 0.5)
   }

   oc.width = cur.width;
   oc.height = cur.height;

   octx.drawImage(img, 0, 0, cur.width, cur.height);

   while (cur.width * 0.5 > width) {
     cur = {
       width: Math.floor(cur.width * 0.5),
       height: Math.floor(cur.height * 0.5)
     };
     octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
   }

   ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}

これまでに見た中で最も過小評価された答え。
Amsakanna 2018

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

申し訳ありませんが、githubのユーザー名を変更しました。リンクgist.github.com/transitive-bullshit/37bac5e741eaec60e983– fisch2 2018
1

3
私は角張った言葉を見ました、私はその面白い感じを得ました
SuperUberDuper 2018

8

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

私はスタックオーバーフローからの「コピーアンドペースト」のファンではないので、開発者にソフトウェアにプッシュされるコードを理解してもらいたいのですが、以下がお役に立てば幸いです。

デモ:JSとHTML CanvasDemoフィドラーを使用した画像のサイズ変更。

このサイズ変更を行うには、コードがどのように機能しているか、およびその理由を理解するのに役立つ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
        }
    };
}}

4

すべてのカラーデータを保持しながら、任意のパーセンテージをダウンステップできるライブラリを作成しました。

https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js

ブラウザに含めることができるそのファイル。結果は、近くのピクセルを取得して他のピクセルを削除するのではなく、すべてのカラーデータを保持し、ピクセルを平均化して、フォトショップやイメージマジックのように見えます。それは平均を推測するために公式を使用しません、それは正確な平均を取ります。


1
私はおそらくwebglを使用してサイズを変更します
Funkodebat 2017

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デモを更新

これが私のオンラインデモです


2
これは機能しません。キャンバスのサイズを変更するたびに、コンテキストがクリアされます。2枚のキャンバスが必要です。ここでは、最終的な寸法でdrawImageを直接呼び出すのと同じです。
Kaiido 2016年

2

なぜ誰も提案していないのかわかりませんcreateImageBitmap

createImageBitmap(
    document.getElementById('image'), 
    { resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap => 
    document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);

美しく機能します(画像とキャンバスのIDを設定すると仮定します)。


これは、広くサポートされていないのでcaniuse.com/#search=createImageBitmap
マット・

createImageBitmapは、すべてのユーザーの73%でサポートされています。ユースケースによっては、それで十分な場合があります。サポートの追加を拒否するのはSafariだけです。考えられる解決策として言及する価値があると思います。
cagdas_ucar

良い解決策ですが、残念ながらFirefoxでは機能しません
vcarel

1

フロントエンドで画像を切り抜いてサイズを変更するための小さなjs-​​utilityを作成しました。これがGitHubプロジェクトへのリンクです。また、最終画像からblobを取得して送信することもできます。

import imageSqResizer from './image-square-resizer.js'

let resizer = new imageSqResizer(
    'image-input',
    300,
    (dataUrl) => 
        document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);

//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.