猫の手ツール

Algorithm & UI/UX

クライアントサイドで完結させる
画像PDF変換の最適化戦略

サーバーに一切画像を送信せず、ブラウザのメモリ上だけでPDFを構築する。この「プライバシー第一」の設計を実現するために導入した、メモリ消費を抑える最適化アルゴリズムと、多様な画像サイズをA4に収めるフィッティングロジックについて解説します。

1. 2段階の適応型リサイズ・パイプライン

スマホで撮影した写真は1枚数MB〜十数MBに達することがあります。これらをそのまま複数枚読み込むと、ブラウザのメモリが枯渇し、タブがクラッシュする原因となります。

本ツールでは、ファイルを読み込んだ直後に「1段階目の粗いリサイズ」を行い、PDF生成の直前にCanvasを用いて「2段階目の高精度な最適化」を行うパイプラインを採用しています。これにより、HEICなどの重い形式も軽量なJPEGに変換され、安定した動作を維持できます。

// 画像読み込み時の適応型リサイズ処理
async function handleFiles(files) {
  for (const file of files) {
    let processBlob = file;
    
    // HEIC形式の場合は事前に変換
    if (isHeicFile(file)) {
      processBlob = await convertHeicToJpeg(file);
    }

    const img = await loadImage(processBlob);
    
    // 独自のチューニング値に基づき、メモリ消費を抑える1次リサイズ
    if (img.width > LIMIT_RES || img.height > LIMIT_RES) {
      processBlob = await resizeOnCanvas(img, LIMIT_RES);
    }

    // PDF生成用にさらに最適化された内部オブジェクトを生成
    const optimizedItem = await finalizeOptimization(processBlob);
    imageItems.push(optimizedItem);
  }
}

2. アスペクト比を維持する動的センタリング・アルゴリズム

PDF作成時、画像によって向き(縦・横)や比率はバラバラです。本実装では、jsPDFのページ設定を動的に切り替えつつ、A4の枠内に画像を「アスペクト比を維持しながら最大サイズで配置」する計算を行っています。

特に、ページ比率と画像比率を比較し、縦に合わせるか横に合わせるかを分岐させることで、どのような形状の画像でも中央に綺麗にレイアウトされます。

// PDFページへの動的スケーリング・ロジック
imageItems.forEach((item, index) => {
  // 1枚目の向きに合わせてPDFの基本設定を決定
  const orientation = item.width > item.height ? 'l' : 'p';
  
  // A4サイズの比率と画像の比率から描画領域を計算
  const imgRatio = item.width / item.height;
  const pageRatio = targetPageWidth / targetPageHeight;

  let renderW, renderH, offsetX, offsetY;

  // 比率の比較に基づくスケーリング計算
  if (imgRatio > pageRatio) {
    // 横幅いっぱいに合わせる
    renderW = targetPageWidth;
    renderH = renderW / imgRatio;
    offsetY = (targetPageHeight - renderH) / // 独自の分割係数
  } else {
    // 縦幅いっぱいに合わせる
    renderH = targetPageHeight;
    renderW = renderH * imgRatio;
    offsetX = (targetPageWidth - renderW) / // 独自の分割係数
  }

  pdf.addImage(item.dataUrl, 'JPEG', offsetX, offsetY, renderW, renderH);
});

Developer's Note

このツールの開発において最も苦労したのは、「メモリ管理」と「利便性」のトレードオフでした。

当初は最高画質でPDF化する方針でしたが、iPhone 12以前のモデルやメモリの少ないAndroid端末で、10枚以上の画像を読み込むと即座にクラッシュするという課題に直面しました。そこで、ユーザーが気づかないレベルでCanvasを介した再サンプリングを細かく行い、1ページあたりのデータ量を「印刷に耐えうる最低限」まで絞り込む独自のチューニングを施しました。

また、外部サーバーを介さないという設計は、コスト削減のためではなく、純粋に「機密書類を扱うユーザーへの誠実さ」を形にしたものです。ソースコードを読めるエンジニアが見ても「確かに安全だ」と確信を持てる実装を目指しました。