<!DOCTYPE HTML>
<HTML LANG="ja">
<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>GIF アニメーション</TITLE>
</HEAD>
<BODY STYLE="background-color:#CCFFFF">
<DIV STYLE="text-align:center">
<B>GIF アニメーション</B>
<BR><BR>
<FORM>
<DIV STYLE="position:relative; display:inline-block; text-align:left; white-space:nowrap">
動画ファイル:<BR>
<INPUT TYPE=FILE ID="file" SIZE=60 onChange="sel_file()">
</DIV>
<BR><BR>
<DIV STYLE="position:relative; display:inline-block">
<VIDEO ID="vid" CONTROLS STYLE="position:relative; background-color:black"></VIDEO>
<IMG ID="gif" STYLE="position:absolute; left:0; top:0; visibility:hidden">
</DIV>
<BR><BR>
<TABLE STYLE="margin-left:auto; margin-right:auto; text-align:left; white-space:nowrap">
<TR><TD>
開始位置:
<INPUT TYPE=TEXT ID="fr_h" SIZE=2 STYLE="text-align:right"> 時間
<INPUT TYPE=TEXT ID="fr_m" SIZE=2 STYLE="text-align:right"> 分
<INPUT TYPE=TEXT ID="fr_s" SIZE=2 STYLE="text-align:right"> 秒
<INPUT TYPE=TEXT ID="fr_ms" SIZE=3 STYLE="text-align:right"> ms
<INPUT TYPE=BUTTON ID="fr_cur" VALUE="現在位置" DISABLED onClick="set_cur(0)" STYLE="padding-left:0.1em; padding-right:0.1em">
</TD></TR>
<TR><TD>
終了位置:
<INPUT TYPE=TEXT ID="to_h" SIZE=2 STYLE="text-align:right"> 時間
<INPUT TYPE=TEXT ID="to_m" SIZE=2 STYLE="text-align:right"> 分
<INPUT TYPE=TEXT ID="to_s" SIZE=2 STYLE="text-align:right"> 秒
<INPUT TYPE=TEXT ID="to_ms" SIZE=3 STYLE="text-align:right"> ms
<INPUT TYPE=BUTTON ID="to_cur" VALUE="現在位置" DISABLED onClick="set_cur(1)" STYLE="padding-left:0.1em; padding-right:0.1em">
</TD></TR>
<TR><TD>
フレーム間隔: <INPUT TYPE=TEXT ID="int" SIZE=4 STYLE="text-align:right"> ×10ms
</TD></TR>
<TR><TD>
ループ再生:
<LABEL><INPUT TYPE=RADIO ID="lp_never" NAME="loop" CHECKED> しない</LABEL>
<LABEL><INPUT TYPE=RADIO NAME="loop" STYLE="margin-left:0.5em"> 無限</LABEL>
<LABEL><INPUT TYPE=RADIO ID="lp_cnt" NAME="loop" STYLE="margin-left:0.5em"> </LABEL><INPUT TYPE=TEXT ID="lp_times" SIZE=4 STYLE="text-align:right"><LABEL FOR="lp_cnt"> 回</LABEL>
</TD></TR>
<TR><TD>
<BR>
<INPUT TYPE=BUTTON ID="cre" VALUE="作成" DISABLED onClick="create_gif()">
<SPAN ID="sts" STYLE="margin-left:1em"></SPAN>
</TD></TR>
<TR><TD>
フレーム: <SPAN ID="frms" STYLE="display:inline-block; min-width:2.5em"></SPAN>
<SPAN STYLE="margin-left:1em">時間:</SPAN> <SPAN ID="time" STYLE="display:inline-block; min-width:8em"></SPAN>
</TD></TR>
<TR><TD>
<BR>
<INPUT TYPE=BUTTON ID="view" VALUE="表示" DISABLED onClick="view_gif()">
<INPUT TYPE=BUTTON ID="cls" VALUE="表示終了" DISABLED onClick="close_gif()" STYLE="margin-left:2em">
<INPUT TYPE=BUTTON ID="save" VALUE="保存" DISABLED onClick="save_gif()" STYLE="margin-left:2em">
</TD></TR>
</TABLE>
</FORM>
</DIV>
<A ID="dl" STYLE="display:none"></A><!--保存処理用-->
<SCRIPT TYPE="text/javascript">
<!--
function sel_file() {
var file = elem_file.files.item(0);
if(elem_vid.src != "")
URL.revokeObjectURL(elem_vid.src);
elem_vid.src = URL.createObjectURL(file);
elem_vid.load();
file_name = file.name;
close_gif();
elem_gif.src = clr_img;
elem_cre.disabled = true;
elem_sts.textContent = elem_frms.textContent = elem_time.textContent = "";
elem_view.disabled = true;
elem_save.disabled = true;
if(gif_img != undefined) {
URL.revokeObjectURL(gif_img);
gif_img = undefined;
}
}
function vid_canplay() {
if(!elem_vid.videoWidth) {
vid_error();
return;
}
elem_fr_cur.disabled = elem_to_cur.disabled = false;
elem_cre.disabled = false;
}
function vid_error() {
frames = undefined;
elem_file.disabled = false;
elem_vid.controls = true;
elem_gif.src = clr_img;
elem_fr_cur.disabled = elem_to_cur.disabled = true;
elem_cre.value = "作成";
elem_sts.textContent = elem_frms.textContent = elem_time.textContent = "";
elem_view.disabled = true;
elem_save.disabled = true;
if(gif_img != undefined) {
URL.revokeObjectURL(gif_img);
gif_img = undefined;
}
alert("動画を再生できません。");
}
// 開始位置/終了位置にビデオの現在位置を設定
function set_cur(i) {
var time = elem_vid.currentTime;
var int = Math.floor(time);
elem_ms[i].value = String(Math.floor((time - int) * 1000));
var rem = int % 60;
elem_s[i].value = String(rem);
int = (int - rem) / 60;
if(int) {
rem = int % 60;
elem_m[i].value = String(rem);
int = (int - rem) / 60;
elem_h[i].value = (int) ? String(int) : "";
}
else {
elem_h[i].value = elem_m[i].value = "";
}
}
function lp_times_click() {
elem_lp_cnt.click();
}
// 作成/中止
function create_gif() {
if(frames != undefined) { // 作成中
cancel = true;
return;
}
var i;
close_gif();
elem_file.disabled = true;
elem_gif.src = clr_img;
elem_sts.textContent = elem_frms.textContent = elem_time.textContent = "";
elem_view.disabled = true;
elem_save.disabled = true;
if(gif_img != undefined) {
URL.revokeObjectURL(gif_img);
gif_img = undefined;
}
// 入力チェック
var err;
// 開始位置
var fr_h, fr_m, fr_s, fr_ms;
err = true;
fr_h = elem_h[0].value.trim();
fr_h = (fr_h == "") ? 0 : (fr_h.search(/^\d+$/) == -1) ? -1 : parseInt(fr_h, 10);
if(fr_h >= 0) {
fr_m = elem_m[0].value.trim();
fr_m = (fr_m == "") ? 0 : (fr_m.search(/^\d+$/) == -1) ? -1 : parseInt(fr_m, 10);
if(fr_m >= 0 && fr_m < 60) {
fr_s = elem_s[0].value.trim();
fr_s = (fr_s == "") ? 0 : (fr_s.search(/^\d+$/) == -1) ? -1 : parseInt(fr_s, 10);
if(fr_s >= 0 && fr_s < 60) {
fr_ms = elem_ms[0].value.trim();
fr_ms = (fr_ms == "") ? 0 : (fr_ms.search(/^\d+$/) == -1) ? -1 : parseInt(fr_ms, 10);
if(fr_ms >= 0 && fr_ms < 1000)
err = false;
}
}
}
if(err) {
alert("開始位置の入力に誤りがあります。");
return;
}
// 終了位置
var to_h, to_m, to_s, to_ms;
err = true;
to_h = elem_h[1].value.trim();
to_m = elem_m[1].value.trim();
to_s = elem_s[1].value.trim();
to_ms = elem_ms[1].value.trim();
if(to_h == "" && to_m == "" && to_s == "" && to_ms == "") { // 入力なし
to_time = Math.floor(elem_vid.duration * 1000);
}
else {
to_time = undefined;
to_h = (to_h == "") ? 0 : (to_h.search(/^\d+$/) == -1) ? -1 : parseInt(to_h, 10);
if(to_h >= 0) {
to_m = (to_m == "") ? 0 : (to_m.search(/^\d+$/) == -1) ? -1 : parseInt(to_m, 10);
if(to_m >= 0 && to_m < 60) {
to_s = (to_s == "") ? 0 : (to_s.search(/^\d+$/) == -1) ? -1 : parseInt(to_s, 10);
if(to_s >= 0 && to_s < 60) {
to_ms = (to_ms == "") ? 0 : (to_ms.search(/^\d+$/) == -1) ? -1 : parseInt(to_ms, 10);
if(to_ms >= 0 && to_ms < 1000)
err = false;
}
}
}
if(err) {
alert("終了位置の入力に誤りがあります。");
return;
}
}
fr_time = (fr_h * 3600 + fr_m * 60 + fr_s) * 1000 + fr_ms;
if(fr_time >= elem_vid.duration * 1000) {
alert("開始位置が動画の長さを超えています。");
return;
}
if(to_time == undefined) { // 終了位置 入力あり
to_time = (to_h * 3600 + to_m * 60 + to_s) * 1000 + to_ms;
if(to_time > elem_vid.duration * 1000) {
alert("終了位置が動画の長さを超えています。");
return;
}
if(fr_time > to_time) {
alert("終了位置が開始位置より前です。");
return;
}
}
// フレーム間隔
interval = elem_int.value;
interval = (interval.search(/^\s*\d+\s*$/) == -1) ? 0 : parseInt(interval, 10);
if(interval <= 0 || interval > 65535) {
alert("フレーム間隔の入力に誤りがあります。");
return;
}
// ループ回数
if(elem_lp_cnt.checked) {
lp_times = elem_lp_times.value;
lp_times = (lp_times.search(/^\s*\d+\s*$/) == -1) ? 0 : parseInt(lp_times, 10);
if(lp_times <= 1 || lp_times > 65536) {
alert("ループ再生の再生回数の入力に誤りがあります。");
return;
}
}
elem_vid.controls = false;
elem_vid.pause()
save_time = elem_vid.currentTime;
with(elem_work) {
width = elem_gif.width = img_width = elem_vid.videoWidth;
height = elem_gif.height = img_height = elem_vid.videoHeight;
ctx_work = getContext("2d");
}
data_len = (img_width * img_height) << 2;
// GIF データ
gif_data = [];
gif_block = new Uint8Array(10000);
i_gif_block = 0;
gif_header[I_SCREEN_WIDTH ] = gif_frame[I_IMAGE_WIDTH ] = img_width & 0xff;
gif_header[I_SCREEN_WIDTH + 1] = gif_frame[I_IMAGE_WIDTH + 1] = img_width >> 8;
gif_header[I_SCREEN_HEIGHT ] = gif_frame[I_IMAGE_HEIGHT ] = img_height & 0xff;
gif_header[I_SCREEN_HEIGHT + 1] = gif_frame[I_IMAGE_HEIGHT + 1] = img_height >> 8;
gif_frame[I_DELAY_TIME ] = interval & 0xff;
gif_frame[I_DELAY_TIME + 1] = interval >> 8;
// Header,Logical Screen Descriptor
for(i = 0; i < gif_header.length; i++)
putc(gif_header[i]);
// ループ再生
if(!elem_lp_never.checked) { // ループ再生 あり
// Application Extension (Loop)
if(elem_lp_cnt.checked) { // 回数指定
var times = lp_times - 1;
gif_loop[I_LOOP_TIMES ] = times & 0xff;
gif_loop[I_LOOP_TIMES + 1] = times >> 8;
}
else { // 無限
gif_loop[I_LOOP_TIMES] = gif_loop[I_LOOP_TIMES + 1] = 0;
}
for(i = 0; i < gif_loop.length; i++)
putc(gif_loop[i]);
}
frames = 0;
cancel = false;
elem_cre.value = "中止";
elem_sts.textContent = "作成中";
// 最初のフレーム
elem_vid.currentTime = fr_time / 1000;
}
function vid_seeked() {
if(frames == undefined) // 作成中でない
return;
if(cancel) { // 中止
frames = undefined;
elem_file.disabled = false;
elem_vid.currentTime = save_time;
elem_vid.controls = true;
elem_cre.value = "作成";
elem_sts.textContent = elem_frms.textContent = elem_time.textContent = "";
return;
}
elem_frms.textContent = String(++frames);
var gif_time = frames * interval;
var time = fr_time + gif_time * 10;
if(time > to_time) {
var excess = Math.floor((time - to_time) / 10);
gif_time -= excess;
var delay = interval - excess;
gif_frame[I_DELAY_TIME ] = delay & 0xff;
gif_frame[I_DELAY_TIME + 1] = delay >> 8;
}
var cs = gif_time % 100;
var int = (gif_time - cs) / 100;
var sec = int % 60;
var min = (int - sec) / 60;
elem_time.textContent = ((min) ? String(min) + "分 " : "") + String(sec) + "秒 " + String(cs * 10) + "ms";
// Firefox では,幅/高さを指定しないと正しく描画されない場合がある.
//ctx_work.drawImage(elem_vid, 0, 0);
ctx_work.drawImage(elem_vid, 0, 0, img_width, img_height);
create_frame(ctx_work.getImageData(0, 0, img_width, img_height).data); // フレーム作成
if(time < to_time) {
// 次のフレーム
elem_vid.currentTime = time / 1000;
return;
}
putc(0x3b); // Trailer
// GIF データ フラッシュ
if(i_gif_block)
gif_data.push(new Uint8Array(gif_block.buffer, 0, i_gif_block));
gif_img = URL.createObjectURL(new Blob(gif_data, {type:"image/gif"}));
frames = undefined;
elem_file.disabled = false;
elem_vid.currentTime = save_time;
elem_vid.controls = true;
elem_cre.value = "作成";
elem_sts.textContent = "作成完了";
elem_view.disabled = false;
elem_save.disabled = false;
}
// フレーム作成
function create_frame(data) {
reduce_color(data); // 256色に減色
var prop;
var i;
// Graphic Control Extension,Image Descriptor
for(i = 0; i < gif_frame.length; i++)
putc(gif_frame[i]);
// Local Color Table
for(i = 0; i < n_colors; i++) {
putc(colors_r[i]);
putc(colors_g[i]);
putc(colors_b[i]);
}
// ダミー
for(i = (256 - i) * 3; i; i--)
putc(0);
// LZW Minimum Code Size
putc(8); // 8 ビット
// 辞書ルート
// 機能的には Map が適しているが,こちらの方が速かった.
var root_prop = [];
while(root_prop.length < 256) {
(prop = []).chara = emp_str;
root_prop.push(prop);
}
var n_code = 258; // コード登録数
var code_bits = 9; // コード ビット数
var max_n = 512; // 最大コード登録数
var dict_code; // コード
var dict_prop; // 辞書情報
var pack_buff = 0; // コード パック用バッファ
var pack_bits = 0; // コード パック用バッファ格納ビット数
var block_cnt = 0; // ブロック バッファ格納バイト数
put_code(256); // Clear
for(var i_data = 0; i_data < data_len; i_data +=4) {
// 近似色のインデックスを求める
var r = data[i_data ];
var g = data[i_data + 1];
var b = data[i_data + 2];
var min = 0x7fffffff;
var c;
for(i = 0; i < n_colors; i++) {
var dr = r - colors_r[i];
var dg = g - colors_g[i];
var db = b - colors_b[i];
var ra = (r << 1) - dr; // r + colors_r[i]
var d2 = ((1024 + ra) * (dr * dr)) + ((dg * dg) << 11) + ((1536 - ra) * (db * db));
if(d2 < min) {
min = d2;
c = i;
}
}
if(!i_data) { // 最初のピクセル
dict_prop = root_prop[dict_code = c];
continue;
}
var cs = String.fromCharCode(c);
// 辞書サーチ
if((i = dict_prop.chara.indexOf(cs)) < 0) { // 未登録
put_code(dict_code);
if(n_code == 4096) { // 辞書が一杯
// 辞書クリア
put_code(256); // Clear
for(i = 0; i < 256; i++) {
prop = root_prop[i];
prop.chara = emp_str;
prop.length = 0;
}
n_code = 258;
code_bits = 9;
max_n = 512;
}
else {
// 辞書に登録
dict_prop.chara += cs;
(prop = []).chara = emp_str;
prop.code = n_code;
dict_prop.push(prop);
if(n_code++ == max_n) {
code_bits++;
max_n <<= 1;
}
}
dict_prop = root_prop[dict_code = c];
}
else { // 登録済み
dict_code = (dict_prop = dict_prop[i]).code;
}
}
put_code(dict_code); // 最後のコード
put_code(257); // End of Information
// コード パック用バッファ フラッシュ
if(pack_bits)
put_code(0);
// ブロック バッファ フラッシュ
if(block_cnt) {
putc(block_cnt);
for(i = 0; i < block_cnt; i++)
putc(block_buff[i]);
}
putc(0x00); // Block Terminator
//----------------
// 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;
}
}
}
// 256色に減色
function reduce_color(data) {
var leaves = 0; // 葉の数
var root = new Map(); // ルート ノード
var internal = {}; // 内部ノード リスト
for(var i_data = 0; i_data < data_len; i_data += 4) {
var r = data[i_data ];
var g = data[i_data + 1];
var b = data[i_data + 2];
var color = (r << 16) | (g << 8) | b;
// 色追加
for(var level = 0, node = root; ; level++, node = child) {
var key = color & (0x808080 >> level);
var child = node.get(key);
if(child == undefined) { // ノードなし
// ノード追加
node.set(key, child = new Map());
if(level == 7) { // 葉
child.pixels = 1;
child.r_sum = r;
child.g_sum = g;
child.b_sum = b;
leaves++;
while(leaves > 256) { // 葉の数が 256 より多い
// 葉を減らす
for(level = 6; ; level--) {
if(internal[level] != undefined)
break;
}
node = internal[level];
internal[level] = node.next;
var pixels = 0;
var r_sum = 0;
var g_sum = 0;
var b_sum = 0;
for(child of node.values()) {
pixels += child.pixels;
r_sum += child.r_sum;
g_sum += child.g_sum;
b_sum += child.b_sum;
}
leaves -= node.size - 1;
node.clear(); // 子ノード リスト クリア
node.pixels = pixels;
node.r_sum = r_sum;
node.g_sum = g_sum;
node.b_sum = b_sum;
}
break;
}
else {
child.next = internal[level];
internal[level] = child;
}
}
else {
if(!child.size) { // 葉
child.pixels++;
child.r_sum += r;
child.g_sum += g;
child.b_sum += b;
break;
}
}
}
}
// カラー テーブル作成
var i_color = 0;
get_colors(root);
n_colors = leaves;
//----------------
// カラー テーブル作成
function get_colors(node) {
for(var child of node.values()) {
if(child.size) { // 内部ノード
get_colors(child);
}
else {
colors_r[i_color] = child.r_sum / child.pixels;
colors_g[i_color] = child.g_sum / child.pixels;
colors_b[i_color] = child.b_sum / child.pixels;
i_color++;
}
if(i_color == leaves)
break;
}
}
}
// GIF データに 1 バイト出力
function putc(c) {
gif_block[i_gif_block++] = c;
if(i_gif_block == 10000) {
gif_data.push(gif_block);
gif_block = new Uint8Array(10000);
i_gif_block = 0;
}
}
// GIF 表示
function view_gif() {
elem_vid.pause()
elem_gif.src = gif_img;
elem_gif.style.visibility = "";
elem_cls.disabled = false;
}
// GIF 表示終了
function close_gif() {
elem_gif.style.visibility = "hidden";
elem_cls.disabled = true;
}
// GIF ファイル保存
function save_gif() {
with(document.getElementById("dl")) {
var i = file_name.lastIndexOf(".");
download = ((i <= 0) ? file_name : file_name.substring(0, i)) + ".gif";
if(href != "")
URL.revokeObjectURL(href);
href = URL.createObjectURL(new Blob(gif_data, {type:"application/octet-stream"}));
click();
}
}
// 整数 キー入力チェック
function check_int(e) {
return (e.which < 0x20 || e.which >= 0x30 && e.which <= 0x39 || e.ctrlKey || e.metaKey);
}
//----------------------------------------------------------
// カラー テーブル
colors_r = new Uint8Array(256);
colors_g = new Uint8Array(256);
colors_b = new Uint8Array(256);
block_buff = new Uint8Array(255); // ブロック バッファ
// Header,Logical Screen Descriptor
gif_header = [
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // Signature,Version GIF89a
0x00, 0x00, 0x00, 0x00, // Logical Screen Width,Logical Screen Height
0x77, // Global Color Table Flag,Color Resolution,
// Sort Flag,Size of Global Color Table
// 8 ビット,256色
0x00, // Background Color Index
0x00 // Pixel Aspect Ratio
];
I_SCREEN_WIDTH = 6;
I_SCREEN_HEIGHT = 8;
// Application Extension (Loop)
gif_loop = [
0x21, // Extension Introducer
0xff, // Application Label
0x0b, // Block Size
0x4e, 0x45, 0x54, 0x53, 0x43, 0x41, // Application Identifier NETSCAPE
0x50, 0x45,
0x32, 0x2e, 0x30, // Appl. Authentication Code 2.0
0x03, // Sub-block Data Size
0x01, // Sub-block ID
0x00, 0x00, // Number of Times
0 // Block Terminator
];
I_LOOP_TIMES = 16
// Graphic Control Extension,Image Descriptor
gif_frame = [
0x21, // Extension Introducer
0xf9, // Graphic Control Label
4, // Block Size
0x04, // Disposal Method,User Input Flag,Transparent Color Flag
// Do not dispose
0x00, 0x00, // Delay Time
0, // Transparent Color Index
0, // Block Terminator
0x2c, // Image Separator
0x00, 0x00, 0x00, 0x00, // Image Left Position,Image Top Position
0x00, 0x00, 0x00, 0x00, // Image Width,Image Height
0x87, // Local Color Table Flag,Interlace Flag,
// Sort Flag,Size of Local Color Table
// Local Color Table,256色
];
I_DELAY_TIME = 4;
I_IMAGE_WIDTH = 13;
I_IMAGE_HEIGHT = 15;
elem_file = document.getElementById("file");
elem_vid = document.getElementById("vid");
elem_gif = document.getElementById("gif");
elem_h = [document.getElementById("fr_h"), document.getElementById("to_h")];
elem_m = [document.getElementById("fr_m"), document.getElementById("to_m")];
elem_s = [document.getElementById("fr_s"), document.getElementById("to_s")];
elem_ms = [document.getElementById("fr_ms"), document.getElementById("to_ms")];
elem_fr_cur = document.getElementById("fr_cur");
elem_to_cur = document.getElementById("to_cur");
elem_int = document.getElementById("int");
elem_lp_never = document.getElementById("lp_never");
elem_lp_cnt = document.getElementById("lp_cnt");
elem_lp_times = document.getElementById("lp_times");
elem_cre = document.getElementById("cre");
elem_sts = document.getElementById("sts");
elem_frms = document.getElementById("frms");
elem_time = document.getElementById("time");
elem_view = document.getElementById("view");
elem_cls = document.getElementById("cls");
elem_save = document.getElementById("save");
elem_work = document.createElement("CANVAS");
elem_vid.addEventListener("canplay", vid_canplay, false);
elem_vid.addEventListener("error", vid_error, false);
elem_vid.addEventListener("seeked", vid_seeked, false);
elem_h[0].onkeypress = elem_m[0].onkeypress = elem_s[0].onkeypress = elem_ms[0].onkeypress =
elem_h[1].onkeypress = elem_m[1].onkeypress = elem_s[1].onkeypress = elem_ms[1].onkeypress
= check_int;
elem_int.onkeypress = check_int;
elem_lp_times.onkeypress = check_int;
elem_lp_times.onclick = lp_times_click;
frames = undefined;
gif_img = undefined
emp_str = "";
clr_img = elem_gif.src;
// ページを再ロードしたときのため
document.forms[0].reset();
elem_file.disabled = false;
elem_fr_cur.disabled = elem_to_cur.disabled = true;
elem_cre.disabled = true;
elem_view.disabled = true;
elem_cls.disabled = true;
elem_save.disabled = true;
//-->
</SCRIPT>
</BODY>
</HTML>
|