mcpField
W
web-files-image-handling

web-files-image-handling

github.com/agents-inc開発2666 回インストール2 日前に更新
リポジトリ

web-files-image-handling について

web-files-image-handlingは、ブラウザーでの画像プレビューとCanvas処理を扱うガイドスキルです。表示と変換を分けた手順により、向き、メモリー、画質に関する問題を防ぎます。

主な機能

  1. 01オブジェクトURLのプレビューと解放
  2. 02Canvasによるリサイズと圧縮
  3. 03EXIF方向の処理
  4. 04段階的な縮小
  5. 05トリミングと形式変換

活用シーン

  1. 01Webアプリで選択画像をプレビュー
  2. 02アップロード前のリサイズや圧縮
  3. 03サムネイル生成や画像のトリミング
  4. 04出力画像データの向きの正規化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/agents-inc/skills --skill web-files-image-handling

インストール後、Claude Code で/web-files-image-handlingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

metadata.yaml
reference.md
examples/
canvas.md
core.md
preview.md

詳細

画像処理のパターン

クイックガイド: 目的は2つあり、それぞれに適したツールも異なります。画像を表示する場合は URL.createObjectURL() を使い、表示後に URL を無効化します。ブラウザーはすでに EXIF の回転を適用するため、 手動で回転すると二重に回転してしまいます。画像を処理する場合は Canvas を使います。サイズは 4096px に制限し、 50%を超えて縮小するときは複数回に分けて縮小し、JPEG に書き込む前に白で塗りつぶし、toDataURL() より非同期の toBlob() を優先します。

詳細なリソース:

  • examples/core.md — 共通の loadImage ヘルパー、プレビュー用フックとコンポーネント、寸法の抽出と検証、EXIF の解析、ギャラリーの状態
  • examples/preview.md — 複数のサムネイルセット、ギャラリーグリッド
  • examples/canvas.md — リサイズ処理、目標サイズへの圧縮、トリミング、透かし、フィルター
  • reference.md — 形式と方法の選択、定数、メソッドの比較、EXIF 値、ブラウザーのサポート

どちらの方法を使うか

2つの方法では EXIF の扱いが異なります。これが最もよくあるバグの原因です。

  • ユーザーに表示する場合 — オブジェクト URL をそのまま <img> に渡し、置き換え時またはアンマウント時に無効化します。ブラウザーが回転してくれるため、向きには何もしません。examples/core.md から始めてください。
  • 別の場所に送る前に処理する場合 — Canvas でリサイズ、圧縮、変換、またはトリミングします。ここでは自動で回転されないため、向きを明示的に正規化し、ブラウザー独自の回転を抑制せずに結果を表示しないでください。examples/canvas.md から始めてください。

<critical_requirements>

画像処理コードを書く前に

アンマウント時と、置き換え用の URL を作成する前に、すべてのオブジェクト URL を無効化してください。 createObjectURL は無効化されるまで Blob をメモリに保持するため、ユーザーが気を変えて選び直すファイル選択では、試行するたびに画像全体がメモリに残ります。

向きを扱う前に、どちらの方法を使うか決めてください。 2020 年以降、ブラウザーでは image-orientation: from-image がデフォルトになっているため、表示用に正規化すると画像が二重に回転します。正規化するのは、ブラウザーの外に送るバイトデータだけです。

Canvas の寸法は 4096px に制限してください。 これを超えると Canvas は失敗してもエラーを通知しないことがあり、タブがクラッシュする場合もあります。モバイルでは上限がさらに低くなります。

半分を超えて縮小する場合は、複数回に分けて縮小してください。 1回で縮小するとサンプリング不足になり、ぼやけたりエイリアスが発生したりします。中間段階を2回設けると鮮明さを保てます。

</critical_requirements>


自動検出: URL.createObjectURL、URL.revokeObjectURL、createImageBitmap、OffscreenCanvas、canvas.toBlob、canvas.toDataURL、convertToBlob、ctx.drawImage、imageSmoothingQuality、image-orientation、EXIF orientation、0x0112、FileReader.readAsDataURL、naturalWidth、image/webp、段階的縮小、サムネイル生成、クライアント側リサイズ、画像のトリミング

対象範囲:

  • ユーザーが選択した画像のプレビューを表示する
  • ブラウザーで画像をリサイズ、圧縮、または変換する
  • EXIF の向きを読み取り、正規化する
  • サムネイルの生成、トリミング、透かしの追加、フィルターの適用
  • その他の処理を行う前に、寸法とアスペクト比を検証する

対象外:

  • ファイルの選択と転送 — このスキルでは、すでに保持している File または Blob から始めます
  • サーバーでのリサイズやトランスコード、URL ベースの変換サービス
  • レイヤー、選択範囲、取り消し機能を備えたインタラクティブな編集画面。生の Canvas 呼び出しではなく、Canvas ライブラリが適しています

## 基本方針

プレビューと処理は相反する方向を向いており、両者を混同することが画像に関するバグの多くの原因です。

プレビューにはコストがかからないはずです。URL.createObjectURL() は、すでにメモリ上にあるバイト列への参照を <img> に渡すため、JavaScript へのデコードもコピーも発生しません。その代わり、ライフタイムを手動で管理する必要があります。参照は無効化しない限り、コンポーネントの破棄後も残ります。

処理はその逆です。ピクセルをキャンバスにデコードし、変換して、再びエンコードする必要があります。負荷の高い処理はすべてここで行われるため、キャンバス処理はフル解像度で何度も行うのではなく、実際に必要なサイズで一度だけ行うのが適切です。

