<!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>
|