fullgif.htm

戻る

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML LANG="ja">

<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html;charset=Shift_JIS">
<TITLE>フルカラー GIF 作成</TITLE>
</HEAD>

<BODY BGCOLOR="#CCFFFF">
<CENTER>
<BR>
<B>フルカラー GIF 作成</B>
<BR><BR>

<FORM>
<TABLE><TR><TD>
入力ファイル(PNG):<BR>
<INPUT TYPE=FILE ID="file" SIZE=60 onChange="in_sel()">
</TD></TR></TABLE>
<DIV STYLE="position:relative; width:100px; height:100px; background-color:white">
<IMG ID="in_img" WIDTH=1 HEIGHT=1 STYLE="position:absolute; visibility:hidden">
</DIV>
<SPAN ID="size"></SPAN>
<BR><BR>
<INPUT TYPE=BUTTON ID="cre" VALUE="作成" DISABLED onClick="create()">
<BR><BR>
<SPAN ID="info"></SPAN>
<BR>
<DIV STYLE="position:relative; width:100px; height:100px; background-color:white">
<IMG ID="out_img" WIDTH=1 HEIGHT=1 STYLE="position:absolute; visibility:hidden">
</DIV>
<TABLE><TR><TD>
<INPUT TYPE=BUTTON ID="view" VALUE="表示" DISABLED onClick="view_gif()">
<INPUT TYPE=BUTTON ID="save" VALUE="保存" DISABLED onClick="save_gif()">
</TD></TR></TABLE>
<BR><BR>
<SPAN ID="msg1"></SPAN>
<BR>
<SPAN ID="msg2"></SPAN>
<BR>
<INPUT TYPE=BUTTON ID="can" VALUE="中止" onClick="cancel = true" STYLE="visibility:hidden">
</FORM>

</CENTER>

<SCRIPT LANGUAGE="JavaScript1.5" TYPE="text/javascript">
<!--

function in_sel() {
  elem_in_img.style.visibility = "hidden";
  elem_size.textContent = "";
  elem_cre.disabled = true;
  elem_info.textContent = "";
  elem_out_img.style.visibility = "hidden";
  elem_view.disabled = true;
  elem_save.disabled = true;

  // Firefox 3 では FileUpload オブジェクトの value プロパティにパスが付かなくなった.
  // そのためファイルのパスを指定して内容を読み込むことができなくなった.
  // 代わりに,FileUpload オブジェクトに新設された files プロパティ(nsIDOMFileList)を使って
  // ファイルの内容を読み込むことができるようになったので,files プロパティが存在すれば files
  // を使い,存在しなければ以前のままの処理とするように修正した.
  // Firefox 7 では nsIDOMFile オブジェクトの getAsBinary(),getAsDataURL() 等が削除された.
  // 代わりに,Firefox 3.6 で新設された FileReader オブジェクトを使って同様のことができるので,
  // FileReader オブジェクトが使えればそれを使い,使えなければ以前のままの処理とするように修正
  // した.
  // 結果的に,現在のバージョンでは次のような処理になっている.
  // FileUpload オブジェクトの files プロパティ
  //   + 存在しない
  //   |     ↓
  //   |   FileUpload オブジェクトの value プロパティを使う
  //   |
  //   + 存在する
  //          ↓
  //        FileReader オブジェクト
  //          + 使える
  //          |     ↓
  //          |   FileReader オブジェクトを使う
  //          |
  //          + 使えない
  //                 ↓
  //               getAsBinary(),getAsDataURL() を使う
  if(elem_file.files == undefined) {
    file = "file:///" + elem_file.value;

    var req = new XMLHttpRequest();
    req.open("GET", file, false);
  //req.overrideMimeType("text/plain;charset=UNKNOWN-8BIT");
    req.overrideMimeType("text/plain;charset=x-user-defined");
    try {
      req.send(null);
    }
    catch(e) {
      alert(e.message);
      return;
    }

    text = req.responseText;
  }
  else {
    if(typeof FileReader == "function") {
      file_reader = new FileReader();
      file_reader.onload = in_sel_fr1;
      file_reader.readAsDataURL(elem_file.files.item(0));

      return;
    }
    else {
      file = elem_file.files.item(0).getAsDataURL();
      text = elem_file.files.item(0).getAsBinary();
    }
  }

  in_sel2();
}

function in_sel_fr1() {
  file = file_reader.result;
  file_reader.onload = in_sel_fr2;
  file_reader.readAsBinaryString(elem_file.files.item(0));
}

function in_sel_fr2() {
  text = file_reader.result;
  file_reader = undefined;

  in_sel2();
}

