20220108のHTMLに関する記事は6件です。

GIF画像のデータ形式とHTML+JavaScript (ローカル生成)のサンプルプログラム

GIF のデータ形式 大雑把に以下の並びになっています。 バイト位置 バイト数 内容 0 6 ヘッダ (Header) 6 7 論理画面記述子 (Logical Screen Descriptor) (13) - (共通色表:存在する場合) - - 記述子 (Descriptor) ... ... ... - 1 終端子 (Trailer) ヘッダ (Header) バイト位置 バイト数 内容 0 3 花押 (Signature) 3 3 版 (Version) : 「87a」または「89a」 花押 (Signature) バイト位置 文字 10進 16進 0 'G' 71 0x47 1 'I' 73 0x49 2 'F' 70 0x46 版 (Version) 「87a」 バイト位置 文字 10進 16進 3 '8' 56 0x38 4 '7' 55 0x37 5 'a' 79 0x61 版 (Version) 「89a」 バイト位置 文字 10進 16進 3 '8' 56 0x38 4 '9' 57 0x39 5 'a' 79 0x61 論理画面記述子 (Logical Screen Descriptor) バイト位置 内容 6 画面の幅の下位8ビット (Logical Screen Width) 7 画面の幅の上位8ビット (Logical Screen Width) 8 画面の高さの下位8ビット (Logical Screen Height) 9 画面の高さの上位8ビット (Logical Screen Height) 10 混合情報 <Packed Fields> 11 背景色番号 (Background Color Index) 12 ピクセル縦横比 (Pixel Aspect Ratio) (13) (共通色表:存在する場合) (Global Color Table) 混合情報 <Packed Fields> ビット位置 ビット数 内容 備考 0 3 共通色数のビット数(Size of Global Color Table) SGCT と略す 3 1 共通色表は重要度順になっている(Sort Flag) 4 3 論理画面の色解像度(Color Resolution) ビット数(1〜8)を(0〜7)とする 7 1 共通色表あり(Global Color Table Flag) ピクセル縦横比 (Pixel Aspect Ratio) 計算式は次のとおり。 Aspect\ Ratio = \frac{ Pixel\ Aspect\ Ratio + 15 }{ 64 } = \frac{ 幅 } { 高 } 正方形 $\left[1:1\right]$ は $\left(Pixel\ Aspect\ Ratio = 49 \right)$ とする。 共通色表 (Global Color Table) 共有色表が存在する場合は $\left( n=2^{SGCT+1} \right)$ 個の色情報が必要です。 色番号 バイト位置(共通色表) +0 +1 +2 $0$ $0$ R[0] G[0] B[0] $1$ $3$ R[1] G[1] B[1] ... ... ... ... ... $n-1$ $3(n-1)$ R[n-1] G[n-1] B[n-1] $n$ は最大で $256$ です。 記述子 (Descriptor) 記述子には以下の種類があります。 先頭バイト値(Descriptor) 後続バイト値(Label) 内容 0x2C - 画像記述子 (Image Descriptor) 0x3B - 終端子 (Trailer) 0x21 必要 「89a」版による拡張 0x21 0xF9 図形制御拡張 (Graphic Control Extension) 0x21 0xFE 注釈拡張 (Comment Extension) 0x21 0x01 文字画面拡張 (Plain Text Extension) 0x21 0xFF 応用拡張 (Application Extension) 画像記述子 (Image Descriptor) バイト位置(画像記述子) 内容 0 画像記述子 (Image Separator) : 0x2C (Image Descriptor) 1 画像の位置(左)の下位8ビット (Image Left Position) 2 画像の位置(左)の上位8ビット (Image Left Position) 3 画像の位置(上)の下位8ビット (Image Top Position) 4 画像の位置(上)の上位8ビット (Image Top Position) 5 画像の幅の下位8ビット (Image Width) 6 画像の幅の上位8ビット (Image Width) 7 画像の高さの下位8ビット (Image Height) 8 画像の高さの上位8ビット (Image Height) 9 混合情報 <Packed Fields> (10) (固有色表:存在する場合) (Local Color Table) (?) 色番式画像 (Table Based Image Data) 混合情報 <Packed Fields> ビット位置 ビット数 内容 備考 0 3 固有色数のビット数(Size of Local Color Table) SLCT と略す 3 2 予約 (Reserved) 5 1 固有色表は重要度順になっている(Sort Flag) 6 1 縞状形式 (Interlace Flag) 7 1 固有色表あり(Local Color Table Flag) 縞状形式 (Interlace Flag) 縦方向を、次の順に並び替える。 先頭行(Y=0)から8行間隔 5行目(Y=4)から8行間隔 3行目(Y=2)から4行間隔 2行目(Y=1)から2行間隔 固有色表 (Local Color Table) 固有色表が存在する場合は $\left( n=2^{SLCT+1} \right)$ 個の色情報が必要です。 色番号 バイト位置(固有色表) +0 +1 +2 $0$ $0$ R[0] G[0] B[0] $1$ $3$ R[1] G[1] B[1] ... ... ... ... ... $n-1$ $3(n-1)$ R[n-1] G[n-1] B[n-1] $n$ は最大で $256$ です。 色番式画像 (Table Based Image Data) バイト位置(色番式画像) 内容 備考 0 LZW 最小コード幅 (LZW Minimum Code Size) LMCS と略す 1 圧縮画像(副塊情報形式 : Data Sub-blocks) LZW 圧縮 圧縮画像 画像の幅と高さを $w,h$ とすると $(w \times h)$ 個の色番号表が画像データとなります。 \ 0 1 ... w-1 0 $c_0$ $c_1$ ... $c_{w-1}$ 1 $c_w$ $c_{w+1}$ ... $c_{2w-1}$ ... ... ... ... ... h-1 $c_{w(h-1)}$ $c_{w(h-1)+1}$ ... $c_{w h - 1}$ 圧縮画像は、画像データを LZW アルゴリズムで圧縮して副塊情報形式 (Data Sub-blocks) で格納します。 副塊情報形式 (Data Sub-blocks) バイト位置(副塊情報形式) 内容 $0$ 後続のバイト数 $N$ $1$ データ列($N$ バイト) $N+2$ 後続のバイト数 $N+3$ データ列(続き) ... ... ... ... ... ... - 後続のバイト数 - データ列(続き) - 後続のバイト数 = 0 後続のバイト数は最大で 255 です。 「89a」版による拡張 「89a」版による拡張は、以下の形式となっています。 バイト位置(識別子) 内容 0 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) 1 表題 (Label) 2 副塊情報形式 (Data Sub-blocks) - 〃 この形式により、未知の表題 (Label) を未知のデータとして処理可能になっています。 図形制御拡張 (Graphic Control Extension) バイト位置(図形制御拡張) 内容 0 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) 1 図形制御表題 (Graphic Control Label):0xF9 2 情報の大きさ:0x04 (副塊の後続バイト数) 3 混合情報 <Packed Fields> 4 遅延時間(1/100秒単位)の下位8ビット (Delay Time) 5 遅延時間(1/100秒単位)の上位8ビット (Delay Time) 6 透明色とする色番号 (Transparet Color Index) 7 終端:0x00 (副塊の後続バイト数) 混合情報 <Packed Fields> ビット位置 ビット数 内容 0 3 予約 (Reserved) 3 3 消去方法 (Disposal Method) 6 1 ユーザー操作による遅延時間の短縮 (User Input Flag) 7 1 透明色が有効 (Transparent Color Flag) 消去方法 (Disposal Method) 値 内容 0 未定義(デコーダは何もしない) 1 消去しない 2 背景色 (論理画面記述子で指定) を使う 3 一つ前の画像に戻す 4-7 (予約) 注釈拡張 (Comment Extension) バイト位置(注釈拡張) 内容 0 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) 1 注釈表題 (Comment Label):0xFE 2 注釈情報 (副塊情報形式:Data Sub-blocks) - 〃 文字画面拡張 (Plain Text Extension) バイト位置(文字画面拡張) 内容 0 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) 1 文字画面表題 (Plain Text Label):0x01 2 文字画面設定のサイズ (副塊情報形式:Data Sub-blocks):0x0C 3 文字画面の位置(左)の下位8ビット (Text Grid Left Position) 4 文字画面の位置(左)の上位8ビット (Text Grid Left Position) 5 文字画面の位置(上)の下位8ビット (Text Grid Top Position) 6 文字画面の位置(上)の上位8ビット (Text Grid Top Position) 7 文字画面の幅の下位8ビット (Text Grid Width) 8 文字画面の幅の上位8ビット (Text Grid Width) 9 文字画面の高さの下位8ビット (Text Grid Height) 10 文字画面の高さの上位8ビット (Text Grid Height) 11 文字の幅 (Character Cell Width) 12 文字の高さ (Character Cell Height) 13 文字の共通色番号 (Text Foreground Color Index) 14 文字背景の共通色番号 (Text Background Color Index) 15 任意の文字列 (副塊情報形式:Data Sub-blocks) (Plain Text Data) - 〃 応用拡張 (Application Extension) バイト位置(応用拡張) 内容 0 「89a」版拡張 (Extension Introducer):0x21 (Descriptor) 1 応用拡張表題 (Application Extension):0xFF 2 識別子と認識コードのサイズ (副塊情報形式: Data Sub-blocks):0x0B 3 8バイトの識別子 (Application Identifier) 11 3バイトの認識コード (Application Authentication Code) 14 任意の応用拡張情報 (副塊情報形式:Data Sub-blocks) (Application Data) - 〃 終端子 (Trailer) バイト位置(オフセット) 内容 0 終端子 (Trailer):0x3B GIF における LZW のパラメータ LZW 最小コード幅 (LZW Minimum Code Size) を LMCS とします。 内容 値         各コードのビット幅(可変長データ) 3 〜 12 LMCS の最小値 2 初期化コード<Clear Code> $2^{LMCS}$ 最初のコード $2^{LMCS} + 1$ コードの最大値 $2^{12} - 1$ LZW の圧縮と展開 クリアコードを省いているので完全ではありませんが、圧縮と展開のイメージが掴めればと思います。 圧縮と展開の動作を示すサンプル プログラム lzw_sample.js // サンプル: (〒899-7103 鹿児島県)志布志市志布志町志布志 // const SAMPLE_TEXT = 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI'; // コードと文字の変換. const CODE_TO_TEXT = ' BCHIOSU'; const codeToChar = ((c) => CODE_TO_TEXT[c]); // 文字列の並びを反転. const stringReverse = ((s) => s.split('').reverse().join('')); /* * LZW 圧縮 */ function encodeLZW(data, minimum_bits=3) { let encode_data = ''; // 圧縮データ列(10進). let encode_binary = ''; // 圧縮データ列(2進). // 空データ対策. if (data.length == 0) return [encode_data, encode_binary]; const code_end = (1 << minimum_bits); // 終端コード. const code_clear = code_end + 1; // 辞書クリアコード. let code_dict; // 文字列の辞書. let code_curr; // 辞書に追加する文字列のコード. // 辞書の初期化処理. // code_dict = { // ' ': 0, 'B': 1, 'C': 2, 'H': 3, // 'I': 4, 'O': 5, 'S': 6, 'U': 7, // } const initDict = function() { code_dict = new Map(); for (let i = 0; i < CODE_TO_TEXT.length; i++) code_dict.set(codeToChar(i), i); code_curr = code_clear; }; // 圧縮コードのビット列を生成. const make_binary = function(code) { bits = 32 - Math.clz32(code_curr); // 出力するデータのビット数を求める. binary = ''; for (let i = 0; i < bits; i++) binary = String((code >> i) & 1) + binary; return binary; } initDict(); // 辞書の初期化. console.log('圧縮: \'' + data + '\''); console.log('最小ビット = ' + minimum_bits); console.log('終端コード = ' + code_end); console.log('クリアコード = ' + code_clear); console.log('圧縮コードの先頭 = ' + (code_clear + 1)); let diter = data[Symbol.iterator](); // 参照するデータのイテレータ. let str = ''; // 検索文字列. let bin_curr; // 最初の圧縮コード for (;;) { let dinf = diter.next(); // 配列から文字を取り出す. if (dinf.done) break; // データがなければ終了. let ch = dinf.value; // 文字データ. str += ch; // 検索文字列の更新. let bin_try = code_dict.get(str); if (bin_try != undefined) { console.log('文字[\'' + ch + '\'] : 辞書あり: \'' + str + '\''); bin_curr = bin_try; // 検索結果を更新. continue; // 既に辞書にあれば検索文字列を長くする(次のデータへ). } console.log('文字[\'' + ch + '\'] : 辞書なし: \'' + str + '\''); // 圧縮コードは前回の検索結果を出力する. let binary = make_binary(bin_curr); encode_data += ' ' + bin_curr; encode_binary += ' ' + binary; // 辞書に文字列を登録する. code_dict.set(str, ++code_curr); console.log(' 圧縮コード(前回分): \'' + str.slice(0, -1) + '\':' + bin_curr + '=' + binary); console.log(' 新規辞書登録文字列: \'' + str + '\':' + code_curr); /* 本当は code_curr が最大コードに達したら、辞書クリアコードの発行と辞書の初期化が必要 */ // 文字列の最終文字は圧縮保留状態なので、その文字から再開する. str = ch; bin_curr = code_dict.get(str); } {// 最後の圧縮コードを出力する. let binary = make_binary(bin_curr); encode_data += ' ' + bin_curr; encode_binary += ' ' + binary; console.log(' 最後の圧縮コード : \'' + str + '\':' + bin_curr + '=' + binary); } {// 終端コードを出力する. let binary = make_binary(code_end); encode_data += ' ' + code_end; encode_binary += ' ' + binary; console.log(' 終端コード : ' + code_end + '=' + binary); } return [encode_data, encode_binary]; } // 圧縮データを得る. const encode_data = function() { const [encode_list, encode_binary] = encodeLZW(SAMPLE_TEXT); const binaries = encode_binary.slice(1).split(' '); const encbin = binaries.reduce(function(p, v) { return p + stringReverse(v); }, ''); let encstr = encbin; let encdat = []; while (encstr.length > 8) { encdat.push(stringReverse(encstr.slice(0, 8))); encstr = encstr.slice(8); } if (encstr.length > 0) encdat.push(stringReverse((encstr + '00000000').slice(0, 8))); const data = encdat.map((d) => Number('0b' + d)); console.log('圧縮コード(10進):' + encode_list); console.log('圧縮コード( 2進):' + encode_binary); console.log(binaries); // console.log(encbin); console.log('リトル・エンディアンで詰めた圧縮データ(バイト列)'); console.log(encdat); console.log(data); return data; }(); /* * LZW 解凍 */ function decodeLZW(data, minimum_bits=3) { const code_end = (1 << minimum_bits); // 終端コード. const code_clear = code_end + 1; // 辞書クリアコード. let code_dict; // 文字列の辞書. let code_curr; // 辞書に追加する文字列のコード. // 辞書の初期化処理. const initDict = function() { code_dict = [...Array(code_end)].map((_, i) => codeToChar(i)); code_dict.push(undefined, undefined); code_curr = code_clear + 1; }; // データからビット列を取り出す関数. let data_bit_pos = 0; const read = function() { bits = 32 - Math.clz32(code_curr); // 入力するデータのビット数を求める. let bps = (data_bit_pos >> 3); let bpe = ((data_bit_pos + bits - 1) >> 3); let v = data[bps]; if (bps != bpe) { v |= data[++bps] << 8; if (bps != bpe) v |= data[++bps] << 16; } let s = (data_bit_pos & 7); let m = ((1 << bits) - 1); data_bit_pos += bits; return ((v >> s) & m); }; initDict(); // 辞書の初期化. console.log('解凍: 入力は ' + data.length + ' バイト'); console.log('最小ビット = ' + minimum_bits); console.log('終端コード = ' + code_end); console.log('クリアコード = ' + code_clear); console.log('圧縮コードの先頭 = ' + (code_clear + 1)); let decode_data = ''; let code_last = undefined; let char_last = undefined; for (;;) { const code = read(); // コード・データの取得. // 終端コードならば終了. if (code == code_end) { console.log('コード(終端): ' + code); break; } // クリアコードならば辞書の初期化. // if (code == code_clear) { // console.log('クリアコード: ' + code); // initDict(); // code_last = undefined; // char_last = undefined; // continue; // } if (code_last == undefined) { // 辞書を初期化した直後のコード処理. code_last = code; char_last = codeToChar(code); decode_data += char_last; // 1文字出力する console.log('コード(先頭): \'' + char_last + '\':' + code); continue; } let data_last = code_dict[code_last]; let code_data; if (code < code_curr) { // コードは辞書内にあるので、データを辞書から取り出す. code_data = code_dict[code]; console.log('コード(辞書): \'' + code_data + '\':' + code); } else if (code == code_curr) { // 圧縮時に辞書に追加されたパターンに追随する. code_data = data_last + char_last; console.log('コード(追随): \'' + code_data + '\':' + code); } else { console.log('コード(失敗): ' + code + ' > ' + code_curr); throw 'デコード失敗:データの異常を検出しました'; } decode_data += code_data; // データを出力する. // 辞書を更新 char_last = code_data[0]; code_dict.push(data_last + char_last); console.log(' 辞書追加: \'' + data_last + char_last + '\':' + code_curr); code_curr++; // 次へ code_last = code; } return decode_data; } // 圧縮データの解凍をする. console.log('解凍結果: \'' + decodeLZW(encode_data) + '\''); 実行結果 $ node Welcome to Node.js v16.13.1. Type ".help" for more information. > $ node --trace-uncaught lzw_sample.js 圧縮: 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI' 最小ビット = 3 終端コード = 8 クリアコード = 9 圧縮コードの先頭 = 10 文字['S'] : 辞書あり: 'S' 文字['H'] : 辞書なし: 'SH' 圧縮コード(前回分): 'S':6=0110 新規辞書登録文字列: 'SH':10 文字['I'] : 辞書なし: 'HI' 圧縮コード(前回分): 'H':3=0011 新規辞書登録文字列: 'HI':11 文字['B'] : 辞書なし: 'IB' 圧縮コード(前回分): 'I':4=0100 新規辞書登録文字列: 'IB':12 文字['U'] : 辞書なし: 'BU' 圧縮コード(前回分): 'B':1=0001 新規辞書登録文字列: 'BU':13 文字['S'] : 辞書なし: 'US' 圧縮コード(前回分): 'U':7=0111 新規辞書登録文字列: 'US':14 文字['H'] : 辞書あり: 'SH' 文字['I'] : 辞書なし: 'SHI' 圧縮コード(前回分): 'SH':10=1010 新規辞書登録文字列: 'SHI':15 文字['S'] : 辞書なし: 'IS' 圧縮コード(前回分): 'I':4=0100 新規辞書登録文字列: 'IS':16 文字['H'] : 辞書あり: 'SH' 文字['I'] : 辞書あり: 'SHI' 文字[' '] : 辞書なし: 'SHI ' 圧縮コード(前回分): 'SHI':15=01111 新規辞書登録文字列: 'SHI ':17 文字['S'] : 辞書なし: ' S' 圧縮コード(前回分): ' ':0=00000 新規辞書登録文字列: ' S':18 文字['H'] : 辞書あり: 'SH' 文字['I'] : 辞書あり: 'SHI' 文字['B'] : 辞書なし: 'SHIB' 圧縮コード(前回分): 'SHI':15=01111 新規辞書登録文字列: 'SHIB':19 文字['U'] : 辞書あり: 'BU' 文字['S'] : 辞書なし: 'BUS' 圧縮コード(前回分): 'BU':13=01101 新規辞書登録文字列: 'BUS':20 文字['H'] : 辞書あり: 'SH' 文字['I'] : 辞書あり: 'SHI' 文字['C'] : 辞書なし: 'SHIC' 圧縮コード(前回分): 'SHI':15=01111 新規辞書登録文字列: 'SHIC':21 文字['H'] : 辞書なし: 'CH' 圧縮コード(前回分): 'C':2=00010 新規辞書登録文字列: 'CH':22 文字['O'] : 辞書なし: 'HO' 圧縮コード(前回分): 'H':3=00011 新規辞書登録文字列: 'HO':23 文字[' '] : 辞書なし: 'O ' 圧縮コード(前回分): 'O':5=00101 新規辞書登録文字列: 'O ':24 文字['S'] : 辞書あり: ' S' 文字['H'] : 辞書なし: ' SH' 圧縮コード(前回分): ' S':18=10010 新規辞書登録文字列: ' SH':25 文字['I'] : 辞書あり: 'HI' 文字['B'] : 辞書なし: 'HIB' 圧縮コード(前回分): 'HI':11=01011 新規辞書登録文字列: 'HIB':26 文字['U'] : 辞書あり: 'BU' 文字['S'] : 辞書あり: 'BUS' 文字['H'] : 辞書なし: 'BUSH' 圧縮コード(前回分): 'BUS':20=10100 新規辞書登録文字列: 'BUSH':27 文字['I'] : 辞書あり: 'HI' 最後の圧縮コード : 'HI':11=01011 終端コード : 8=01000 圧縮コード(10進): 6 3 4 1 7 10 4 15 0 15 13 15 2 3 5 18 11 20 11 8 圧縮コード( 2進): 0110 0011 0100 0001 0111 1010 0100 01111 00000 01111 01101 01111 00010 00011 00101 10010 01011 10100 01011 01000 [ '0110', '0011', '0100', '0001', '0111', '1010', '0100', '01111', '00000', '01111', '01101', '01111', '00010', '00011', '00101', '10010', '01011', '10100', '01011', '01000' ] リトル・エンディアンで詰めた圧縮データ(バイト列) [ '00110110', '00010100', '10100111', '11110100', '11000000', '01101011', '01001111', '10001100', '00100010', '00010111', '01011101', '00001000' ] [ 54, 20, 167, 244, 192, 107, 79, 140, 34, 23, 93, 8 ] 解凍: 入力は 12 バイト 最小ビット = 3 終端コード = 8 クリアコード = 9 圧縮コードの先頭 = 10 コード(先頭): 'S':6 コード(辞書): 'H':3 辞書追加: 'SH':10 コード(辞書): 'I':4 辞書追加: 'HI':11 コード(辞書): 'B':1 辞書追加: 'IB':12 コード(辞書): 'U':7 辞書追加: 'BU':13 コード(辞書): 'SH':10 辞書追加: 'US':14 コード(辞書): 'I':4 辞書追加: 'SHI':15 コード(辞書): 'SHI':15 辞書追加: 'IS':16 コード(辞書): ' ':0 辞書追加: 'SHI ':17 コード(辞書): 'SHI':15 辞書追加: ' S':18 コード(辞書): 'BU':13 辞書追加: 'SHIB':19 コード(辞書): 'SHI':15 辞書追加: 'BUS':20 コード(辞書): 'C':2 辞書追加: 'SHIC':21 コード(辞書): 'H':3 辞書追加: 'CH':22 コード(辞書): 'O':5 辞書追加: 'HO':23 コード(辞書): ' S':18 辞書追加: 'O ':24 コード(辞書): 'HI':11 辞書追加: ' SH':25 コード(辞書): 'BUS':20 辞書追加: 'HIB':26 コード(辞書): 'HI':11 辞書追加: 'BUSH':27 コード(終端): 8 解凍結果: 'SHIBUSHISHI SHIBUSHICHO SHIBUSHI' サンプル プログラム HTML+JavaScriptでローカル生成[少々長いので折りたたみ] gif_test.html <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" > <title>GIF プログラムのテスト</title> <style> body { margin-left: auto; margin-right: auto; width: 750px; } table { border: solid 1px gray; border-collaspe: collaspe; border-spacing: 0; } th, td { border: solid 1px gray; padding: 4px; } </style> </head> <body> <h2>GIF プログラムのテスト</h2> <table> <tr> <td style="text-align: center;"> Sample 1<br/> <span id="sample1"></span> </td> </tr> <tr> <td style="text-align: center;"> Sample 2<br/> <span id="sample2"></span><br/> 乱数を使用しているのでページを開く度に異なる </td> </tr> <tr> <td style="text-align: center;"> Sample 3<br/> <span id="sample3"></span><br/> 乱数を使用しているのでページを開く度に異なる </td> </tr> <tr> <td style="text-align: center;"> Sample 4<br/> <span id="sample4"></span><br/> 乱数を使用しているのでページを開く度に異なる </td> </tr> <tr> <td style="text-align: center;"> Sample 5<br/> <span id="sample5"></span><br/> 乱数を使用しているのでページを開く度に異なる </td> </tr> <!-- --> <tr> <td style="text-align: center;"> Sample A1<br/> <span id="sampleA1"></span><br/> 乱数を使用しているのでページを開く度に異なる </td> </tr> </table> <script type="text/javascript"> <!-- /* ********************************** */ /* * GIF 形式データを生成するクラス */ class MyGIF { static signature = [0x47, 0x49, 0x46]; static version87a = [0x38, 0x37, 0x61]; static version89a = [0x38, 0x39, 0x61]; // コンストラクタ. constructor(width, height, resolution, background, global_color, aspect) { const S = MyGIF; this.S = S; this.signature = S.signature; this.version = S.version89a; this.logicalScreen = S.createLogicalScreenDescriptor( width, height, resolution, background, global_color, aspect); this.descriptor = []; }; // GIF形式のバイナリを取得する. get_binary() { return [ this.signature, this.version, this.logicalScreen.get_binary(), this.descriptor.map((d, _) => d.get_binary()).flat(), ].flat(); }; // 任意の Descriptor を追加する. appendDescriptor(descriptor) { this.descriptor.push(descriptor); }; // Image Descriptor を追加する. appendImageDescriptor(x, y, w, h, data, color, interlace) { this.descriptor.push(this.S.createImageDescriptor(x, y, w, h, data, color, interlace)); }; // Graphic Control Extension を追加する. appendGraphicControlExtension(delay, method, transparent, input) { this.descriptor.push(this.S.createGraphicControlExtension(delay, method, transparent, input)); }; // Comment Extension を追加する. appendCommentExtension(text) { this.descriptor.push(this.S.createCommentExtension(text)); }; // Plain Text Extension を追加する. appendPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) { this.descriptor.push(this.S.createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text)); }; // Application Extension を追加する. appendApplicationExtension(id, code, data) { this.descriptor.push(this.S.createApplicationExtension(id, code, data)); }; // アニメーションのループ回数を設定する. appendApplicationExtensionForLoop(count) { this.appendApplicationExtension('NETSCAPE', '2.0', [1, (count & 0xff), ((count >> 8) & 0xff)]); }; // Trailer を追加する. appendTrailer() { this.descriptor.push(this.S.createTrailer()); }; // 8 ビット形式の取得. static uint8el(x) { return ((!x ? 0 : x) & 0xff); }; // 16 ビット・リトル・エンディアン形式の取得. static uint16el(x) { const y = (!x ? 0 : x); return [(y & 0xff), ((y >> 8) & 0xff)]; }; // 色情報の過不足を調整. static fixColorTable(depth, color) { const colen = (1 << (depth + 1)) * 3; let table = color.flat(); return ((table.length > colen) ? table.slice(0, colen) : table.concat(Array(colen - table.length).fill(0))); }; // 文字の UTF-8 バイナリ化. static charToUTF8(c) { if (c < 0x0080) return c; if (c < 0x0800) return [ (0xc0 | (c >> 6)), (0x80 | (c & 0x3f)), ]; return [ (0xe0 | (c >> 12)), (0x80 | ((c >> 6) & 0x3f)), (0x80 | (c & 0x3f)), ]; }; static stringToUTF8(s) { return [...Array(s.length)].map((_, i) => MyGIF.charToUTF8(s.charCodeAt(i))).flat(); }; // Data Sub-blocks の生成. static createDataSubBlocks(data) { let block = []; let pos = 0; let length = data.length; while (length) { let slen = (length < 256) ? length : 255; block.push(slen); block.push(data.slice(pos, pos + slen)); pos += slen; length -= slen; }; block.push(0); return block.flat(); }; // Logical Screen Descriptor の生成. static createLogicalScreenDescriptor(width, height, resolution, background, color, sort, aspect) { const S = MyGIF; const D = { logical_screen_width: width, logical_screen_height: height, size_of_global_color_table: 0, sort_flag: sort, color_resolution: resolution, global_color_table_flag: false, background_color_index: background, pixel_aspect_ratio: ((aspect == undefined) ? 49 : aspect), global_color_table: undefined, set_color: (function(table) { const flag = (table != undefined); D.size_of_global_color_table = (!flag ? 0 : (31 - Math.clz32(table.length-1))); D.global_color_table_flag = flag; D.global_color_table = table; }), get_binary: (function() { const sgct = (!D.size_of_global_color_table ? 0 : (7 & D.size_of_global_color_table)); const sort = (!D.sort_flag ? 0 : 1); const res = (!D.color_resolution ? 0 : (7 & D.color_resolution)); const gcf = (!D.global_color_table_flag ? 0 : 1); const bgc = (!D.background_color_index ? 0 : D.background_color_index); const aspect = ((D.pixel_aspect_ratio == undefined) ? 49 : D.pixel_aspect_ratio); const color = (!gcf ? [] : S.fixColorTable(sgct, D.global_color_table)); return [ S.uint16el(D.logical_screen_width), S.uint16el(D.logical_screen_height), (sgct | (sort << 3) | (res << 4) | (gcf << 7)), S.uint8el(bgc), S.uint8el(aspect), color.map((v, _) => S.uint8el(v)), ].flat(); }), }; D.set_color(color); return D; }; // Image Descriptor の生成. static createImageDescriptor(x, y, w, h, data, color, interlace, sort) { const S = MyGIF; const D = { descriptor: 0x2c, image_left_position: x, image_top_position: y, image_width: w, image_height: h, size_of_local_color_table: 0, sort_flag: sort, interlace_flag: interlace, local_color_table_flag: false, local_color_table: undefined, table_based_image_data: S.createTableBasedImageData(data), set_color_table: (function(table) { const flag = (table != undefined); D.size_of_local_color_table = (!flag ? 0 : (31 - Math.clz32(table.length-1))); D.local_color_table_flag = flag; D.local_color_table = table; }), get_binary: (function() { const slct = (!D.size_of_local_color_table ? 0 : (7 & D.size_of_local_color_table)); const sort = (!D.sort_flag ? 0 : 1); const interlace = (!D.interlace ? 0 : 1); const lcf = (!D.local_color_table_flag ? 0 : 1); const color = (!lcf ? [] : S.fixColorTable(slct, D.local_color_table)); return [ D.descriptor, S.uint16el(D.image_left_position), S.uint16el(D.image_top_position), S.uint16el(D.image_width), S.uint16el(D.image_height), (slct | (sort << 5) | (interlace << 6) | (lcf << 7)), color.map((v, _) => S.uint8el(v)), D.table_based_image_data.lzw_minimum_code_size, D.table_based_image_data.data, ].flat(); }), }; D.set_color_table(color); return D; }; // Table Based Image の生成 (LZW圧縮) static createTableBasedImageData(source) { const S = MyGIF; const source_size = source.length; // const src_max = Math.max.apply(null, source); // 'Maximum call stack size exceeded' がでる. const src_max = function() { let m = 0; for (const d of source) if (m < d) m = d; return m; }(); const src_max_clz = (32 - Math.clz32(src_max)); const bits_init = ((src_max_clz < 2) ? 2 : src_max_clz); let bits_curr = (bits_init + 1); const code_base = (1 << bits_init); const code_clear = code_base; const code_end = code_base + 1; const code_max = ((1 << 12) - 1); let code_curr = code_end; let code_step = (1 << bits_curr); const tree = [...Array(code_max + 2)].map((_, i) => ({ code: i, next: -1, down: -1, data: 0 })); const buffer = []; let bs_pos = 0; const write = function(data) { const bits = bits_curr; let idxs = (bs_pos >> 3); const idxe = ((bs_pos + bits - 1) >> 3); data <<= (bs_pos & 7); if (idxs < buffer.length) { buffer[idxs] |= (data & 0xff); data >>= 8; idxs++; }; while (idxs <= idxe) { buffer.push(data & 0xff); data >>= 8; idxs++; }; bs_pos += bits; }; write(code_clear); if (!source_size) { write(code_end); return { lzw_minimum_code_size: bits_init, data: S.createDataSubBlocks(buffer), }; }; let siter = source.values(); let sdat = siter.next(); lzw: for (;;) { let data, next; let node = tree[(data = sdat.value)]; scan: for (;;) { if ((sdat = siter.next()).done) { write(node.code); break lzw; }; data = sdat.value; if ((next = tree[node.down]) == undefined) break; while (data != next.data) if ((next = tree[next.next]) == undefined) break scan; node = next; }; write(node.code); { const next = tree[++code_curr]; // next.code = code_curr; next.next = node.down; next.down = -1; next.data = data; node.down = code_curr; } if (code_curr < code_step) continue; if (code_curr < code_max) { bits_curr++; if ((code_step <<= 1) < code_max) continue; code_step = code_max; continue; }; write(code_clear); bits_curr = bits_init + 1; code_step = (1 << bits_curr); code_curr = code_end; for (let i = 0; i < code_base; i++) { const node = tree[i]; // node.code = i; node.next = -1; node.down = -1; // node.data = 0; }; }; write(code_end); return { lzw_minimum_code_size: bits_init, data: S.createDataSubBlocks(buffer), }; }; // Graphic Control Extension の生成. static createGraphicControlExtension(delay, method, transparent, input) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xf9, disposal_method: method, user_input_flag: input, transparent_color_flag: false, delay_time: delay, transparent_color_index: 0, set_transparent: (function(index) { D.transparent_color_flag = (index != undefined); D.transparent_color_index = (!index ? 0 : index); }), get_binary: (function() { const method = (!D.disposal_method ? 0 : (7 & D.disposal_method)); const input = (!D.user_input_flag ? 0 : 1); const ftrans = (!D.transparent_color_flag ? 0 : 1); return [ D.descriptor, D.label, 4, /* Data Sub-blocks */ ((method << 3) | (input << 6) | (ftrans << 7)), S.uint16el(D.delay_time), S.uint8el(D.transparent_color_index), 0, /* Data Sub-blocks */ ].flat(); }), }; D.set_transparent(transparent); return D; }; // Comment Extension の生成. static createCommentExtension(text) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xfe, text: text, get_binary: (() => [ D.descriptor, D.label, S.createDataSubBlocks(S.stringToUTF8(D.tex)), ].flat()), }; return D; }; // Plain Text Extension の生成. static createPlainTextExtension(x, y, w, h, cw, ch, fc, bc, text) { const S = MyGIF; const D = { descriptor: 0x21, label: 0x01, text_grid_left_position: x, text_grid_top_position: y, text_grid_width: w, text_grid_height: h, character_cell_width: cw, character_cell_height: ch, text_foreground_color_index: fc, text_background_color_index: bc, plain_text_data: text, get_binary: (() => [ D.descriptor, D.label, 12, /* Data Sub-blocks */ S.uint16el(D.text_grid_left_position), S.uint16el(D.text_grid_top_position), S.uint16el(D.text_grid_width), S.uint16el(D.text_grid_height), S.uint8el(D.character_cell_width), S.uint8el(D.character_cell_height), S.uint8el(D.text_foreground_color_index), S.uint8el(D.text_background_color_index), S.createDataSubBlocks(S.stringToUTF8(D.plain_text_data)), ].flat()), }; return D; }; // Application Extension の生成. static createApplicationExtension(id, code, data) { const S = MyGIF; const D = { descriptor: 0x21, label: 0xff, application_identifier: id, application_authentication_code: code, application_data: data, get_binary: (() => [ D.descriptor, D.label, 11, /* Data Sub-blocks */ S.stringToUTF8(D.application_identifier + ' ').slice(0, 8), S.stringToUTF8(D.application_authentication_code + ' ').slice(0, 3), S.createDataSubBlocks(D.application_data.map((v, _) => S.uint8el(v))), ].flat()), }; return D; }; // Trailer の生成. static createTrailer() { return { descriptor: 0x3b, get_binary: (() => 0x3b), }; }; }; /* **************************************** */ /* * Base 64 符号化 */ class MyBase64 { static EncodeTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; static encode(b) { const table = MyBase64.EncodeTable; const blen = b.length; const brem = blen % 3; const bcnt = blen - brem; let s = ''; let i = 0; while (i < bcnt) { const d0 = b[i++]; const d1 = b[i++]; const d2 = b[i++]; const d = (d0 << 16) | (d1 << 8) | d2; s += table[(d >> 18) & 0x3f]; s += table[(d >> 12) & 0x3f]; s += table[(d >> 6) & 0x3f]; s += table[d & 0x3f]; }; if (brem) { const b2 = (brem == 2); const d0 = b[i++]; const d1 = (b2 ? b[i++] : 0); const d = (d0 << 16) | (d1 << 8); s += table[(d >> 18) & 0x3f]; s += table[(d >> 12) & 0x3f]; s += (b2 ? table[(d >> 6) & 0x3f] : '='); s += '='; }; return s; }; }; /* ********************************** */ /* * GIF 画像生成用 */ class MyPixelBuffer { static grayScaleColor = function(count) { const m = 255; const n = count - 1; return [...Array(count)].map(function(_, k) { const l = Math.trunc((k * m) / n); return [l, l, l]; }); }; static grayScaleColor4 = MyPixelBuffer.grayScaleColor(4); static grayScaleColor256 = MyPixelBuffer.grayScaleColor(256); static simpleColorBase = [...Array(8)].map((_, i) => Math.trunc(i * 255 / 7)); static simpleColor = [...Array(256)].map((_, i) => [ MyPixelBuffer.simpleColorBase[(i >> 5) & 7], MyPixelBuffer.simpleColorBase[(i >> 2) & 7], [0, 85, 170, 255][i & 3], ]); static interlaceMap = new Map(); static createInterlaceTable(h) { const m = MyPixelBuffer.interlaceMap; if (m.has(h)) return m.get(h); const ph = [ [0, 8, ((h + 7) >> 3)], [4, 8, ((h + 3) >> 3)], [2, 4, ((h + 1) >> 2)], [1, 2, (h >> 1)], ]; const tab = [...Array(4)].map( (_, i) => [...Array(ph[i][2])].map( (_, j) => ph[i][0] + ph[i][1] * j)).flat(); m.set(h, tab); return tab; }; // コンストラクタ. constructor(color, width, height, defcol=0) { this.S = MyPixelBuffer; this.GIF = MyGIF; this.color = color; this.width = width; this.height = height; this.pixel = Array(width * height).fill(defcol); }; // 点を打つ. setPoint(x, y, c) { const w = this.width; if ((0 <= x) && (x < w) && (0 <= y) && (y < this.height)) { const p = y * w + x; if ((p < 0) || (this.pixel.length <= p)) { console.log(p); }; this.pixel[p] = c; } }; // 線を引く. setLine(x1, y1, x2, y2, c, sep) { const dx = Math.trunc(Math.abs(x1 - x2)); const dy = Math.trunc(Math.abs(y1 - y2)); if (!dx) this.setVirticalLine(x1, y1, y2, c); else if (!dy) this.setHorizontalLine(x1, x2, y1, c); else if (dx >= dy) this.setLineH(x1, y1, x2, y2, dx, dy, c, sep); else this.setLineV(x1, y1, x2, y2, dx, dy, c, sep); }; setLineH(x1, y1, x2, y2, dx, dy, c, sep) { const S = this.S; const w = this.width; const h = this.height; let xs, ys, xe, ye; if (x1 < x2) { xs = x1; ys = y1; xe = x2; ye = y2; } else { xs = x2; ys = y2; xe = x1; ye = y1; }; if ((xe < 0) || (w <= xs)) return; const ay = ((ys < ye) ? +1 : -1); const ap = (ay < 0) ? -w: +w; let k = (dy >> 1); dx++; if (sep) { dy++; k = 0; }; if (xs < 0) { const kt = k - (dy * xs); const yh = Math.trunc(kt / dx); xs = 0; ys += ((ay < 0) ? -yh : +yh); k = (kt % dx); }; if (w <= xe) xe = w - 1; const yt = Math.min(ys, ye); const yb = Math.max(ys, ye); if ((yb < 0) || (h <= yt)) return; if (ay < 0) { if (ye < 0) ye = 0; if (h <= ys) { const yh = ys - h + 1; const kt = k + (dx * yh); const xw = Math.trunc(kt / dy); xs += xw; ys = h - 1; k = kt % dy; }; } else { if (h <= ye) ye = h - 1; if (ys < 0) { const kt = k - (dx * ys); const xw = Math.trunc(kt / dy); xs += xw; ys = 0; k = kt % dy; }; }; ye += ay; const pixel = this.pixel; let p = ys * w + xs; while (xs++ <= xe) { pixel[p++] = c; if ((k += dy) >= dx) { k -= dx; p += ap; if ((ys += ay) == ye) break; }; }; }; setLineV(x1, y1, x2, y2, dx, dy, c, sep) { const S = this.S; const w = this.width; const h = this.height; let xs, ys, xe, ye; if (y1 < y2) { xs = x1; ys = y1; xe = x2; ye = y2; } else { xs = x2; ys = y2; xe = x1; ye = y1; }; if ((ye < 0) || (h <= ys)) return; const ax = ((xs < xe) ? +1 : -1); let k = (dy >> 1); dy++; if (sep) { dx++; k = 0; }; if (ys < 0) { const kt = k - (dx * ys); const xw = Math.trunc(kt / dy); xs += ((ax < 0) ? -xw : +xw); ys = 0; k = (kt % dy); }; if (h <= ye) ye = h - 1; const xl = Math.min(xs, xe); const xr = Math.max(xs, xe); if ((xr < 0) || (w <= xl)) return; if (ax < 0) { if (xe < 0) xe = 0; if (w <= xs) { const xw = xs - w + 1; const kt = k + (dy * xw); const yh = Math.trunc(kt / dx); xs = w - 1; ys += yh; k = kt % dx; }; } else { if (w <= xe) xe = w - 1; if (xs < 0) { const kt = k - (dy * xs); const yh = Math.trunc(kt / dx); xs = 0; ys += yh; k = kt % dx; }; }; xe += ax; const pixel = this.pixel; let p = ys * w + xs; while (ys++ <= ye) { pixel[p] = c; p += w; if ((k += dx) >= dy) { k -= dy; p += ax; if ((xs += ax) == xe) break; }; }; }; // 水平線を引く. setHorizontalLine(x1, x2, y, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const xs = Math.trunc(Math.min(x1, x2)); const xe = Math.trunc(Math.max(x1, x2)); if ((xe < 0) || (w <= xs) || (y < 0) || (h <= y)) return; const us = Math.trunc(Math.max(xs, 0)); const ue = Math.trunc(Math.min(xe, w-1)); const yp = y * w; pixel.fill(c, yp + us, yp + ue + 1); } // 垂直線を引く. setVirticalLine(x, y1, y2, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const ys = Math.trunc(Math.min(y1, y2)); const ye = Math.trunc(Math.max(y1, y2)); if ((x < 0) || (w <= x) || (ye < 0) || (h <= ys)) return; const vs = Math.trunc(Math.max(ys, 0)); const ve = Math.trunc(Math.min(ye, h-1)); const q = ve * w + x; for (let p = vs * w + x; p <= q; p += w) pixel[p] = c; } // 四角形を描く. setBox(x1, y1, x2, y2, c) { const xs = Math.trunc(Math.min(x1, x2)); const xe = Math.trunc(Math.max(x1, x2)); const ys = Math.trunc(Math.min(y1, y2)); const ye = Math.trunc(Math.max(y1, y2)); this.setHorizontalLine(xs, xe, ys, c); if (ys != ye) { this.setVirticalLine(xs, ys+1, ye-1, c); if (xs != xe) this.setVirticalLine(xe, ys+1, ye-1, c); this.setHorizontalLine(xs, xe, ye, c); }; }; // 四角形で塗りつぶす. setBoxFill(x1, y1, x2, y2, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const xs = Math.min(x1, x2); const xe = Math.max(x1, x2); const ys = Math.min(y1, y2); const ye = Math.max(y1, y2); if ((xe < 0) || (w <= xs) || (ye < 0) || (h <= ys)) return; const us = Math.trunc(Math.max(xs, 0)); const ue = Math.trunc(Math.min(xe, w-1)); const vs = Math.trunc(Math.max(ys, 0)); const ve = Math.trunc(Math.min(ye, h-1)); const nu = (ue - us) + 1; const nv = (ve - vs) + 1; let vp = vs * w + us; const vq = nv * w + vp; for (; vp < vq; vp += w) pixel.fill(c, vp, vp + nu); }; // 円描画用の座標情報を生成する. static getCirclePos(radius) { if (radius <= 0) return [[0, 0]]; let x = radius; let y = 0; let s = 0; let t = 1; let u = (radius << 1) - 1; let v = u; /* 大きな円は(上下左右)端の直線を少し短くする */ u -= ((u >> 2) + (u >> 3) + (u >> 4)); let otmp = new Array(); const out1 = new Array(); const out2 = new Array(); while (x > y) { otmp.push(y); out2.push([x]); y = y + 1; s = s + t; t = t + 2; if (s < u) continue; out1.push(otmp); x = x - 1; v = v - 2; u = u + v; otmp = new Array(); }; if (x == y) out2.push([x]); if (otmp.length) out1.push(otmp); out1.reverse(); return out2.concat(out1); }; // 円を描く. setCircle(x, y, r, c) { const w = this.width; const h = this.height; if (((x + r + 1) < 0) || (w < (x - r)) || ((y + r + 1) < 0) || (h < (y - r))) return; const pixel = this.pixel; const ypos = this.S.getCirclePos(r); const ylen = ypos.length; { let yi = 0; let ys = y; if (ys < 0) { yi = -ys; ys = 0; }; let yp = ys * w; while (yi < ylen) { if (ys >= h) break; for (const xp of ypos[yi]) { const xl = x - xp; const xr = x + xp; if (w <= xl) continue; if (xr < 0) continue; if (xr < w) pixel[yp + xr] = c; if (0 <= xl) pixel[yp + xl] = c; }; yi++; ys++; yp += w; }; }; { let yi = 0; let ys = y; if (ys >= h) { yi += (ys - h + 1); ys = h - 1; }; let yp = ys * w; while (yi < ylen) { if (ys < 0) break; for (const xp of ypos[yi]) { const xl = x - xp; const xr = x + xp; if (w <= xl) continue; if (xr < 0) continue; if (xr < w) pixel[yp + xr] = c; if (0 <= xl) pixel[yp + xl] = c; }; ys--; yi++; yp -= w; }; }; }; // 円で塗りつぶす. setCircleFill(x, y, r, c) { const w = this.width; const h = this.height; if (((x + r + 1) < 0) || (w < (x - r)) || ((y + r + 1) < 0) || (h < (y - r))) return; const pixel = this.pixel; const ypos = this.S.getCirclePos(r); const ylen = ypos.length; { let yi = 0; let ys = y; if (ys < 0) { yi = -ys; ys = 0; }; let yp = ys * w; while (yi < ylen) { if (ys >= h) break; const yl = ypos[yi]; const xp = yl[yl.length - 1]; let xl = x - xp; let xr = x + xp; if ((0 <= xr) && (xl < w)) { if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); } yi++; ys++; yp += w; }; }; { let yi = 0; let ys = y; if (ys >= h) { yi += (ys - h + 1); ys = h - 1; }; let yp = ys * w; while (yi < ylen) { if (ys < 0) break; const yl = ypos[yi]; const xp = yl[yl.length - 1]; let xl = x - xp; let xr = x + xp; if ((0 <= xr) && (xl < w)) { if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); } ys--; yi++; yp -= w; }; }; }; // 凸多角形を描画する. setPolygon(vertices, c) { const w = this.width; const h = this.height; const pixel = this.pixel; const vlen = vertices.length; if (!vlen) return; let xmin = w, xmax = -1; let ymin = h, ymax = -1; for (const [x, y] of vertices) { xmin = Math.min(xmin, x); xmax = Math.max(xmax, x); ymin = Math.min(ymin, y); ymax = Math.max(ymax, y); }; xmin = Math.trunc(xmin); xmax = Math.trunc(xmax); ymin = Math.trunc(ymin); ymax = Math.trunc(ymax); if ((xmax < 0) || (w <= xmin) || (ymax < 0) || (h <= ymin)) return; const raster = [...Array(h)].map(() => []); let [nx, ny] = vertices[vlen - 1]; for (const np of vertices) { const lx = nx; const ly = ny; [nx, ny] = np; const ay = (ny < ly) ? -1 : +1; const dx = nx - lx; const dy = (ay < 0) ? (ly - ny) : (ny - ly); if (dy == 0) { // if ((0 <= ly) && (ly < h)) // raster[ly].push(lx, nx); continue; }; let ys = ly; let ye = ny; let yp = 0; if (ay >= 0) { if ((ye < 0) || (h <= ys)) continue; if (ys < 0) { yp -= ys; ys = 0; }; if (ye >= h) ye = h - 1; } else { if ((ys < 0) || (h <= ye)) continue; if (ys >= h) { yp += ys - h + 1; ys = h - 1; }; if (ye < 0) ye = -1; }; for (; ys != ye; ys += ay, yp++) raster[ys].push(lx + Math.trunc(dx * yp / dy)); }; const rcmp = ((a, b) => (a - b)); let rs = Math.trunc(Math.max(ymin, 0)); let re = Math.trunc(Math.min(ymax, h - 1)); for (let yp = rs * w; rs <= re; yp += w) { const rp = raster[rs++]; const rplen = rp.length; if (rplen == 1) continue; rp.sort(rcmp); for (let np = 0; np < rplen; np += 2) { let xl = rp[np]; let xr = rp[np + 1]; if (xl > xr) [xl, xr] = [xr, xl]; if ((xr < 0) || (w <= xl)) continue; if (xl < 0) xl = 0; if (xr >= w) xr = w - 1; pixel.fill(c, yp + xl, yp + xr + 1); } }; }; // インターレース画像を作成する. createInterlaced() { const tab = this.S.createInterlaceTable(h); const w = this.width; const h = this.height; const pixel = this.pixel; const line = [...Array(h)].map( function(_, y) { const p = y * w; return pixel.slice(p, p + w); }); return [...Array(h)].map((_, y) => line[tab[y]]).flat(); }; // GIF 処理用オブジェクトを作成する. createGIF(global_color) { const color = (global_color ? this.color : undefined); return new this.GIF(this.width, this.height, (8 - 1), 0, color); }; // Image Descriptor を作成する. createImageDescriptor(local_color, x, y, interlace) { const color = (local_color ? this.color : undefined); const w = this.width; const h = this.height; const pixel = this.pixel; const data = (!interlace ? pixel : this.createInterlaced()); return this.GIF.createImageDescriptor(x, y, w, h, data, color, interlace); }; }; /* ********************************** */ /* * テスト */ function createImgWithBase64(b64s, id) { const priority = 'important'; const img = document.createElement('img'); if (id != undefined) img.setAttribute('id', id); img.setAttribute('class', 'b64img'); img.setAttribute('src', 'data:image/gif;charset=utf-8;base64,' + b64s); img.style.setProperty('margin', '0', priority); img.style.setProperty('padding', '8px', priority); img.style.setProperty('image-rendering', 'pixelated', priority); return img; } function intRand(w) { return Math.trunc(Math.random() * w); } function makeSampleGIF(fb) { const gif = fb.createGIF(true); gif.appendDescriptor(fb.createImageDescriptor()); gif.appendTrailer(); return gif; } function makeSample1FB() { const fb = new MyPixelBuffer(MyPixelBuffer.grayScaleColor256, 640, 480); for (let p = 0; p < 256; p++) fb.setBox(p, p, 639-p, 479-p, p); return fb; } function makeSample1() { return makeSampleGIF(makeSample1FB()); } function makeSample2FB() { const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480); for (let n = 0; n < 20; n++) { const x1 = intRand(640 + 320) - 160; const y1 = intRand(480 + 240) - 120; const w = intRand(320) + 16; const h = intRand(240) + 16; const c = intRand(256); const x2 = x1 + w; const y2 = y1 + h; fb.setLine(x1, y1, x2, y2, c); fb.setLine(x2, y1, x1, y2, c); fb.setBox(x1, y1, x2, y2, c); fb.setCircle(y1, x1, Math.min(x2, y2), c); } return fb; } function makeSample2() { return makeSampleGIF(makeSample2FB()); } function makeSample3FB() { const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480); for (let n = 0; n < 100; n++) { const x = intRand(640+32) - 16; const y = intRand(480+32) - 16; const w = intRand(160); const h = intRand(120); const c = intRand(256); fb.setBoxFill(x, y, x+w, y+h, c); } return fb; } function makeSample3() { return makeSampleGIF(makeSample3FB()); } function makeSample4FB() { const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480); for (let n = 0; n < 100; n++) { const x = intRand(640 + 320) - 160; const y = intRand(480 + 240) - 120; const r = intRand(80); const c = intRand(256); fb.setCircleFill(x, y, r, c); } return fb; } function makeSample4() { return makeSampleGIF(makeSample4FB()); } function makeSample5FB() { const fb = new MyPixelBuffer(MyPixelBuffer.simpleColor, 640, 480); for (let n1 = 0; n1 < 100; n1++) { const np = intRand(5) + 3; let x = intRand(640 + 320) - 160; let y = intRand(480 + 240) - 120; const c = intRand(256); const pl = new Array([x, y]); let da = intRand(360); let ra = 300; for (let n2 = 1; n2 < np; n2++) { const dr = da * Math.PI / 180; const dl = intRand(120) + 10; const dx = Math.trunc(dl * Math.cos(dr)); const dy = Math.trunc(dl * Math.sin(dr)); pl.push([(x += dx), (y += dy)]); const ma = Math.trunc(ra / (np - n2)) >> 1; const na = intRand(ma) + ma; da += na; ra -= na; } fb.setPolygon(pl, c); } fb.setPolygon([[10, 10], [100, 10], [10, 100]], 255); // fb.setPolygon([[640-10, 480-10], [640-100, 480-10], [640-100, 480-100], [640-10, 480-100]], 0x1c); fb.setPolygon([[640-10, 480-10], [640-100, 480-10], [640-10, 480-100]], 255); // fb.setPolygon([[640-10, 10], [10, 150], [640-150, 480-10]], 0x03); return fb; } function makeSample5() { return makeSampleGIF(makeSample5FB()); } function makeSampleA() { const fb1 = makeSample1FB(); const fb2 = makeSample2FB(); const fb3 = makeSample3FB(); const fb4 = makeSample4FB(); const fb5 = makeSample5FB(); const gif = fb2.createGIF(); gif.appendApplicationExtensionForLoop(0); gif.appendGraphicControlExtension(30); gif.appendDescriptor(fb1.createImageDescriptor(true)); gif.appendGraphicControlExtension(30); gif.appendDescriptor(fb2.createImageDescriptor(true)); gif.appendGraphicControlExtension(30); gif.appendDescriptor(fb3.createImageDescriptor(true)); gif.appendGraphicControlExtension(30); gif.appendDescriptor(fb4.createImageDescriptor(true)); gif.appendGraphicControlExtension(30); gif.appendDescriptor(fb5.createImageDescriptor(true)); gif.appendTrailer(); return gif; } function setSample(id, gif) { const tag = document.getElementById(id); if (tag == undefined) return; const img = createImgWithBase64(MyBase64.encode(gif().get_binary())); while (tag.firstChild) tag.removeChild(giftag.firstChild); tag.append(img); } function onLoad() { setSample('sample1', makeSample1); setSample('sample2', makeSample2); setSample('sample3', makeSample3); setSample('sample4', makeSample4); setSample('sample5', makeSample5); setSample('sampleA1', makeSampleA); } onLoad(); /* ********************************** */ // !--> </script> </body> </html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Scratchの作品をサイトに埋め込む方法

