document

HTML5 & JavaScript side
twitter: @y_imaya

PNG 画像の解析と最適化ツール

はじめに

この記事は Graphical Web Advent Calendar の 16 日目の記事として書かれました。
Graphical Web ということで、PNG フォーマットの簡単な説明と Web ブラウザ上で動作する PNG 解析ツールを作ったので使い方と解析結果の見方について書いていこうと思います。
また、人気のある PNG 画像最適化ツールがどのような最適化を行っているのか調べていきます。

PNG の仕様に入る前に

ここから、PNG の仕様について最低限の説明を書いていきます。
PNG の最適化や検証するときに必要になるので、退屈かもしれませんが軽く目を通してください。
以下の項目について、なんとなく分かれば良いです。

  • シグネチャ
  • 必須チャンクの役割
    • IHDR
    • PLTE
    • IDAT
    • IEND

PNG 仕様概要

PNG フォーマットは、先頭にシグネチャがあり、そこからチャンクと呼ばれるデータが連続する形で構成されています。

structure

シグネチャ

シグネチャは PNG ファイルであることを確認するために使用されます。
シグネチャは必ず以下の8バイトとなっています。

0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a

PNG ファイルをテキストエディタなどで開いたときに先頭のあたりに「PNG」という文字を見かけた事はないでしょうか?
それはこのシグネチャを ASCII にしたときの表示の一部です。

00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 01 00 00 00 01 00  08 06 00 00 00 5c 72 a8  |.............\r.|
00000020  66 00 00 00 19 74 45 58  74 53 6f 66 74 77 61 72  |f....tEXtSoftwar|
00000030  65 00 41 64 6f 62 65 20  49 6d 61 67 65 52 65 61  |e.Adobe ImageRea|
...

チャンク

チャンクはデータを格納する形式で、PNG ではシグネチャをのぞいて全てのデータがこの形式で記述されています。
チャンクは以下のような形になっています。

データの大きさチャンクタイプデータ破損チェック
4 Byte4 Byte可変4 Byte

チャンクの大きさは計算可能なので、対応していないチャンクタイプでも読み飛ばすことで表示に影響を与えないようにする事ができます。

  • データの大きさ
    • 文字通り、データの大きさが整数で入っている
    • つまり、チャンクの大きさは 12 + この数値となる
  • チャンクタイプ
    • ASCII で 4 文字はいっている。先頭が大文字なら必須チャンク
  • データ
    • チャンクタイプごとに決められた形式ではいっているデータ
  • 破損チェック
    • CRC-32 という形式でチャンクタイプ+データが破損していないか確認するためのデータ

必須チャンク

PNG では画像として最低限必要な情報を必須チャンクと呼び、以下のものが必須チャンクとされています。

  • IHDR
  • PLTE (ただし、画像タイプが Indexed Colour のときのみ必須)
  • IDAT
  • IEND

PNG はこの4つのチャンクさえサポートしていれば大体なんとかなるので、今回は必須チャンクに絞って説明します。

IHDR チャンク

IHDR チャンクは画像の形式に関する情報を扱います。

横幅縦幅ビット深度カラータイプ圧縮方法フィルタ方法インタレース方法
4 Byte4 Byte1 Byte1 Byte1 Byte1 Byte1 Byte
  • 横幅
    • 画像の横幅
  • 縦幅
    • 画像の縦幅
  • ビット深度
    • 色情報につかうビット数を指定します
    • RGB の時、ビット深度が 8 だったら R8G8B8 で 24 bit になります
    • グレイスケールの時、ビット深度が 8 だったら 8 bit になります
    • 1, 2, 4, 8, 16 が有効ですが、カラータイプによって選択できない組み合わせもあります
  • カラータイプ
    • 0: Grayscale (グレイスケール)
    • 2: True Colour (RGB形式)
    • 3: Indexed Colour (パレット形式、カラーマップ形式)
    • 4: Grayscale with Alpha (アルファチャンネル付きグレイスケール)
    • 6: True Colour with Alpha (RGBA形式)
  • 圧縮方法
    • 0: Deflate
  • フィルタ方法
    • 0: Basic
  • インタレース方法
    • 0: None
    • 1: Adam7

