mcpField
I
image-processing

image-processing

github.com/claude-dev-suite開発4642 回インストール3 日前に更新
リポジトリ

image-processing について

Node.jsのSharp、Cloudinary、Python Pillowを使った画像処理の実践ガイドです。アップロード時の変換や検証、レスポンシブ画像、運用上の指針を扱い、単独の画像処理サービスを提供するものではありません。

主な機能

  1. 01Sharpによる画像のリサイズと変換
  2. 02Expressとmulterによるアップロード検証
  3. 03Python Pillowでの画像処理
  4. 04Cloudinaryによる画像管理
  5. 05レスポンシブ画像配信の指針
  6. 06運用チェックリストとアンチパターン

活用シーン

  1. 01アップロード時に複数サイズの画像を生成
  2. 02処理前にファイル形式とサイズを検証
  3. 03アップロード画像をWebPまたはAVIFに変換
  4. 04バックグラウンド処理とCDN配信を計画

インストール

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

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill image-processing

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

詳細

画像処理

Sharp(Node.js — 推奨)

import sharp from 'sharp';

// Resize and convert
async function processUpload(input: Buffer, filename: string) {
  const variants = await Promise.all([
    sharp(input).resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
      .webp({ quality: 80 }).toBuffer(),
    sharp(input).resize(400, 400, { fit: 'cover' })
      .webp({ quality: 75 }).toBuffer(),
    sharp(input).resize(100, 100, { fit: 'cover' })
      .webp({ quality: 70 }).toBuffer(),
  ]);

  return {
    original: { buffer: variants[0], key: `images/${filename}.webp` },
    medium: { buffer: variants[1], key: `images/${filename}-400.webp` },
    thumb: { buffer: variants[2], key: `images/${filename}-100.webp` },
  };
}
Express アップロードパイプライン
import multer from 'multer';

const upload = multer({ limits: { fileSize: 10 * 1024 * 1024 } }); // 10MB

app.post('/api/images', upload.single('image'), async (req, res) => {
  // Validate image type
  const metadata = await sharp(req.file!.buffer).metadata();
  if (!['jpeg', 'png', 'webp'].includes(metadata.format!)) {
    return res.status(400).json({ error: 'Invalid image format' });
  }

  const variants = await processUpload(req.file!.buffer, uuid());

  // Upload to S3
  await Promise.all(Object.values(variants).map((v) =>
    s3.putObject({ Bucket: BUCKET, Key: v.key, Body: v.buffer, ContentType: 'image/webp' })
  ));

  res.json({ url: `${CDN_URL}/${variants.original.key}` });
});

Cloudinary(マネージドサービス)

import { v2 as cloudinary } from 'cloudinary';

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD,
  api_key: process.env.CLOUDINARY_KEY,
  api_secret: process.env.CLOUDINARY_SECRET,
});

// Upload with auto-optimization
const result = await cloudinary.uploader.upload(filePath, {
  folder: 'products',
  transformation: [
    { width: 1200, height: 1200, crop: 'limit' },
    { quality: 'auto', fetch_format: 'auto' },
  ],
});

// URL-based transformations
const thumbUrl = cloudinary.url(result.public_id, {
  width: 200, height: 200, crop: 'fill', gravity: 'face',
  quality: 'auto', format: 'webp',
});

Python(Pillow)

from PIL import Image
from io import BytesIO

def process_image(data: bytes, max_size: int = 1200) -> bytes:
    img = Image.open(BytesIO(data))
    img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)

    if img.mode in ('RGBA', 'P'):
        img = img.convert('RGB')

    output = BytesIO()
    img.save(output, format='WEBP', quality=80)
    return output.getvalue()

レスポンシブ画像(HTML)

<picture>
  <source srcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w, /images/hero-1200.webp 1200w"
          sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
          type="image/webp" />
  <img src="/images/hero-800.jpg" alt="Hero image" loading="lazy" decoding="async" />
</picture>

アンチパターン

アンチパターン対策
リクエストハンドラー内で処理するバックグラウンドジョブキューで処理する
元のサイズのみ保存するアップロード時に複数のサイズを生成する
フォーマット変換を行わないサイズを小さくするためにWebP/AVIFに変換する
ファイルサイズを検証しない処理前に検証する
アプリサーバーから画像を配信するCDNまたはクラウドストレージを使用する

本番環境チェックリスト

  • バックグラウンドキューで画像を処理する
  • 複数のサイズ(サムネイル、中サイズ、フルサイズ)を生成する
  • 最新のブラウザー向けにWebP/AVIFへ変換する
  • 画像の配信にCDNを使用する
  • ファイルの種類とサイズを検証する
  • プライバシー保護のためメタデータを削除する

コメント