Scratchの作品をサイトに埋め込む方法 ①作品ページに行き、「リンクをコピー」ボタンを押す ②「埋め込みコードをコピー」をクリック ③HTMLの中に書く <body> <iframe src="https://scratch.mit.edu/projects/568126788/embed" allowtransparency="true" width="485" height="402" frameborder="0" scrolling="no" allowfullscreen ></iframe> </body> ④完了 こんな感じに表示されます Scratchのマークをクリックすると、Scratchのトップページを開けます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

アクセシビリティを意識した<Table>の作り方

概要 HTMLのTABLEタグでテーブルを作るとき、いろんなタグが使うことができる。 <table>からはじまり、<thead>、<tbody>、<tr>、<th>、<rd>など、 Tableを作るのにたくさんタグが使うことができ、複雑でややこしい。 この記事では、テーブルで使うことができるタグの役割を会せる、 テーブルができる最小のサンプルと アクセシビリティを意識したサンプルの解説を行います。 TABLEで使えるタグについて <table> : 表要素 テーブル形式のデータを表すフローコンテンツです。 <table> <!-- 以下にテーブルの要素が入ります。--> </table> <caption> : 表キャプション要素 テーブルのキャプション (またはタイトル) を指定する要素です。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <!-- 以下にテーブルの要素が入ります。--> </table> <colgroup> : 列グループ定義要素 テーブル内の列(column)のグループを定義する要素です。 子要素の、<col>のspan属性を指定することで、列をグループ化できます。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <!-- 以下にテーブルの要素が入ります。--> </table> <thead> : 表ヘッダー要素 テーブルの列の見出しを定義する要素です。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th>りんご</th> <th>みかん</th> <th>にんじん</th> <th>とまと</th> </tr> </thead> <!-- 以下にテーブルの要素が入ります。--> </table> <tbody> : 表本体要素 テーブルの本体要素で、行要素の<tr>を内包します。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th>りんご</th> <th>みかん</th> <th>にんじん</th> <th>とまと</th> </tr> </thead> <tbody> <tr> <th>太郎くん</th> <td>5個</td>             <td>10個</td> <td>3個</td>             <td>2個</td> </tr> <tr> <th>二郎くん</th> <td>2個</td>             <td>15個</td> <td>2個</td>             <td>4個</td> </tr> </tbody> <!-- 以下にテーブルの要素が入ります。--> </table> <tfoot> : 表本体要素 テーブル内の列を総括する行を追加する要素です。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th>りんご</th> <th>みかん</th> <th>にんじん</th> <th>とまと</th> </tr> </thead> <tbody> <tr> <th>太郎くん</th> <td>5個</td>             <td>10個</td> <td>3個</td>             <td>2個</td> </tr> <tr> <th>二郎くん</th> <td>2個</td>             <td>15個</td> <td>2個</td>             <td>4個</td> </tr> </tbody> <tfoot> <tr> <th>合計</th> <td>7個</td>             <td>25個</td> <td>5個</td>             <td>6個</td> </tr> </tfoot> </table> <tr> : 表本体要素 表内でセルの行を定義する要素で、<th> と <td>を内容します。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <!-- 以下にテーブルの要素が入ります。--> </tr> </thead> <tbody> <tr> <!-- 以下にテーブルの要素が入ります。--> </tr> <tr> <!-- 以下にテーブルの要素が入ります。--> </tr> </tbody> <tfoot> <tr> <!-- 以下にテーブルの要素が入ります。--> </tr> </tfoot> </table> <th> : table header要素 表のセルのヘッダーであるセルを定義する要素です。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th>りんご</th> <th>みかん</th> <th>にんじん</th> <th>とまと</th> </tr> </thead> <tbody> <tr> <th>太郎くん</th> <!-- 以下にテーブルの要素が入ります。--> </tr> <tr> <th>二郎くん</th> <!-- 以下にテーブルの要素が入ります。--> </tr> </tbody> <tfoot> <tr> <th>合計</th> <!-- 以下にテーブルの要素が入ります。--> </tr> </tfoot> </table> <td> : table data要素 表のセルのデータを定義する要素です。 <table> <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th>りんご</th> <th>みかん</th> <th>にんじん</th> <th>とまと</th> </tr> </thead> <tbody> <tr> <th>太郎くん</th> <td>5個</td>             <td>10個</td> <td>3個</td>             <td>2個</td> </tr> <tr> <th>二郎くん</th> <td>2個</td>             <td>15個</td> <td>2個</td>             <td>4個</td> </tr> </tbody> <tfoot> <tr> <th>合計</th> <td>7個</td>             <td>25個</td> <td>5個</td>             <td>6個</td> </tr> </tfoot> </table> TABLEの最小サンプル <table>タグは以下の要素が必要になります。 <table>タグの中には以下コンテンツが入る 1 任意の1個の<caption> 要素 省略可 2 0個以上の<colgroup> 要素 省略可 3 任意の1個の<thead> 要素 省略可 4 次の2つの選択肢から1つ ・ 0個以上の<tbody> 要素 ・ 1個以上の<tr> 要素 5 任意の1個の<tfoot> 要素 この要素をかみすると最小でテーブルを作るとこうなります。 <table> <tr> <th>太郎くん</th> <td>10さい</td> </tr> </table> アクセシビリティを意識したサンプル <table > <caption>太郎くんと二郎くんがスーパーで買った果物と野菜の表</caption> <colgroup> <col span="2"> <!--果物--> <col span="2"> <!--野菜--> </colgroup> <thead> <tr> <td> </td> <th scope="col">りんご</th> <th scope="col">みかん</th> <th scope="col">にんじん</th> <th scope="col">とまと</th> </tr> </thead> <tbody> <tr> <th scope="row">太郎くん</th> <td>5個</td>             <td>10個</td> <td>3個</td>             <td>2個</td> </tr> <tr> <th scope="row">二郎くん</th> <td>2個</td>             <td>15個</td> <td>2個</td>             <td>4個</td> </tr> </tbody> <tfoot> <tr> <th scope="row">合計</th> <td>7個</td>             <td>25個</td> <td>5個</td>             <td>6個</td> </tr> </tfoot> </table> アクセシビリティで、意識すること caption要素やsummary属性を追加することで、スクリーンリーダーユーザーが概略を理解できるようにする HTML5では、summary属性は廃止されたので使う際は注意が必要。 summary属性をサポートしている人としてない人で情報に差が出てしまうので、要注意と評価される。 そのため、サンプルでは、caption要素しか追加してません。 ただ、caption要素とsummary属性を追加する場合は、違う内容を提供しなければなりません。 ref H39: データテーブルのキャプションとデータテーブルを関連付けるために、caption 要素を使用する H73: データテーブルの概要を提供するために、table 要素の summary 属性を使用する データテーブルで見出しセルとデータセルを関連付けるために、scope 属性を使用する th要素全てに、scope 属性があることが必要です。 見出しとしての役割を果たす全ての td 要素に、scope 属性があることが必要です。 全てのscope属性に、値として row、col、rowgroup、又は colgroup があることを確認する。 ref H63: データテーブルで見出しセルとデータセルを関連付けるために、scope 属性を使用する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?カメラ機能を作成してみよう *リアカメラ編

