0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

画像を Data URL にすると33%増える、は gzip の前の話だった。PNG はほぼ元の大きさに戻り、SVG は1.6倍のまま残った

0
Posted at

背景

画像を Data URL に変換するツールを作っています。
画像ファイルを選ぶと、<img src="data:image/png;base64,..."> や CSS の background-image にそのまま貼れる文字列が出てくるものです。

Data URL は、ファイルの中身を URL の中に直接書き込む形式だ。
仕様は RFC 2397 にあります。
https://www.rfc-editor.org/rfc/rfc2397

ツールには「Data URL にすると約33%増えます」という注意書きを出しています。
Base64 は3バイトを4文字で表すので、4÷3 で約1.33倍、という理屈です。

ただ、HTML や CSS は gzip で圧縮して配信されることがほとんどです。
gzip は、繰り返しの多いデータを小さくする圧縮方式で、Web サーバーが自動でかけていることが多い。
「33%増える」は、gzip の後でも本当なのか…
気になったので測ってみました。

結論

PNG のようにすでに圧縮されている画像は、gzip 後にはほぼ元の大きさに戻ります。
増えるのは、むしろ SVG のような文字のファイルのほうでした。

SVG を埋め込むなら Base64 ではなく、パーセントエンコード(< を %3C のように書く形)の Data URL にすると、gzip 後の増え方はかなり小さくなります。

測り方

ツール本体はブラウザの FileReader.readAsDataURL で変換していて、出てくる文字列は data:{MIMEタイプ};base64,{Base64の本文} の形です。
同じ形の文字列を Node.js で作り、元のファイル・Data URL・それぞれを gzip したものの大きさを比べました。

下記がそのスクリプト(measure.js)です。
引数に画像ファイルのパスを並べて渡すと、1ファイル1行で結果を出します。

const fs = require('node:fs');
const zlib = require('node:zlib');
const path = require('node:path');
const MIME = { '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.webp': 'image/webp' };
for (const f of process.argv.slice(2)) {
  const buf = fs.readFileSync(f);
  const dataUrl = 'data:' + MIME[path.extname(f).toLowerCase()] + ';base64,' + buf.toString('base64');
  const gzRaw = zlib.gzipSync(buf, { level: 9 }).length;
  const gzUrl = zlib.gzipSync(Buffer.from(dataUrl), { level: 9 }).length;
  console.log(path.basename(f), 'raw', buf.length, 'dataUrl', dataUrl.length,
    'gzip(raw)', gzRaw, 'gzip(dataUrl)', gzUrl, 'ratio', (gzUrl / buf.length).toFixed(3));
}

ratio は「Data URL を gzip したもの」が「元のファイル」の何倍か、です。
ブラウザが実際に受け取るのは gzip 後の HTML なので、この値で比べました。

実測

Node.js v24.11.1 で、手元にあった4つの画像を渡しました。
写真っぽい PNG、スクリーンショットの PNG、295 バイトの小さな PNG、3,005 バイトの SVG です。

$ node measure.js photo-noisy-640x420.png chmod.png hero-texture.png og-image.svg
photo-noisy-640x420.png raw 256586 dataUrl 342138 gzip(raw) 256569 gzip(dataUrl) 256389 ratio 0.999
chmod.png raw 100053 dataUrl 133426 gzip(raw) 98018 gzip(dataUrl) 99050 ratio 0.990
hero-texture.png raw 295 dataUrl 418 gzip(raw) 293 gzip(dataUrl) 359 ratio 1.217
og-image.svg raw 3005 dataUrl 4034 gzip(raw) 1097 gzip(dataUrl) 1787 ratio 0.595

Data URL の段階では、どれもきっちり約1.33倍に増えています。
ところが gzip 後を見ると、写真の PNG は 256,586 バイトが 256,389 バイトで、ほぼ同じです。

理由は、Base64 の1文字が6ビットぶんの情報しか持っていないことだと思います。
1文字8ビットのうち2ビットは余りなので、gzip の符号化がその余りをほぼ取り返してくれる。
PNG の中身はもともと圧縮済みで、それ以上は縮まないので、結果として元の大きさに戻るわけです。

小さな PNG は 1.217 倍でした。
data:image/png;base64, という22文字の前置きが、295 バイトに対しては無視できない重さになります。

SVG だけは増えたまま残る

問題は SVG です。
SVG はただの文字のファイルなので、元のままなら gzip で 3,005 バイトが 1,097 バイトまで縮みます。

Base64 にすると、同じタグの繰り返しがバラバラの文字列になって、gzip が繰り返しを見つけられなくなる。
gzip 後は 1,787 バイトで、元の SVG を gzip した 1,097 バイトの1.6倍です(上の ratio は gzip していない元ファイルとの比なので 0.595 になっています)。

下記は、同じ SVG をパーセントエンコードの Data URL にした場合です。
measure.js と同じく fs と zlib を読み込んだうえで動かしました。

const svg = fs.readFileSync('og-image.svg', 'utf8');
const gz = (s) => zlib.gzipSync(Buffer.from(s), { level: 9 }).length;
const b64 = 'data:image/svg+xml;base64,' + Buffer.from(svg).toString('base64');
const pct = 'data:image/svg+xml,' + encodeURIComponent(svg);
console.log('raw', Buffer.byteLength(svg), 'gzip', gz(svg));
console.log('base64', b64.length, 'gzip', gz(b64));
console.log('percent', pct.length, 'gzip', gz(pct));

出力は下記でした。

raw 3005 gzip 1097
base64 4034 gzip 1787
percent 5566 gzip 1189

パーセントエンコードは文字数だと 5,566 で一番長くなります。
それでも gzip 後は 1,189 バイトで、元の SVG を gzip したものの1.08倍で済みました。
%3C や %22 の繰り返しを gzip が拾えるからです。

ツールでの扱い

今のツールは readAsDataURL の結果をそのまま出しているので、SVG も Base64 になります。
「約33%増えます」の注意書きも、gzip 前の話としては正しいものの、PNG では言いすぎで、SVG では言い足りない、というのが今回わかったことです。

作ったツールはここに置いています。
https://hashitosystem.com/tools/imagebase64/

Data URL の重さは、文字数ではなく gzip 後のバイト数で比べるのが正確です。
PNG や JPEG は気にしすぎなくてよく、SVG を埋め込むときだけは Base64 をやめてパーセントエンコードにしてください。


本記事はAI補助で執筆した、個人開発の紹介記事です。

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?