function in_sel2() {
  if(text.length < 8) {
    alert("入力ファイルは PNG 形式ではありません。");
    return;
  }
  in_data = new Array();
  in_data.length = text.length;
  for(var i = 0; i < text.length; i++)
    in_data[i] = text.charCodeAt(i) & 0xff;

  // signature
  if(data_str(in_data, 0, 8) != "\x89PNG\x0d\x0a\x1a\x0a") {
    alert("入力ファイルは PNG 形式ではありません。");
    return;
  }

  // ヘッダ取得
  var err = true;
  if(in_data.length >= 20 /* 8+4+4+4 */) {
    if(data_str(in_data, 12, 4) == "IHDR") {  // Chunk Type
      var len = data_int4(in_data, 8);
      if(20 + len <= in_data.length) {
        if(len >= 13) {
          // CRC チェック
          if(chunk_crc(in_data, 12, 4 + len) == data_int4(in_data, 16 + len)) {
            // Width,Height の最大は (2**31)-1
            img_width  = data_int4(in_data, 16);  // Width
            img_height = data_int4(in_data, 20);  // Height
            img_depth  = in_data[24];             // Bit depth
            img_color  = in_data[25];             // Color type
            img_comp   = in_data[26]              // Compression method
            img_filter = in_data[27];             // Filter method
            img_inter  = in_data[28];             // Interlace method

            if(img_width > 0 && img_height > 0) {
              switch(img_color) {
              case 2:  // RGB
                break;
              case 6:  // RGB + アルファ
                alert("アルファ チャネルは無視されます。");
                break;
              default:
                alert("RGB カラー以外は使用できません。");
                return;
              }

              switch(img_depth) {
              case 8:
              case 16:
                if(img_comp != 0) {
                  alert("Deflate 以外の圧縮方法は使用できません。");
                  return;
                }
                if(img_inter) {
                  alert("インタレースは使用できません。");
                  return;
                }

                err = false;
                break;
              }
            }
          }
        }
      }
    }
  }
  if(err) {
    alert("入力ファイルの内容に誤りがあります。");
    return;
  }

  // PNG サムネイル表示
  with(elem_in_img) {
    if(img_width > 100 || img_height > 100) {
      if(img_width > img_height) {
        width = 100;
        height = Math.floor(100 * img_height / img_width);
      }
      else {
        height = 100;
        width = Math.floor(100 * img_width / img_height);
      }
    }
    else {
      width  = img_width;
      height = img_height;
    }
    style.left = String((100 - width)  >> 1) + "px";
    style.top  = String((100 - height) >> 1) + "px";
    style.visibility = "visible";

    src = file;
  }
  elem_size.textContent = String(img_width) + "x" + String(img_height);

  elem_cre.disabled = false;
}

// 作成開始
function create() {
  elem_cre.disabled = true;
  elem_view.disabled = true;
  elem_save.disabled = true;

  elem_msg1.textContent = "読み込み中...";
  elem_can.style.visibility = "visible";
  cancel = false;
  setTimeout(read_png, 0);
}

// 表示
function view_gif() {
  open("data:image/gif;base64," + gif_data, "view");
}

// 保存
function save_gif() {
  location.href = "data:application/octet-stream;base64," + gif_data;
}

//----------------------------------------------------------

// PNG データ読み込み
function read_png() {
  var err;

  def_data = [];
  transp = "";

  var p;
  for(p = 8; p < in_data.length; ) {
    // 次のチャンク
    var len;
    err = true;
    if(p + 12 <= in_data.length) {
      len = data_int4(in_data, p);
      if(p + 12 + len <= in_data.length) {
        // CRC チェック
        if(chunk_crc(in_data, p + 4, 4 + len) == data_int4(in_data, p + 8 + len))
          err = false;
      }
    }
    if(err)
      break;

    switch(data_str(in_data, p + 4, 4)) {  // Chunk Type
    case "IEND":
      p = Infinity;
      continue;

    case "IDAT":
      def_data = def_data.concat(in_data.slice(p + 8, p + 8 + len));
      break;

    case "tRNS":
      if(len != 6) {
        err = true;
        break;
      }
      var p2 = p + 8;
      if(img_depth == 16) {
        transp_r2 = in_data[p2 + 1];
        transp_g2 = in_data[p2 + 3];
        transp_b2 = in_data[p2 + 5];
      }
      else {
        p2++;
      }
      transp = String.fromCharCode(((in_data[p2] & 0x0f) << 8) | in_data[p2 + 2])
             + String.fromCharCode(((in_data[p2] & 0xf0) << 4) | in_data[p2 + 4] | 0x1000);
      break;

    case "gAMA":
      alert("ガンマ補正は無視されます。");
      break;
    }
    if(err)
      break;

    p += 12 + len;
  }
  err = true;
  if(!isFinite(p)) {  // エラーなし(IEND まで到達)
    i_data = 0;

    var cmf, flg;
    if((cmf = get_def_byte()) != null) {  // CMF
      if((flg = get_def_byte()) != null) {  // FLG
        if(!(((cmf << 8) | flg) % 31)) {  // FCHECK チェック
          if(cmf == 0x78) {  // Deflate & ウィンドウ サイズ 32K
            if(!(flg & 0x20))  // プリセット辞書なし
              err = false;
          }
        }
      }
    }
  }

  if(err) {
    alert("入力ファイルの内容に誤りがあります。");
    end_proc();
    return;
  }

  setTimeout(read_png2, 0);
}