PLTE チャンク

カラータイプが Indexed Colour のときだけ必須となり、パレットの情報を扱います。
それ以外のカラータイプでは優先して表示する色を表すチャンクとなります。
データ形式は以下のように RGB でそれぞれ 1 Byte ずつのものが最大 256 個まで連続しています。
いくつ連続しているかはチャンクのデータサイズを 3 で割って算出します。

1 Byte1 Byte1 Byte

また、 Indexed Colour 形式でアルファチャンネルが必要なときは、この後にアルファチャンネルのみの tRNS チャンクが追加されることがあります。

IDAT チャンク

画像本体です。IDAT チャンクでは ZLIB という形式で圧縮された画像の色情報がはいっています。
画像の色情報は、行ごとに分かれていて以下のようになっています。

フィルタ色情報色情報色情報...
1 Byte可変可変可変...

色情報はビット深度と Colour Type によって変わります。

  • Grayscale: (ビット深度) bit
  • Indexed Colour: (ビット深度) bit
  • Truecolor: (ビット深度 * 3) bit
  • Truecolor with alpha: (ビット深度 * 4) bit
  • Grayscale with alpha: (ビット深度 * 2) bit

フィルタについては以下のようになっています。

  • 0: None (そのまま)
  • 1: Sub (左の Byte との差)
  • 2: Up (上の Byte との差)
  • 3: Average (左と上のバイトの平均との差)
  • 4: Paeth (Paethアルゴリズムによる値(詳細は仕様を見てください))

フィルタというとぼかしたりなどのエフェクトを思い浮かべる方もいるかもしれませんが、PNG でいうフィルタとはデータを偏った形で表現するための仕組みです。
グラデーションが多用される画像などでは、隣接する色との差分にした方が圧縮しやすくなるためです。

フィルタに関して注意する点が一つあり、一部の解説では「隣の色」とされていることがありますが「隣の色を含むバイト」との比較です。例えば、RGBA それぞれ 8bit ずつだとすると、隣の対応するバイトとの距離は 4 バイトになります。以前は「隣のバイト」と書いてしまっていたので修正しました。(2013/06/28追記)

また、IDAT チャンクはデータ部分を分割して複数の IDAT チャンクにすることができます。その際、IDAT は必ず連続していなければいけません。

IEND チャンク

画像の終わりを示す空チャンク。データ部分にはなにも入りません。

PNG ファイルの解析

ここまで、PNG の仕様について説明してきました。
いきなり、延々と仕様の話しをされてうんざりした人もいるかもしれません。
しかし、多くの Web 開発関係者は画像サイズを小さくしようとしていますが、そのために PNG とはどういうものか把握しておくのはとても重要な事です。
残念な事に Web 上の情報を見ていると、PNG がどういうものか理解せずにサイズを小さくすると謳って、結果誤った事を書いたものが多く見られます。

ここからは、最近人気のある最適化ツールが一体どのような最適化を行っているのか検証していきます。
最適化については、別のブログで簡単に書いたのでそちらも参考になるかもしれません。

png Identify

では、解析をどのようにするかですが、ちょうど良いツールが見当たらなかったので JavaScript で自作しました。
これは他の PNG の情報ツールでは見られないような圧縮関連の情報も表示するツールとなっていて、圧縮効率の調査や最適化アルゴリズムの調査に活用する事が出来ます。

調査するツール

以下の最近人気のある最適化ツールなどについて調べていきたいと思います。

最適化対象の画像

ライセンスがはっきりしていて、PNG に向いているイラスト調の画像なので「うぶんちゅ!」という漫画の表紙絵を例に使わせていただきます。

ubunchu01_01

サイズ比較

まずは、一番気になるサイズ比較から行っていきましょう。

元画像PNGGauntletImageOptimAzConvPNG
サイズ1,698,5061,629,6681,631,5061,702,386
差分--68,838-67,000+3,880

Indexed Colour 形式での挙動も調べるため、ImageAlpha で 256 色に減色した画像も比較します。