今回はリアカメラ編を作成しようと思います リアカメラとは、キーボードがある方向のカメラです じゃ作ってみるでー <li><a href="#area-1">WEBカメラの映像を表示</a></li> <li><a href="#area-2">リアカメラでの、撮影</a></li> <li><a href="#area-3">フロントカメラでの撮影</a></li> <section class="area" id="area-1"> <footer id="footer"> <p class="js-scroll scroll-top scroll-view"><a href="#area-2">Scroll</a></p> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <section class="area" id="area-1"> <h2>WEBカメラの映像を表示</h2> </head> <body> <div> <h1>WEBカメラの映像を表示</h1> <div> <video id="video"></video> </div> </div> <script> const video = document.getElementById("video") navigator.mediaDevices.getUserMedia({ video: true, audio: false, }).then(stream => { video.srcObject = stream; video.play() }).catch(e => { console.log(e) }) </script> </body> </html> </body> </html> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Camera Test</title> <style> canvas, video{ border: 1px solid gray; } </style> </head> <body> <html> <body> <ol> <pre> <section class="area" id="area-2"> <h1>撮影します。</h1> 撮影したものは、右側の画面です。 <video id="camera" width="300" height="200"></video> <canvas id="picture" width="300" height="200"></canvas> <form> <button type="button" id="shutter">撮影する</button> </form> <audio id="se" preload="auto"> <source src="camera-shutter1.mp3" type="audio/mp3"> </audio> <script> window.onload = () => { const video = document.querySelector("#camera"); const canvas = document.querySelector("#picture"); const se = document.querySelector('#se'); /** カメラ設定 */ const constraints = { audio: false, video: { width: 300, height: 200, facingMode: "user" // フロントカメラを利用する // facingMode: { exact: "environment" } // リアカメラを利用する場合 } }; /** * カメラを<video>と同期 */ navigator.mediaDevices.getUserMedia(constraints) .then( (stream) => { video.srcObject = stream; video.onloadedmetadata = (e) => { video.play(); }; }) .catch( (err) => { console.log(err.name + ": " + err.message); }); /** * 撮影 */ document.querySelector("#shutter").addEventListener("click", () => { const ctx = canvas.getContext("2d"); // 演出的な目的で一度映像を止めてSEを再生する video.pause(); // 映像を停止 se.play(); // シャッター音 setTimeout( () => { video.play(); // 1.5秒後にカメラ再開 }, 1500); // canvasに画像を貼り付ける ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const c = document.getElementById('picture'); c.toBlob((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); var hours = nowDate.getHours(); var minutes = nowDate.getMinutes(); var Seconds = nowDate.getSeconds(); var today = year + '年' + month + '月' + date + '日' + hours + '時' + minutes + '分' + Seconds +'秒'; a.download = today + '.png'; a.href = url; a.click(); a.remove(); setTimeout(() => { URL.revokeObjectURL(url); }, 1E4); }, 'image/png'); }); }; </script> </body> </html> <html> <body> <ol> <pre> <section class="area" id="area-3"> <h1>撮影します。</h1> 撮影したものは、右側の画面です。 <video id="camera" width="300" height="200"></video> <canvas id="picture" width="300" height="200"></canvas> <form> <button type="button" id="shutter">撮影する</button> </form> <audio id="se" preload="auto"> <source src="camera-shutter1.mp3" type="audio/mp3"> </audio> <script> window.onload = () => { const video = document.querySelector("#camera"); const canvas = document.querySelector("#picture"); const se = document.querySelector('#se'); /** カメラ設定 */ const constraints = { audio: false, video: { width: 300, height: 200, facingMode: "user" // フロントカメラを利用する // facingMode: { exact: "environment" } // リアカメラを利用する場合 } }; /** * カメラを<video>と同期 */ navigator.mediaDevices.getUserMedia(constraints) .then( (stream) => { video.srcObject = stream; video.onloadedmetadata = (e) => { video.play(); }; }) .catch( (err) => { console.log(err.name + ": " + err.message); }); /** * 撮影 */ document.querySelector("#shutter").addEventListener("click", () => { const ctx = canvas.getContext("2d"); // 演出的な目的で一度映像を止めてSEを再生する video.pause(); // 映像を停止 se.play(); // シャッター音 setTimeout( () => { video.play(); // 3秒後にカメラ再開 }, 3000); // canvasに画像を貼り付ける ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const c = document.getElementById('picture'); c.toBlob((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); var hours = nowDate.getHours(); var minutes = nowDate.getMinutes(); var Seconds = nowDate.getSeconds(); var today = year + '年' + month + '月' + date + '日' + hours + '時' + minutes + '分' + Seconds +'秒'; a.download = today + '.png'; a.href = url; a.click(); a.remove(); setTimeout(() => { URL.revokeObjectURL(url); }, 1E4); }, 'image/png'); }); }; </script> </body> </html> <footer id="footer"> <p class="js-scroll scroll-top scroll-view"><a href="#area-2">Scroll</a></p> <p class="js-pagetop scroll-top"><a href="#">Page Top</a></p> <small>&copy; copyright.</small> </footer> すべてコピペで使用できます 保存名は、その時の時間を、秒数で表します まとめ      参考になると嬉しいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?カメラ機能を作成しよう *フロントカメラ編

カメラ機能が、パソコンに入ってなかった人優先でカメラ機能を 作ってみました 有線カメラを、パソコンに付けると監視カメラにもなりますね それとピクチャインピクチャを利用するといつでも見れますね それじゃ作ってみよう <li><a href="#area-1">WEBカメラの映像を表示</a></li> <li><a href="#area-2">リアカメラでの、撮影</a></li> <li><a href="#area-3">フロントカメラでの撮影</a></li> <section class="area" id="area-1"> <footer id="footer"> <p class="js-scroll scroll-top scroll-view"><a href="#area-2">Scroll</a></p> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <section class="area" id="area-1"> <h2>WEBカメラの映像を表示</h2> </head> <body> <div> <h1>WEBカメラの映像を表示</h1> <div> <video id="video"></video> </div> </div> <script> const video = document.getElementById("video") navigator.mediaDevices.getUserMedia({ video: true, audio: false, }).then(stream => { video.srcObject = stream; video.play() }).catch(e => { console.log(e) }) </script> </body> </html> </body> </html> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Camera Test</title> <style> canvas, video{ border: 1px solid gray; } </style> </head> <body> <html> <body> <ol> <pre> <section class="area" id="area-2"> <h1>撮影します。</h1> 撮影したものは、右側の画面です。 <video id="camera" width="300" height="200"></video> <canvas id="picture" width="300" height="200"></canvas> <form> <button type="button" id="shutter">撮影する</button> </form> <audio id="se" preload="auto"> <source src="camera-shutter1.mp3" type="audio/mp3"> </audio> <script> window.onload = () => { const video = document.querySelector("#camera"); const canvas = document.querySelector("#picture"); const se = document.querySelector('#se'); /** カメラ設定 */ const constraints = { audio: false, video: { width: 300, height: 200, facingMode: "user" // フロントカメラを利用する // facingMode: { exact: "environment" } // リアカメラを利用する場合 } }; /** * カメラを<video>と同期 */ navigator.mediaDevices.getUserMedia(constraints) .then( (stream) => { video.srcObject = stream; video.onloadedmetadata = (e) => { video.play(); }; }) .catch( (err) => { console.log(err.name + ": " + err.message); }); /** * 撮影 */ document.querySelector("#shutter").addEventListener("click", () => { const ctx = canvas.getContext("2d"); // 演出的な目的で一度映像を止めてSEを再生する video.pause(); // 映像を停止 se.play(); // シャッター音 setTimeout( () => { video.play(); // 1.5秒後にカメラ再開 }, 1500); // canvasに画像を貼り付ける ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const c = document.getElementById('picture'); c.toBlob((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); var hours = nowDate.getHours(); var minutes = nowDate.getMinutes(); var Seconds = nowDate.getSeconds(); var today = year + '年' + month + '月' + date + '日' + hours + '時' + minutes + '分' + Seconds +'秒'; a.download = today + '.png'; a.href = url; a.click(); a.remove(); setTimeout(() => { URL.revokeObjectURL(url); }, 1E4); }, 'image/png'); }); }; </script> </body> </html> <html> <body> <ol> <pre> <section class="area" id="area-3"> <h1>撮影します。</h1> 撮影したものは、右側の画面です。 <video id="camera" width="300" height="200"></video> <canvas id="picture" width="300" height="200"></canvas> <form> <button type="button" id="shutter">撮影する</button> </form> <audio id="se" preload="auto"> <source src="camera-shutter1.mp3" type="audio/mp3"> </audio> <script> window.onload = () => { const video = document.querySelector("#camera"); const canvas = document.querySelector("#picture"); const se = document.querySelector('#se'); /** カメラ設定 */ const constraints = { audio: false, video: { width: 300, height: 200, // facingMode: "user" // フロントカメラを利用する facingMode: { exact: "environment" } // リアカメラを利用する場合 } }; /** * カメラを<video>と同期 */ navigator.mediaDevices.getUserMedia(constraints) .then( (stream) => { video.srcObject = stream; video.onloadedmetadata = (e) => { video.play(); }; }) .catch( (err) => { console.log(err.name + ": " + err.message); }); /** * 撮影 */ document.querySelector("#shutter").addEventListener("click", () => { const ctx = canvas.getContext("2d"); // 演出的な目的で一度映像を止めてSEを再生する video.pause(); // 映像を停止 se.play(); // シャッター音 setTimeout( () => { video.play(); // 3秒後にカメラ再開 }, 3000); // canvasに画像を貼り付ける ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const c = document.getElementById('picture'); c.toBlob((blob) => { const url = URL.createObjectURL(blob); const a = document.createElement("a"); document.body.appendChild(a); var nowDate = new Date(); var year = nowDate.getFullYear(); var month = nowDate.getMonth()+1; var date = nowDate.getDate(); var hours = nowDate.getHours(); var minutes = nowDate.getMinutes(); var Seconds = nowDate.getSeconds(); var today = year + '年' + month + '月' + date + '日' + hours + '時' + minutes + '分' + Seconds +'秒'; a.download = today + '.png'; a.href = url; a.click(); a.remove(); setTimeout(() => { URL.revokeObjectURL(url); }, 1E4); }, 'image/png'); }); }; </script> </body> </html> <footer id="footer"> <p class="js-scroll scroll-top scroll-view"><a href="#area-2">Scroll</a></p> <p class="js-pagetop scroll-top"><a href="#">Page Top</a></p> <small>&copy; copyright.</small> </footer> すべてコピペで使用できます まとめ 今回はフロントカメラ編を作成してみました 今度は、リアカメラ編を、投稿します お待ちください 参考になってくれると辛いです  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTMLの<picture>要素をIE11でも使えるように見せかける簡易版JSを作った

IE11も対象の案件。ブラウザの画面幅に応じてページのレイアウトが変わる際、一部の<img>要素の画像については縦横比が異なるものを表示させる必要がありました。そこで、サイズや縦横比が異なる複数の画像をあらかじめ用意しておき、画面幅に応じて自動で切り替わるようにしたい。最近のブラウザならHTMLの<picture>要素をサポートしているので、mediaクエリを付けた<source>要素を並べて書くだけで実現できますが、IE11は<picture>要素が未対応。もちろん、polyfill picturefill.jsの存在は知っています。が、今回はとりあえずmax-widthだけを基準にして画像を切り替えできれば十分だったので、自分でIE11用に簡易版のコードをJavaScriptで書いてみることにしました1。 ※コンテントの画像 via Unsplash 簡易版の仕様 簡易版jsが扱うことができるシンタックスは以下のとおりです。 <source>要素にはsrcset属性とmedia属性のみ指定します。 media属性にはmax-width1つのみ、px単位で指定します。 <source>要素はmax-widthの値が小さい順に並べなければなりません。 example.html <picture> <source srcset="/path/to/img1" media="(max-width: 360px)"> <source srcset=/path/to/img2" media="(max-width: 420px)"> ..... <source srcset="/path/to/imgN" media="(max-width: 768px)"> <img src="/path/to/fallback/img" alt=""> </picture> See the Pen [JS] picture element in IE11 by Kazuhiro Hashimoto (@kaz_hashimoto) on CodePen. このCodePenはIE11以外でも動作を確認できるように2、HTMLネイティブの機能の代わりに簡易版jsを使って<picture>要素の画像を切り替え表示しています。実際の場面では、IE11以外には不要なコードなので、最初にブラウザのUA文字列によってIE11かどうかを判定し、IE11でなければ即returnさせます。 javascript document.addEventListener('DOMContentLoaded', function() { // 実際に使用する時は最初にUAでブラウザ判定を行い、 // IE以外のブラウザでは以降のコードが実行されないようにする。 // 例) // const parser = new UAParser(); // const ua = parser.getResult(); // if (ua.browser.name != 'IE') { // return; // } ..... 実装メモ 処理の本体はIE11でも実行可能なようにコーディングする必要があるため、NodeListの各要素に対してcallback関数を呼ぶ箇所は、一旦Arrayを作ってからforEachを呼ぶ形式で書いてあります。 javascript const targets = document.querySelectorAll('picture'); Array.prototype.forEach.call(targets, function(picture) { ... <source>要素に設定した一連のmedia="(max-width:は、区間が重ならないように "(min-width: A px) and (max-width: B px)" に変換したクエリでMediaQueryListオブジェクトを作成し、リスナーを設定します。 クエリとsrcsetの値のペアは、ハンドラ内からも参照するためMapオブジェクトで保持しておきます。ここで、Mapのキーに使うクエリ文字列は、matchMediaの戻り値から取り出した方のmql.mediaを使います。IE11の場合、それはmatchMedia(query)に指定したquery文字列と必ずしも一致しないためです。 javascript const mql = window.matchMedia(query); item.map.set(mql.media, source.getAttribute('srcset')); const func = handler.bind(item) mql.addListener(func); func(mql); 変数itemは<picture>要素ごとに設定情報を保持するオブジェクトです。ハンドラー内でitemをthisキーワードから参照可能にするため、bind()を呼んで生成した関数の方をリスナーに設定します。 javascript function handler(mql) { if (!mql.matches) { return; } const item = this; const src = item.map.get(mql.media); if (!src) { return; } item.img.src = src; } ハンドラーが呼ばれると、対象の<picture>要素に紐付けたitemを取り出し、ステータスが変化したmediaクエリをキーにsrcsetの値を取り出し、<img>要素のsrcに画像をセットします。 picturefill.jsは2017年3月リリースのver.3.0.3を最後に更新が止まっているようで、projectのstatusがよくわからなかったのも1つの理由。 ↩ CodePenはIE11をサポートしないため、このPenをIE11で確認したい時はPenのソース一式をExportし、localhost上のコンテンツとしてIE11からアクセスします。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む