function read_png2() {
  if(cancel) {
    end_proc();
    return;
  }

  var i;

  // Inflate

  inf_data = [];

  n_bits = 0;

  var sts;
  for(; ; ) {
    sts = false;

    var bfinal;
    if((bfinal = get_bits(1)) == null)
      break;

    var btype;
    if((btype = get_bits(2)) == null)
      break;
    switch(btype) {
    case 0x0:  // no compression
      n_bits = 0;
      var buff;
      if((buff = get_def_bytes(4)) != null) {  // LEN, NLEN
        if((buff = get_def_bytes(buff[0] | (buff[1] << 8))) != null) {
          sts = true;
          inf_data = inf_data.concat(buff);
        }
      }
      break;

    case 0x1:  // compressed with fixed Huffman codes
      sts = fixed();
      break;

    case 0x2:  // compressed with dynamic Huffman codes
      sts = dynamic();
      break;
    }
    if(!sts)
      break;

    if(bfinal)
      break;
  }

  if(sts) {
    // Adler-32 チェック
    var adler32;
    if((adler32 = get_def_bytes(4)) == null) {  // ADLER32
      sts = false;
    }
    else {
      var s1 = 1;
      var s2 = 0;
      for(i = 0; i < inf_data.length; i++) {
        s1 = (s1 + inf_data[i]) % 65521;
        s2 = (s2 + s1) % 65521;
      }
      if((s1 | (s2 << 16)) != data_int4(adler32, 0))
        sts = false;
    }
  }

  def_data = undefined;

  if(!sts) {
    alert("入力ファイルの内容に誤りがあります。");
    end_proc();
    return;
  }

  png_bpc = (img_depth == 16) ? 2 : 1;  // バイト数/1 原色
  png_bpp = ((img_color == 2) ? 3 : 4) * png_bpc;  // バイト数/ピクセル
  png_bpl = img_width * png_bpp;  // バイト数/ライン
  png_stride = png_bpl + 1;  // ライン間バイト数

  i_data = 0;

  // 画像 インデックス配列
  pixels = new Array();
  pixels.length = img_height;
  for(i = 0; i < img_height; i++) {
    pixels[i] = new Array();
    pixels[i].length = img_width;
  }

  colors = "";
  frames = [];

  elem_msg1.textContent = "色情報収集中...";
  setTimeout(color_index, 0, 0);
}

// チャンクの CRC を計算する.
// X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X+1
// 結果の MSB が 1 の場合,Integer の値としては負になる.
function chunk_crc(data, index, len) {
  var rem = ~0;
  for(; len; len--) {
    var data2 = rem & 0xff ^ data[index++];
    data2 ^= (data2 << 6) & 0xff;
    rem = ((rem >>> 8) | (data2 << 24))
            ^ (data2 >>  2) ^ (data2 <<  1) ^ (data2 <<  2) ^ (data2 <<  8) ^ (data2 << 12)
            ^ (data2 << 13) ^ (data2 << 14) ^ (data2 << 16) ^ (data2 << 17) ^ (data2 << 19)
            ^ (data2 << 20) ^ (data2 << 22) ^ (data2 << 23);
  }
  return ~rem;
}

// バイト配列から文字列を取り出す.
function data_str(data, index, len) {
  var str = "";
  for(var i = 0; i < len; i++)
    str += String.fromCharCode(data[index++]);
  return str;
}

// バイト配列から 4 バイト Integer を取り出す.
// 結果の MSB が 1 の場合,Integer の値としては負になる.
function data_int4(data, index) {
  return (data[index] << 24) | (data[index + 1] << 16) | (data[index + 2] << 8) | data[index + 3];
}

//----------------------------------------------------------

// fixed Huffman codes
function fixed() {
  for(; ; ) {
    var bits;

    if(!get_huff(7))
      return false;
    if(huff == 0x00)  // 256 = end-of-block
      return true;

    var i_len;
    if     (huff < 0x18) {  // 257 - 279
      i_len = huff - 1;
    }
    else if(huff < 0x60) {  // 0 - 143
      if(!append_huff(1))
        return false;
      inf_data.push(huff - 0x30);
      continue;
    }
    else if(huff < 0x64) {  // 280 - 287
      if(!append_huff(1))
        return false;
      i_len = huff - 0xc0 + (280 - 257);
    }
    else {  // 144 - 255
      if(!append_huff(2))
        return false;
      inf_data.push(huff - 0x190 + 144);
      continue;
    }
    if((bits = get_bits(len_def[i_len][1]/*extra bits*/)) == null)
      return false;
    var length = len_def[i_len][0]/*length*/ + bits;

    if(!get_huff(5))
      return false;
    if((bits = get_bits(dist_def[huff][1]/*extra bits*/)) == null)
      return false;
    copy_def_bytes(dist_def[huff][0]/*distance*/ + bits, length);
  }
}

