const colourCode = {'#000000': '黒', '#0000ff': 'ブルー', '#00ff00': 'ライム', '#00ffff': '青', '#ff0000': '赤', '#ff00ff': 'マジェンタ', '#ffff00': 'イェロゥ', '#ffffff': '白', '#f1f1f1': '白', '#a0a0a0': '銀ねず', '#868686': 'ねずみ色', '#797979': '灰色', '#313131': '墨', '#ed542a': '金赤'}; const canvasArea = document.getElementById ('canvas-area'); const canvasRoot = document.getElementById ('canvas'); const canvasRootCtx = canvasRoot.getContext ('2d'); const canvas = []; // 各レイァごとの Canvas 要素 const canvasCtx = []; // 各レイァごとの Canvas コンテクスト let layerMax = 3; // レイァの最大値 let layerName = ['下', '中', '上']; // レイァ名 let canvasWidth = canvasRoot.width; // Canvas 幅 let canvasHeight = canvasRoot.height; // Canvas 高さ // canvas[0] = document.getElementById ('canvas'); // canvas[1] = document.getElementById ('canvas1'); // canvas[2] = document.getElementById ('canvas2'); for (let i = 0; i < layerMax; ++i) { // 各レイァにたぃし,要素代入 canvas[i] = document.createElement ('canvas'); canvas[i].width = canvasWidth; canvas[i].height = canvasHeight; // 各レイァにたぃし,コンテクスト代入 canvasCtx[i] = canvas[i].getContext ('2d'); canvasCtx[i].willReadFrequently = true; } const canvasPerfect = document.getElementById ('canvas-perfect'); // レイァ合成用作業 Canvas const userName = document.getElementById ('user-name'); // 投稿者名 const password = document.getElementById ('password'); // 削除用パスワード const message = document.getElementById ('message'); // 投稿メシジ const buttonNew = document.getElementById ('new'); // 新規作成ボタン const buttonSend = document.getElementById ('send'); // 送信ボタン const buttonSave = document.getElementById ('save'); // ダウンロード・ボタン const buttonChangeSize = document.getElementById ('change-size'); // サイズ変更 const buttonUndo = document.getElementById ('undo'); // 元に戻す const buttonRedo = document.getElementById ('redo'); // やり直し // const formColour = document.getElementById ('colour'); // 色フォーム const formSize = document.getElementById ('size'); // 太さフォーム // const radioColour = formColour.colour; // 選択中の色 let colour = 'black'; const radioSize = formSize.size; // 選択中の太さ const pen = document.getElementById ('pen'); // ペン const rubber = document.getElementById ('rubber'); // 消ゴム const bucket = document.getElementById ('bucket'); // 塗りつぶし const layer = document.getElementById ('layer').layer; // レイァ const layerAdd = document.getElementById ('layer').add; // レイァ追加 const layerDel = document.getElementById ('layer').del; // レイァ削除 const layerUp = document.getElementById ('layer').up; // レイァ上へ const layerDown = document.getElementById ('layer').down; // レイァ下へ const layerSep = document.getElementById ('layer').sep; // レイァ分け const delId = document.getElementById ('del-id'); const delPass = document.getElementById ('del-pass'); const deleteButton = document.getElementById ('delete'); const changeSizeWindow = document.getElementById ('modal'); const changeWidth = document.getElementById ('change-width'); const changeHeight = document.getElementById ('change-height'); let imageUrl = (getCookie ('backup') == null) ? '' : getCookie ('backup'); let uniqId = (imageUrl == '') ? uniqueId () : imageUrl.split ('.')[0]; if (imageUrl != '') { let imgData = new Image (); imgData.addEventListener ('load', function () { canvasWidth = canvasRoot.width = canvasPerfect.width = imgData.width; canvasHeight = canvasRoot.height = canvasPerfect.height = imgData.height; canvasArea.style.width = canvasWidth + 'px'; canvasArea.style.height = canvasHeight + 'px'; for (let i = 0; i < layerMax; ++i) { canvas[i].width = canvasWidth; canvas[i].height = canvasHeight; } canvasCtx[0].drawImage (imgData, 0, 0, canvasWidth, canvasHeight); reDraw (); }, false); imgData.src = `//miteruzo.ml/kekec/bbs/draft/${imageUrl}`; } let nowDraw = false; // 描画フラグ let lastX; // 直前の X 座標 let lastY; // 直前の Y 座標 let zoomX; // 定義上の Canvas 幅と実際のそれとの比率 let zoomY; // 定義上の Canvas 高さと実際のそれとの比率 let scaleFactor = 1; let startDistance = 0; let lastDistance = null; // undo/redo のための変数 let history = []; // フレィムごとの履歴 let count = 0; // フレィムごとの履歴の数 let historySt = []; // 一筆ごとの履歴 let countSt = 0; // 一筆ごとの履歴の数 let countMax = 0; // 履歴の最大値(やり直し用) let img = new Image (); // 取込用画像オブジェクト let held = false; /* * サーヴァに送信する. * url:フェッチ先,param:JSON パラメタ * * 戻り値は,なし. */ async function SendServer (url, param) { fetch (url, param).then ( (response) => response.json ()).then (function (json) { if (json.status) return 'success'; else return 'miss'; }).catch ( (error) => 'error'); } /* * Canvas から画像を取得する. * id:要素オブジェクト * * 戻り値は,Promise オブジェクト. */ function getImageFromCanvas (id) { return new Promise ( function (resolve, reject) { const w2 = new Image (); // 作業用画像オブジェクト w2.onload = () => resolve (w2); w2.onerror = (e) => reject (e); w2.src = id.toDataURL ('image/png'); }); } /* * Canvas を合成する. * * 戻り値は,なし. */ async function concatCanvas () { canvasPerfect.getContext ('2d').fillStyle = 'white'; canvasPerfect.getContext ('2d').fillRect (0, 0, canvasWidth, canvasHeight); // Canvas 合成 for (let i = 0; i < layerMax; ++i) { const w = await getImageFromCanvas (canvas[i]); canvasPerfect.getContext ('2d').drawImage (w, 0, 0, canvasWidth, canvasHeight); } } /* * サーヴァに Canvas 画像を送信する. * backup:ドラフト・フラグ * * 戻り値は,なし. */ async function SendToServer (backup) { await concatCanvas (); let image = canvasPerfect.toDataURL ('image/png'); let param = {method: 'POST', headers: {'Content-Type': 'application/json; charset=utf-8'}, body: JSON.stringify ({data: image}), mode: 'cors'}; if (backup) await SendServer (`/modules/backup.php?id=${uniqId}`, param); else { // SendServer (`upload.php?name=${userName.value}&pass=${password.value}&msg=${message.value}`, param); await SendServer (`/modules/upload.php${window.location.search}&name=${userName.value}&pass=${password.value}&held=${held ? 1 : 0}&uniqid=${uniqId}`, param); } } /* * 定義上の Canvas サイズと実際のそれとの比率を取得する. * * 戻り値は,なし. */ function GetZoom () { zoomX = canvasWidth / canvasRootCtx.canvas.clientWidth; zoomY = canvasHeight / canvasRootCtx.canvas.clientHeight; } /* * ローカル画像 files を取込む. * * 戻り値は,なし. */ function LoadFile (files) { let reader = new FileReader (); // ファイル読込ダイアログ // ファイルが読込まれた場合 reader.onload = function (evt) { // 画像が読込まれた場合 img.onload = function () { canvasCtx[layer.value].drawImage (img, 0, 0, canvasWidth, canvasHeight); held = true; }; img.src = evt.target.result; // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; history[count] = {type: 'load', layer: layer.value}; ++count; }; reader.readAsDataURL (files[0]); // 選択された最初のファイルを指定 } GetZoom (); NewCanvas (); /* * ペンの座標および履歴を記録し,ペン移動中の処理に移る. * クリックまたはタップ開始と同時に実行する. * * 戻り値は,なし. */ function Down (evt) { let mousePos = getMousePosition (canvasRoot, evt); // マウス座標 if (bucket.checked) { bucketArea (parseInt (mousePos.x, 10), parseInt (mousePos.y, 10), colour, layer.value, false); } else { // 座標を記録 lastX = mousePos.x + 1; lastY = mousePos.y; nowDraw = true; // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; Drawing (evt); } } /* * ペン移動中の処理を行ふ. * * 戻り値は,なし. */ function Move (evt) /* 筆移動なうみ */ { Drawing (evt); } async function Up () /* 筆離したお (^∇^)/ */ { nowDraw = false; await SendToServer (true); historySt[countSt] = count; } canvasRoot.addEventListener ('mousedown', function (evt) { Down (evt); }); canvasRoot.addEventListener ('mousemove', function (evt) { Move (evt); }); canvasRoot.addEventListener ('mouseup', function () { Up (); }); canvasRoot.addEventListener ('touchstart', function (evt) { Down (evt.changedTouches[0]); }); canvasRoot.addEventListener ('touchmove', function (evt) { evt.preventDefault (); Move (evt.changedTouches[0]); }); canvasRoot.addEventListener ('touchend', function () { Up (); }); canvasRoot.addEventListener ('touchcancel', function () { Up (); }); addEventListener ('resize', function () { GetZoom (); }); buttonNew.addEventListener ('click', function () { if (confirm ('作成中の絵を初期化しても大丈夫ですか.')) NewCanvas (); }); buttonSend.addEventListener ('click', async function () { const searchParams = new URLSearchParams (window.location.search); await SendToServer (false); document.getElementsByTagName ('body')[0].style.marginTop = 'auto'; document.getElementsByTagName ('body')[0].style.marginBottom = 'auto'; document.getElementsByTagName ('body')[0].innerHTML = '
待ってね.
'; window.setTimeout ( function () { window.location.href = `./?thread=${searchParams.get ('thread')}&sort=${ searchParams.get ('sort')}`; }, 2000); }); buttonSave.addEventListener ('click', function () { let download = document.createElement ('a'); concatCanvas (); download.href = canvasPerfect.toDataURL ('image/png'); download.download = 'canvas.png'; document.body.appendChild (download); download.click (); document.body.removeChild (download); }); buttonChangeSize.addEventListener ('click', function () { changeWidth.value = canvasWidth; changeHeight.value = canvasHeight; changeSizeWindow.style.display = 'block'; }); buttonUndo.addEventListener ('click', function () { if (countSt > 0) { const _layer = Number (layer.value); --countSt; count = historySt[countSt]; while (layerMax != 3) { if (layerMax > 3) delLayer (0, true); else addLayer (0, true); } ClearCanvas (); for (let i = 0; i < count; ++i) Trace (i); layer.value = Math.max (0, Math.min (_layer, layerMax - 1)); } reDraw (); }); buttonRedo.addEventListener ('click', function () { if (countSt < countMax) { const _layer = Number (layer.value); ++countSt; count = historySt[countSt]; for (let i = historySt[countSt - 1]; i < count; ++i) Trace (i); layer.value = Math.max (0, Math.min (_layer, layerMax - 1)); } reDraw (); }); pen.addEventListener ('click', function () { for (let i = 0; i < layerMax; ++i) canvasCtx[i].globalCompositeOperation = 'source-over'; }) rubber.addEventListener ('click', function () { for (let i = 0; i < layerMax; ++i) canvasCtx[i].globalCompositeOperation = 'destination-out'; }); layerAdd.addEventListener ('click', function () { addLayer (Number (layer.value) + 1, false); }); layerDel.addEventListener ('click', function () { if (layerMax > 1) delLayer (Number (layer.value), false); else window.alert ('消せるレイアがありません.'); }); layerUp.addEventListener ('click', function () { if (layer.value < layerMax - 1) changeLayer (Number (layer.value), Number (layer.value) + 1, false); }); layerDown.addEventListener ('click', function () { if (layer.value > 0) changeLayer (Number (layer.value), Number (layer.value) - 1, false); }); function Trace (his) { switch (history[his].type) { case 'draw': canvasCtx[history[his].layer].globalCompositeOperation = ( ((history[his].mode == 'rubber') ? 'destination-out' : 'source-over')); if (history[his].mode == 'bucket') { bucketArea (history[his].start.x, history[his].start.y, history[his].colour, history[his].layer, true); } else { canvasCtx[history[his].layer].lineWidth = history[his].size; canvasCtx[history[his].layer].strokeStyle = history[his].colour; canvasCtx[history[his].layer].beginPath (); canvasCtx[history[his].layer].moveTo (history[his].start.x, history[his].start.y); canvasCtx[history[his].layer].lineTo (history[his].end.x, history[his].end.y); canvasCtx[history[his].layer].closePath (); canvasCtx[history[his].layer].stroke (); } break; case 'load': canvasCtx[history[his].layer].drawImage ( img, 0, 0, canvasWidth, canvasHeight); break; case 'addLayer': addLayer (history[his].layer, true); break; case 'delLayer': delLayer (history[his].layer, true); break; case 'changeLayer': changeLayer (history[his].from, history[his].to, true); break; } } function getMousePosition (canvas, evt) { let rect = canvas.getBoundingClientRect (); return {x: (evt.clientX - rect.left) * zoomX, y: (evt.clientY - rect.top) * zoomY}; } function ClearCanvas () { for (let i = 0; i < layerMax; ++i) canvasCtx[i].clearRect (0, 0, canvasWidth, canvasHeight); reDraw (); } function NewCanvas () { ClearCanvas (); count = 0; history = []; countSt = 0; historySt = []; countMax = 0; } function Drawing (evt) { if (nowDraw) { let mousePos = getMousePosition (canvasRoot, evt); let nowX = mousePos.x; let nowY = mousePos.y; if ((nowX == lastX) && (nowY == lastY)) --nowX; if (radioSize.value == 0) { canvasCtx[layer.value].lineWidth = document.getElementById ( 'size-free').value; } else canvasCtx[layer.value].lineWidth = radioSize.value; canvasCtx[layer.value].strokeStyle = colour; canvasCtx[layer.value].beginPath (); canvasCtx[layer.value].moveTo (lastX, lastY); canvasCtx[layer.value].lineTo (nowX, nowY); canvasCtx[layer.value].closePath (); canvasCtx[layer.value].stroke (); reDraw (); history[count] = { type: 'draw', start: {x: lastX, y: lastY}, end: {x: nowX, y: nowY}, colour: canvasCtx[layer.value].strokeStyle, size: canvasCtx[layer.value].lineWidth, layer: layer.value, mode: ((canvasCtx[layer.value].globalCompositeOperation == 'destination-out') ? 'rubber' : (bucket.checked ? 'bucket' : 'pen'))}; ++count; lastX = mousePos.x; lastY = mousePos.y; } } function changeColour (rgb) { const colourPicker = document.getElementById ('colour-picker'); let cps = colourPicker.getAttribute ('cmanCPat').split (','); let clCode; cps[0] = 'def_color:cns=' + rgbTo16 (rgb).substr (0, 7); clCode = rgbTo16 (rgb).substr (0, 7); if (clCode in colourCode) clCode = colourCode[clCode]; document.getElementById ('irorororo').innerHTML = clCode; colourPicker.setAttribute ('cmanCPat', cps.join ()); colour = rgb; } function rgbTo16 (col) { return "#" + col.match(/\d+/g).map(function(a){return ("0" + parseInt(a).toString(16)).slice(-2)}).join(""); } function bucketArea (px, py, cl, lyr, fromHistory) { console.log ([px, py]); let cwnt = 0; let r, g, b, a; let r_, g_, b_, a_; [r, g, b, a] = canvasCtx[lyr].getImageData (px, py, 1, 1).data; let dx = new Array (canvasWidth * canvasHeight); let dy = new Array (canvasWidth * canvasHeight); let dxy = new Array (canvasHeight); for (let i = 0; i < canvasHeight; ++i) dxy[i] = new Array (canvasWidth).fill (0); dx[0] = px; dy[0] = py; dxy[py][px] = 1; ++cwnt; for (let i = 0; true; ++i) { let x = dx[i]; let y = dy[i]; if (x + 1 < canvasWidth) { if (dxy[y][x + 1] == 0) { dxy[y][x + 1] = 1; [r_, g_, b_, a_] = canvasCtx[lyr].getImageData ( x + 1, y, 1, 1).data; if ((r_ == r) && (g_ == g) && (b_ == b) && (a_ == a)) { dx[cwnt] = x + 1; dy[cwnt] = y; ++cwnt; } } } if (0 <= x - 1) { if (dxy[y][x - 1] == 0) { dxy[y][x - 1] = 1; [r_, g_, b_, a_] = canvasCtx[lyr].getImageData ( x - 1, y, 1, 1).data; if ((r_ == r) && (g_ == g) && (b_ == b) && (a_ == a)) { dx[cwnt] = x - 1; dy[cwnt] = y; ++cwnt; } } } if (y + 1 < canvasHeight) { if (dxy[y + 1][x] == 0) { dxy[y + 1][x] = 1; [r_, g_, b_, a_] = canvasCtx[lyr].getImageData ( x, y + 1, 1, 1).data; if ((r_ == r) && (g_ == g) && (b_ == b) && (a_ == a)) { dx[cwnt] = x; dy[cwnt] = y + 1; ++cwnt; } } } if (y - 1 >= 0) { if (dxy[y - 1][x] == 0) { dxy[y - 1][x] = 1; [r_, g_, b_, a_] = canvasCtx[lyr].getImageData ( x, y - 1, 1, 1).data; if ((r_ == r) && (g_ == g) && (b_ == b) && (a_ == a)) { dx[cwnt] = x; dy[cwnt] = y - 1; ++cwnt; } } } if (i == cwnt - 1) break; } canvasCtx[lyr].fillStyle = cl; for (let i = 0; i < cwnt; ++i) canvasCtx[lyr].fillRect (dx[i], dy[i], 1, 1); reDraw (); if (!(fromHistory)) { // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; history[count] = { type: 'draw', start: {x: px, y: py}, colour: canvasCtx[lyr].fillStyle, layer: lyr, mode: 'bucket'}; ++count; } } function getCookie (nameOfCookie) { const r = document.cookie.split (';'); let f = null; r.forEach ( function (element) { const content = element.split ('='); if (content[0] == nameOfCookie) f = content[1]; }); return f; } function uniqueId (digits) { var strong = typeof digits !== 'undefined' ? digits : 1000; return Date.now().toString(16) + Math.floor ( (strong * Math.random ())).toString (16); } function deletePost (id) { delId.value = id; } function deletePostReally () { if (delId.value == '') window.alert ('削除したいレスの番号を入れろ!!!!!'); else if (delPass.value == '') window.alert ('設定した削除用パスワードを入れろ!!!!!!!!!!!!!!!!!'); else { window.location.href = `#${delId.value}`; setTimeout ( function () { if (window.confirm (`レス番号 ${delId.value} の投稿を削除します.\nよろしいですかい?????`)) { const searchParams = new URLSearchParams (window.location.search); window.location.href = `./modules/delete.php?thread=${searchParams.get ( 'thread')}&id=${delId.value}&pass=${delPass.value}`; } else window.location.href = '#del'; }, 100); } } function closeModal () { modal.style.display = 'none'; } function applyResolution () { if (!(((changeWidth.value < canvasRoot.width) || changeHeight.value < canvasRoot.height) && !(window.confirm ('指定されている幅もしくは高さの値がサイズ変更前よりも小さいです.\nこのままだと右端もしくは下端が変更後のサイズに合わせてカットされてしまいます.\nよろしいかな?????')))) { canvasWidth = Math.max (32, Math.min (changeWidth.value, 640)); canvasHeight = Math.max (24, Math.min (changeHeight.value, 480)); canvasArea.style.width = canvasWidth + 'px'; canvasArea.style.height = canvasHeight + 'px'; for (let i = 0; i < layerMax; ++i) { const canvas4replace = document.createElement ('canvas'); canvas4replace.width = canvasWidth; canvas4replace.height = canvasHeight; const canvas4replaceCtx = canvas4replace.getContext ('2d'); if (i == 0) { canvas4replaceCtx.fillStyle = 'white'; canvas4replaceCtx.fillRect (0, 0, canvasWidth, canvasHeight); } canvas4replaceCtx.drawImage (canvas[i], 0, 0); canvas[i].width = canvasWidth; canvas[i].height = canvasHeight; canvasCtx[i].drawImage (canvas4replace, 0, 0); } canvasPerfect.width = canvasRoot.width = canvasWidth; canvasPerfect.height = canvasRoot.height = canvasHeight; reDraw (); closeModal (); } } function reDraw () { canvasRootCtx.fillStyle = 'white'; canvasRootCtx.fillRect (0, 0, canvasWidth, canvasHeight); for (let i = 0; i < layerMax; ++i) { if (!(layerSep.checked && (i != layer.value))) canvasRootCtx.drawImage (canvas[i], 0, 0); } } function changeLayer (from, to, fromHistory) { const _canvas = canvas[from]; const _canvasCtx = canvasCtx[from]; canvas[from] = canvas[to]; canvas[to] = _canvas; canvasCtx[from] = canvasCtx[to]; canvasCtx[to] = _canvasCtx; if (!(fromHistory)) { layer.value = to; // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; history[count] = {type: 'changeLayer', from: from, to: to}; ++count; } reDraw (); } function addLayer (lyr, fromHistory) { const newLayer = document.createElement ('label'); canvas.splice (lyr, 0, document.createElement ('canvas')); canvasCtx.splice (lyr, 0, canvas[lyr].getContext ('2d')); canvas[lyr].width = canvasWidth; canvas[lyr].height = canvasHeight; newLayer.innerHTML = ` ${layerMax}`; layer[layerMax - 1].parentNode.after (newLayer); ++layerMax; reDraw (); if (!(fromHistory)) { layer.value = lyr; // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; history[count] = {type: 'addLayer', layer: lyr}; ++count; } } function delLayer (lyr, fromHistory) { --layerMax; canvas[lyr].remove (); canvas.splice (lyr, 1); canvasCtx.splice (lyr, 1); layer[layerMax].parentNode.remove (); if (!(fromHistory)) { layer.value = Math.max (0, lyr - 1); // 履歴を更新 historySt[countSt] = count; ++countSt; countMax = countSt; history[count] = {type: 'delLayer', layer: lyr}; ++count; } }