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 = `./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 (`backup.php?id=${uniqId}`, param);
else
{
// SendServer (`upload.php?name=${userName.value}&pass=${password.value}&msg=${message.value}`, param);
await SendServer (`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 = `./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;
}
}