// dynamic Huffman codes
function dynamic() {
  var hlit;
  if((hlit = get_bits(5)) == null)  // HLIT
    return false;
  var hdist;
  if((hdist = get_bits(5)) == null)  // HDIST
    return false;
  var hclen;
  if((hclen = get_bits(4)) == null)  // HCLEN
    return false;

  var code;
  var repeat;
  var bit_len;
  var bit_len2;
  var prev_len;
  var bits;
  var i;

  // code length codes
  var len_code = [];
  for(i = 0; i < hclen + 4; i++) {
    if((bits = get_bits(3)) == null)
      return false;
    if(bits)
      len_code.push({bit_len:bits, code:len_code_def[i]});
  }

  var len_dec = pre_decode(len_code);

  var lit_code = [];
  repeat = 0;
  for(code = 0; code < hlit + 257; ) {
    bit_len = 0;
    huff = 0;
    for(i = 0; i < len_dec.length; i++) {
      if(!append_huff(len_code[len_dec[i].i_code].bit_len - bit_len))
        return false;
      if(huff < len_dec[i].from + len_dec[i].count) {
        switch(bit_len2 = len_code[len_dec[i].i_code + (huff - len_dec[i].from)].code) {
        case 0:
          code++;
          break;
        case 16:
          if((bits = get_bits(2)) == null)
            return false;
          for(repeat = bits + 3; repeat; repeat--) {
            if(code > hlit + 257)
              break;
            lit_code.push({bit_len:prev_len, code:code++});
          }
          break;
        case 17:
          if((bits = get_bits(3)) == null)
            return false;
          if((code += bits + 3) > hlit + 257) {
            repeat = code - (hlit + 257);
            prev_len = 0;
          }
          break;
        case 18:
          if((bits = get_bits(7)) == null)
            return false;
          if((code += bits + 11) > hlit + 257) {
            repeat = code - (hlit + 257);
            prev_len = 0;
          }
          break;
        default:
          lit_code.push({bit_len:prev_len = bit_len2, code:code++});
          break;
        }
        break;
      }
      bit_len = len_code[len_dec[i].i_code].bit_len;
    }
  }

  var dist_code = [];
  code = 0;
  if(repeat) {
    if(prev_len) {
      for(; repeat; repeat--)
        dist_code.push({bit_len:prev_len, code:code++});
    }
    else {
      code = repeat;
    }
  }
  for(; code < hdist + 1; ) {
    bit_len = 0;
    huff = 0;
    for(i = 0; i < len_dec.length; i++) {
      if(!append_huff(len_code[len_dec[i].i_code].bit_len - bit_len))
        return false;
      if(huff < len_dec[i].from + len_dec[i].count) {
        switch(bit_len2 = len_code[len_dec[i].i_code + (huff - len_dec[i].from)].code) {
        case 0:
          code++;
          break;
        case 16:
          if((bits = get_bits(2)) == null)
            return false;
          for(repeat = bits + 3; repeat; repeat--)
            dist_code.push({bit_len:prev_len, code:code++});
          break;
        case 17:
          if((bits = get_bits(3)) == null)
            return false;
          code += bits + 3;
          break;
        case 18:
          if((bits = get_bits(7)) == null)
            return false;
          code += bits + 11;
          break;
        default:
          dist_code.push({bit_len:prev_len = bit_len2, code:code++});
          break;
        }
        break;
      }
      bit_len = len_code[len_dec[i].i_code].bit_len;
    }
  }

  var lit_dec = pre_decode(lit_code);
  var dist_dec = pre_decode(dist_code);

  for(; ; ) {
    // length
    bit_len = 0;
    huff = 0;
    for(i = 0; i < lit_dec.length; i++) {
      if(!append_huff(lit_code[lit_dec[i].i_code].bit_len - bit_len))
        return false;
      if(huff < lit_dec[i].from + lit_dec[i].count) {
        if((code = lit_code[lit_dec[i].i_code + (huff - lit_dec[i].from)].code) == 256)  // end-of-block
          return true;

        if(code < 256) {
          inf_data.push(code);
        }
        else {
          var i_len = code - 257;
          if((bits = get_bits(len_def[i_len][1]/*extra bits*/)) == null)
            return false;
          var length = len_def[i_len][0]/*length*/ + bits;

          // distance
          bit_len = 0;
          huff = 0;
          for(i = 0; i < dist_dec.length; i++) {
            if(!append_huff(dist_code[dist_dec[i].i_code].bit_len - bit_len))
              return false;
            if(huff < dist_dec[i].from + dist_dec[i].count) {
              var i_dist = dist_code[dist_dec[i].i_code + (huff - dist_dec[i].from)].code;
              if((bits = get_bits(dist_def[i_dist][1]/*extra bits*/)) == null)
                return false;
              copy_def_bytes(dist_def[i_dist][0]/*distance*/ + bits, length);
              break;
            }
            bit_len = dist_code[dist_dec[i].i_code].bit_len;
          }
        }
        break;
      }
      bit_len = lit_code[lit_dec[i].i_code].bit_len;
    }
  }
}