元画像PNGGauntletImageOptimAzConvPNG
サイズ520,806494,704493,989519,658
差分--26,102-26,817-1,148

Truecolor 形式では PNGGauntlet, Indexed Colour では ImageOptim が優れた結果となりました。
では、ここから各ツールでどのような最適化が行われたか見ていきます。

チャンク構成

Truecolor

元の画像では以下のチャンクが付与されていますが、各ツールでは除去されています。

  • pHYs (Physical pixel dimensions)
  • iCCP (Embedded ICC profile)
  • cHRM (Primary chromaticities and white point)
元画像のチャンク構成
chunk_source
ImageOptim で最適化した後のチャンク構成
chunk_imageoptim

これらは、表示する際に色や大きさを決定する際に使われることもありますが、ブラウザによっては無視される事もあるため削除しても特に問題にならない事が多いです。
(これらのチャンクに対応したブラウザで、きっちりと表示色などを制御したい場合は削除してはいけません。)

Indexed Colour

元画像を ImageAlpha で減色した後のチャンク構成
chunk_imagealpha
上記のものを PNGGauntlet で最適化した後のチャンク構成
chunk_imagealpha_pnggauntlet

ImageAlpha で減色した画像では gAMA (Image gamma) という表示に関するチャンクが付与されていますが、これも無視されることがあるので削除してもあまり影響しません。

それよりも特徴的なのは、IDAT が細かく分割されている点です。これはファイルサイズが増えるだけなので最適化ツールにかけると一つにまとめられています。

フィルタ最適化

PNG の特徴であるデータを偏らせる行毎のフィルタですが、どういう状況でどのフィルタを選択するのがベストかというのは実際にやってみるしかありません。
また、仕様にも「Indexed Colour の時はフィルタを使わない方が良い」と書いてあるように、最適化ツールでも Indexed Colour の時はすべて None になっています。(ので省略します)

元画像

filter_source

PNGGauntlet

filter_pnggauntlet

ImageOptim

filter_imageoptim

AzConvPNG

filter_azconvpng

PNGGauntlet と ImageOptim のフィルタの並びが同じになりましたが、これは共に PNGOUT, OptiPNG を使用しているので、同じ結果になったのだと思います。AzConvPNG はフィルタの最適化は行っていないようです。

パレット最適化

ImageAlpha で減色した画像は (Indexed Colour なので当然ですが) パレットをもっており、このパレットの並び順によっても ZLIB の圧縮率に影響する事があります。

記事が縦長になってきたので画像は省略して結論だけ以下にまとめます。

  • AzConvPNG: 独自の並び替え(元のパレットと全然違う)
  • PNGGauntlet: 元のパレットと似ているけど多少異なる
  • ImageOptim: 並び替えを行っていない

ZLIB の圧縮率

ここからが本題です。

フィルタやパレットの最適化を行っても、それだけではファイルサイズは変わりません。
しかし、フィルタやパレット最適化によって「圧縮しやすい状態」になった画像は最終的なファイルサイズで大きな差がでます。
実際に見てみます。

Truecolor

元画像
zlib_source
PNGGauntlet
zlib_pnggauntlet
ImageOptim
zlib_imageoptim
AzConvPNG
zlib_azconvpng

Truecolor では PNGGauntlet と ImageOptim が優秀な結果を出しています。ファイルによって優劣が入れ替わる事も多々あるので、この二つはどちらもほぼ互角といって良い性能ではないでしょうか。
それぞれの各ブロックの圧縮をみると、LZSSの平均長(LZSS-Avg) が似た傾向にあり、どちらもかなりの高圧縮となっています。

Indexed Colour

元画像
zlib_azconvpng
PNGGauntlet (すいません、ブロック数が多すぎたので省略してます)
zlib_pnggauntlet_p
ImageOptim
zlib_imageoptim_p
AzConvPNG
zlib_azconvpng_p

