ゲームプログラミング/画像ファイルの作成プログラム

もし、画像ファイルそのものをゲームから出力したい場合、
そういう出力機能がOSに用意されていればそれを使えばよいが、実は専用の出力機能がなくてもC言語でバイナリー・データーファイルを作成・編集することによって、画像ファイルを作ることが原理的には可能である。
画像ファイルだけでなく音声ファイルなども同様にバイナリー・データーの編集によって作成可能であるが、本ページでは画像ファイルを例に説明する。
ただし、それら画像フォーマットの、バイナリー・データーでの記述の仕様を知る必要がある。かなり専門的な専門書や、もし出版されていなければネット検索などで、調べることになるだろう。
一応、バイナリー・データーの直接編集をしなくても、windows にある MFC という機能にある cimage クラスのsaveメソッドなどの機能によって、画像ファイルの出力作成が可能ではある。 しかし、それをどうDirectX形式またはDXライブラリ形式のC++のプログラムに組み込めばいいのか、現在では書籍が乏しく不明である(MFCの書式は、一般のC++プログラミングとは微妙に異なる)。だからMFCによる方法は、ゲームとしては2020年以降の現在では期待できないだろう。

C言語のプログラムで、バイナリー・データーの読み書きをしたい場合、
fopen の引数で「r」(読み込み)とか「w」(書き込み)とか編集モードの指定がありますが、それに「b」をつけます。つまり「rb」や「wb」などの引数になります。
また、書き込む関数には fwrite を使う必要があります。 いっぽう、 fprintf では、テキストファイルに自動的に変換してしまいます。
fopenやfwriteの機能については、一般的な市販のC言語入門書に解説があるので、本ページではこれ以上は説明しない。



たとえばビットマップ画像を作りたい場合、
いわゆる「ビットマップ構造体」の仕様で決められた様式で、バイナリー・データーをファイルに書き込む。
すると、たいていのデスクトップ用OS(ウィンドウズも含む)では、ビットマップ形式(BMP)をサポートしているので、そのバイナリー・データーを画像ファイルとして認識してくれるので、クリックなどをするとOSが画像表示などをしてくれる。
なお、ネットや専門書の解説では画像フォーマットの仕様を説明するためによくC言語の「構造体」の書式が説明されるが、しかし、その構造体を入力しても、画像は生成されない。
さて、たとえば何らかのビットマップ画像をバイナリエディタで読み込むと、ファイルの先頭は必ず「42 4D」という16進数である。この数字は、アスキーコードで翻訳すると「BM」になる。
この「42 4D」という冒頭の数字を識別子とすることで、ファイルの種類を認識している。
誤解しないでほしいが、けっして「BM」とバイナリー・データーで書かれているのでなく(そもそも十六進数に「M」は無い)、書かれているのは あくまで「42 4D」である。
ネット検索で調べる場合は「42 4D」というキーワードを付け加えて「ビットマップ 42 4D」などで調べれば、ビットマップ画像のバイナリでの扱いに関する有益な情報が入手しやすいだろう。

たとえば、Windows用のフリーのバイナリエディタ stiring で3×5ピクセルの白色だけのビットマップファイルを見ると、下記のようになっている。
stiring に限らず一般にバイナリエディタの画面の構成は、上記のようになっている。(なお、「ファイル」→「ダンプイメージの保存」などの操作で、上記のようなバイナリー・データーのイメージをテキストエディタ(Windowsの場合は『ワードパッド』など)で見られる形式に変換して出力できる。)
バイナリー・データーはそのままでは、テキスト表示できない。なので、それをテキスト化したり、見やすくするために書式を整えたりして、バイナリー・データーの内容を出力したものをダンプ イメージという。
また、そのようなダンプイメージを出力することを、俗に(ぞくに)「ダンプする」などとIT業界では言う。
さて、このダンプイメージのうち、バイナリー・データーの本体は、真ん中のブロックである、下記の
の部分である。
一番左のブロック、
は、ファイル内容のバイナリー・データーをアスキーコードに変換して表示している。
このアスキーは、バイナリー・データーのファイル内容ではなく、バイナリエディタ側がユーザーが内容を探しやすくする目的などのために表示している。

一番上の行の
は、単に何バイト目かをあらわしているだけである。これは、バイナリー・データーのファイル内容ではなく、バイナリエディタ側がユーザーが見やすくするために表示している。
また、一番左の列の
は、アドレスをあらわしている。