// デコード用ワーク作成
function pre_decode(code) {
  code.sort(pre_decode_comp);

  // i_code:コード テーブル インデックス, from:先頭コード, count:コード数
  var dec = [];
  var bit_len = 0;
  var count = 0;
  var huff = 0;
  for(var i = 0; i < code.length; i++) {
    if(code[i].bit_len > bit_len) {
      if(i) {
        dec[dec.length - 1].count = count;
        huff = (huff + count) << 1;
        huff <<= code[i].bit_len - bit_len - 1;
        count = 0;
      }
      dec.push({i_code:i, from:huff, count:0});
      bit_len = code[i].bit_len;
    }
    count++;
  }
  dec[dec.length - 1].count = count;

  return dec;
}

function pre_decode_comp(elm1, elm2) {
  if(elm1.bit_len < elm2.bit_len
       || elm1.bit_len == elm2.bit_len && elm1.code < elm2.code)
    return -1;
  return 1;
}

function get_bits(n) {
  var bits = 0;
  var bit = 0;
  for(; n; n--) {
    if(!n_bits) {
      if((def_byte = get_def_byte()) == null)
        return null;
      n_bits = 8;
    }
    bits |= (def_byte & 0x01) << bit;
    def_byte >>= 1;
    n_bits--;
    bit++;
  }
  return bits;
}

function get_huff(n) {
  huff = 0;
  return append_huff(n);
}

function append_huff(n) {
  for(; n; n--) {
    if(!n_bits) {
      if((def_byte = get_def_byte()) == null)
        return false;
      n_bits = 8;
    }
    huff = (huff << 1) | def_byte & 0x01;
    def_byte >>= 1;
    n_bits--;
  }
  return true;
}

function get_def_bytes(len) {
  if(i_data + len > def_data.length)
    return null;
  var i = i_data;
  i_data += len;
  return def_data.slice(i, i_data);
}

function get_def_byte() {
  if(i_data == def_data.length)
    return null;
  return def_data[i_data++];
}

function copy_def_bytes(dist, length) {
  var i = inf_data.length - dist;
  for(; length; length--)
    inf_data.push(inf_data[i++]);
}

//----------------------------------------------------------

// カラー テーブル作成 & インデックス化
function color_index(y) {
  if(cancel) {
    end_proc();
    return;
  }

  var cnt;
  var x;
  var i;

  // フィルタ
  switch(inf_data[i_data++]) {  // filter type
  case 1:  // Sub
    i = i_data + png_bpp;
    for(cnt = png_bpl - png_bpp; cnt; cnt--, i++)
      inf_data[i] = (inf_data[i - png_bpp] + inf_data[i]) & 0xff;
    break;

  case 2:  // Up
    if(!y)
      break;
    i = i_data;
    for(cnt = png_bpl; cnt; cnt--, i++)
      inf_data[i] = (inf_data[i - png_stride] + inf_data[i]) & 0xff;
    break;

  case 3:  // Average
    i = i_data;
    for(x = 0; x < png_bpl; x++, i++)
      inf_data[i] = (((((x >= png_bpp) ? inf_data[i - png_bpp] : 0)
                         + ((y) ? inf_data[i - png_stride] : 0)) >> 1) + inf_data[i]) & 0xff;
    break;

  case 4:  // Paeth
    i = i_data;
    for(x = 0; x < png_bpl; x++, i++) {
      var a = (x >= png_bpp) ? inf_data[i - png_bpp] : 0;
      var b = (y) ? inf_data[i - png_stride] : 0;
      var c = (y && x >= png_bpp) ? inf_data[i - png_stride - png_bpp] : 0;
      var p = a + b - c;
      var pa = Math.abs(p - a);
      var pb = Math.abs(p - b);
      var pc = Math.abs(p - c);
      var d;
      if(pa <= pb && pa <= pc)
        d = a;
      else if(pb <= pc)
        d = b;
      else
        d = c;
      inf_data[i] = (d + inf_data[i]) & 0xff;
    }
    break;
  }

  // 色集積 & インデックス化
  for(x = 0; x < img_width; x++) {
    var color = String.fromCharCode(((inf_data[i_data] & 0x0f) << 8) | inf_data[i_data + png_bpc])
              + String.fromCharCode(((inf_data[i_data] & 0xf0) << 4) | inf_data[i_data + (png_bpc << 1)] | 0x1000);
    if((color == transp) ? ((img_depth == 16) ? (inf_data[i_data + 1] == transp_r2
                                              && inf_data[i_data + 3] == transp_g2
                                              && inf_data[i_data + 5] == transp_b2) : true) : false) {  // 透過色
      pixels[y][x] = -1;  // すべてのフレームで透過になる
    }
    else {
      i = colors.lastIndexOf(color);
      if(i == -1) {
        i = colors.length;
        if(i == 131072 /* 65536*2 */) {
          alert("使用色数が多すぎます。");
          end_proc();
          return;
        }
        colors += color;
        if(!(i % 510 /* 255*2 */))  // フレーム数増加
        //frames.push({x_min:Infinity, y_min:y, x_max:0, y_max:0});
          // IE 対策
          // IE では,フレームの幅が画像全体の幅と一致していないと表示が一部欠ける
          frames.push({x_min:0, y_min:y, x_max:img_width - 1, y_max:0});
      }
      pixels[y][x] = i >> 1;

      with(frames[Math.floor(i / 510 /* 255*2 */)]) {
        y_max = y;
        if(x < x_min)
          x_min = x;
        if(x > x_max)
          x_max = x;
      }
    }
    i_data += png_bpp;
  }

  if(++y == img_height) {
    inf_data = undefined;

    create_gif();
  }
  else {
    setTimeout(color_index, 0, y);
  }
}