こちらも PNGGauntlet と ImageOptim がほぼ互角です。しかし、パレットの入れ替えを行っていない ImageOptim の方が最終的に小さくなっているため、ImageOptim はこの結果を見越して、あえて入れ替えなかったのかもしれません。フィルタとパレットの選択によっても結果が変わるため、このあたりのアルゴリズムが当たるかどうかで結果が変わるのかもしれません。

また、全体的に各ツールでの圧縮の特徴ですが、元画像や AzConvPNG ではブロックごとの圧縮後のサイズが大体同じサイズになっているのに対し、ImageOptim と PNGGauntlet は大きくばらついています。 ImageOptim と PNGGauntlet の特徴が似ている理由は推測になりますが後述します。

まとめ

最終的な ZLIB の再圧縮では ImageOptim と PNGGauntlet が優秀な結果となりました。

AzConvPNG がこの二つと比べてあまり結果が出せなかったのは、ZLIB 最適化のアルゴリズムにあるとおもいます。
これは推測ですが、ImageOptim と PNGGauntlet はどちらも PNGOUT というツールを使っているのですが、そこで採用されている ZLIB 部分の最適化を担う Kflate (と一部で呼ばれている最適化アルゴリズム)の影響があると思います。
(AzConvPNG は zlib ver 1.2.5 の Deflate パラメータの中で最もサイズの小さくなるものを選択している模様。 zlib のパラメータはバッファのサイズなどを一律で扱うために、圧縮後のブロックサイズがほぼ平坦になるものだと思われます。)

今回は一つのファイルのみを対象としましたが、対象となるファイルによって優劣が変わる事もあるので、どのツールで最適化するか悩んだ場合は自分で解析してみて比べるのも一つの手段です。

また、PNGGauntlet と ImageOptim どちらを選択したほうが良いかについては、わずかでも小さくしたい場合は両方使用して優れている方を採用、そうでない場合は使用しているOSで選択すれば良いのではないでしょうか。

以上、長くなりましたが PNG の仕様と最適化の挙動について書いてきました。
何かの参考になれば幸いです。

3連休だし Web ブラウザで動作する Markdown Editor 作った

きっかけ

ドキュメントを書く機会があったので、最近使う事の多い Markdown(GFM) で書こうと思った。
普段は gist で適当に書いてるんだけど、長くなりそうだしリアルタイムプレビューのあるツールが欲しかった。

少し探しても良さそうなのがなかったので、せっかくなので自作しようと思った。

デモ

あ、保存は gist にでもコピペするといいと思います。

使用ライブラリ、ツール群

このエディタはほとんどが他の方のつくったライブラリを組み合わせただけです。
特に Ace は素晴らしい出来で、リッチなエディタが簡単に Web アプリケーションに組み込めます。

また、Ace, marked の GFM 固有の表現で GitHub 側の挙動と一致していないところがあるので一部改造して使用しています。

今後やりたいこと

  • このアプリケーションに名前をつける(命名センスほしい)
  • ローカルでの保存
  • より完璧な GFM 対応
  • Syntax Highlighing をもっと見やすくしたい

ほかにもこんなの欲しいとかあったら意見いただけると嬉しいです。

最後に

Markdown Editor 作ってたら何のドキュメントを書くのだか忘れてしまった。
未だに思い出せない…。

ちなみに、今回の記事はつくった Markdown Editor で書いてます。

「改行削除の代わりにGZIP」を実践してみた

はじめに

この記事の内容は「改行削除するくらいなら gzip したらいいじゃない」という記事の内容を受けて書いたものですので、まずはそちらをご覧になると良いかと思います。

記事を読んで、なんとなく呼ばれたんじゃないかという気になったので間違った方向でこれに取り組んでいこうと思います。

GZIP の展開を JavaScript で行う

まずは、GZIP ファイルを JavaScript で展開します。 いくつか実装はあると思いますが、ここは自作で使い慣れてる zlib.js を使ってやります。 そして、展開したらそれを Object URL にして適用します。 簡単ですね。

大体こんな感じです。

で、できたものはこちらになります。

http://imaya.github.com/demo/gunzip/