赤い背景色のブロックで表した部分を「BITMAP FILE HEADER」といい、これの長さは14バイトで固定である。「42 4D」で2バイト、同じように数えて12バイトの長さ。「BITMAP FILE HEADER」とは、つまり、「42 42」「画像のファイルサイズ」「予約領域」「予約領域」「画像の場所の指定」の部分である。
日本語では、「BITMAP FILE HEADER」のことを、ビットマップの「ファイルヘッダ」と言う場合もある。 つまり、ビットマップのファイルヘッダの長さは14バイトで固定である。
さて、青い背景色のブロックで表した部分を「BITMAP INFO HEADER」という。
つまり、「BITMAP INFO HEADER」とは、
の部分である。日本語では、「BITMAP INFO HEADER」のことを、ビットマップの「情報ヘッダ」と言う場合もある。
ビットマップ情報ヘッダの長さは40バイトで固定である。 バイナリー・データー本体の右上にあるビットマップ情報ヘッダの「28 00」と次の段の「00 00」は、ヘッダサイズに相当するが、28は十進数に直すと40である(16×2+8=40)。 リトルエンディアンなので、あとの00の3回くりかえしは無視していい。

さて、ビットマップ構造体の図を、ダンプイメージと照らし合わせてみよう。

「42 4D」のあとに 「82 00 00 00」と4バイトぶんの数字が続くが、これはファイルサイズを表している。
なお、このファイルのサイズは130バイトである。16進数の「82」は 10進数で「130」である。算数で10進数の計算で 8×16+2=130 である。
読者のWindowsパソコンでもバージョンによるかもしれないが、『ペイント』などで3×5ピクセルの16色ビットマップ画像を作成すれば、おおむね、このあたりのファイルサイズになるだろう。
なお、ペイントのキャンバスサイズの変更は、左上のメニューアイコンから「プロパティ」を選ぶと「イメージのプロパティ」設定ウィンドウが開き、下のほうにキャンバス幅とキャンバス高さの指定欄があるので、そこで数値を入力してサイズを変形できる。
この「82 00 00 00」の意味は、10進数にすれば
という意味である。こういう数え方を「リトル エンディアン」という。
なお「256」の由来は 16×16=256 である。
けっして、8200万バイトでないし、130万バイトでもないので、勘違いしないように。

次の、2つの 予約領域 は、ゼロで埋める決まりになっており、なので「00 00 」「00 00」である。
1段目アドレスのABCDの部分にある「 76 00 00 00」は、まず意味は10進数で118である。7×16+6=118 より。
で、「バイナリー・データーの118バイト目から、画像データが始まります」と宣言している(ファイル先頭から画像デーマまでの「オフセット」という)。1バイトは256で、16×16=256なので16進数では2ケタぶんです。画像データの先頭のバイトが118バイト目であると、を指し示している。
なお、数え方は左上の「42 4D」ですでに2バイトである。 この調子で118バイト目を探すと
なので、8行目(商7にプラス1)の7列目(あまり「6」+1)のバイト(6)目が、開始位置である。下図の太字にした部分のバイトである。
よくみると、この太字の部分からFが5個続き、「000」というのが3回数くりかえすので、合計でFが15回繰り返される。これは、この画像が横5×3であることに対応している。
さて、「画像の横幅」「画像の縦幅」に対応するのは「05 00 00 00」「 03 00 00 00」である。これは、横5ピクセル×縦3ピクセルという意味である。数値の数え方はリトルエンディアンである。
ビットマップ情報ヘッダの終わりから画像の開始までは、カラーパレットが16個、続いています。
上ダンプの太字の直前の「FF FF FF 00」とは、16番目のビットマップのカラーパレットです。カラーパレットは合計で16個あります。また、この16番目パレットを呼び出すのに、画像データ部分のF5回の繰り返しの部分では「F」でパレットを呼び出しています。
その直前のパレットの「FF FF 00 00」が15番目のパレットです。このパレットは「E」で呼び出されますが、今回の画像データでは使われてないです。

「プレーン数」と「色ビット数」の「01 00 」「04 00」は。プレーン数が十進数で1で、色ビット数が十進数で4という意味である。
「圧縮形式」とあるが、無圧縮にする場合はゼロにする。つまり「00 00 00 00」で無圧縮である。ビットマップは、仕様上では、実は圧縮も可能な仕様である。
解像度「C4 0E 00 00」は、印刷などをする際の1メートルあたりのピクセル数ですが、今回は印刷をしないので、気にしないことにします。
「パレット数」は、仕様上は策定されていますが、実際には利用されていません。マイクロソフトの『ペイント』アプリで作ったファイルですらも、ここはゼロのままの「00 00 00 00」です。
「重要な色数」も、仕様上は策定されていますが、やはり実際には利用されていません。これまたマイクロソフトの『ペイント』アプリで作ったファイルですらも、ここはゼロのままの「00 00 00 00」です。
なお、ビットマップの仕様を策定した起業は、昔のIBMとマイクロソフトです。
なお、マイクロソフトのサイトでは

のようにC言語っぽいコードのようなモノが書いてあるが、これは単に構造を説明しているだけであり、このコードをコンパイラなどに入力しても何も起きない。

