10x10pxのgifのjavaでの作成(LZW圧縮付き)の覚書

 
tiutiu.net/ プログラム/language/java/gif02.html
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/


Google