![]() |
<!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> |