背景が白色だと分かりづらいので、黄色にしてみましょう。
それを横5×縦3ピクセルにしたファイルのバイナリー・データーダンプは、次のようになります。
「BB BB B0 00 」と3回繰り返すことで、パレットBでピクセルを塗るのを横5マスを3行続けていることが分かります。
末尾の「0」や「00」はおそらく改行のようなものでしょう。

また、キャンバスサイズを拡大し、横6×縦9ピクセルで黄色一色のビットマップ画像にすると、ダンプは下記のようになります。

よくみると後半「BB BB BB 00」が9回(※ この画像のタテのピクセル数が9である)、繰り返されています。
また、Bの個数も、6回ずつ(※ この画像の横幅は6である)です。
最後の「00」で、このことから、00バイトで改行を表すのだろう事が分かります。
とすると、パレット番号に「0」や「00」は使えないハズです。

なお、画像データの読み方は、バイナリー・データーの情報が、画像では左下から右下に対応します。
なので、2色以上を使った画像で、調べてみましょう。
下記の画像は、ややサイズが大きい画像ですが(サイズが小さいとビューワーの性能によっては色ボケしたりして不正確になるので)、
幅30×高さ10 の画像です。白地を基調としておりますが、左上に赤い帯があります。下側に青い帯があります。右上に短く青い帯があります。

ADDRESS 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 0123456789ABCDEF
太字にしたところから、画像の始まりです。ほぼパレット「C」が30回繰り返されています。2行目の最初が「6C」になってるのは、ペイントが勝手にそういう色に変えただけです。2行目は6のあと「C」が28回繰り返され、最後にパレット「6」番の色です。
パレットCは、斜線にした 「FF 00 00 00」の部分です。「赤色(256段階), 緑色(256段階), 青色(256段階)」の順番になっています。
なので、
です。
さて、幅30×高さ10 の画像は、画像で見ると、青い帯は下側にあります。しかしバイナリー・データーでは、青い帯は、最初に書かれています。
このように、上下が反転しています。そういう仕様だからです。

Linux だと、このバイナリー・データーのテキスト形式のダンプ内容を、バイナリー・データーのビットマップ画像形式に変換できるコマンドがある。
xxd というコマンドを使えばいい。Fedora 31 では標準では、この xxd がついていない。なのでFedoraにこのコマンドをインストールするために vim-common をインストールする必要がある。Fedora ではvim-common に xxd が含まれている。
xxdはもともと、バイナリー・データーを16進ダンプするためのコマンドであるが、このソフトウェアには、さらに16進ダンプされた内容のテキストをバイナリー・データーに戻す機能もある。
引数に -r をつける事により、16進ダンプをバイナリー・データーに戻せる。
たとえば、なんらかのテキストエディタ(Fedora標準の gedit でもよい)に、下記のテキスト(これはstirringで出力したテキストから、上2行の見出しを除去しただけの物である)をそのままコピーペーストしてテキストエディタに貼り付けし、普通に「test」などの名前で保存しよう。この段階では、ファイル「test」は、まだ単なるテキストファイルである。
だが、コマンド端末で
と入力して実行すると、なんと『testout』というファイル名で、画像ファイルが新規作成されている。

言葉だけだと分かり辛いので、実際に下記コード例でC言語でバイナリー・データーを書き込むことによって画像ファイルを作成してみましょう。
とりあえず、上記コードを実行すると、黄色いビットマップが作成されます。
もちろん、こうやって一個ずつ作成するのは非効率だし、今さらパレット数が限られる形式は古いビットマップは非効率ですが、とりあえず上記コードで実際にビットマップが作成できるのです。
後半部
の英数字1文字が 5×3 のサイズの1ピクセルに対応していることが一目瞭然です。
実際、英数字のBを十六進で 0 から F の別の 英数字に変えると、コンパイル後のビットマップの対応する画素の色が変わります。
なお、画像は上下反転して表示されることに注意してください。
たとえば、元データが
の配置なら、表示画像は
のように上下逆に配置されます。
実際、色変えを試したところ、色と英数字の関係は
でした。カラーパレットどおりです。
となっていますが、なぜか16個のパレットを使用できます。
さて、上記の黄色5×3のビットマップに、右端に青色を1列くわえて横6マスにしたい場合、やればいいことは単に
のように横幅の数値を5から6に変えて、
のように青色に対応す「C」を一番右列の次に加えれば済みます。

なお、ビットマップにはいくつかの方式がありますが、 上述の例で示した方式は 4bitビットマップ
という方式です。
色ビット数の「04」に対応しています。
圧縮形式が「00」の場合、色ビット数の数値によって画像の形式が決まり、 色ビット数が「01」なら白黒2階調、「04」なら4bitカラー、「08」なら8bitカラーのように決まる仕様です
4bitビットマップでは、16色までしか使えません。
2010年以降の現在、一般のr=0~255,b=0~255,g=0~255の合計1677万色でよく使われているのは、これではなく24bitビットマップや32bitビットマップと呼ばれる方式です。
なお8bitビットマップは256色です。
重要色数から画素対応(イメージデータ)までの部分は、下記のように4個ずつ改行すれば16行になるので、
これはカラーパレットです。
ただし、各行の数値は前から青、緑、赤の順番に対応しています。「RGB」の逆ですので、注意してください。
実際、たとえば、0からFまでの数字を使った画素情報
に書き換えておいたあと、カラーパレットに相当する部分でたとえば 0xFF, 00, 00, 00, を 0x00, 00, 0xFF, 00, に書き換えたら、今まで青だったところが赤に変わります。

