GIF のデータ形式
大雑把に以下の並びになっています。
バイト位置 | バイト数 | 内容 |
---|---|---|
0 | 6 | ヘッダ (Header) |
6 | 7 | 論理画面記述子 (Logical Screen Descriptor) |
(13) | - | (共通色表:存在する場合) |
- | - | 記述子 (Descriptor) |
... | ... | ... |
- | 1 | 終端子 (Trailer) |
ヘッダ (Header)
バイト位置 | バイト数 | 内容 |
---|---|---|
0 | 3 | 花押 (Signature) |
3 | 3 | 版 (Version) : 「87a」または「89a」 |
花押 (Signature)
バイト位置 | 文字 | 10進 | 16進 |
---|---|---|---|
0 | 'G' | 71 | 0x47 |
1 | 'I' | 73 | 0x49 |
2 | 'F' | 70 | 0x46 |
版 (Version) 「87a」
バイト位置 | 文字 | 10進 | 16進 |
---|---|---|---|
3 | '8' | 56 | 0x38 |
4 | '7' | 55 | 0x37 |
5 | 'a' | 79 | 0x61 |
版 (Version) 「89a」
バイト位置 | 文字 | 10進 | 16進 |
---|---|---|---|
3 | '8' | 56 | 0x38 |
4 | '9' | 57 | 0x39 |
5 | 'a' | 79 | 0x61 |
論理画面記述子 (Logical Screen Descriptor)
バイト位置 | 内容 |
---|---|
6 | 画面の幅の下位8ビット (Logical Screen Width) |
7 | 画面の幅の上位8ビット (Logical Screen Width) |
8 | 画面の高さの下位8ビット (Logical Screen Height) |
9 | 画面の高さの上位8ビット (Logical Screen Height) |
10 | 詰合情報 <Packed Fields> |
11 | 背景色番号 (Background Color Index) |
12 | ピクセル縦横比 (Pixel Aspect Ratio) |
(13) | (共通色表:存在する場合) (Global Color Table) |
詰合情報 <Packed Fields>
ビット位置 | ビット数 | 内容 | 備考 |
---|---|---|---|
0 | 3 | 共通色数のビット数 (Size of Global Color Table) |
SGCT と略す |
3 | 1 | 共通色表は重要度順になっている (Sort Flag) |
|
4 | 3 | 論理画面の色解像度 (Color Resolution) |
ビット数(1〜8) を(0〜7)とする |
7 | 1 | 共通色表あり (Global Color Table Flag) |
ピクセル縦横比 (Pixel Aspect Ratio)
計算式は次のとおり。
Aspect\ Ratio = \frac{ Pixel\ Aspect\ Ratio + 15 }{ 64 } = \frac{ 幅 } { 高 }
正方形 $\left[1:1\right]$ は $\left(Pixel\ Aspect\ Ratio = 49 \right)$ とする。
共通色表 (Global Color Table)
共有色表が存在する場合は $\left( n=2^{SGCT+1} \right)$ 個の色情報が必要です。
色番号 | バイト位置 (共通色表) |
+0 | +1 | +2 |
---|---|---|---|---|
$0$ | $0$ | R[0] | G[0] | B[0] |
$1$ | $3$ | R[1] | G[1] | B[1] |
... | ... | ... | ... | ... |
$n-1$ | $3(n-1)$ | R[n-1] | G[n-1] | B[n-1] |
$n$ は最大で $256$ です。
記述子 (Descriptor)
記述子には以下の種類があります。
先頭バイト値 (Descriptor) |
後続バイト値 (Label) |
内容 |
---|---|---|
0x2C | - | 画像記述子 (Image Descriptor) |
0x3B | - | 終端子 (Trailer) |
0x21 | 必要 | 「89a」版による拡張 |
0x21 | 0xF9 | 図形制御拡張 (Graphic Control Extension) |
0x21 | 0xFE | 注釈拡張 (Comment Extension) |
0x21 | 0x01 | 文字画面拡張 (Plain Text Extension) |
0x21 | 0xFF | 応用拡張 (Application Extension) |
画像記述子 (Image Descriptor)
バイト位置 (画像記述子) |
内容 |
---|---|
0 | 画像記述子 (Image Separator) : 0x2C (Image Descriptor) |
1 | 画像の位置(左)の下位8ビット (Image Left Position) |
2 | 画像の位置(左)の上位8ビット (Image Left Position) |
3 | 画像の位置(上)の下位8ビット (Image Top Position) |
4 | 画像の位置(上)の上位8ビット (Image Top Position) |
5 | 画像の幅の下位8ビット (Image Width) |
6 | 画像の幅の上位8ビット (Image Width) |
7 | 画像の高さの下位8ビット (Image Height) |
8 | 画像の高さの上位8ビット (Image Height) |
9 | 詰合情報 <Packed Fields> |
(10) | (固有色表:存在する場合) (Local Color Table) |
(?) | 色番式画像 (Table Based Image Data) |
詰合情報 <Packed Fields>
ビット位置 | ビット数 | 内容 | 備考 |
---|---|---|---|
0 | 3 | 固有色数のビット数 (Size of Local Color Table) |
SLCT と略す |
3 | 2 | 予約 (Reserved) | |
5 | 1 | 固有色表は重要度順になっている (Sort Flag) |
|
6 | 1 | 縞状形式 (Interlace Flag) | |
7 | 1 | 固有色表あり (Local Color Table Flag) |
縞状形式 (Interlace Flag)
縦方向を、次の順に並び替える。
- 先頭行(Y=0)から8行間隔
- 5行目(Y=4)から8行間隔
- 3行目(Y=2)から4行間隔
- 2行目(Y=1)から2行間隔
固有色表 (Local Color Table)
固有色表が存在する場合は $\left( n=2^{SLCT+1} \right)$ 個の色情報が必要です。
色番号 | バイト位置 (固有色表) |
+0 | +1 | +2 |
---|---|---|---|---|
$0$ | $0$ | R[0] | G[0] | B[0] |
$1$ | $3$ | R[1] | G[1] | B[1] |
... | ... | ... | ... | ... |
$n-1$ | $3(n-1)$ | R[n-1] | G[n-1] | B[n-1] |
$n$ は最大で $256$ です。
色番式画像 (Table Based Image Data)
バイト位置 (色番式画像) |
内容 | 備考 |
---|---|---|
0 | LZW 最小コード幅 (LZW Minimum Code Size) | LMCS と略す |
1 | 圧縮画像(副塊情報形式 : Data Sub-blocks) | LZW 圧縮 |
圧縮画像
画像の幅と高さを $w,h$ とすると $(w \times h)$ 個の色番号表が画像データとなります。
\ | 0 | 1 | ... | w-1 |
---|---|---|---|---|
0 | $c_0$ | $c_1$ | ... | $c_{w-1}$ |
1 | $c_w$ | $c_{w+1}$ | ... | $c_{2w-1}$ |
... | ... | ... | ... | ... |
h-1 | $c_{w(h-1)}$ | $c_{w(h-1)+1}$ | ... | $c_{w h - 1}$ |
圧縮画像は、画像データを LZW アルゴリズムで圧縮して副塊情報形式 (Data Sub-blocks) で格納します。
副塊情報形式 (Data Sub-blocks)
バイト位置 (副塊情報形式) |
内容 |
---|---|
$0$ | 後続のバイト数 $N$ |
$1$ | データ列($N$ バイト) |
$N+2$ | 後続のバイト数 |
$N+3$ | データ列(続き) |
... | ... |
... | ... |
... | ... |
- | 後続のバイト数 |
- | データ列(続き) |
- | 後続のバイト数 = 0 |
後続のバイト数は最大で 255 です。
「89a」版による拡張
「89a」版による拡張は、以下の形式となっています。
バイト位置 (識別子) |
内容 |
---|---|
0 | 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) |
1 | 表題 (Label) |
2 | 副塊情報形式 (Data Sub-blocks) |
- | 〃 |
この形式により、未知の表題 (Label) を未知のデータとして処理可能になっています。
図形制御拡張 (Graphic Control Extension)
バイト位置 (図形制御拡張) |
内容 |
---|---|
0 | 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) |
1 | 図形制御表題 (Graphic Control Label):0xF9 |
2 | 情報の大きさ:0x04 (副塊の後続バイト数) |
3 | 詰合情報 <Packed Fields> |
4 | 遅延時間(1/100秒単位)の下位8ビット (Delay Time) |
5 | 遅延時間(1/100秒単位)の上位8ビット (Delay Time) |
6 | 透明色とする色番号 (Transparet Color Index) |
7 | 終端:0x00 (副塊の後続バイト数) |
詰合情報 <Packed Fields>
ビット位置 | ビット数 | 内容 |
---|---|---|
0 | 3 | 予約 (Reserved) |
3 | 3 | 消去方法 (Disposal Method) |
6 | 1 | ユーザー操作による遅延時間の短縮 (User Input Flag) |
7 | 1 | 透明色が有効 (Transparent Color Flag) |
消去方法 (Disposal Method)
値 | 内容 |
---|---|
0 | 未定義(デコーダは何もしない) |
1 | 消去しない |
2 | 背景色 (論理画面記述子で指定) を使う |
3 | 一つ前の画像に戻す |
4-7 | (予約) |
注釈拡張 (Comment Extension)
バイト位置 (注釈拡張) |
内容 |
---|---|
0 | 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) |
1 | 注釈表題 (Comment Label):0xFE |
2 | 注釈情報 (副塊情報形式:Data Sub-blocks) |
- | 〃 |
文字画面拡張 (Plain Text Extension)
バイト位置 (文字画面拡張) |
内容 |
---|---|
0 | 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) |
1 | 文字画面表題 (Plain Text Label):0x01 |
2 | 文字画面設定のサイズ (副塊情報形式:Data Sub-blocks):0x0C |
3 | 文字画面の位置(左)の下位8ビット (Text Grid Left Position) |
4 | 文字画面の位置(左)の上位8ビット (Text Grid Left Position) |
5 | 文字画面の位置(上)の下位8ビット (Text Grid Top Position) |
6 | 文字画面の位置(上)の上位8ビット (Text Grid Top Position) |
7 | 文字画面の幅の下位8ビット (Text Grid Width) |
8 | 文字画面の幅の上位8ビット (Text Grid Width) |
9 | 文字画面の高さの下位8ビット (Text Grid Height) |
10 | 文字画面の高さの上位8ビット (Text Grid Height) |
11 | 文字の幅 (Character Cell Width) |
12 | 文字の高さ (Character Cell Height) |
13 | 文字の共通色番号 (Text Foreground Color Index) |
14 | 文字背景の共通色番号 (Text Background Color Index) |
15 | 任意の文字列 (副塊情報形式:Data Sub-blocks) (Plain Text Data) |
- | 〃 |
応用拡張 (Application Extension)
バイト位置 (応用拡張) |
内容 |
---|---|
0 | 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) |
1 | 応用拡張表題 (Application Extension Label):0xFF |
2 | 識別子と認識コードのサイズ (副塊情報形式: Data Sub-blocks):0x0B |
3 | 8バイトの識別子 (Application Identifier) |
11 | 3バイトの認識コード (Application Authentication Code) |
14 | 任意の応用拡張情報 (副塊情報形式:Data Sub-blocks) (Application Data) |
- | 〃 |
終端子 (Trailer)
バイト位置 (オフセット) |
内容 |
---|---|
0 | 終端子 (Trailer):0x3B |
GIF における LZW のパラメータ
LZW 最小コード幅 (LZW Minimum Code Size) を LMCS とします。
内容 | 値 |
---|---|
各コードのビット幅 (可変長データ) |
3 〜 12 |
LMCS の最小値 | 2 |
初期化コード <Clear Code> |
$2^{LMCS}$ |
最初のコード | $2^{LMCS} + 1$ |
コードの最大値 | $2^{12} - 1$ |
LZW の圧縮と展開
クリアコードを省いているので完全ではありませんが、圧縮と展開のイメージが掴めればと思います。
圧縮と展開の動作を示すサンプル プログラム
// サンプル: (〒899-7103 鹿児島県)志布志市志布志町志布志 //
const SAMPLE_TEXT = 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI';
// コードと文字の変換.
const CODE_TO_TEXT = ' BCHIOSU';
const codeToChar = ((c) => CODE_TO_TEXT[c]);
// 文字列の並びを反転.
const stringReverse = ((s) => s.split('').reverse().join(''));
/*
* LZW 圧縮
*/
function encodeLZW(data, minimum_bits=3) {
let encode_data = ''; // 圧縮データ列(10進).
let encode_binary = ''; // 圧縮データ列(2進).
// 空データ対策.
if (data.length == 0)
return [encode_data, encode_binary];
const code_end = (1 << minimum_bits); // 終端コード.
const code_clear = code_end + 1; // 辞書クリアコード.
let code_dict; // 文字列の辞書.
let code_curr; // 辞書に追加する文字列のコード.
// 辞書の初期化処理.
// code_dict = {
// ' ': 0, 'B': 1, 'C': 2, 'H': 3,
// 'I': 4, 'O': 5, 'S': 6, 'U': 7,
// }
const initDict = function() {
code_dict = new Map();
for (let i = 0; i < CODE_TO_TEXT.length; i++)
code_dict.set(codeToChar(i), i);
code_curr = code_clear;
};
// 圧縮コードのビット列を生成.
const make_binary = function(code) {
bits = 32 - Math.clz32(code_curr); // 出力するデータのビット数を求める.
binary = '';
for (let i = 0; i < bits; i++)
binary = String((code >> i) & 1) + binary;
return binary;
}
initDict(); // 辞書の初期化.
console.log('圧縮: \'' + data + '\'');
console.log('最小ビット = ' + minimum_bits);
console.log('終端コード = ' + code_end);
console.log('クリアコード = ' + code_clear);
console.log('圧縮コードの先頭 = ' + (code_clear + 1));
let diter = data[Symbol.iterator](); // 参照するデータのイテレータ.
let str = ''; // 検索文字列.
let bin_curr; // 最初の圧縮コード
for (;;) {
let dinf = diter.next(); // 配列から文字を取り出す.
if (dinf.done) break; // データがなければ終了.
let ch = dinf.value; // 文字データ.
str += ch; // 検索文字列の更新.
let bin_try = code_dict.get(str);
if (bin_try != undefined) {
console.log('文字[\'' + ch + '\'] : 辞書あり: \'' + str + '\'');
bin_curr = bin_try; // 検索結果を更新.
continue; // 既に辞書にあれば検索文字列を長くする(次のデータへ).
}
console.log('文字[\'' + ch + '\'] : 辞書なし: \'' + str + '\'');
// 圧縮コードは前回の検索結果を出力する.
let binary = make_binary(bin_curr);
encode_data += ' ' + bin_curr;
encode_binary += ' ' + binary;
// 辞書に文字列を登録する.
code_dict.set(str, ++code_curr);
console.log(' 圧縮コード(前回分): \'' + str.slice(0, -1) + '\':' + bin_curr + '=' + binary);
console.log(' 新規辞書登録文字列: \'' + str + '\':' + code_curr);
/* 本当は code_curr が最大コードに達したら、辞書クリアコードの発行と辞書の初期化が必要 */
// 文字列の最終文字は圧縮保留状態なので、その文字から再開する.
str = ch;
bin_curr = code_dict.get(str);
}
{// 最後の圧縮コードを出力する.
let binary = make_binary(bin_curr);
encode_data += ' ' + bin_curr;
encode_binary += ' ' + binary;
console.log(' 最後の圧縮コード : \'' + str + '\':' + bin_curr + '=' + binary);
}
{// 終端コードを出力する.
let binary = make_binary(code_end);
encode_data += ' ' + code_end;
encode_binary += ' ' + binary;
console.log(' 終端コード : ' + code_end + '=' + binary);
}
return [encode_data, encode_binary];
}
// 圧縮データを得る.
const encode_data = function() {
const [encode_list, encode_binary] = encodeLZW(SAMPLE_TEXT);
const binaries = encode_binary.slice(1).split(' ');
const encbin = binaries.reduce(function(p, v) { return p + stringReverse(v); }, '');
let encstr = encbin;
let encdat = [];
while (encstr.length > 8) {
encdat.push(stringReverse(encstr.slice(0, 8)));
encstr = encstr.slice(8);
}
if (encstr.length > 0)
encdat.push(stringReverse((encstr + '00000000').slice(0, 8)));
const data = encdat.map((d) => Number('0b' + d));
console.log('圧縮コード(10進):' + encode_list);
console.log('圧縮コード( 2進):' + encode_binary);
console.log(binaries);
// console.log(encbin);
console.log('リトル・エンディアンで詰めた圧縮データ(バイト列)');
console.log(encdat);
console.log(data);
return data;
}();
/*
* LZW 解凍
*/
function decodeLZW(data, minimum_bits=3) {
const code_end = (1 << minimum_bits); // 終端コード.
const code_clear = code_end + 1; // 辞書クリアコード.
let code_dict; // 文字列の辞書.
let code_curr; // 辞書に追加する文字列のコード.
// 辞書の初期化処理.
const initDict = function() {
code_dict = [...Array(code_end)].map((_, i) => codeToChar(i));
code_dict.push(undefined, undefined);
code_curr = code_clear + 1;
};
// データからビット列を取り出す関数.
let data_bit_pos = 0;
const read = function() {
bits = 32 - Math.clz32(code_curr); // 入力するデータのビット数を求める.
let bps = (data_bit_pos >> 3);
let bpe = ((data_bit_pos + bits - 1) >> 3);
let v = data[bps];
if (bps != bpe) {
v |= data[++bps] << 8;
if (bps != bpe)
v |= data[++bps] << 16;
}
let s = (data_bit_pos & 7);
let m = ((1 << bits) - 1);
data_bit_pos += bits;
return ((v >> s) & m);
};
initDict(); // 辞書の初期化.
console.log('解凍: 入力は ' + data.length + ' バイト');
console.log('最小ビット = ' + minimum_bits);
console.log('終端コード = ' + code_end);
console.log('クリアコード = ' + code_clear);
console.log('圧縮コードの先頭 = ' + (code_clear + 1));
let decode_data = '';
let code_last = undefined;
let char_last = undefined;
for (;;) {
const code = read(); // コード・データの取得.
// 終端コードならば終了.
if (code == code_end) {
console.log('コード(終端): ' + code);
break;
}
// クリアコードならば辞書の初期化.
// if (code == code_clear) {
// console.log('クリアコード: ' + code);
// initDict();
// code_last = undefined;
// char_last = undefined;
// continue;
// }
if (code_last == undefined) {
// 辞書を初期化した直後のコード処理.
code_last = code;
char_last = codeToChar(code);
decode_data += char_last; // 1文字出力する
console.log('コード(先頭): \'' + char_last + '\':' + code);
continue;
}
let data_last = code_dict[code_last];
let code_data;
if (code < code_curr) {
// コードは辞書内にあるので、データを辞書から取り出す.
code_data = code_dict[code];
console.log('コード(辞書): \'' + code_data + '\':' + code);
} else if (code == code_curr) {
// 圧縮時に辞書に追加されたパターンに追随する.
code_data = data_last + char_last;
console.log('コード(追随): \'' + code_data + '\':' + code);
} else {
console.log('コード(失敗): ' + code + ' > ' + code_curr);
throw 'デコード失敗:データの異常を検出しました';
}
decode_data += code_data; // データを出力する.
// 辞書を更新
char_last = code_data[0];
code_dict.push(data_last + char_last);
console.log(' 辞書追加: \'' + data_last + char_last + '\':' + code_curr);
code_curr++;
// 次へ
code_last = code;
}
return decode_data;
}
// 圧縮データの解凍をする.
console.log('解凍結果: \'' + decodeLZW(encode_data) + '\'');
$ node
Welcome to Node.js v16.13.1.
Type ".help" for more information.
>
$ node --trace-uncaught lzw_sample.js
圧縮: 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI'
最小ビット = 3
終端コード = 8
クリアコード = 9
圧縮コードの先頭 = 10
文字['S'] : 辞書あり: 'S'
文字['H'] : 辞書なし: 'SH'
圧縮コード(前回分): 'S':6=0110
新規辞書登録文字列: 'SH':10
文字['I'] : 辞書なし: 'HI'
圧縮コード(前回分): 'H':3=0011
新規辞書登録文字列: 'HI':11
文字['B'] : 辞書なし: 'IB'
圧縮コード(前回分): 'I':4=0100
新規辞書登録文字列: 'IB':12
文字['U'] : 辞書なし: 'BU'
圧縮コード(前回分): 'B':1=0001
新規辞書登録文字列: 'BU':13
文字['S'] : 辞書なし: 'US'
圧縮コード(前回分): 'U':7=0111
新規辞書登録文字列: 'US':14
文字['H'] : 辞書あり: 'SH'
文字['I'] : 辞書なし: 'SHI'
圧縮コード(前回分): 'SH':10=1010
新規辞書登録文字列: 'SHI':15
文字['S'] : 辞書なし: 'IS'
圧縮コード(前回分): 'I':4=0100
新規辞書登録文字列: 'IS':16
文字['H'] : 辞書あり: 'SH'
文字['I'] : 辞書あり: 'SHI'
文字[' '] : 辞書なし: 'SHI '
圧縮コード(前回分): 'SHI':15=01111
新規辞書登録文字列: 'SHI ':17
文字['S'] : 辞書なし: ' S'
圧縮コード(前回分): ' ':0=00000
新規辞書登録文字列: ' S':18
文字['H'] : 辞書あり: 'SH'
文字['I'] : 辞書あり: 'SHI'
文字['B'] : 辞書なし: 'SHIB'
圧縮コード(前回分): 'SHI':15=01111
新規辞書登録文字列: 'SHIB':19
文字['U'] : 辞書あり: 'BU'
文字['S'] : 辞書なし: 'BUS'
圧縮コード(前回分): 'BU':13=01101
新規辞書登録文字列: 'BUS':20
文字['H'] : 辞書あり: 'SH'
文字['I'] : 辞書あり: 'SHI'
文字['C'] : 辞書なし: 'SHIC'
圧縮コード(前回分): 'SHI':15=01111
新規辞書登録文字列: 'SHIC':21
文字['H'] : 辞書なし: 'CH'
圧縮コード(前回分): 'C':2=00010
新規辞書登録文字列: 'CH':22
文字['O'] : 辞書なし: 'HO'
圧縮コード(前回分): 'H':3=00011
新規辞書登録文字列: 'HO':23
文字[' '] : 辞書なし: 'O '
圧縮コード(前回分): 'O':5=00101
新規辞書登録文字列: 'O ':24
文字['S'] : 辞書あり: ' S'
文字['H'] : 辞書なし: ' SH'
圧縮コード(前回分): ' S':18=10010
新規辞書登録文字列: ' SH':25
文字['I'] : 辞書あり: 'HI'
文字['B'] : 辞書なし: 'HIB'
圧縮コード(前回分): 'HI':11=01011
新規辞書登録文字列: 'HIB':26
文字['U'] : 辞書あり: 'BU'
文字['S'] : 辞書あり: 'BUS'
文字['H'] : 辞書なし: 'BUSH'
圧縮コード(前回分): 'BUS':20=10100
新規辞書登録文字列: 'BUSH':27
文字['I'] : 辞書あり: 'HI'
最後の圧縮コード : 'HI':11=01011
終端コード : 8=01000
圧縮コード(10進): 6 3 4 1 7 10 4 15 0 15 13 15 2 3 5 18 11 20 11 8
圧縮コード( 2進): 0110 0011 0100 0001 0111 1010 0100 01111 00000 01111 01101 01111 00010 00011 00101 10010 01011 10100 01011 01000
[
'0110', '0011', '0100',
'0001', '0111', '1010',
'0100', '01111', '00000',
'01111', '01101', '01111',
'00010', '00011', '00101',
'10010', '01011', '10100',
'01011', '01000'
]
リトル・エンディアンで詰めた圧縮データ(バイト列)
[
'00110110', '00010100',
'10100111', '11110100',
'11000000', '01101011',
'01001111', '10001100',
'00100010', '00010111',
'01011101', '00001000'
]
[
54, 20, 167, 244, 192,
107, 79, 140, 34, 23,
93, 8
]
解凍: 入力は 12 バイト
最小ビット = 3
終端コード = 8
クリアコード = 9
圧縮コードの先頭 = 10
コード(先頭): 'S':6
コード(辞書): 'H':3
辞書追加: 'SH':10
コード(辞書): 'I':4
辞書追加: 'HI':11
コード(辞書): 'B':1
辞書追加: 'IB':12
コード(辞書): 'U':7
辞書追加: 'BU':13
コード(辞書): 'SH':10
辞書追加: 'US':14
コード(辞書): 'I':4
辞書追加: 'SHI':15
コード(辞書): 'SHI':15
辞書追加: 'IS':16
コード(辞書): ' ':0
辞書追加: 'SHI ':17
コード(辞書): 'SHI':15
辞書追加: ' S':18
コード(辞書): 'BU':13
辞書追加: 'SHIB':19
コード(辞書): 'SHI':15
辞書追加: 'BUS':20
コード(辞書): 'C':2
辞書追加: 'SHIC':21
コード(辞書): 'H':3
辞書追加: 'CH':22
コード(辞書): 'O':5
辞書追加: 'HO':23
コード(辞書): ' S':18
辞書追加: 'O ':24
コード(辞書): 'HI':11
辞書追加: ' SH':25
コード(辞書): 'BUS':20
辞書追加: 'HIB':26
コード(辞書): 'HI':11
辞書追加: 'BUSH':27
コード(終端): 8
解凍結果: 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI'
サンプル プログラム
HTML+JavaScriptでローカル生成[少々長いので折りたたみ]
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
<title>GIF プログラムのテスト</title>
<style>
body {
margin-left: auto;
margin-right: auto;
width: 750px;
}
table {
border: solid 1px gray;
border-collaspe: collaspe;
border-spacing: 0;
}
th, td {
border: solid 1px gray;
padding: 4px;
}
</style>
</head>
<body>
<h2>GIF プログラムのテスト</h2>
<table>
<tr>
<td style="text-align: center;">
Sample 1<br/>
<span id="sample1"></span>
</td>
</tr>
<tr>
<td style="text-align: center;">
Sample 2<br/>
<span id="sample2"></span><br/>
乱数を使用しているのでページを開く度に異なる
</td>
</tr>
<tr>
<td style="text-align: center;">
Sample 3<br/>
<span id="sample3"></span><br/>
乱数を使用しているのでページを開く度に異なる
</td>
</tr>
<tr>
<td style="text-align: center;">
Sample 4<br/>
<span id="sample4"></span><br/>
乱数を使用しているのでページを開く度に異なる
</td>
</tr>
<tr>
<td style="text-align: center;">
Sample 5<br/>
<span id="sample5"></span><br/>
乱数を使用しているのでページを開く度に異なる
</td>
</tr>
<!--
-->
<tr>
<td style="text-align: center;">
Sample A1<br/>
<span id="sampleA1"></span><br/>
乱数を使用しているのでページを開く度に異なる
</td>
</tr>
</table>
<script type="text/javascript">
<!--
/* ********************************** */
/*
* GIF 形式データを生成するクラス
*/
class MyGIF {
static signature = [0x47, 0x49, 0x46];
static version87a = [0x38, 0x37, 0x61];
static version89a = [0x38, 0x39, 0x61];
// コンストラクタ.
constructor(width, height, resolution, background, global_color, aspect) {
const S = MyGIF;
this.S = S;
this.signature = S.signature;
this.version = S.version89a;
this.logicalScreen = S.createLogicalScreenDescriptor(
width, height, resolution, background, global_color, aspect);
this.descriptor = [];
};
// GIF形式のバイナリを取得する.
get_binary() {
return [
this.signature,
this.version,
this.logicalScreen.get_binary(),
this.descriptor.map((d, _) => d.get_binary()).flat(),
].flat();
};
// 任意の Descriptor を追加する.
appendDescriptor(descriptor) {
this.descriptor.push(descriptor);
};
// Image Descriptor を追加する.
appendImageDescriptor(x, y, w, h, data, color, interlace) {
this.descriptor.push(this.S.createImageDescriptor(x, y, w, h, data, color, interlace));
};
// Graphic Control Extension を追加する.
appendGraphicControlExtension(delay, method, transparent, input) {
this.descriptor.push(this.S.createGraphicControlExtension(delay, method, transparent, input));
};
// Comment Extension を追加する.
appendCommentExtension(text) {
this.descriptor.push(this.S.createCommentExtension(text));
};
// Plain Text Extension を追加する.
appendPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) {
this.descriptor.push(this.S.createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text));
};
// Application Extension を追加する.
appendApplicationExtension(id, code, data) {
this.descriptor.push(this.S.createApplicationExtension(id, code, data));
};
// アニメーションのループ回数を設定する.
appendApplicationExtensionForLoop(count) {
this.appendApplicationExtension('NETSCAPE', '2.0', [1, (count & 0xff), ((count >> 8) & 0xff)]);
};
// Trailer を追加する.
appendTrailer() {
this.descriptor.push(this.S.createTrailer());
};
// 8 ビット形式の取得.
static uint8el(x) {
return ((!x ? 0 : x) & 0xff);
};
// 16 ビット・リトル・エンディアン形式の取得.
static uint16el(x) {
const y = (!x ? 0 : x);
return [(y & 0xff), ((y >> 8) & 0xff)];
};
// 色情報の過不足を調整.
static fixColorTable(depth, color) {
const colen = (1 << (depth + 1)) * 3;
let table = color.flat();
return ((table.length > colen) ? table.slice(0, colen) :
table.concat(Array(colen - table.length).fill(0)));
};
// 文字の UTF-8 バイナリ化.
static charToUTF8(c) {
if (c < 0x0080) return c;
if (c < 0x0800) return [
(0xc0 | (c >> 6)),
(0x80 | (c & 0x3f)),
];
return [
(0xe0 | (c >> 12)),
(0x80 | ((c >> 6) & 0x3f)),
(0x80 | (c & 0x3f)),
];
};
static stringToUTF8(s) {
return [...Array(s.length)].map((_, i) => MyGIF.charToUTF8(s.charCodeAt(i))).flat();
};
// Data Sub-blocks の生成.
static createDataSubBlocks(data) {
let block = [];
let pos = 0;
let length = data.length;
while (length) {
let slen = (length < 256) ? length : 255;
block.push(slen);
block.push(data.slice(pos, pos + slen));
pos += slen;
length -= slen;
};
block.push(0);
return block.flat();
};
// Logical Screen Descriptor の生成.
static createLogicalScreenDescriptor(width, height, resolution, background, color, sort, aspect) {
const S = MyGIF;
const D = {
logical_screen_width: width,
logical_screen_height: height,
size_of_global_color_table: 0,
sort_flag: sort,
color_resolution: resolution,
global_color_table_flag: false,
background_color_index: background,
pixel_aspect_ratio: ((aspect == undefined) ? 49 : aspect),
global_color_table: undefined,
set_color: (function(table) {
const flag = (table != undefined);
D.size_of_global_color_table =
(!flag ? 0 : (31 - Math.clz32(table.length-1)));
D.global_color_table_flag = flag;
D.global_color_table = table;
}),
get_binary: (function() {
const sgct = (!D.size_of_global_color_table ? 0 : (7 & D.size_of_global_color_table));
const sort = (!D.sort_flag ? 0 : 1);
const res = (!D.color_resolution ? 0 : (7 & D.color_resolution));
const gcf = (!D.global_color_table_flag ? 0 : 1);
const bgc = (!D.background_color_index ? 0 : D.background_color_index);
const aspect = ((D.pixel_aspect_ratio == undefined) ? 49 : D.pixel_aspect_ratio);
const color = (!gcf ? [] : S.fixColorTable(sgct, D.global_color_table));
return [
S.uint16el(D.logical_screen_width),
S.uint16el(D.logical_screen_height),
(sgct | (sort << 3) | (res << 4) | (gcf << 7)),
S.uint8el(bgc),
S.uint8el(aspect),
color.map((v, _) => S.uint8el(v)),
].flat();
}),
};
D.set_color(color);
return D;
};
// Image Descriptor の生成.
static createImageDescriptor(x, y, w, h, data, color, interlace, sort) {
const S = MyGIF;
const D = {
descriptor: 0x2c,
image_left_position: x,
image_top_position: y,
image_width: w,
image_height: h,
size_of_local_color_table: 0,
sort_flag: sort,
interlace_flag: interlace,
local_color_table_flag: false,
local_color_table: undefined,
table_based_image_data: S.createTableBasedImageData(data),
set_color_table: (function(table) {
const flag = (table != undefined);
D.size_of_local_color_table =
(!flag ? 0 : (31 - Math.clz32(table.length-1)));
D.local_color_table_flag = flag;
D.local_color_table = table;
}),
get_binary: (function() {
const slct = (!D.size_of_local_color_table ? 0 : (7 & D.size_of_local_color_table));
const sort = (!D.sort_flag ? 0 : 1);
const interlace = (!D.interlace ? 0 : 1);
const lcf = (!D.local_color_table_flag ? 0 : 1);
const color = (!lcf ? [] : S.fixColorTable(slct, D.local_color_table));
return [
D.descriptor,
S.uint16el(D.image_left_position),
S.uint16el(D.image_top_position),
S.uint16el(D.image_width),
S.uint16el(D.image_height),
(slct | (sort << 5) | (interlace << 6) | (lcf << 7)),
color.map((v, _) => S.uint8el(v)),
D.table_based_image_data.lzw_minimum_code_size,
D.table_based_image_data.data,
].flat();
}),
};
D.set_color_table(color);
return D;
};
// Table Based Image の生成 (LZW圧縮)
static createTableBasedImageData(source) {
const S = MyGIF;
const source_size = source.length;
// const src_max = Math.max.apply(null, source); // 'Maximum call stack size exceeded' がでる.
const src_max = function() { let m = 0; for (const d of source) if (m < d) m = d; return m; }();
const src_max_clz = (32 - Math.clz32(src_max));
const bits_init = ((src_max_clz < 2) ? 2 : src_max_clz);
let bits_curr = (bits_init + 1);
const code_base = (1 << bits_init);
const code_clear = code_base;
const code_end = code_base + 1;
const code_max = ((1 << 12) - 1);
let code_curr = code_end;
let code_step = (1 << bits_curr);
const tree = [...Array(code_max + 2)].map((_, i) => ({ code: i, next: -1, down: -1, data: 0 }));
const buffer = [];
let bs_pos = 0;
const write = function(data) {
const bits = bits_curr;
let idxs = (bs_pos >> 3);
const idxe = ((bs_pos + bits - 1) >> 3);
data <<= (bs_pos & 7);
if (idxs < buffer.length) {
buffer[idxs] |= (data & 0xff);
data >>= 8;
idxs++;
};
while (idxs <= idxe) {
buffer.push(data & 0xff);
data >>= 8;
idxs++;
};
bs_pos += bits;
};
write(code_clear);
if (!source_size) {
write(code_end);
return {
lzw_minimum_code_size: bits_init,
data: S.createDataSubBlocks(buffer),
};
};
let siter = source.values();
let sdat = siter.next();
lzw: for (;;) {
let data, next;
let node = tree[(data = sdat.value)];
scan: for (;;) {
if ((sdat = siter.next()).done) {
write(node.code);
break lzw;
};
data = sdat.value;
if ((next = tree[node.down]) == undefined)
break;
while (data != next.data)
if ((next = tree[next.next]) == undefined)
break scan;
node = next;
};
write(node.code);
{
const next = tree[++code_curr];
// next.code = code_curr;
next.next = node.down;
next.down = -1;
next.data = data;
node.down = code_curr;
}
if (code_curr < code_step)
continue;
if (code_curr < code_max) {
bits_curr++;
if ((code_step <<= 1) < code_max)
continue;
code_step = code_max;
continue;
};
write(code_clear);
bits_curr = bits_init + 1;
code_step = (1 << bits_curr);
code_curr = code_end;
for (let i = 0; i < code_base; i++) {
const node = tree[i];
// node.code = i;
node.next = -1;
node.down = -1;
// node.data = 0;
};
};
write(code_end);
return {
lzw_minimum_code_size: bits_init,
data: S.createDataSubBlocks(buffer),
};
};
// Graphic Control Extension の生成.
static createGraphicControlExtension(delay, method, transparent, input) {
const S = MyGIF;
const D = {
descriptor: 0x21,
label: 0xf9,
disposal_method: method,
user_input_flag: input,
transparent_color_flag: false,
delay_time: delay,
transparent_color_index: 0,
set_transparent: (function(index) {
D.transparent_color_flag = (index != undefined);
D.transparent_color_index = (!index ? 0 : index);
}),
get_binary: (function() {
const method = (!D.disposal_method ? 0 : (7 & D.disposal_method));
const input = (!D.user_input_flag ? 0 : 1);
const ftrans = (!D.transparent_color_flag ? 0 : 1);
return [
D.descriptor,
D.label,
4, /* Data Sub-blocks */
((method << 3) | (input << 6) | (ftrans << 7)),
S.uint16el(D.delay_time),
S.uint8el(D.transparent_color_index),
0, /* Data Sub-blocks */
].flat();
}),
};
D.set_transparent(transparent);
return D;
};
// Comment Extension の生成.
static createCommentExtension(text) {
const S = MyGIF;
const D = {
descriptor: 0x21,
label: 0xfe,
text: text,
get_binary: (() => [
D.descriptor,
D.label,
S.createDataSubBlocks(S.stringToUTF8(D.tex)),
].flat()),
};
return D;
};
// Plain Text Extension の生成.
static createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) {
const S = MyGIF;
const D = {
descriptor: 0x21,
label: 0x01,
text_grid_left_position: x,
text_grid_top_position: y,
text_grid_width: w,
text_grid_height: h,
character_cell_width: cw,
character_cell_height: ch,
text_foreground_color_index: fc,
text_background_color_index: bc,
plain_text_data: text,
get_binary: (() => [
D.descriptor,
D.label,
12, /* Data Sub-blocks */
S.uint16el(D.text_grid_left_position),
S.uint16el(D.text_grid_top_position),
S.uint16el(D.text_grid_width),
S.uint16el(D.text_grid_height),
S.uint8el(D.character_cell_width),
S.uint8el(D.character_cell_height),
S.uint8el(D.text_foreground_color_index),
S.uint8el(D.text_background_color_index),
S.createDataSubBlocks(S.stringToUTF8(D.plain_text_data)),
].flat()),
};
return D;
};
// Application Extension の生成.
static createApplicationExtension(id, code, data) {
const S = MyGIF;
const D = {
descriptor: 0x21,
label: 0xff,
application_identifier: id,
application_authentication_code: code,
application_data: data,
get_binary: (() => [
D.descriptor,
D.label,
11, /* Data Sub-blocks */
S.stringToUTF8(D.application_identifier + ' ').slice(0, 8),
S.stringToUTF8(D.application_authentication_code + ' ').slice(0, 3),
S.createDataSubBlocks(D.application_data.map((v, _) => S.uint8el(v))),
].flat()),
};
return D;
};
// Trailer の生成.
static createTrailer() {
return {
descriptor: 0x3b,
get_binary: (() => 0x3b),
};
};
};
/* **************************************** */
/*
* Base 64 符号化
*/
class MyBase64 {
static EncodeTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
static encode(b) {
const table = MyBase64.EncodeTable;
const blen = b.length;
const brem = blen % 3;
const bcnt = blen - brem;
let s = '';
let i = 0;
while (i < bcnt) {
const d0 = b[i++];
const d1 = b[i++];
const d2 = b[i++];
const d = (d0 << 16) | (d1 << 8) | d2;
s += table[(d >> 18) & 0x3f];
s += table[(d >> 12) & 0x3f];
s += table[(d >> 6) & 0x3f];
s += table[d & 0x3f];
};
if (brem) {
const b2 = (brem == 2);
const d0 = b[i++];
const d1 = (b2 ? b[i++] : 0);
const d = (d0 << 16) | (d1 << 8);
s += table[(d >> 18) & 0x3f];
s += table[(d >> 12) & 0x3f];
s += (b2 ? table[(d >> 6) & 0x3f] : '=');
s += '=';
};
return s;
};
};
/* ********************************** */
/*
* GIF 画像生成用
*/
class MyPixelBuffer {
static grayScaleColor = function(count) {
const m = 255;
const n = count - 1;
return [...Array(count)].map(function(_, k) {
const l = Math.trunc((k * m) / n);
return [l, l, l];
});
};
static grayScaleColor4 = MyPixelBuffer.grayScaleColor(4);
static grayScaleColor256 = MyPixelBuffer.grayScaleColor(256);
static simpleColorBase = [...Array(8)].map((_, i) => Math.trunc(i * 255 / 7));
static simpleColor = [...Array(256)].map((_, i) => [
MyPixelBuffer.simpleColorBase[(i >> 5) & 7],
MyPixelBuffer.simpleColorBase[(i >> 2) & 7],
[0, 85, 170, 255][i & 3],
]);
static interlaceMap = new Map();
static createInterlaceTable(h) {
const m = MyPixelBuffer.interlaceMap;
if (m.has(h))
return m.get(h);
const ph = [
[0, 8, ((h + 7) >> 3)],
[4, 8, ((h + 3) >> 3)],
[2, 4, ((h + 1) >> 2)],
[1, 2, (h >> 1)],
];
const tab = [...Array(4)].map(
(_, i) => [...Array(ph[i][2])].map(
(_, j) => ph[i][0] + ph[i][1] * j)).flat();
m.set(h, tab);
return tab;
};
// コンストラクタ.
constructor(color, width, height, defcol=0) {
this.S = MyPixelBuffer;
this.GIF = MyGIF;
this.color = color;
this.width = width;
this.height = height;
this.pixel = Array(width * height).fill(defcol);
};
// 点を打つ.
setPoint(x, y, c) {
const w = this.width;
if ((0 <= x) && (x < w) &&
(0 <= y) && (y < this.height)) {
const p = y * w + x;
if ((p < 0) || (this.pixel.length <= p)) {
console.log(p);
};
this.pixel[p] = c;
}
};
// 線を引く.
setLine(x1, y1, x2, y2, c, sep) {
const dx = Math.trunc(Math.abs(x1 - x2));
const dy = Math.trunc(Math.abs(y1 - y2));
if (!dx)
this.setVirticalLine(x1, y1, y2, c);
else if (!dy)
this.setHorizontalLine(x1, x2, y1, c);
else if (dx >= dy)
this.setLineH(x1, y1, x2, y2, dx, dy, c, sep);
else
this.setLineV(x1, y1, x2, y2, dx, dy, c, sep);
};
setLineH(x1, y1, x2, y2, dx, dy, c, sep) {
const S = this.S;
const w = this.width;
const h = this.height;
let xs, ys, xe, ye;
if (x1 < x2) {
xs = x1; ys = y1;
xe = x2; ye = y2;
} else {
xs = x2; ys = y2;
xe = x1; ye = y1;
};
if ((xe < 0) || (w <= xs))
return;
const ay = ((ys < ye) ? +1 : -1);
const ap = (ay < 0) ? -w: +w;
let k = (dy >> 1);
dx++;
if (sep) {
dy++;
k = 0;
};
if (xs < 0) {
const kt = k - (dy * xs);
const yh = Math.trunc(kt / dx);
xs = 0;
ys += ((ay < 0) ? -yh : +yh);
k = (kt % dx);
};
if (w <= xe)
xe = w - 1;
const yt = Math.min(ys, ye);
const yb = Math.max(ys, ye);
if ((yb < 0) || (h <= yt))
return;
if (ay < 0) {
if (ye < 0)
ye = 0;
if (h <= ys) {
const yh = ys - h + 1;
const kt = k + (dx * yh);
const xw = Math.trunc(kt / dy);
xs += xw;
ys = h - 1;
k = kt % dy;
};
} else {
if (h <= ye)
ye = h - 1;
if (ys < 0) {
const kt = k - (dx * ys);
const xw = Math.trunc(kt / dy);
xs += xw;
ys = 0;
k = kt % dy;
};
};
ye += ay;
const pixel = this.pixel;
let p = ys * w + xs;
while (xs++ <= xe) {
pixel[p++] = c;
if ((k += dy) >= dx) {
k -= dx;
p += ap;
if ((ys += ay) == ye)
break;
};
};
};
setLineV(x1, y1, x2, y2, dx, dy, c, sep) {
const S = this.S;
const w = this.width;
const h = this.height;
let xs, ys, xe, ye;
if (y1 < y2) {
xs = x1; ys = y1;
xe = x2; ye = y2;
} else {
xs = x2; ys = y2;
xe = x1; ye = y1;
};
if ((ye < 0) || (h <= ys))
return;
const ax = ((xs < xe) ? +1 : -1);
let k = (dy >> 1);
dy++;
if (sep) {
dx++;
k = 0;
};
if (ys < 0) {
const kt = k - (dx * ys);
const xw = Math.trunc(kt / dy);
xs += ((ax < 0) ? -xw : +xw);
ys = 0;
k = (kt % dy);
};
if (h <= ye)
ye = h - 1;
const xl = Math.min(xs, xe);
const xr = Math.max(xs, xe);
if ((xr < 0) || (w <= xl))
return;
if (ax < 0) {
if (xe < 0)
xe = 0;
if (w <= xs) {
const xw = xs - w + 1;
const kt = k + (dy * xw);
const yh = Math.trunc(kt / dx);
xs = w - 1;
ys += yh;
k = kt % dx;
};
} else {
if (w <= xe)
xe = w - 1;
if (xs < 0) {
const kt = k - (dy * xs);
const yh = Math.trunc(kt / dx);
xs = 0;
ys += yh;
k = kt % dx;
};
};
xe += ax;
const pixel = this.pixel;
let p = ys * w + xs;
while (ys++ <= ye) {
pixel[p] = c; p += w;
if ((k += dx) >= dy) {
k -= dy;
p += ax;
if ((xs += ax) == xe)
break;
};
};
};
// 水平線を引く.
setHorizontalLine(x1, x2, y, c) {
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const xs = Math.trunc(Math.min(x1, x2));
const xe = Math.trunc(Math.max(x1, x2));
if ((xe < 0) || (w <= xs) || (y < 0) || (h <= y))
return;
const us = Math.trunc(Math.max(xs, 0));
const ue = Math.trunc(Math.min(xe, w-1));
const yp = y * w;
pixel.fill(c, yp + us, yp + ue + 1);
}
// 垂直線を引く.
setVirticalLine(x, y1, y2, c) {
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const ys = Math.trunc(Math.min(y1, y2));
const ye = Math.trunc(Math.max(y1, y2));
if ((x < 0) || (w <= x) || (ye < 0) || (h <= ys))
return;
const vs = Math.trunc(Math.max(ys, 0));
const ve = Math.trunc(Math.min(ye, h-1));
const q = ve * w + x;
for (let p = vs * w + x; p <= q; p += w)
pixel[p] = c;
}
// 四角形を描く.
setBox(x1, y1, x2, y2, c) {
const xs = Math.trunc(Math.min(x1, x2));
const xe = Math.trunc(Math.max(x1, x2));
const ys = Math.trunc(Math.min(y1, y2));
const ye = Math.trunc(Math.max(y1, y2));
this.setHorizontalLine(xs, xe, ys, c);
if (ys != ye) {
this.setVirticalLine(xs, ys+1, ye-1, c);
if (xs != xe)
this.setVirticalLine(xe, ys+1, ye-1, c);
this.setHorizontalLine(xs, xe, ye, c);
};
};
// 四角形で塗りつぶす.
setBoxFill(x1, y1, x2, y2, c) {
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const xs = Math.min(x1, x2);
const xe = Math.max(x1, x2);
const ys = Math.min(y1, y2);
const ye = Math.max(y1, y2);
if ((xe < 0) || (w <= xs) ||
(ye < 0) || (h <= ys))
return;
const us = Math.trunc(Math.max(xs, 0));
const ue = Math.trunc(Math.min(xe, w-1));
const vs = Math.trunc(Math.max(ys, 0));
const ve = Math.trunc(Math.min(ye, h-1));
const nu = (ue - us) + 1;
const nv = (ve - vs) + 1;
let vp = vs * w + us;
const vq = nv * w + vp;
for (; vp < vq; vp += w)
pixel.fill(c, vp, vp + nu);
};
// 円描画用の座標情報を生成する.
static getCirclePos(radius) {
if (radius <= 0)
return [[0, 0]];
let x = radius;
let y = 0;
let s = 0;
let t = 1;
let u = (radius << 1) - 1;
let v = u;
/* 大きな円は(上下左右)端の直線を少し短くする */
u -= ((u >> 2) + (u >> 3) + (u >> 4));
let otmp = new Array();
const out1 = new Array();
const out2 = new Array();
while (x > y) {
otmp.push(y);
out2.push([x]);
y = y + 1;
s = s + t;
t = t + 2;
if (s < u)
continue;
out1.push(otmp);
x = x - 1;
v = v - 2;
u = u + v;
otmp = new Array();
};
if (x == y)
out2.push([x]);
if (otmp.length)
out1.push(otmp);
out1.reverse();
return out2.concat(out1);
};
// 円を描く.
setCircle(x, y, r, c) {
const w = this.width;
const h = this.height;
if (((x + r + 1) < 0) || (w < (x - r)) ||
((y + r + 1) < 0) || (h < (y - r)))
return;
const pixel = this.pixel;
const ypos = this.S.getCirclePos(r);
const ylen = ypos.length;
{
let yi = 0;
let ys = y;
if (ys < 0) {
yi = -ys;
ys = 0;
};
let yp = ys * w;
while (yi < ylen) {
if (ys >= h)
break;
for (const xp of ypos[yi]) {
const xl = x - xp;
const xr = x + xp;
if (w <= xl) continue;
if (xr < 0) continue;
if (xr < w) pixel[yp + xr] = c;
if (0 <= xl) pixel[yp + xl] = c;
};
yi++;
ys++;
yp += w;
};
};
{
let yi = 0;
let ys = y;
if (ys >= h) {
yi += (ys - h + 1);
ys = h - 1;
};
let yp = ys * w;
while (yi < ylen) {
if (ys < 0)
break;
for (const xp of ypos[yi]) {
const xl = x - xp;
const xr = x + xp;
if (w <= xl) continue;
if (xr < 0) continue;
if (xr < w) pixel[yp + xr] = c;
if (0 <= xl) pixel[yp + xl] = c;
};
ys--;
yi++;
yp -= w;
};
};
};
// 円で塗りつぶす.
setCircleFill(x, y, r, c) {
const w = this.width;
const h = this.height;
if (((x + r + 1) < 0) || (w < (x - r)) ||
((y + r + 1) < 0) || (h < (y - r)))
return;
const pixel = this.pixel;
const ypos = this.S.getCirclePos(r);
const ylen = ypos.length;
{
let yi = 0;
let ys = y;
if (ys < 0) {
yi = -ys;
ys = 0;
};
let yp = ys * w;
while (yi < ylen) {
if (ys >= h)
break;
const yl = ypos[yi];
const xp = yl[yl.length - 1];
let xl = x - xp;
let xr = x + xp;
if ((0 <= xr) && (xl < w)) {
if (xl < 0) xl = 0;
if (xr >= w) xr = w - 1;
pixel.fill(c, yp + xl, yp + xr + 1);
}
yi++;
ys++;
yp += w;
};
};
{
let yi = 0;
let ys = y;
if (ys >= h) {
yi += (ys - h + 1);
ys = h - 1;
};
let yp = ys * w;
while (yi < ylen) {
if (ys < 0)
break;
const yl = ypos[yi];
const xp = yl[yl.length - 1];
let xl = x - xp;
let xr = x + xp;
if ((0 <= xr) && (xl < w)) {
if (xl < 0) xl = 0;
if (xr >= w) xr = w - 1;
pixel.fill(c, yp + xl, yp + xr + 1);
}
ys--;
yi++;
yp -= w;
};
};
};
// 凸多角形を描画する.
setPolygon(vertices, c) {
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const vlen = vertices.length;
if (!vlen) return;
let xmin = w, xmax = -1;
let ymin = h, ymax = -1;
for (const [x, y] of vertices) {
xmin = Math.min(xmin, x);
xmax = Math.max(xmax, x);
ymin = Math.min(ymin, y);
ymax = Math.max(ymax, y);
};
xmin = Math.trunc(xmin);
xmax = Math.trunc(xmax);
ymin = Math.trunc(ymin);
ymax = Math.trunc(ymax);
if ((xmax < 0) || (w <= xmin) ||
(ymax < 0) || (h <= ymin))
return;
const raster = [...Array(h)].map(() => []);
let [nx, ny] = vertices[vlen - 1];
for (const np of vertices) {
const lx = nx;
const ly = ny;
[nx, ny] = np;
const ay = (ny < ly) ? -1 : +1;
const dx = nx - lx;
const dy = (ay < 0) ? (ly - ny) : (ny - ly);
if (dy == 0) {
// if ((0 <= ly) && (ly < h))
// raster[ly].push(lx, nx);
continue;
};
let ys = ly;
let ye = ny;
let yp = 0;
if (ay >= 0) {
if ((ye < 0) || (h <= ys))
continue;
if (ys < 0) {
yp -= ys;
ys = 0;
};
if (ye >= h)
ye = h - 1;
} else {
if ((ys < 0) || (h <= ye))
continue;
if (ys >= h) {
yp += ys - h + 1;
ys = h - 1;
};
if (ye < 0)
ye = -1;
};
for (; ys != ye; ys += ay, yp++)
raster[ys].push(lx + Math.trunc(dx * yp / dy));
};
const rcmp = ((a, b) => (a - b));
let rs = Math.trunc(Math.max(ymin, 0));
let re = Math.trunc(Math.min(ymax, h - 1));
for (let yp = rs * w; rs <= re; yp += w) {
const rp = raster[rs++];
const rplen = rp.length;
if (rplen == 1) continue;
rp.sort(rcmp);
for (let np = 0; np < rplen; np += 2) {
let xl = rp[np];
let xr = rp[np + 1];
if (xl > xr)
[xl, xr] = [xr, xl];
if ((xr < 0) || (w <= xl))
continue;
if (xl < 0) xl = 0;
if (xr >= w) xr = w - 1;
pixel.fill(c, yp + xl, yp + xr + 1);
}
};
};
// インターレース画像を作成する.
createInterlaced() {
const tab = this.S.createInterlaceTable(h);
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const line = [...Array(h)].map(
function(_, y) { const p = y * w; return pixel.slice(p, p + w); });
return [...Array(h)].map((_, y) => line[tab[y]]).flat();
};
// GIF 処理用オブジェクトを作成する.
createGIF(global_color) {
const color = (global_color ? this.color : undefined);
return new this.GIF(this.width, this.height, (8 - 1), 0, color);
};
// Image Descriptor を作成する.
createImageDescriptor(local_color, x, y, interlace) {
const color = (local_color ? this.color : undefined);
const w = this.width;
const h = this.height;
const pixel = this.pixel;
const data = (!interlace ? pixel : this.createInterlaced());
return this.GIF.createImageDescriptor(x, y, w, h, data, color, interlace);
};
};
/* ********************************** */
/*
* テスト
*/
function createImgWithBase64(b64s, id) {
const priority = 'important';
const img = document.createElement('img');
if (id != undefined)
img.setAttribute('id', id);
img.setAttribute('class', 'b64img');
img.setAttribute('src', 'data:image/gif;charset=utf-8;base64,' + b64s);
img.style.setProperty('margin', '0', priority);
img.style.setProperty('padding', '8px', priority);
img.style.setProperty('image-rendering', 'pixelated', priority);
return img;
}
function intRand(w) { return Math.trunc(Math.random() * w); }
function makeSampleGIF(fb) {
const gif = fb.createGIF(true);
gif.appendDescriptor(fb.createImageDescriptor());
gif.appendTrailer();
return gif;
}
function makeSample1FB() {
const fb = new MyPixelBuffer(MyPixelBuffer.grayScaleColor256, 640, 480);
for (let p = 0; p < 256; p++) fb.setBox(p, p, 639-p, 479-p, p);
return fb;
}
function makeSample1() { return makeSampleGIF(makeSample1FB()); }
function makeSample2FB() {
const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480);
for (let n = 0; n < 20; n++) {
const x1 = intRand(640 + 320) - 160;
const y1 = intRand(480 + 240) - 120;
const w = intRand(320) + 16;
const h = intRand(240) + 16;
const c = intRand(256);
const x2 = x1 + w;
const y2 = y1 + h;
fb.setLine(x1, y1, x2, y2, c);
fb.setLine(x2, y1, x1, y2, c);
fb.setBox(x1, y1, x2, y2, c);
fb.setCircle(y1, x1, Math.min(x2, y2), c);
}
return fb;
}
function makeSample2() { return makeSampleGIF(makeSample2FB()); }
function makeSample3FB() {
const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480);
for (let n = 0; n < 100; n++) {
const x = intRand(640+32) - 16;
const y = intRand(480+32) - 16;
const w = intRand(160);
const h = intRand(120);
const c = intRand(256);
fb.setBoxFill(x, y, x+w, y+h, c);
}
return fb;
}
function makeSample3() { return makeSampleGIF(makeSample3FB()); }
function makeSample4FB() {
const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480);
for (let n = 0; n < 100; n++) {
const x = intRand(640 + 320) - 160;
const y = intRand(480 + 240) - 120;
const r = intRand(80);
const c = intRand(256);
fb.setCircleFill(x, y, r, c);
}
return fb;
}
function makeSample4() { return makeSampleGIF(makeSample4FB()); }
function makeSample5FB() {
const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480);
for (let n1 = 0; n1 < 100; n1++) {
const np = intRand(5) + 3;
let x = intRand(640 + 320) - 160;
let y = intRand(480 + 240) - 120;
const c = intRand(256);
const pl = new Array([x, y]);
let da = intRand(360);
let ra = 300;
for (let n2 = 1; n2 < np; n2++) {
const dr = da * Math.PI / 180;
const dl = intRand(120) + 10;
const dx = Math.trunc(dl * Math.cos(dr));
const dy = Math.trunc(dl * Math.sin(dr));
pl.push([(x += dx), (y += dy)]);
const ma = Math.trunc(ra / (np - n2)) >> 1;
const na = intRand(ma) + ma;
da += na;
ra -= na;
}
fb.setPolygon(pl, c);
}
fb.setPolygon([[10, 10], [100, 10], [10, 100]], 255);
// fb.setPolygon([[640-10, 480-10], [640-100, 480-10], [640-100, 480-100], [640-10, 480-100]], 0x1c);
fb.setPolygon([[640-10, 480-10], [640-100, 480-10], [640-10, 480-100]], 255);
// fb.setPolygon([[640-10, 10], [10, 150], [640-150, 480-10]], 0x03);
return fb;
}
function makeSample5() { return makeSampleGIF(makeSample5FB()); }
function makeSampleA() {
const fb1 = makeSample1FB();
const fb2 = makeSample2FB();
const fb3 = makeSample3FB();
const fb4 = makeSample4FB();
const fb5 = makeSample5FB();
const gif = fb2.createGIF();
gif.appendApplicationExtensionForLoop(0);
gif.appendGraphicControlExtension(30);
gif.appendDescriptor(fb1.createImageDescriptor(true));
gif.appendGraphicControlExtension(30);
gif.appendDescriptor(fb2.createImageDescriptor(true));
gif.appendGraphicControlExtension(30);
gif.appendDescriptor(fb3.createImageDescriptor(true));
gif.appendGraphicControlExtension(30);
gif.appendDescriptor(fb4.createImageDescriptor(true));
gif.appendGraphicControlExtension(30);
gif.appendDescriptor(fb5.createImageDescriptor(true));
gif.appendTrailer();
return gif;
}
function setSample(id, gif) {
const tag = document.getElementById(id);
if (tag == undefined) return;
const img = createImgWithBase64(MyBase64.encode(gif().get_binary()));
while (tag.firstChild)
tag.removeChild(giftag.firstChild);
tag.append(img);
}
function onLoad() {
setSample('sample1', makeSample1);
setSample('sample2', makeSample2);
setSample('sample3', makeSample3);
setSample('sample4', makeSample4);
setSample('sample5', makeSample5);
setSample('sampleA1', makeSampleA);
}
onLoad();
/* ********************************** */
// !-->
</script>
</body>
</html>