//----------------------------------------------------------

// GIF 作成
function create_gif() {
  if(!colors.length) {
    alert("使用色数が 0 です。");
    end_proc();
    return;
  }
  if(colors.length <= 512) {
    if(!confirm("使用色数が 256 以下です。GIF 作成を続けますか?")) {
      end_proc();
      return;
    }
  }

  elem_info.textContent = String(colors.length >> 1) + "色 " + String(frames.length) + "フレーム";
  elem_msg1.textContent = "GIF 画像作成中...";
  setTimeout(create_gif2, 0);
}

function create_gif2() {
  gif_data = "";
  b64_buff = "";

  gif_header[I_SCREEN_WIDTH     ] = img_width & 0xff;
  gif_header[I_SCREEN_WIDTH  + 1] = img_width >> 8;
  gif_header[I_SCREEN_HEIGHT    ] = img_height & 0xff;
  gif_header[I_SCREEN_HEIGHT + 1] = img_height >> 8;
  // Header,Logical Screen Descriptor
  for(var i = 0; i < gif_header.length; i++)
    putc(gif_header[i]);

  i_color = 0;
  i_frame = 0;
  create_gif_frame();
}

// GIF 1 フレーム出力
function create_gif_frame() {
  if(cancel) {
    end_proc();
    return;
  }

  with(frames[i_frame]) {
    gif_frame[I_IMAGE_LEFT    ] = x_min & 0xff;
    gif_frame[I_IMAGE_LEFT + 1] = x_min >> 8;
    gif_frame[I_IMAGE_TOP     ] = y_min & 0xff;
    gif_frame[I_IMAGE_TOP  + 1] = y_min >> 8;
    var w = x_max - x_min + 1;
    gif_frame[I_IMAGE_WIDTH     ] = w & 0xff;
    gif_frame[I_IMAGE_WIDTH  + 1] = w >> 8;
    w = y_max - y_min + 1;
    gif_frame[I_IMAGE_HEIGHT    ] = w & 0xff;
    gif_frame[I_IMAGE_HEIGHT + 1] = w >> 8;
  }

  elem_msg2.textContent = "フレーム " + String(i_frame + 1) + "/" + String(frames.length);
  setTimeout(create_gif_frame2, 0);
}

function create_gif_frame2() {
  var i;

  // Graphic Control Extension,Image Descriptor
  gif_frame[I_DELAY_TIME] = gif_frame[I_DELAY_TIME + 1] = (i_frame + 1 < frames.length) ? 0x00 : 0xff;
                                                                                // 最終フレームのみ 65535,他は 0
  for(i = 0; i < gif_frame.length; i++)
    putc(gif_frame[i]);
  // Local Color Table
  i = i_color << 1;
  for(var cnt = 255; cnt; cnt--) {
    if(i == colors.length) {  // 色テーブル終了
      // ダミー
      for(cnt *= 3; cnt; cnt--)
        putc(0);
      break;
    }
    var c1 = colors.charCodeAt(i++);
    var c2 = colors.charCodeAt(i++);
    putc((c1 >> 8) | ((c2 & 0xf00) >> 4));  // R
    putc(c1 & 0xff);  // G
    putc(c2 & 0xff);  // B
  }
  putc(0);
  putc(0);
  putc(0);

  // LZW Minimum Code Size
  putc(8);  // 8 ビット

  // 辞書ルート初期化
  for(i = 0; i < 256; i++) {
    root_prop[i].chara = undefined;
    root_prop[i].prop = undefined;
  }

  block_cnt = 0;

  pack_buff = 0;
  pack_bits = 0;

  n_code = 258;
  code_bits = 9;
  max_n = 512;

  put_code(256);  // Clear

  with(frames[i_frame]) {
    dict_code = pixels[y_min][x_min] - i_color;
  }
  // 0 〜 254 以外は透過(255 はそのままでよい)
  if(dict_code & ~0xff)
    dict_code = 255;
  dict_prop = root_prop[dict_code];

  setTimeout(create_gif_frame3, 0, frames[i_frame].y_min);
}