なお、ビットマップ形式は透明化をサポートしていません。
コードでは、たとえばパレットの6行目あたりに
RGB(コード青、緑、赤で並んでいますが)の色情報のあとにある 4列目の00 は、透明度でなければ不透明度でもなく、単なるゼロ・ゼロです。
実際、後述しますが24bitカラーになると、ゼロゼロはなくなり、RGB値だけを画素部分に直接記入しています。
ビットマップの2bit カラーのモノクロ2諧調で下記のように5×3でキャンバスに色を塗ると、バイナリー・データーは下記のようになります。
42 4D 4A 00 00 00 00 00 00 00 3E 00 00 00 28 00 00 00 05 00 00 00 03 00 00 00 01 00 01 00 00 00 00 00 0C 00 00 00 C4 0E 00 00 C4 0E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF 00 78 00 00 00 E8 00 00 00 D8 00 00 00
末尾の FF FF FF 00 78 00 00 00 E8 00 00 00 D8 00 00 00
の FF FF FF 00 の後が画素の情報です。
この意味は、 まず「78」の最初の7を 2進法に変換すれば
0111 です。
0が黒色、1が白色に相当し、
これで一番下の行の
が確定します。
次の8は、2進数では「1000」です。1が白なので、
となりますが、横幅が5までなので、6個目以上はカットされて、
となり、こうして一番下の行が確定します。 下から2行目や3行目の色も、1行目と同様に決まります。
もしカットされた横方向の6列目以降を見たいなら、バイナリエディタ2行目辺りにある画像の横幅の 05 を 08 に変えてみれば、見ることができ、確かに黒色になっています。
また、カラーパレットに相当する部分は
のうちの
のあたりです。
のようにいじってみると、(白と黒ではなく)色が水色と青色のものに変わります。
次のFF FF FF が、青FF,緑FF,赤FFを意味しています。
だから例えば紫色を使いたいなら、FF 00 FF を入力すればいいわけですが、実際には色解像度などの都合でピンク色が表示されます。
赤色の濃さをきめるブロックが本当に赤に対応しているかを確認するには、青と緑の部分を 00 に変えてみれば、確かに残りのブロックが赤色に対応していることがわかります。
5×3の黄色画像を24bitカラーで作ると、バイナリー・データーは下記のようになります。
42 4D 66 00 00 00 00 00 00 00 36 00 00 00 28 00 00 00 05 00 00 00 03 00 00 00 01 00 18 00 00 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00 F2 FF 00
これは意味的には、
です。
24bitカラーでは、カラーパレットが無くなります。
色は、16進数の2ケタ×3原色ぶんで直接、指定する方式です。ただし、青,緑,赤の配置で並んでいます。
は
の意味です。
だから、バイナリエディタを使って画素の最初の
をたとえば FF 00 00 に変えれば、ビットマップ画像の左下が青く変わります。
例によって 横 5× 縦 3 の黄色画像を作ってバイナリー・データーを調べてみましょう
結果は
です。
これは意味は
数値はビッグエンディアンで並んでいます。
CRCは巡回冗長検査というものです。
PNGは圧縮ライブラリにオープンソースの圧縮伸張ライブラリ ZLIB を採用しています。なので、ZLIB を調べなければなりません。
PNGについて、企業 GREE の技術者ブログが詳しくわかりやすくまとまっています。 『PNG軽量化の減色と圧縮について | エンジニアブログ | GREE Engineering』
詳しくは、そちらを読んでください。当wikiよりも遥かにレベルの高い技術者がわかりやすく解説してくれています。
いちおう、当wiki側で念のためにある程度は技術解説しておくと(リンク先サイトが将来的に消失するような場合もあるので念のため)、 zlib自体は統一的に普及していますが、しかしPNGの圧縮はzlibだけによるものではなく、フィルタやパレットによる圧縮の効果もあります。
このため、どういったアルゴリズムを採用するかによって圧縮率が変わるので、実はPNGには統一的な圧縮方法がなく、ソフトウェアやアルゴリズムによって変わります。だから代表的なPNG編集可能ソフトウェアでは、さまざまなパターンの圧縮方法を試して、一番圧縮率の高かったものを採用するとのことです。
こういう複雑な仕様のため、個人でPNG編集ソフトウェアを作るのは、あまり現実的ではないでしょう。企業やベンチャーなどに任せましょう。
カラータイプを見て、パレットを使う方式かどうかを判別しています。パレットを使う方式の場合、 PLTE というチャンクがある。
カラータイプは、0,2,3,4,6 のいずれか。カラータイプ 3 ならパレットを使う方式。カラータイプが4または6のとき、透過情報をもつことができる。
ビット深度は、青1の強さが何ビットかということです。
8bitなら、0~255までの合計256段階ということです。
RGBと合計で三原色あるので、24ビットになります。
なので、バイナリー・データーを見ると8bitですが、しかしマウス操作の右クリックなどで画像のプロパティを見ると 「ビットの深さ」が24ビットになっています(8×3=24 なので)。
なので、上記の黄色画像のファイル形式でなら フルカラー(約1670万色)を扱えます。PNG 24 は透過情報を持ちません。
上記、黄色画像は、後述するPNG 24 というものです。

