10x10pxのgifのjavaでの作成(LZW圧縮付き)の覚書
2007/9/14
概要
10x10pxの赤gifファイルを作成します。 LZWでの圧縮, イメージデータのパックを 関数で行います。
ソース
ソースコードのファイル名はmkgif.javaです。
// mkgif // 10x10のgifを作成 // o LZW圧縮関数, pack関数を使用 // x 透過色の設定ができていない。 // x 255byte以上になるimage dataに対応していない。 // x 複数のimage dataに対応していない。 // x image dataが複数になる場合の画像の分割方法が未決定 // x エラー処理がまったくない。 // x 縦幅, 横幅, イメージデータの設定が柔軟でない。 import java.io.*; class mkgif { public static void main(String[] args) { gif gif = new gif(); gif.create(); } // /main } // mkgif // gif class gif { // 固定 // header static byte[] signature = {0x47, 0x49, 0x46}; // GIF static byte[] version = {0x38, 0x39, 0x61}; // 89a // image descriptor static byte[] image_separator = {0x2c}; static byte[] image_pack; // table based image data static byte[] field_lzw_code_size = {0x00}; // 0x00は仮 static byte[] field_block_size = {0x00}; static byte[] field_data_values; // block terminator static byte[] block_terminator = {0x00}; // trailer static byte[] trailer = {0x3b}; // 固定 // /固定 // コンストラクタ public gif() { // 何もすることはない。 } // 横幅の設定 // 縦幅の設定 // gifの作成 public void create() { try { // オリジナルデータ int[] image_index_list = new int[10*10]; for(int i=0;i<100;i++) { image_index_list[i] = 0x00; } // logical screen descriptor byte[] screen_width = {0x0a, 0x00}; // 横幅=4px, リトルエンディアン byte[] screen_height = {0x0a, 0x00}; // 縦幅=1px, リトルエンディアン byte[] screen_pack = new byte[1]; int gct_flag = 0x80; // gct=global color table int color_resolution = 0x00; // ここには色の指定(global color tableへのインデックス)に // 必要なbit数-1を設定する。 // 赤だけなので1bitあればいいから0を設定 int sort_flag = 0x00; // global table is not sorted. int size_of_gct = 0x00; screen_pack[0] = (byte)(gct_flag | color_resolution | sort_flag | size_of_gct); byte[] background_color_index = {0x01}; byte[] pixel_aspect_ratio = {0x00}; // 縦横比は設定しない。 // global color table byte[] global_color_table = { -0x01, 0x00, 0x00, // RGB順, -0x01=0xff(ややこしい!) 0x00, 0x00, 0x00 }; // image descriptor byte[] image_left_pos = {0x00, 0x00}; byte[] image_top_pos = {0x00, 0x00}; byte[] image_width = {0x0a, 0x00}; byte[] image_height = {0x0a, 0x00}; image_pack = new byte[1]; // int local_color_table_flag = 0; // int interlace_flag = 0; // int sort_flag = 0; // int reserverd = 0; // int size_of_lct = 0; image_pack[0] = 0x00; // table based image data // color_resolution+1と2で大きいほう field_lzw_code_size[0] = (byte)(color_resolution+1); if(field_lzw_code_size[0]<2) { field_lzw_code_size[0] = 2; } // 圧縮データの作成 make_image_data(image_index_list); // ファイルへの書き込み output(screen_width, screen_height, screen_pack, background_color_index, pixel_aspect_ratio, global_color_table, image_separator, image_left_pos, image_top_pos, image_width, image_height, image_pack); } catch(Exception e) {} } // /create() // 圧縮を行う。 // 伝統だとwとkとwkを使うようだが, // 現代において, 一文字はわかりにくいと // 思ったので, plain_code, raw[i], code_candidate // などを使わせてもらいました。 // よけいわかりにくくなった? // // パック時のlzw_code_sizeの関係で // パックも同時に行っていく。 // // 000 000 000 は clear_codeが100だとして // 000 110 と圧縮することができます。 // 伸長するときに失敗しそうですが, ちゃんとできます。 // 詳しくはhttp://marknelson.us/1989/10/01/lzw-data-compression/ // を参考のこと。 private void make_image_data(int[] raw) { // lzw_code_sizeを引数に持ちたくない。 // 値を返すのも大変。javaではプリミティブ型は // 参照できないから。 // lzwをgifから継承すればいいか? // 圧縮関連 int clear_code = 1<<field_lzw_code_size[0]; int[] compressdata = new int[256]; int data_length = 0; int compress_code; int[][] compress_table = new int[256][]; int compress_table_length = 0; int[] plain_code = new int[256]; int plain_code_length = 0; int code_candidate; int t; // image data関連 image_data obj_image_data; // 初期化コードのセット obj_image_data = new image_data((int)field_lzw_code_size[0]+1); obj_image_data.pack(clear_code); // 全ての元データについて圧縮開始。 code_candidate = raw[0]; plain_code[plain_code_length++] = raw[0]; for(int i=1;i<raw.length;i++) { plain_code[plain_code_length++] = raw[i]; // テーブルを検索 - あぁ, 別関数にしたい! compress_code = -1; if(plain_code_length>1) { for(int j=0;j<compress_table_length;j++) { if(compress_table[j].length!=plain_code_length) { continue; } compress_code = j; for(int k=0;k<plain_code_length;k++) { if(plain_code[k] != compress_table[j][k]) { compress_code = -1; break; } } if(compress_code>=0) { // 見つかった。 compress_code += clear_code+2; // +clear_code_org+2はgifの仕様 // もっと長いのが見つかるかもしれないので // 候補に入れる。 break; } } } if(compress_code>=0) { // 圧縮テーブルからコードが見つかれば code_candidate = compress_code; continue; } // 圧縮テーブルから圧縮コードが見つからなければ // 出力 = image_dataに追加 obj_image_data.pack(code_candidate); compressdata[data_length++] = code_candidate; // 圧縮コードに登録 if(plain_code_length>1) { compress_table[compress_table_length] = new int[plain_code_length]; for(int k=0;k<plain_code_length;k++) { compress_table[compress_table_length][k] = plain_code[k]; } compress_table_length++; t = compress_table_length+clear_code+1; if(t>=(1<<obj_image_data.code_size())) { // 圧縮コードが表現できるビット数を超えたら // コードサイズをインクリメント obj_image_data.code_size_inc(); } } // 平文コードを更新 plain_code[0] = raw[i]; plain_code_length=1; code_candidate = raw[i]; } obj_image_data.pack(code_candidate); obj_image_data.pack(clear_code+1); field_data_values = obj_image_data.data_values(); field_block_size[0] = (byte)field_data_values.length; } // make_image_data // ファイルへの書き込み private void output(byte[] screen_width, byte[] screen_height, byte[] screen_pack, byte[] background_color_index, byte[] pixel_aspect_ratio, byte[] global_color_table, byte[] image_separator, byte[] image_left_pos, byte[] image_top_pos, byte[] image_width, byte[] image_height, byte[] image_pack) { try { // 準備 File fp = new File("red.gif"); fp.createNewFile(); // 存在していた場合がないが... FileOutputStream fs = new FileOutputStream(fp); // 書き込んで行きましょう。 fs.write(signature); fs.write(version); fs.write(screen_width); fs.write(screen_height); fs.write(screen_pack); fs.write(background_color_index); fs.write(pixel_aspect_ratio); fs.write(global_color_table); // image descriptor fs.write(image_separator); fs.write(image_left_pos); fs.write(image_top_pos); fs.write(image_width); fs.write(image_height); fs.write(image_pack); fs.write(field_lzw_code_size); fs.write(field_block_size); fs.write(field_data_values); fs.write(block_terminator); fs.write(trailer); // 書き込み終了 fs.close(); } catch(Exception e) {} } // output } // class gif // image_dataを作成(パック)するためのクラス // * うまくできていないクラスだな... // 継承すればcode_sizeまわりはきれいになるか? class image_data { private byte[] v_data_values = new byte[256]; private int data_values_length = 0; private int v_code_size; private int byte_index = 0; private int packing = 0; // コンストラクタ public image_data(int in_code_size) { v_code_size = in_code_size; }; // codeを追加してパック public void pack(int code) { int tmp = code<<byte_index; packing += tmp; // 無駄も多いが, 取りこぼしも許されない。 v_data_values[data_values_length] = (byte)packing; byte_index += v_code_size; while(byte_index>=8) { v_data_values[data_values_length++] = (byte)packing; packing = packing>>8; byte_index -= 8; // 無駄も多いが, 取りこぼしも許されない。 v_data_values[data_values_length] = (byte)packing; } } public int code_size() { return v_code_size; } // code_sizeを一つ増やす。 // * 変更の都度, 設定しないといけないのが格好わるい。 public void code_size_inc() { v_code_size++; } // data_valuesを返す。 public byte[] data_values() { byte[] tmp = new byte[data_values_length]; for(int i=0;i<data_values_length;i++) { tmp[i] = v_data_values[i]; } return tmp; } } // /class image_data // /mkgif
参考
仕様書, http://www.w3.org/Graphics/GIF/spec-gif89a.txt
Mark Nelson Programming, mostly., http://marknelson.us/1989/10/01/lzw-data-compression/