function create_gif_frame3(y) {
  if(cancel) {
    end_proc();
    return;
  }

  var frame = frames[i_frame];
  var x = frame.x_min;
  if(y == frame.y_min)
    x++;

  while(x <= frame.x_max) {
    // 次の 1 ピクセル
    var c = pixels[y][x++] - i_color;
    // 0 〜 254 以外は透過(255 はそのままでよい)
    if(c & ~0xff)
      c = 255;

    var cs = String.fromCharCode(c);
    // 辞書サーチ
    var i = -1;
    if(dict_prop.chara != undefined)
      i = dict_prop.chara.indexOf(cs);
    if(i == -1) {  // 未登録
      put_code(dict_code);

      if(n_code == 4095) {  // 辞書が一杯(のひとつ前)
        // 辞書クリア
        put_code(256);  // Clear

        for(i = 0; i < 256; i++) {
          root_prop[i].chara = undefined;
          root_prop[i].prop = undefined;
        }
        n_code = 258;
        code_bits = 9;
        max_n = 512;
      }
      else {
        // 辞書に登録
        if(dict_prop.chara == undefined) {
          dict_prop.chara = cs;
          dict_prop.prop = [{code:n_code, chara:undefined, prop:undefined}];
        }
        else {
          dict_prop.chara += cs;
          dict_prop.prop.push({code:n_code, chara:undefined, prop:undefined});
        }
        if(n_code++ == max_n) {
          code_bits++;
          max_n <<= 1;
        }
      }
      dict_code = c;
      dict_prop = root_prop[c];
    }
    else {  // 登録済み
      dict_code = dict_prop.prop[i].code;
      dict_prop = dict_prop.prop[i];
    }
  }

  if(y == frame.y_max)
    create_gif_frame4();
  else
    setTimeout(create_gif_frame3, 0, y + 1);
}

function create_gif_frame4() {
  put_code(dict_code);  // 最後のコード
  put_code(257);  // End of Information
  // コード パック用バッファ フラッシュ
  if(pack_bits)
    put_code(0);

  // ブロック バッファ フラッシュ
  if(block_cnt) {
    putc(block_cnt);
    for(var i = 0; i < block_cnt; i++)
      putc(block_buff[i]);
  }

  putc(0x00);  // Block Terminator

  if(++i_frame < frames.length) {
    i_color += 255;
    create_gif_frame();
    return;
  }

  putc(0x3b);  // Trailer

  // BASE64 バッファ フラッシュ
  if(b64_buff.length)
    gif_data += btoa(b64_buff);

  // GIF サムネイル表示
  elem_out_img.width  = elem_in_img.width;
  elem_out_img.height = elem_in_img.height;
  elem_out_img.style.left = elem_in_img.style.left;
  elem_out_img.style.top  = elem_in_img.style.top;
  elem_out_img.src = "data:image/gif;base64," + gif_data;
  elem_out_img.style.visibility = "visible";

  end_proc();
  elem_view.disabled = false;
  elem_save.disabled = false;
}

// LZW コード出力
function put_code(code) {
  pack_buff |= code << pack_bits;
  pack_bits += code_bits;
  while(pack_bits >= 8) {
    block_buff[block_cnt++] = pack_buff & 0xff;
    if(block_cnt == 255) {
      putc(255);
      for(var i = 0; i < 255; i++)
        putc(block_buff[i]);
      block_cnt = 0;
    }
    pack_buff >>= 8;
    pack_bits -= 8;
  }
}

// イメージ データに 1 バイト出力
function putc(c) {
  b64_buff += String.fromCharCode(c);
  if(b64_buff.length == 3) {
    gif_data += btoa(b64_buff);
    b64_buff = "";
  }
}

//----------------------------------------------------------

// 作成終了
function end_proc() {
  def_data = undefined;
  inf_data = undefined;
  colors = undefined;
  frames = undefined;
  pixels = undefined;
  for(var i = 0; i < 256; i++) {
    root_prop[i].chara = undefined;
    root_prop[i].prop = undefined;
  }

  elem_msg1.textContent = "";
  elem_msg2.textContent = "";
  elem_can.style.visibility = "hidden";

  elem_cre.disabled = false;
}

