0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptで画像をリサイズする方法(Canvas)— 縦横比維持・きれいに縮小するコツ

0
Posted at

「アップロードできる画像は2MBまで」「プロフィール画像は正方形で」——画像のサイズを変えたい場面は地味に多いです。わざわざアプリを入れなくても、ブラウザ(JavaScript)だけで画像はリサイズできます。しかもファイルをサーバーに送らずに完結できます。

この記事では、Canvas を使った画像リサイズの基本と、縦横比を保つ計算きれいに縮小するコツをまとめます。おまけに Python(Pillow)版も載せます。

ゴール:画像を指定した幅・高さに、縦横比を保ったまま、きれいにリサイズできるようになること。


基本:Image を読み込んで Canvas に描き直す

リサイズの流れは「画像を読み込む → 目的のサイズの canvas に描画する → 画像として書き出す」だけです。

<input type="file" id="file" accept="image/*">
document.getElementById("file").addEventListener("change", async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  // 1. 画像を読み込む
  const img = await loadImage(file);

  // 2. 目的のサイズを決める(ここでは幅400pxに、縦横比維持)
  const targetW = 400;
  const targetH = Math.round((targetW * img.naturalHeight) / img.naturalWidth);

  // 3. canvasに描画してリサイズ
  const canvas = document.createElement("canvas");
  canvas.width = targetW;
  canvas.height = targetH;
  const ctx = canvas.getContext("2d");
  ctx.imageSmoothingEnabled = true;
  ctx.imageSmoothingQuality = "high"; // 縮小をなめらかに
  ctx.drawImage(img, 0, 0, targetW, targetH);

  // 4. 書き出し(Blob推奨。大きい画像でも軽い)
  canvas.toBlob((blob) => download(blob, "resized.jpg"), "image/jpeg", 0.9);
});

function loadImage(file) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => { URL.revokeObjectURL(url); resolve(img); };
    img.onerror = reject;
    img.src = url;
  });
}

function download(blob, name) {
  const url = URL.createObjectURL(blob);
  const a = Object.assign(document.createElement("a"), { href: url, download: name });
  a.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}

ポイントは toDataURL ではなく toBlob を使うこと。Data URL は文字列なので大きい画像だとメモリを食いますが、Blob なら効率的で、そのままダウンロードにも使えます。


縦横比を保つ計算

指定の仕方に応じて、もう片方の辺を計算します。

// 幅を基準に高さを求める
const h = Math.round((w * img.naturalHeight) / img.naturalWidth);

// 高さを基準に幅を求める
const w2 = Math.round((h2 * img.naturalWidth) / img.naturalHeight);

// 「長辺を maxSize に収める」パターン(サムネイル向け)
function fit(img, maxSize) {
  const scale = Math.min(1, maxSize / Math.max(img.naturalWidth, img.naturalHeight));
  return {
    w: Math.round(img.naturalWidth * scale),
    h: Math.round(img.naturalHeight * scale),
  };
}

Math.min(1, …) を挟むと「元より大きくしない(拡大しない)」制御になります。


きれいに縮小するコツ

  • imageSmoothingQuality = "high" を指定する。 既定より補間がなめらかになります。
  • 大幅な縮小は createImageBitmap が手軽できれい。 ブラウザ側で高品質にリサイズしてくれます。
// 幅400pxへ高品質リサイズ(縦横比は自分で計算して渡す)
const bitmap = await createImageBitmap(file, {
  resizeWidth: 400,
  resizeQuality: "high",
});
const canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext("2d").drawImage(bitmap, 0, 0);

createImageBitmap はデコード&リサイズを一括で行うので、drawImage で一気に縮小するより輪郭のジャギーが出にくいです。


出力形式と容量

toBlob の第2・第3引数で形式と画質を選びます。

canvas.toBlob(cb, "image/jpeg", 0.8); // 写真向け。0〜1で画質
canvas.toBlob(cb, "image/webp", 0.8); // さらに軽い。透過も可
canvas.toBlob(cb, "image/png");       // 透過を保ちたいとき(qualityは無視)

写真はJPEGかWebP、透過が必要ならPNGかWebP、が基本の使い分けです。


つまずきやすいところ

  • iPhone写真などの向き(EXIF Orientation)。 生のピクセルを canvas に描くと、回転情報が反映されず横倒しになることがあります。createImageBitmap(file, { imageOrientation: "from-image" }) を使うと、EXIF の向きに合わせて起こしてくれます。
  • 拡大は粗くなる。 元より大きくすると引き伸ばしになるので、基本は「縮小」で使うのが無難です。
  • toBlob は非同期。 コールバック(またはPromise化)で受け取ります。書き出し直後にサイズを測りたいときは順番に注意。
  • 巨大画像はメモリに注意。 スマホで数千万画素を扱うと重いので、必要なら描画dpiや最大辺を制限します。

Python でやる場合(Pillow)

サーバーやバッチなら Pillow が定番です。

from PIL import Image, ImageOps

img = Image.open("input.jpg")
img = ImageOps.exif_transpose(img)          # EXIFの向きを反映

# 幅400pxへ(縦横比維持)
w = 400
h = round(w * img.height / img.width)
img = img.resize((w, h), Image.LANCZOS)     # LANCZOSできれいに縮小
img.save("resized.jpg", quality=90)

# 「長辺512pxに収める」ならthumbnailが手軽(アスペクト比を自動維持)
img.thumbnail((512, 512), Image.LANCZOS)

まとめ

  • ブラウザだけなら Image → canvas に drawImagetoBlob でリサイズできる
  • 縦横比は もう片方の辺を計算Math.min(1, …) で拡大を防げる
  • きれいに縮小したいなら imageSmoothingQuality="high"createImageBitmapresizeQuality:"high"
  • EXIFの向きimageOrientation:"from-image" / Pillowの exif_transpose で対処
  • Python は PillowresizeLANCZOS / thumbnail

「画像をちょっと小さくしたい」に、ブラウザ完結はよく合います。参考になれば嬉しいです。


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?