ImageMagick

ImageMagick埋め込み画像

はじめに

ImageMagick は実行ファイル(正確にはmagickライブラリのファイル)の中に幾つかの画像を埋め込んでいます。

% convert logo: logo.gif

といったコマンドを見る事があると思いますが、この logo: の事です。

logo:
logo.gif

画像ファイルを用意しなくても ImageMagick のコマンドをとりあえず試せる便利機能です。

ちなみに大文字小文字の区別はありません。内部的には大文字で処理されます。

% convert LOGO: logo.gif
% convert LogO: logo.gif

画像一覧

-list coder で MAGICK に対応するエントリを見ると何の画像が埋め込まれているか分かります。

% convert -list coder | grep MAGICK
LOGO        MAGICK
GRANITE     MAGICK
ROSE        MAGICK
NETSCAPE    MAGICK
WIZARD      MAGICK
H           MAGICK

この最後の H だけ特別で、埋め込み画像ではなく出力画像を C/C++配列のコード形式にします。

% convert -size 8x8 xc:white H:
/*
   (PNM).
*/
static const unsigned char
  MagickImage[] =
  {
    0x50, 0x34, 0x0A, 0x38, 0x20, 0x38, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00,
  };

デフォルトでは PNM ですが、-define magick:format=〜 指定すると GIF 等の任意のフォーマットで出力できます。

% convert -size 8x8 xc:white -define magick:format=GIF H:
/*
   (GIF).
*/
static const unsigned char
  MagickImage[] =
  {
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x08, 0x00, 0x08, 0x00, 0xF0, 0x00,
    0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00,
    0x00, 0x02, 0x07, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x5D, 0x00, 0x00, 0x3B,

  };

-list coders の結果に含まれませんが、実は出力に於いて H: と同じ挙動をする MAGICK: 指定もあります。

% convert -size 8x8 xc:black MAGICK:
/*
   (PNM).
*/
static const unsigned char
  MagickImage[] =
  {
    0x50, 0x34, 0x0A, 0x38, 0x20, 0x38, 0x0A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF,
  };
logo: wizard:
logo.gif wizard.gif
granite: netscape: rose:
granite.gif netscape.gif rose.png

画像が埋め込まれてるコード

coders/magick.c に画像データが C/C++配列で埋め込まれています。画像形式は GIF と PNM の2種類があります。

