マガジン

カバー画像を文字で保存すると何が起きるか:「96,000文字」という数字を自分の手で確かめる

遊びを見つける

執筆にClaudeを使用しています。

色鉛筆の線やゲームのスケッチが描かれたノートとカード。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

「カバー画像は、画像ファイルではなく長い文字列としてデータベースに入れることができます」と聞くと、少し奇妙に感じます。画像は画像ファイルとして置くものだと思っていると、なおさらです。それでも、画像を文字に変えて文書の一項目として持たせる方法は、小さな画像であれば現実的な選択肢です。題名にある「96,000文字」は、その方法にかかる上限として出てきた数字です。 最初に線を引いておきます。アソバースの公式作品について確認できているのは、カバー画像が生成したアートワークであり、ゲーム画面のスクリーンショットではないという点までです。カバー画像をFirestoreにデータURLで保存しているかどうか、96,000文字が実際の上限値かどうかは、確認済みの情報に含まれていません。この記事はそれらを事実として語りません。「文字として保存する」設計を取ると上限がどう決まるのか、96,000という数字をどう読めばよいのかを、計算と手元の確認で読者自身が検証できる形にします。

この記事の目次
  1. 画像が「文字」になるしくみ:データURL
  2. 96,000文字は、画像にすると何バイトか
  3. なぜ「文字」にするのか:得るものと失うもの
  4. 96,000という数字から読み取れること、読み取れないこと
  5. 自分の画像で、上限に収まるかを測る
  6. 起こりやすい誤り
  7. 次の行動
  8. 参考資料

画像が「文字」になるしくみ:データURL

データURLは、画像の中身そのものをURLの形で書いたものです。先頭に「data:image/webp;base64,」のような宣言が付き、その後ろに画像のバイト列を文字に直した長い並びが続きます。この文字への変換方式がBase64です。

Base64は、3バイトのデータを4文字で表します。ここから次のことが決まります。

  • 文字数は元のバイト数の約4/3倍(約1.33倍)になります。
  • 使う文字は英数字と一部の記号だけなので、UTF-8で保存しても1文字は1バイトです。つまり「文字数」と「保存されるバイト数」はほぼ一致します。
  • 先頭の宣言部分が20文字あまり加わります。「data:image/webp;base64,」で23文字、PNGなら22文字です。

この三つがあれば、上限から元の画像の大きさを逆算できます。

96,000文字は、画像にすると何バイトか

上限が96,000文字だと仮定して、元の画像が最大どれだけの大きさまで許されるかを計算します。

  1. 宣言部分を引きます。96,000 − 23 = 95,977文字です。
  2. 4文字が3バイトに対応するので、95,977 × 3 ÷ 4 ≒ 71,982バイトです。
  3. 末尾の詰め物(「=」)の分を考えても、元の画像は約72,000バイト弱、つまり約70KiBが目安になります。

70KiBは、写真をそのまま載せるには小さい容量です。一方、サイズを絞った絵柄のカバー画像なら、形式と品質の選び方次第で収まる範囲です。つまり96,000文字という上限は、画像を大きく見せたい欲求ではなく、小さく軽くまとめる前提のもとで置かれた数字だと読めます。

なぜ「文字」にするのか:得るものと失うもの

画像ファイルを別の場所に置かず、文書の一項目として文字で持たせる設計には、次のような特徴があります。

  • 取得、データURL(文字で保存):文書を読めば画像も一緒に届く、画像ファイルを別に置く:文書を読んだあと、画像を別途取りに行く
  • 容量、データURL(文字で保存):元より約33%増える、画像ファイルを別に置く:元のまま
  • ブラウザのキャッシュ、データURL(文字で保存):画像単位では効きにくい、画像ファイルを別に置く:画像のURL単位で効きやすい
  • 管理するもの、データURL(文字で保存):保存先が一つ、画像ファイルを別に置く:保存先が二つ(文書と画像置き場)
  • 上限、データURL(文字で保存):文書全体の大きさに縛られる、画像ファイルを別に置く:画像側の上限に縛られる

利点は、保存先が一つで済み、読み込みの流れが単純になることです。欠点は、画像の分だけ文書が重くなることです。

とくに注意したいのは、Firestoreが文書単位で読み書きするデータベースだという一般的な性質です。一覧を表示するために文書を読むと、その中に入っている画像の文字列も丸ごと付いてきます。

仮に1枚が72,000バイト近い画像で、一覧に50件を並べるなら、画像分だけで約3.6MBになります(72,000 × 50)。これは計算上の例であり、アソバースの画面でそうなっているという意味ではありません。「文字で持つ」設計は、一覧に出る枚数と1枚あたりの重さを掛け合わせて考える必要があります。