ブラウザー独自の EXIF 処理は、その境界をまたいでいます。表示時には画像を回転しますが、キャンバスでは回転しません。そのため、同じファイルでも、読み込み経路によって向きが異なります。


基本パターン

パターン 1:クリーンアップを伴うオブジェクト URL プレビュー

次の URL を作成する前に以前の URL を無効化し、アンマウント時にも無効化します。レンダー中に URL を作成すると、レンダーごとに 1 つずつリークします。

useEffect(() => {
  const url = URL.createObjectURL(file);
  setPreviewUrl(url);
  return () => URL.revokeObjectURL(url);
}, [file]);

コード全文:examples/core.md

パターン 2:キャンバスのリサイズ

ブラウザーの上限に合わせてサイズを制限し、アスペクト比を維持して、高品質のスムージングを指定します。出力形式が JPEG の場合は、描画前に白で塗りつぶしてください。JPEG にはアルファチャンネルがないため、透明部分は黒としてエンコードされます。

const MAX_CANVAS_DIMENSION = 4096;

const ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
const width = Math.round(img.width * Math.min(ratio, 1)); // never upscale
const height = Math.round(img.height * Math.min(ratio, 1));

ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
if (mimeType === "image/jpeg") {
  ctx.fillStyle = "#ffffff";
  ctx.fillRect(0, 0, width, height);
}
ctx.drawImage(img, 0, 0, width, height);

コード全文:examples/canvas.md

パターン 3:段階的な縮小

drawImage は固定サイズの近傍をサンプリングするため、一度に大きく縮小すると、元画像の大部分が失われます。何度も半分ずつ縮小すれば、すべてのピクセルを処理に反映できます。

const STEP_DOWN_THRESHOLD = 0.5;

if (targetWidth / img.width < STEP_DOWN_THRESHOLD) {
  // 4000 → 400 → 100 rather than 4000 → 100
  const factor = Math.pow(targetWidth / img.width, 1 / steps);
  for (let i = 0; i < steps; i++) {
    /* draw into a canvas scaled by factor, then use it as the next source */
  }
}

コード全文:examples/canvas.md

パターン 4:EXIF の向き
// Display: the browser has already rotated it
<img src={URL.createObjectURL(file)} />

// Outbound bytes: normalise, because the next consumer may not rotate
const orientation = await getExifOrientation(file);
const normalized = orientation === 1 ? file : await normalizeOrientation(file);

// Displaying an already-normalised image: suppress the second rotation
<img src={url} style={{ imageOrientation: "none" }} />

コード全文:examples/core.md

パターン 5:形式の変換

品質の意味は形式によって異なり、PNG ではパラメーターが完全に無視されます。

const FORMAT_QUALITY_DEFAULTS: Record<string, number> = {
  "image/jpeg": 0.85,
  "image/webp": 0.82,
  "image/png": 1, // lossless — the quality argument is ignored
};

WebP は、Safari 14 以降を含む最新の環境でエンコードできます。品質設定ではなくバイト数の上限を目指す場合は、品質パラメーターを二分探索します。

コード全文:examples/canvas.md

パターン 6:切り抜き

9 つの引数を使う drawImage は、ソースの矩形と描画先の矩形を受け取るため、切り抜きとリサイズを 1 回の呼び出しで行えます。範囲外の領域はエラーにならず何も描画されないため、まずソースの矩形を画像の範囲内に収めてください。

// drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh)
ctx.drawImage(
  img,
  cropX,
  cropY,
  cropWidth,
  cropHeight,
  0,
  0,
  outputWidth,
  outputHeight,
);

コード全文:examples/canvas.md


<red_flags>

注意すべき点

実行時に問題が発生します:

  • URL.revokeObjectURL() がありません。ページが存続する間、選択するたびに別の画像がメモリに保持されます。エフェクトのクリーンアップ時と URL を置き換える前に取り消してください。
  • レンダリング中に URL.createObjectURL() を呼び出しています。レンダリングごとに新しい URL が作成され、どれも取り消されません。エフェクト内に移動してください。
  • キャンバスのサイズが 4096px を超えています。タブがクラッシュするか、キャンバスが何も出力せずに終わることがあります。canvas.width に代入する前に上限を設定してください。
  • 向きを正規化した後、結果を <img> でレンダリングしています。ブラウザーがもう一度回転させてしまいます。その要素に image-orientation: none を設定するか、表示用に正規化しないでください。
  • 透明な PNG を背景色を塗りつぶさずに JPEG に変換しています。透明部分が黒くなります。先に白で fillRect を実行してください。

予想外の動作:

  • 1 回の処理で 50% を超えて縮小するとぼやけてギザギザになりますが、2 回に分けて縮小すると鮮明になります。
  • toBlob() は非同期ですが、toDataURL() は同期です。また、同期処理のほうがコストがかかります。
  • データ URL は、エンコード対象のファイルよりおよそ 3 分の 1 大きくなります。そのため、10MB の画像はメインスレッド上で 13MB の文字列になります。
  • Node には image-orientation がないため、サーバー側でキャンバスを処理する場合も EXIF を手動で処理する必要があります。
  • createImageBitmap はレイアウトとデコードを省略します。また、その結果は close() が呼び出されるまでメモリを保持します。
  • 1 辺のサイズが上限内でも、画像がキャンバス全体の面積制限を超えることがあります。
  • toBlob() による AVIF エンコードは、すべてのブラウザーで利用できるわけではありません。デコードは可能です。
  • すでに最適化された画像を再エンコードすると、通常はサイズが小さくならず、むしろ大きくなります。

コメント