/*
  Predefined ImageMagick images.
*/
static const unsigned char
  GraniteImage[] =
  {
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x80, 0x00, 0x80, 0x00, 0xf3, 0x00,
    0x00, 0xa0, 0xa0, 0xa0, 0xa0, 0x98, 0xa0, 0xa9, 0xb2, 0xa9, 0xa0, 0xa9,
<略>
  LogoImage[] =
  {
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x80, 0x02, 0xE0, 0x01, 0xF7, 0x00,
    0x00, 0x04, 0x07, 0x07, 0x06, 0x09, 0x0E, 0x0A, 0x0B, 0x0B, 0x14, 0x0B,
<略>
  NetscapeImage[] =
  {
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0xd8, 0x00, 0x90, 0x00, 0xf7, 0x00,
    0x00, 0xcc, 0xff, 0x00, 0xcc, 0xcc, 0x00, 0xcc, 0x99, 0x00, 0xcc, 0x66,
<略>
  RoseImage[] =
  {
    0x50, 0x36, 0x0a, 0x37, 0x30, 0x20, 0x34, 0x36, 0x0a, 0x32, 0x35, 0x35,
    0x0a, 0x30, 0x2f, 0x2d, 0x32, 0x30, 0x2e, 0x36, 0x32, 0x2f, 0x38, 0x33,
<略>
  WizardImage[] =
  {
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0xE0, 0x01, 0x80, 0x02, 0xF7, 0x00,
    0x00, 0x0C, 0x0A, 0x0B, 0x06, 0x05, 0x08, 0x13, 0x0C, 0x0C, 0x0C, 0x0B,
<略>
typedef struct _MagickImageInfo
{
  char
    name[MaxTextExtent],
    magick[MaxTextExtent];

  const void
    *blob;

  size_t
    extent;
} MagickImageInfo;

static const MagickImageInfo
  MagickImageList[] =
  {
    { "LOGO", "GIF", LogoImage, sizeof(LogoImage) },
    { "GRANITE", "GIF", GraniteImage, sizeof(GraniteImage) },
    { "NETSCAPE", "GIF", NetscapeImage, sizeof(NetscapeImage) },
    { "ROSE", "PNM", RoseImage, sizeof(RoseImage) },
    { "WIZARD", "GIF", WizardImage, sizeof(WizardImage) },
    { "", "", (const void *) NULL, 0 }
  };

netscape: について

パレット画像

先ほども画像を貼りましたが、netscape: が何故かパレット画像です。普通は Netscape ブラウザのロゴが出るのを期待しますよね。

netscape: こっちじゃないの?
netscape.gif image.png

さて、これは画像に使われる色を数えると察しがつきます。

% convert netscape: netscape.gif
% identify -verbose -format "%k\n" netscape.gif
216

216 を因数分解すると 6x6x6 。つまり RGB色空間を R,G,B 各々5等分した格子点です。

rgbcube2.png
http://app.awm.jp/misc/js/threejs/rgbcube2.html

全てのRGB色を似たパレット色に吸収させると、このような色立体で表現できます。

rgbcube.png
http://app.awm.jp/misc/js/threejs/rgbcube.html

255 / 5 => 51 = 0x33 の間隔で単調増加した数列を使います。

0 51 102 153 204 255
0x00 0x33 0x66 0x99 0cc 0xff

この6種類の輝度の R,G,B を組み合わせた色パレットという事です。

昔の事情

Netscape ブラウザが世に現れた1994年。21世紀の現在と比べて VRAM が非常に高価で多くは積めなかった事もあり、モニタ上に同時発色できるのが256色限定という環境も珍しくない時代でした。
多くの RGB (bitdepth:8) データは 256x256x256 = 16777216 色(トゥルーカラーと呼ばれます)を保持できます。256 色では全然足りません。

そこで実際に表示する色を限定してパレット色とし、それ以外の色はパレット色を併置混色して色を作り出す、いわゆるディザ表示がよく利用されました。

なお、更にチープな環境用に 5x5x5 = 125色 ディザ無しのモードもあったようです。

6x6x6 = 216色 5x5x5 = 125色
rgbcube.png image.png

パレット指定で減色してみる

実際に、netscape パレットの 216色限定で表示してみましょう。

検証用画像をまず作ります。

convert -size 300x900 gradient:'#FFF-#0FF' -rotate 90 \
    -virtual-pixel Transparent +distort Polar 149 +repage \
    -rotate 90 -set colorspace HSB -colorspace RGB \
    -flatten gradient_hue_polar.png
gradient_hue_polar.png
gradient_hue_polar.png

6x6x6 = 216色 (ディザ有り)

ディザつき Netscape パレットです。

convert gradient_hue_polar.png -remap netscape.gif 666-dither.png

-remap オプションに指定した画像に含まれる色しか使わないようにする。といった減色処理が出来ます。

666-dither.png
666-dither.png

これなら遠目に見れば何とか誤魔化せそうです。

6x6x6 = 216色 (ディザ無し)

更にディザをかけないとこうなります。+dither でディザを無効化します。オプションが基本的に -〜なので ImageMagick の +〜 はその逆を表します。

convert gradient_hue_polar.png +dither -remap netscape.gif 666+dither.png
666+dither.png
666+dither.png

元の画像にない階調がくっきり出てしまっています。

5x5x5 = 125色 (ディザ無し)

ついでに 5x5x5 = 125 色のディザ無しだとこうなります。

# 5x5x5パレット画像(palette125.png)作成
% convert -size 5x5 gradient:black-red black-red.png
% convert -size 5x5 gradient:black-green1 -rotate 270 black-green.png
% composite -compose plus black-red.png black-green.png black-red-green.png
% convert -size 125x5 tile:black-red-green.png black-red-green-tiled.png
% convert -size 5x5 gradient:black-blue -scale 100%x2500% -rotate 270 black-blue.png
% composite black-red-green-tiled.png -compose plus black-blue.png palette125.png
palette125.png
palette125.png
↓ 参考のため、その5倍表示
palette125x5.png
palette125x5.png
% convert gradient_hue_polar.png +dither -remap palette125.png 555+dither.png
555+dither.png
555+dither.png

これはひどい。。(;'-')

216色の理由

なぜ216色止まりにして、256 目いっぱい使わないのかというと、6x6x6 の次にキリの良い 7x7x7 だと 343色になって溢れてしまうのもありますが、Windows や Mac が予約しているシステムカラーもありますし、背景のテーマ用に(もしかしたらブラウザ以外のアプリケーションも?)少しは色を残しておくという配慮があったようです。

その実装を元に Netscape 社が提唱したパレット仕様が、今では Webセーフカラーとして世に知られています。

おまけ

netscape: 画像がどのように作られているのか。R,G,B 別に分解するとこんなでした。

netscape: red green blue
image.png image.png image.png image.png

(色コンポーネント分解ツール) http://app.awm.jp/image.js/colorcomponent.html

実は 6x6x6 と同じ要領で 5x5x5 のパレット画像を作って参考の為に貼ろうとしたのですが、この組み合わせは偶数でないとできないので、諦めたのでした。

最後に

あまりマニアックな記事を1日目にあてるとハードルが上がって後が辛いので、考えられる限り最もゆるふわなテーマにしたつもりですが、思ったより手間がかかっちゃいました。工数見積もりが下手すぎです。。次の記事どうしよう。。

参考URL