PNG のビット深度には一般にrgbとアルファの合計で8bitと24bitと32bitのものがありますが、拡張子はどれも「.png」です。
通称、ビット深度がrgb+アルファで合計24bitのものは、PNG 24 などと言われます。PNG 24 はフルカラー(約1670万色)を扱えます。PNG 24 は透過情報を持ちません。
これに透過情報を持たせるには別途、外部の画像編集ソフトで透過情報を追加させてビット深度をrgb+アルファで合計32bitにさせた PNG 32 を作る必要があります。
ビット深度がrgb+アルファで合計8bitのものは256色程度までしか扱えず、PNG 8 などといわれます。 png 8 も png 24 も png 32 も、どれも拡張子は「.png」です。
なお、一原色あたりビット深度 16 のものは、つまり青一色でも 0 ~ 65535 の合計 65536 段階あるものです。
ビット深度は通常は 1, 2, 4, 8, 16 のいずれかです。カラータイプ 2 や 6などのトゥルーカラーが対応しているのはビット深度が 8 以上のものです。
なお実はPNGにはPNG 48 という色数が約280兆色 のものもあり、フォトショップなど一部の画像処理が対応しているようです。
また、PNGには、画像の行ごとに「フィルタ」と言う処理があり・・・(調査中)
PNGはハフマン符号と言うのを使っています。これは、出現頻度の多い文字にほど、短い文字を割り当てるものです。ただし、1バイト単位で文字と見なして、それを符号化するようです。
ハフマン符号は、情報工学系の大学教科書などで一般的な『符号理論』のような科目の教科書で解説される、典型的な技術です。
PNGは、このほか LZSS という処理を使っています。
LZSS とハフマン符号を組み合わせたものが、PNGの採用している Deflate 圧縮です。
そして、このDeflate圧縮に補助的に色々なものをつけてライブラリ化したものが ZLIB です。
当ページは現状『ゲームプログラミング』をうたっています。
同人ゲームプログラミングなどでよくDXライブラリが使われますが、幸運なことに、DXライブラリの作者さまは圧縮にも興味を持っており、リンク先 『データ圧縮プログラムについてざっくり学ぶ』 のように圧縮についての技術解説をしたwebページを作ってくれています。
PNGはこのように複雑ですので、オープンソースのライブラリを使わずにPNGの読み書きソフトをゼロから作るのは非現実的です。zllbなどのオープンソースのライブラリを使って作ることになるでしょう。そのためには、windowsユーザーならwindowsでのライブラリの作り方やdllなどの作り方や使い方なども学ばなければならず、なかなか面倒そうです。
画像編集ソフトの企業の技術者でもない限り、PNGのフォーマットについては教養にとどめておいて、個人での編集ソフト製作はしないのが現実的かもしれません。
さて、PNGは解析には予備知識が膨大なのでプログラマーにはシンドイですが、しかし画像中での透過部分の保有に対応している画像フォーマットが、PNGやGIFなどの限られた画像フォーマットしかありません。(あとは、フォトショップなど一部の画像編集ソフトのフォーマットなので、そのソフトのユーザー以外には関係ない。)
ビットマップとJPEGは、透過部分の保有には対応していません。このため、画像中に透明部分の保有をしたい場合には、PNGがよく使われます。
なお、GIFはかつて特許問題があった等の事情があるためか、静止画にはGIFがあまり使われていないのが現状です。2004年にGIFの特許は切れましたが、しかしそれから10年以上経った2020年代の現在でも、静止画像にはGIFはあまり使われておらず、もっぱら動画において透過部分を保有したい場合にGIFが使われます。いわゆる「GIF動画」です。
よく圧縮の原理の説明で、「AAAAAAA」(Aが7個ある)を「A7」に置き換える、などと習います(なお「ランレングス圧縮」の原理)。高等学校でも、「ランレングス」という用語は習いませんが、こういう感じの原理を習います。
さて、上記「AAAAAAA」の説明では、都合よく続いている元の文字が、数字ではなく英字です。
ですが、もし「3333333」という数字の並びを圧縮するとき、どうするのかという問題があります。「37」とでも書くのでしょうか? しかしこれだと、もし三十七という内容の文字を表したい場合に困ります。
そう、つまり、実際には「A7」のような原理だけでは、圧縮できないのです。さらに必要な工夫として、圧縮後のどこからどこまでが文字の1単位であり、どこが反復回数なのかを、なんらかの方法で区別する必要性があるのです。
この問題を解決するための方法は単純であり、
という方法を使います。
たとえ1回しか現れない文字でも、反復回数を併記します。そのほうがアルゴリズムが簡単だからです。
具体例
「3」はアスキーコードで 0x03 です。これを7回続ける 3333333 なら、
0x03 0x07 です。
一方、「37」はアスキーコードで「0x03 0x07」です。1バイト単位で扱うので、「3」を1回、「7」を1回なので、
0x03 0x01 0x07 0x01
となります。
こうすることで、アスキー「3333333」とアスキー「37」を区別できます。
追記
なお、上記のような方法だと、反復回数は1バイト単位の最大数である FF までなので、つまり 255 回までしか反復を1フレーズでは処理できません。
だからもし「3」が262回続いたら、たとえば
0x03 0xFF 0x03 0x07
とでもなるでしょうか。圧縮後の記録が2バイトからバイトに増えましたが、しかしそれでも「FFFFF・・・」とFを262回記録する262バイトと比べたら大幅に圧縮できているので、意義のある圧縮です。