96,000という数字から読み取れること、読み取れないこと

Firestoreの1文書には大きさの上限があり、公式ドキュメントでは1MiB(1,048,576バイト)と案内されています。この値は更新されることがあるため、実際の数字はFirebaseの公式ドキュメントで確認してください。また、この上限は画像の項目だけでなく、他の項目や項目名なども含めた文書全体にかかります。

ここから、96,000という数字について次のことが考えられます。

  • 1MiBに対して約10倍強の余裕があります(1,048,576 ÷ 96,000 ≒ 10.9)。文書にはほかの項目も入るので、画像だけで上限いっぱいを使わない設計だと読めます。
  • 96KiBなら98,304ですが、96,000はそれより切りのよい十進の数です。バイト計算から導いたというより、分かりやすい文字数の枠として決めた可能性があります。

ただし、これらは数字の形から立てた仮説にすぎません。なぜその値になったのか、どのコードが上限を確認しているのかは、確認済みの情報にないため断定できません。実際の仕様は現在の案内で確認する必要があります。

自分の画像で、上限に収まるかを測る

次の最小の道具で、手元の画像が上限に収まるかを確かめられます。画像を選び、幅と品質を決めると、データURLの文字数を表示します。外部ライブラリは使わず、ブラウザだけで動きます。

html
<input type="file" id="f" accept="image/*">
幅 <input id="w" type="number" value="512">
品質 <input id="q" type="number" value="0.8" step="0.1" min="0.1" max="1">
上限文字数 <input id="m" type="number" value="96000">
<pre id="out"></pre>
<script>
const $ = id => document.getElementById(id);
$('f').onchange = () => {
  const file = $('f').files[0];
  if (!file) return;
  const img = new Image();
  img.onload = () => {
    const w = +$('w').value, h = Math.round(img.height * w / img.width);
    const c = document.createElement('canvas');
    c.width = w; c.height = h;
    c.getContext('2d').drawImage(img, 0, 0, w, h);
    const url = c.toDataURL('image/webp', +$('q').value);
    const max = +$('m').value;
    $('out').textContent =
      '形式: ' + url.slice(0, 15) + '\n' +
      '元ファイル: ' + file.size + ' バイト\n' +
      '縮小後: ' + w + '×' + h + '\n' +
      'データURLの文字数: ' + url.length + '\n' +
      '元ファイル比: ' + (url.length / file.size).toFixed(2) + ' 倍\n' +
      (url.length <= max ? '上限内です' : '上限を超えています');
    URL.revokeObjectURL(img.src);
  };
  img.src = URL.createObjectURL(file);
};
</script>

記録の取り方と結果の読み方

同じ画像で、次の条件を変えて記録します。

  1. 幅を 256、384、512、768 の4通りにします。
  2. 品質を 0.5、0.7、0.9 の3通りにします。
  3. 各条件でデータURLの文字数を表に書き留めます。

読み方の目安は次のとおりです。

  • 幅を半分にすると、文字数は大まかに4分の1に近づきます。面積が変わるためです。品質を下げたときの減り方より、幅を変えたときの減り方のほうが大きいかどうかを見てください。
  • 「形式」の欄が「data:image/png」になった場合は、使っているブラウザがWebPで書き出せず、PNGに切り替わっています。PNGは品質の指定が効かないので、その条件の比較は除外してください。
  • 絵柄によって差が出ます。色数が少ない切り絵風の絵と、細かい模様が多い絵では、同じ幅でも文字数が大きく違うかを見てください。

起こりやすい誤り

  • 文字数とバイト数を取り違える:Base64のデータURLでは両者はほぼ一致しますが、日本語の文字が混じる文字列では一致しません。上限の単位が「文字」か「バイト」かは、必ず案内で確かめてください。
  • 元ファイルの大きさだけで判断する:70KiBに収まる元画像でも、Base64で増えた結果が上限を超えることがあります。必ず変換後の文字数を測ってください。
  • 画像以外の項目を忘れる:文書全体の上限には、題名や説明文などの項目も含まれます。画像だけで上限いっぱいまで使うのは避けます。
  • 確認済みでない仕組みを前提にする:この記事の計算は「文字で保存する方式の一般論」です。アソバースの実際の保存先や上限は、現在の案内で確認してください。

次の行動

手元のカバー候補の画像を1枚選び、上のコードで幅と品質の組み合わせを表にしてみてください。96,000文字という枠に収めるには幅と品質のどちらを動かすのが効くかが、数字で見えてきます。アソバースの実際の画像の扱いと上限は、現在の案内で確認してください。

参考資料

  • Firebase公式ドキュメント「Cloud Firestore の割り当てと上限」
  • MDN Web Docs「data: URL」「HTMLCanvasElement.toDataURL()」