「ゲームプログラミング/画像ファイルの作成プログラム」の版間の差分

削除された内容 追加された内容
→‎PNGフォーマット: png 48 の計算ミス。16×4は48にはならない(64)
→‎そもそもの圧縮の理論について: 簡易的なコード例。初学者用の理解優先のものなので、想定漏れあり(明記済み)。
1,009 行
とでもなるでしょうか。圧縮後の記録が2バイトからバイトに増えましたが、しかしそれでも「FFFFF・・・」とFを262回記録する262バイトと比べたら大幅に圧縮できているので、意義のある圧縮です。
 
 
 
;例
座学ばかりを聴いても頭に入らないでしょう。頭に入った気になるだけです。
 
実際に、ランレングス圧縮するコードを簡易的に作ってみましょう。
 
たとえば下記のテキストファイル "test.txt"
<pre>
aaaaabbbbbbbbbbbbbb
</pre>
 
をランレングス圧縮するコードを簡易的に書いてみましょう。簡易なので、色々と想定漏れがありますが、しかしこういうのは簡単な例からコードを作っていくのが勉強のコツです。いきなり色々なパターンを想定したものを書くと、挫折してしまいます。
 
例えば、下記のようになります。簡易的なものなので、2種類の文字までしか圧縮に対応していませんが、とりあえずこれでも実装時の方針はつかめます。
想定漏れはいろいろありますが、文句をつけるのではなく、読者がご自身で改良したコードを手元で自分で作っていってください。そうやってプログラミングは勉強していくものです。
 
 
<syntaxhighlight lang="C">
#include <stdio.h>
 
#pragma warning(disable : 4996)
 
int main() {
FILE *fp1 = fopen("test2.txt", "rb");
 
if (fp1 == NULL) {
perror("ファイルを開けませんでした。\n");
return 1;
} else {
printf("ファイルをオープンしました。\n");
}
 
char str1[70]; // 表示結果を短くするために数値を微妙に小さくした
printf("機械語を読み取っています。\n"); // 「文字列」ではなく機械語
 
fread(str1, sizeof(char), 50, fp1);
 
printf("ファイルに書いてある機械語\n");
 
for (int i = 0; i < sizeof(str1) / sizeof(str1[0]); i = i + 1) {
printf("%02x ", str1[i]); // 最低でも2桁を表示、の意味
}
 
printf("\n文字の繰り返しを検出しようとしています...\n");
 
int bufRep[3]; // 繰り返し文字の回数を記録するカウンターをとりあえず3つ用意。
int bufWord[3]; // 繰り返し文字の字を記録するバッファをとりあえず3つ用意。
 
bufWord[0] = str1[0];
bufRep[0] = 1;
 
int repFlag = 1;
 
char press1[70]; // 圧縮結果の保存用
int preCount = 0; // 何文字目まで書き込んだかのカウンタ
 
int temp = 0;
int breakFor = 0; // for から抜けるためのフラグ
 
for (temp = 0; temp <= 12; temp = temp + 1) {
 
if (repFlag == 1) {
if (str1[temp + 1] == str1[temp] && str1[temp] != 0) {
bufRep[0] = bufRep[0] + 1;
repFlag = 1;
} else if (str1[temp] != 0) {
repFlag = 0;
press1[0] = bufWord[0];
press1[1] = bufRep[0];
preCount = +2;
breakFor = 1;
break;
} else if (str1[temp] == 0) {
breakFor = 1;
// ヌルが来たら文末などと判断し、他に何もせずに抜け出す
break;
} // else if
 
if (breakFor == 1) {
break;
}
 
} // if
} // for
 
printf("記録する文字1の機会語: %02x \n",
bufWord[0]); // 最低でも2桁を表示、の意味
printf("その文字の現在の回数: %02x \n",
bufRep[0]); // 最低でも2桁を表示、の意味
 
printf("圧縮する文字1の機会語: %02x ",
press1[0]); // 最低でも2桁を表示、の意味
printf("その文字の現在の回数: %02x ", press1[1]); // 最低でも2桁を表示、の意味
 
printf("\n次の書き込みカウンター位置: %02x \n",
preCount); // 最低でも2桁を表示、の意味
 
printf("\n", preCount); // 最低でも2桁を表示、の意味
 
 
 
int hokan = temp; // 次のforの開始番号用
 
repFlag = 1; // 次のループ用に再セット
 
bufWord[1] = str1[hokan + 1];
bufRep[1] = 1;
 
for (temp = hokan + 1; temp <= 255; temp = temp + 1) {
if (repFlag == 1) {
 
if (str1[temp + 1] == str1[temp] && str1[temp] != 0) {
bufRep[1] = bufRep[1] + 1;
repFlag = 1;
 
} else if (str1[temp] != 0) {
 
repFlag = 0;
press1[preCount + 0] = bufWord[1];
press1[preCount + 1] = bufRep[1];
preCount = +2;
 
break;
} else if (str1[temp] == 0) {
// ヌルが来たら文末などと判断し、何もせずに抜け出す
break;
} // else if
} // if
} // for
 
printf("記録する文字2の機会語: %02x \n",
bufWord[1]); // 最低でも2桁を表示、の意味
printf("そcの文字の現在の回数: %02x \n",
bufRep[1]); // 最低でも2桁を表示、の意味
 
printf("圧縮する文字2の機会語: %02x ",
press1[2 * 1]); // 最低でも2桁を表示、の意味
printf("その文字の現在の回数: %02x ",
press1[2 * 1 + 1]); // 最低でも2桁を表示、の意味
 
printf("\n次の書き込みカウンター位置: %02x \n",
preCount); // 最低でも2桁を表示、の意味
 
printf("\nランレングス的に圧縮のシミュレーション...\n");
 
for (int i = 0; i < preCount +2; i = i + 1) {
printf("%02x ", press1[i]); // 最低でも2桁を表示、の意味
}
 
 
fclose(fp1);
printf("\nファイルをクローズしました。\n");
}
</syntaxhighlight>
 
実行結果
<pre>
ファイルをオープンしました。
機械語を読み取っています。
ファイルに書いてある機械語
61 61 61 61 61 62 62 62 62 62 62 62 62 62 62 62 62 62 62 0a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
文字の繰り返しを検出しようとしています...
記録する文字1の機会語: 61
その文字の現在の回数: 05
圧縮する文字1の機会語: 61 その文字の現在の回数: 05
次の書き込みカウンター位置: 02
 
記録する文字2の機会語: 62
その文字の現在の回数: 0e
圧縮する文字2の機会語: 62 その文字の現在の回数: 0e
次の書き込みカウンター位置: 02
 
ランレングス的に圧縮のシミュレーション...
61 05 62 0e
ファイルをクローズしました。
</pre>
 
これだけの簡易的なものでも、実際にコードを作るとなると、何時間も掛かってしまいます。
 
上記コードでは、本当はもっと短くできますが(たとえば文字aを読み取るブロックと文字bを読み取るブロックを分けているので、コードが長くなっている)、しかし読者がコードの構造を理解しやすいように、意図的に文字aと文字bを読み取るブロックを分けています。
 
なお、実際にコードを作る場合は、けっしていきなり for とかwhile のコードを書くのではなく、愚直にまずは if 文のコードで「文字が2回続いた場合」「文字が3回続いた場合」などと場合ワケをしていき、あとからそれら場合分けをしたいくつもあるif文ブロックをfor などの構造でまとめていくことで、上記のように雛形(ひながた)を作っていきます。