座学ばかりを聴いても頭に入らないでしょう。頭に入った気になるだけです。
実際に、ランレングス圧縮するコードを簡易的に作ってみましょう。
たとえば下記のテキストファイル "test.txt"
をランレングス圧縮するコードを簡易的に書いてみましょう。簡易なので、色々と想定漏れがありますが、しかしこういうのは簡単な例からコードを作っていくのが勉強のコツです。いきなり色々なパターンを想定したものを書くと、挫折してしまいます。
例えば、下記のようになります。簡易的なものなので、2種類の文字までしか圧縮に対応していませんが、とりあえずこれでも実装時の方針はつかめます。 想定漏れはいろいろありますが、文句をつけるのではなく、読者がご自身で改良したコードを手元で自分で作っていってください。そうやってプログラミングは勉強していくものです。
1バイトの整数を宣言するには char 型を使います。(一方、int 型だと4バイトになるので、エラーの原因になりかねず危険です。)
また、ランレングスでは繰り返し回数は必ず0以上の正(ただし0も正とした)の数なので unsigned char 型で宣言するのが安全です。(unsigned をつけないと、繰り返し回数 128以上の結果が異なってしまうので。)

実行結果
一方、もし
と表示されたら、単に読み取り先のテキストファイルを作成し忘れているか、あるいはファイルのアクセス許可などのセキュリティ設定が不適切だろうと考えられますので、テキストファイル側を修正してください。
上記コードだけの簡易的なものでも、実際にコードを作るとなると、何時間も掛かってしまいます。
上記コードでは、本当はもっと短くできますが(たとえば文字aを読み取るブロックと文字bを読み取るブロックを分けているので、コードが長くなっている)、しかし読者がコードの構造を理解しやすいように、意図的に文字aと文字bを読み取るブロックを分けています。
なお、実際にコードを作る場合は、けっしていきなり for とかwhile のコードを書くのではなく、愚直にまずは if 文のコードで「文字が2回続いた場合」「文字が3回続いた場合」などと場合ワケをしていき、あとからそれら場合分けをしたいくつもあるif文ブロックをfor などの構造でまとめていくことで、上記のように雛形(ひながた)を作っていきます。
ランレングスは、符号の種類が少ない場合ほど、符号が連続しやすいので、圧縮効率が高くなります。なので、ファクシミリなどの白黒印刷で、よくランレングス圧縮が使われます。
説明の都合上、文字の圧縮で解説していますが、どちらかというとランレングスはある種の画像データの圧縮向きであり、文書データの圧縮には向かない手法です。
ランレングス圧縮の欠点として、
ABCDE
のような文字を圧縮すると、
A1B1C1D1E1
となり、逆に長くなってしまいます。
そこで、長さが2以上または3以上のときにだけ圧縮する手法が考えられますが、しかしこの問題は、どこが圧縮された文字で、圧縮されてない文字かの区別をつけるのが難しいことです。
そこで、圧縮する部分としない部分との区別を、負数で区別する方法が考えられました。
たとえば
AAAAABCDEEEE
を
5A-3BCD4E
のように処理する方法です。
負数の場合、その絶対値の大きさのぶんだけ、不連続の文字が続きます。
また、このような処理のため、文字より先に数値を付記する必要が生じます。
ただし、この方法だと、負数を保管するために負号付き整数を利用するため、 つまり 255文字ではなく127文字までしか長さを保管できません。
C言語では、unsigned をつけないchar型で宣言すれば、-128~127を保管できる1バイト整数型になります。

