fullgif2.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 作成(Canvas 版)</TITLE>
</HEAD>

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

<FORM>
<TABLE><TR><TD>
入力ファイル:<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 onLoad="in_sel2()" 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;

  if(elem_file.files == undefined) {
    elem_in_img.src = "file:///" + elem_file.value;
  }
  else {
    if(typeof FileReader == "function") {
      file_reader = new FileReader();
      file_reader.onload = in_sel_fr;
      file_reader.readAsDataURL(elem_file.files.item(0));
    }
    else {
      elem_in_img.src = elem_file.files.item(0).getAsDataURL();
    }
  }
}

function in_sel_fr() {
  elem_in_img.src = file_reader.result;
  file_reader = undefined;
}

function in_sel2() {
  img_width  = elem_in_img.naturalWidth;
  img_height = elem_in_img.naturalHeight;

  // 入力画像サムネイル表示
  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";
  }
  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_work.width  = Math.floor(img_width  / dpu_x);
  elem_work.height = Math.floor(img_height / dpu_y);
  ctx_work.globalCompositeOperation = "copy";
  ctx_work.drawImage(elem_in_img, 0, 0, elem_work.width, elem_work.height);
  with(ctx_work.getImageData(0, 0, elem_work.width, elem_work.height)) {
    img_width  = width;
    img_height = height;
    img_data = data;
  }

  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 = "色情報収集中...";
  elem_can.style.visibility = "visible";
  cancel = false;
  setTimeout(color_index, 0, 0);
}

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

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

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

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

  // 色集積 & インデックス化
  for(var x = 0; x < img_width; x++) {
    if(img_data[i_data + 3]) {  // アルファ チャネルが 0 以外
      var color = String.fromCharCode(((img_data[i_data] & 0x0f) << 8) | img_data[i_data + 1])
                + String.fromCharCode(((img_data[i_data] & 0xf0) << 4) | img_data[i_data + 2] | 0x1000);
      var 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;
      }
    }
    else {
      pixels[y][x] = -1;  // すべてのフレームで透過になる
    }
    i_data += 4;
  }

  if(++y == img_height) {
    img_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() {
  img_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 img_width;   // Width
  var img_height;  // Height
  var img_data;  // 入力データ
  var cancel;  // 中止フラグ
  var i_data;  // データ インデックス

  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");

  // 作業用 Canvas
  elem_work = document.createElement("CANVAS");
  ctx_work = elem_work.getContext("2d");
  elem_work.width  = 1000;
  elem_work.height = 1000;
  dpu_x = ctx_work.getImageData(0, 0, 1000, 1).width  / 1000;
  dpu_y = ctx_work.getImageData(0, 0, 1, 1000).height / 1000;

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

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

//-->
</SCRIPT>

</BODY>

</HTML>