こちらのデモでは割と最近の機能(Blob, TypedArray, createObjectURL などなど)をつかっていますので Google Chrome などで見ると良いかと思います。( polyfill などを使えばそこそこ古いブラウザでも動かす事はできます)

問題点など

一応動作するのですが、gzip ファイルを XHR で取得しなくてはいけないことから、ページの読み込みからスタイルの適用まで、素のページが表示されてしまいます。

また、複雑な構成のサイトではレイアウトの再計算が発生して体感速度の低下ということも考えられます。

CSS ではあまり効果的な使い方は思い浮かびませんが、遅延しても良いようなリソースを取得するのには良いかもしれません。

宣伝

zlib.js 0.1.1 をひっそりリリースしました。 圧縮サイズが小さくなるようにハフマン符号の処理を置き換えましたので、よろしくお願いします。

SWF 研究会 #2 で LT してきました

はじめに

まずはLT枠5分というはずだったのに15分ほど話してしまい、会場の皆様すみませんでした。

最近 SWF の Lossless, Lossless2, Jpeg3 あたりのデコード実装を JavaScript で行ったので、Lossless 系について話しました。 SWF の話と見せかけて、PNG の仕様の話と JavaScript のパフォーマンスチューニングが大半で、あとは少しだけ zlib.js の宣伝です。

スライドについて

GitHub Pages です。

JSPerfView のグラフを埋め込みたかったので、ブラウザで実行できるスライドツールを探しました。 その中で html5slides が良さそうだったので採用しました。 JSPerfView の使い方には以前書いたものがあるのでご参照ください。

また、JavaScript で画像を扱う話しだったので、実際にブラウザでデコードして表示というのがそのままスライドに埋め込めるのは便利でした。

Android Browser の putImageData のバグについて

現象と対策

@fchiba さんに教えていただいた内容そのままなので、自分が説明するというのも恐縮なんですが備忘録として書いておきます。

Android Browser (確認した限りでは 2.3, 4.0) では CanvasRenderingContext2D#putImageData で描画する際に、描画する ImageData の RGB 値を premultiplied alpha として扱うバグがあるようです。 Android Browser でこのバグがある場合には、CanvasPixelArray に RGBA をセットする際に以下のように alpha 値を掛けてやれば良いようです。

red   = red   * alpha / 255 | 0;
green = green * alpha / 255 | 0;
blue  = blue  * alpha / 255 | 0;
alpha = alpha;

比較

対策前対策後のページを用意しましたので、Android Browser で確認してください。

なお、ここでは上記の対策だけを入れたコードになっていますので、正常なブラウザで見た際も premultiplied alpha になっています。

バグの有無を判定

このバグを持っているかどうか判別するのをどうするか考えてみたのですが、実際に putImageData して判別するのが分かりやすいかと思いました。 他にもっと良い方法があればください。

Lossless2 のデコードへの影響

Lossless2 では RGB 値を premultiplied alpha で持っているため、Android Browser の場合はそのまま使えば良いことになります。 そのため、Android Browser で Canvas を使用する場合はさらに(特に Direct で)速度を向上させることができます。

お礼

発表の機会を与えていただいた @yoya さんと @tkihira さん、 Android Browser の putImageData バグの解決方法を教えていただいた @fchiba さん、 前日に iPhone5 でベンチマークを実行していただいた @constellation さん、 どうもありがとうございました!

zlib.js 0.1.0 をリリースしました

はじめに

最近はいろいろなところで使っていただいているのに、いまだに develop ブランチが安定版というのも申し訳ないので zlib.js としては初のリリースとなる 0.1.0 をリリースしました。

使い方など

GitHub のプロジェクトページをご覧ください。

以前の master ブランチからの変更点など

  • パフォーマンス改善
  • BusterJS ユニットテスト(一部)の追加
  • Node.js 対応
  • GZIP圧縮・伸張対応
  • ZLIB Inflate のストリーム実装の追加
  • 圧縮時不正な出力が行われることがあるバグを修正
記事検索
最新コメント
最新トラックバック
カテゴリ別アーカイブ
タグクラウド
QRコード
QRコード
  • ライブドアブログ