コード例としては、まずいきなりPackBitsのコードを書くのは難しいので、まずランレングスをきちんと作ろう。
読み取り対象のテキストファイル "test2.txt" を下記のようにすれば(aが5回連続、bとcとdは不連続、eは6回連続)、
ランレングスのコードは、
実験結果
のような実行結果になる。
あとはこのコードを、PackBitsになるように改良していけばいいだけである。
上記コードでバイナリー・データーを格納する配列を2種類(str1 と str2)用意している理由は、なぜか最後に書き込みしたバイナリー・データーを格納している配列が書き換わってしまうバグ的な現象があるため、それを波及させないように2つ配列を用意しているという理由にもとづく。(「バイナリー・データー1」と「バイナリー・データー2」の結果がなぜか違っている。)

さて、我々が知りたいのはランレングスではなく、Pack bitsのコードである。それでは、そのコードを作ろう。
とりあえず、読者の簡便のため圧縮したい文字列を再掲する。
このコードを、下記のコードで圧縮しよう。 下記のように、長いコードになったが、とりあえず下記のコードで、実際に英数字だけの文字列なら、実験の結果、いくつか圧縮できる。
なお、コードをなるべく短くするために、前コードにあったデバッグ用メッセージの printf のいくつかは削除した。
コード例
実験結果
さて、実行結果中にある文字列 fffffffd が一見すると負数に見えないが、しかしこれが負数の -3 のことである。
なぜなら、16進数で8桁 である数
に1を足すと
になるので、ffff ffffはつまり-1のことである
同様に
ffff fffe とは -2 のこと。
よって
ffff fffd とは -3 のことである。
もっとも char型の1バイト(16進数で2桁のはず)の整数を宣言したのに、なぜ負数のときに 16進数で8桁の数が表示されるか疑問であるが、実行環境がそういう実装になっているので、妥協してもらいたい(Windows7 上のgcc で実行した結果である)。
ある種の画像はランレングスで効果的に圧縮できそうです。
しかしテキストファイルでは、普通、同じ文字が長々と反復しているということはありません。
なので、普通のテキストファイルを圧縮してしまうと、むしろ長くなってしまいます。
たとえば
hello を圧縮すると、
なので、無圧縮なら
である一方、「圧縮」すると回数が付記されるので
とむしろ増えてしまいます。
そこで改善案として、 続いていない文字は圧縮しないよう改良したモデルが既に考えられているのですが、問題点は、あるバイト情報が反復回数であるのか元文字のASCIIなのかをどう区別するか、という問題です。
さて、ASCII コードには十進で 127 (0x7f)までしかありません。
そこで、テキストファイルの圧縮を扱う際、128以上255以下の数字を、圧縮の開始記号として区別する方法もあります。
とりあえず、説明の簡単のため 0x80 を開始記号としたとしましょう(実際に採用されているほう恣意とは違う)。
たとえば helllllllo という小文字エル l が7回続いているヘロロロロロロローは、
となり、たった 6 バイトで表せます。なお、4バイト目の「0x80」が開始記号であり、その次の「0x07」が反復回数です。
さて、たった1個しかない開始記号に、1バイトも使うのは、人間にはわかりやすいですが、しかし機械的には冗長すぎて無駄です。現代では 1 バイトは 8ビット(十進で255、十六進でFF)です。
開始記号には1ビットか2ビットを使えば十分そうです。
アスキーは0x7f までですが、
7は二進数で 0111 ですので、つまりアスキーコードでは文字のビットの最初1ケタ目は必ず最初は0になります。
つまりアスキーでは最初1ビットに「1」は不使用です。
これに注目すれば、「1」をビット単位での開始記号とするアイデアが思いつきます。
そこで、先頭1ビットが「1」なら開始記号とする、という方式が採用されています。この方式では、残り7ビットを反復回数に利用できます。すると、おおよそ127回(256÷2 - 1 = 127)までの反復を処理できます。 256÷2 - 1の -1のぶんをしない方式でも、128回までの反復を処理できます。
英文の半角英数の直接入力によるテキストファイルは、これで効率的に圧縮できるでしょう。
ただし、英語以外の文字は別問題です。
また、テキスト以外の画像や音声なども、別問題です。
あくまで上記の方法は、ASCIIコードだけで表せる文字だけを使ったテキストファイルの場合のみの事例です。


連続していない1文字を圧縮しても、かえってバイトが増えるだけです。
AAのように二文字だけ連続している単語なら、(圧縮方法にも寄りますが)圧縮してもしなくても変わらないか、むしろ少し増えるリスクもあります。
そこで、反復回数がたとえば3回以上など、一定回数の反復があった場合にだけ圧縮するという方式も提案されています。
最低でも、長さに1文字、距離に1文字を使うわけですから、なので2文字よりも多い、3文字以上を圧縮しないと、効果がありません。