//----------------------------------------------------------

  var in_data;  // 入力ファイル データ
  var img_width;   // Width
  var img_height;  // Height
  var img_depth;   // Bit depth
  var img_color;   // Color type
  var img_comp;    // Compression method
  var img_filter;  // Filter method
  var img_inter;   // Interlace method
  var cancel;  // 中止フラグ
  var def_data;  // 圧縮データ
  var inf_data;  // 展開データ
  var i_data;  // データ インデックス
  var transp;     // transparent color
  var transp_r2;  // transparent color 16 ビットのときの LSB R
  var transp_g2;  //                                         G
  var transp_b2;  //                                         B
  var png_bpc;  // バイト数/1 原色
  var png_bpp;  // バイト数/ピクセル
  var png_bpl;  // バイト数/ライン
  var png_stride;  // ライン間バイト数

  var def_byte;  // 圧縮データ バッファ
  var n_bits;    // 圧縮データ バッファ格納ビット数
  var huff;  // ハフマン コード

  // length code → length
  // [length, extra bits]
  var len_def = [
    [  3, 0], [  4, 0], [  5, 0], [  6, 0], [  7, 0], [  8, 0], [  9, 0], [ 10, 0],
    [ 11, 1], [ 13, 1], [ 15, 1], [ 17, 1], [ 19, 2], [ 23, 2], [ 27, 2], [ 31, 2],
    [ 35, 3], [ 43, 3], [ 51, 3], [ 59, 3], [ 67, 4], [ 83, 4], [ 99, 4], [115, 4],
    [131, 5], [163, 5], [195, 5], [227, 5], [258, 0]
  ];

  // distance code → distance
  // [distance, extra bits]
  var dist_def = [
    [    1,  0], [    2,  0], [    3,  0], [    4,  0],
    [    5,  1], [    7,  1], [    9,  2], [   13,  2],
    [   17,  3], [   25,  3], [   33,  4], [   49,  4],
    [   65,  5], [   97,  5], [  129,  6], [  193,  6],
    [  257,  7], [  385,  7], [  513,  8], [  769,  8],
    [ 1025,  9], [ 1537,  9], [ 2049, 10], [ 3073, 10],
    [ 4097, 11], [ 6145, 11], [ 8193, 12], [12289, 12],
    [16385, 13], [24577, 13]
  ];

  // code length code
  var len_code_def = [
    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
  ];

  var colors;  // 色テーブル
  var frames;  // フレーム領域
  var pixels;  // 画像 インデックス配列
  var i_color;  // 色テーブル インデックス
  var i_frame;  // フレーム インデックス

  // Header,Logical Screen Descriptor
  gif_header = [
    0x47, 0x49, 0x46, 0x38, 0x39, 0x61,  // GIF89a
    0x00, 0x00, 0x00, 0x00,
    0x77,                                // 8 ビット,256色
    0x00,                                // Background Color Index
    0x00                                 // Pixel Aspect Ratio
  ];
  I_SCREEN_WIDTH = 6;
  I_SCREEN_HEIGHT = 8;
  // Graphic Control Extension,Image Descriptor
  gif_frame = [
    0x21,                                // Extension Introducer
    0xf9,                                // Graphic Control Label
    4,                                   // Block Size
    0x05,                                // Do not dispose,Transparent Color
    0x00, 0x00,
    255,                                 // Transparent Color Index
    0,                                   // Block Terminator
    0x2c,                                // Image Separator
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x87,                                // Local Color Table,256色
  ];
  I_DELAY_TIME = 4;
  I_IMAGE_LEFT = 9;
  I_IMAGE_TOP = 11;
  I_IMAGE_WIDTH = 13;
  I_IMAGE_HEIGHT = 15;

  // 辞書ルート
  root_prop = new Array();
  root_prop.length = 256;
  for(i = 0; i < 256; i++)
    root_prop[i] = {chara:undefined, prop:undefined};

  var n_code;  // コード登録数
  var code_bits;  // コード ビット数
  var max_n;  // 最大コード登録数
  var dict_code;  // コード
  var dict_prop;  // 辞書情報
  var pack_buff;  // コード パック用バッファ
  var pack_bits;  // コード パック用バッファ格納ビット数
  block_buff = new Array();  // ブロック バッファ
  block_buff.length = 255;
  var block_cnt;             // ブロック バッファ格納バイト数
  var gif_data;  // GIF イメージ データ
  var b64_buff;  // BASE64 バッファ

  elem_file = document.getElementById("file");
  elem_in_img = document.getElementById("in_img");
  elem_size = document.getElementById("size");
  elem_cre = document.getElementById("cre");
  elem_out_img = document.getElementById("out_img");
  elem_info = document.getElementById("info");
  elem_view = document.getElementById("view");
  elem_save = document.getElementById("save");
  elem_msg1 = document.getElementById("msg1");
  elem_msg2 = document.getElementById("msg2");
  elem_can = document.getElementById("can");

  document.forms[0].reset();  // ページを再ロードしたとき,「入力ファイル」項目の内容が残ってしまうため

  if(elem_file.files == undefined) {
    if(location.protocol != "file:")
      alert("お使いのブラウザでは、このプログラムをオンラインで実行することはできません。\n"
          + "プログラムをダウンロードして、ローカルのストレージから起動してください。");
  }

//-->
</SCRIPT>

</BODY>

</HTML>