単純に反復を見るだけだと、ABCABCFのように、ABCが2回以上続いているようなものは圧縮できません。
タイトルでは説明の簡単のため2回だけの反復にしていますが、もしこれが ABCABCABCABCABCF のようにABCが5回続いているものだとすると、
これを圧縮できないのは困ります。
そこで、こういうのを圧縮できるようにした方式として LZSS という方式が考えられています。

ABCDEABCDK を ABCDE[5,4]K
のように圧縮する方式です。
最初の5は、何文字戻った位置が繰り返しの起点かを示す数値です。
そして「4」は、繰り返しの単語の長さです。
ABCDEABCDABCDK の場合、
まず、前から圧縮していき、
ABCDE[5,4]ABCDK
のようになります。
そして、さらに圧縮されてない別の部分について
ABCDEABCD[9,4]K
のようになります。なお、ABCDEABCD[4,4]Kのようにはしません。なぜそうかと言われても、そういう慣習なので、それに従ってください。
そしてともかく、圧縮されてない部分どうしをあわせた
ABCDE[5,4][9,4]K
が最終的に保存されます。
これなら、複合するときに、まず最初のカッコから複合していき、
ABCDEABCD[9,4]K
が中間的に複合されます。さらに続けて
ABCDEABCDABCDK
が複合され、元の文字列が得られます。

ABABABABKのように繰り返しがある場合(ABが4回続いたあとにK)、カッコ内の数値の大小関係を [小、大] にして、
AB[2,4]K
のように表します。です。
こうすることで、単純な繰り返しにも対応できます。
また、それぞれの文字の冒頭に、1bitのフラグをつけて、圧縮しているものかどうかを判断します。 アスキー文字を1バイト(8ビット)で表した場合、先頭1ビットは必ず0なので、つまり先頭1ビットが「1」の場合なら次の数ビットを制御用のビットとして認識することができます。
これをもちいて上記 [2,4] などの情報を検出します。
制御文字には3ビットを使い、0x100 なら終端ビットである。
一致長さは0x101 ~ 0x11D (十進で257~285に相当)で制御されるが、しかしこのままではdeflate圧縮での繰り返しの仕様である3から258までの数をあわらせない。そこで拡張ビット(extra bit)という手法により、
拡張ビットのあとに補完で追加されるビットの長さは一定ではなく、基準値によって異なり、下記のように定められている。基本的に参照元の基準値が大きいほど、表す一致長さも大きくなるので拡張ビット数も大きくなるが(最大で5)、しかし例外的に最後の0x11Dは表す一致長さが1通りしかないので拡張ビット数が0である。
このように下段の大きい数値ほど拡張ビット数も増えている理由は、使用頻度の高いと考えられている短い一致長さほど、保存に要する容量も小さくすることで、より効率的に圧縮できる、というアイデアである。なお、このようなアイデア(使用頻度の高いものにほど短い符号を割りえてることで容量節約するアイデア)を「ハフマン符号」という。
数学書などでの「ハフマン符号」の説明では、確率論などを使って厳密に 使用頻度 が定式化されたりするが、しかし本ページで語るような実際の応用では、そんなに厳密に確率計算する必要はない。実際、上記の対応表も、別に確率方程式などから算出されたわけではないだろうし、表のどこにも確率計算は無い。
拡張ビットでは、表現できる値からの不足分の数値を表記する。
たとえば基準値 0x10A では 13~14の表現範囲だが、これで14を表現したい場合、
13に1をプラスすれば1になるので、
のように表現される。0x10Aの拡張ビット数は「1」と決まっているので、10A の次の「1」と書かれた1ビットを読み取った時点で、自動的に拡張ビットの終わりだと判断される。
さて一方、(一致長さの符号ではなく)距離符号の拡張ビットの対応表は、別に定められている。下記のように 0x00 ~ 0x1D の距離で 1 ~ 32768 の距離に対応させています。
Deflate 圧縮における LZSS 方式では、
の順番でそれを表す。
たとえば一致長が4、距離が5なら、
まず表現「4」は0x102で拡張ビット数0なので、
まず、0x102 が先にくることが決まり、拡張ビットはその直後には無い事が決まり、直後はすぐに距離が来ることが決まる。
つまり、
となる。
そして、距離の表現「5」に対応するのは、 0x04 が5~6で拡張ビット数1だから、5+0により0が拡張ビットの値になるので、
が来ることが決まる。
そして、
一致長さ、距離
の順序なのだから、
が来る。
ABCDEABCDK の場合、まずアスキーコードに変換して、
である。
距離や一致長さの単位はバイト単位(十六進で2ケタ)で測定するので、
となる。
そして、いくつか前の段落で説明したように、
となる。
最後に終端記号 0x100 がつくので、
となる。これで圧縮がひとまず完了。これがLZSS圧縮の原理である。