/** * @typedef CPosition * @prop {number} X 平面座標上的X。 * @prop {number} Y 平面座標上的Y。 */ /** * 滑鼠狀態的列舉。 * @readonly * @enum {number} */ const MouseStatus = Object.freeze({MouseDown: 0, Drawing: 1, MouseUp: 2, MouseMoving: 3}); /** @type {HTMLElement} */ let cvsCanvas = document.getElementById("cvsCanvas"); // 畫布(Canvas)的HTML物件 /** @type {CanvasRenderingContext2D} */ let context = cvsCanvas.getContext("2d"); // 操縱繪圖動作的相關函式集。 /** 以Id對應不同的筆刷模式。有: 筆刷、滑筆、燕筆、刺筆、印筆 */ let BrushMethods = { "normalBrush": NormalBrush_Drawing, "slideBrush": SlideBrush_Drawing, "featherBrush": FeatherBrush_Drawing, "furBrush": FurBrush_Drawing, "stampBrush": StampBrush_Drawing }; /* ============================ 紀錄HTML物件 ============================ */ let sizeGroupText = document.getElementById("sizeGroupText"); let strokeSizeGroupText = document.getElementById("strokeSizeGroupText"); /* ======================== 紀錄繪圖工具的相關狀態 ======================== */ let isMouseDown = false; // 紀錄滑鼠是否為壓下的狀態 let lastPosition = {X: 0, Y: 0}; // 紀錄上一次的點的座標 let curPosition = {X: 0, Y: 0}; // 紀錄目前的滑鼠座標位置 let brushWidth = 1; // 紀錄目前使用者設定的筆刷大小 let fillWidth = 1; // 紀錄目前使用者設定的填充大小 let strokeColor = "#000000"; // 紀錄目前使用者所設定的比刷顏色 let fillColor = "#000000"; // 紀錄目前使用者所設定的填滿顏色 let activedBrush = document.getElementById("normalBrush"); // 紀錄目前在使用的比刷模式的HTML物件 let DrawingMethod = NormalBrush_Drawing; // 紀錄目前的繪畫模式 /* ============================== 簽署事件 ============================== */ cvsCanvas.addEventListener("mousedown", cvsCanvas_MouseDown); cvsCanvas.addEventListener("mousemove", cvsCanvas_MouseMove, false); cvsCanvas.addEventListener("mouseup", cvsCanvas_MouseUp, false); document.getElementById("resetCanvas").addEventListener("click", ClearCanvas); document.getElementById("normalBrush").addEventListener("click", ChangeBrushMethod); document.getElementById("slideBrush").addEventListener("click", ChangeBrushMethod); document.getElementById("featherBrush").addEventListener("click", ChangeBrushMethod); document.getElementById("furBrush").addEventListener("click", ChangeBrushMethod); document.getElementById("stampBrush").addEventListener("click", ChangeBrushMethod); document.getElementById("mainColorPicker").addEventListener("change", ChangeColor, false); document.getElementById("subColorPicker").addEventListener("change", ChangeColor, false); document.getElementById("subColorPicker").addEventListener("click", e => e.stopPropagation() ); //防止按下按鈕時,會動到上一層的sub-color-picker document.getElementById("sizeUp").addEventListener("click", () => ChangeFillSize(1)); document.getElementById("sizeDown").addEventListener("click", () => ChangeFillSize(-1)); document.getElementById("sizeUp10").addEventListener("click", () => ChangeFillSize(10)); document.getElementById("sizeDown10").addEventListener("click", () => ChangeFillSize(-10)); document.getElementById("strokeSizeUp").addEventListener("click", () => ChangeStrokeSize(1)); document.getElementById("strokeSizeDown").addEventListener("click", () => ChangeStrokeSize(-1)); document.getElementById("strokeSizeUp10").addEventListener("click", () => ChangeStrokeSize(10)); document.getElementById("strokeSizeDown10").addEventListener("click", () => ChangeStrokeSize(-10)); /** * 畫布初始化。 */ if (paintingId) { let paintImg = document.createElement("img"); paintImg.style = "display: none;"; paintImg.onload = () => context.drawImage(paintImg, 0, 0, 800, 450); paintImg.src = "/db/paintings/" + paintingId + ".png"; document.body.appendChild(paintImg); } else { context.fillStyle = "#FFFFFF"; context.fillRect(0, 0, 800, 450); context.fill(); } /** * 當「重置」按鈕按下之後,清除畫布。 * @param {Event} event 事件物件。 */ function ClearCanvas(event) { context.fillStyle = "#FFFFFF"; context.fillRect(0, 0, 800, 450); context.fill(); } /** * 變更筆刷模式。當筆刷模式按鈕按下時所做的處理。 * @param {Event} event 事件物件。 */ function ChangeBrushMethod(event) { activedBrush.classList.remove("active"); activedBrush = event.target; activedBrush.classList.add("active"); DrawingMethod = BrushMethods[activedBrush.id]; } /** * 變更顏色。當筆刷顏色或填充顏色按鈕按下時,並選取完顏色後的事件處理。 * @param {Event} event 事件物件。 */ function ChangeColor(event) { let target = event.target ? event.srcElement : event.target; if (target.id == "mainColorPicker") { fillColor = target.value; } else { strokeColor = target.value; } } /** * 變更筆刷的粗細大小。 * @param {number} value 變更量。 */ function ChangeStrokeSize(value) { brushWidth += value; if (brushWidth > 128) brushWidth = 128; if (brushWidth < 1) brushWidth = 1; strokeSizeGroupText.innerText = "筆刷粗細 " + brushWidth; } /** * 變更填充的粗細大小。 * @param {number} value 變更量。 */ function ChangeFillSize(value) { fillWidth += value; if (fillWidth > 128) fillWidth = 128; if (fillWidth < 1) fillWidth = 1; sizeGroupText.innerText = "填色大小 " + fillWidth; } /** * 取得滑鼠在畫布中的位置。 * @param {Event} event 事件物件。限定滑鼠動作相關的事件。 * @return {Position} 目前游標的座標。 */ function GetMousePosition(event) { let rect = cvsCanvas.getBoundingClientRect(); return {X: event.clientX - rect.left, Y: event.clientY - rect.top}; } /** * 當滑鼠在畫布上按下時,所觸發的事件。 * @param {Event} event 事件物件。 */ function cvsCanvas_MouseDown(event) { isMouseDown = true; DrawingMethod(event, MouseStatus.MouseDown); } /** * 當滑鼠在畫布上移動時,所觸發的事件。 * @param {Event} event 事件物件。 */ function cvsCanvas_MouseMove(event) { DrawingMethod(event, isMouseDown ? MouseStatus.Drawing : MouseStatus.MouseMoving); } /** * 當滑鼠在畫布上放開時,所觸發的事件。 * @param {Event} event 事件物件。 */ function cvsCanvas_MouseUp(event) { isMouseDown = false; DrawingMethod(event, MouseStatus.MouseUp); } /** * 在指定位置畫上圓。 * @param {CPosition} pos 圓心位置。 * @param {number} radius 圓的半徑。 * @param {string?} color 顏色。 */ function FillCircle(pos, radius, color) { if (color) context.fillStyle = color; context.beginPath(); context.arc(pos.X, pos.Y, radius, 0, 2 * Math.PI); context.closePath(); context.fill(); } /** * 在指定兩個座標之間畫線。 * @param {CPosition} posA 起點座標位置。 * @param {CPosition} posB 終點座標位置。 * @param {string?} color 顏色。 */ function DrawLine(posA, posB, color) { if (color) context.strokeStyle = color; context.beginPath(); context.moveTo(posA.X, posA.Y); context.lineTo(posB.X, posB.Y); context.closePath(); context.stroke(); } /** * 在指定坐標上繪製外內圓。 * @param {CPosition} pos 圓心座標位置。 * @param {number} radius 圓的半徑。 * @param {string} strokeColor 外圓的顏色。 * @param {string} fillColor 內圓的顏色。 */ function StampCircle(pos, radius, strokeColor, fillColor) { if (strokeColor) context.strokeStyle = strokeColor; if (fillColor) context.fillStyle = fillColor; context.beginPath(); context.arc(pos.X, pos.Y, radius, 0, 6.28); context.closePath(); context.fill(); context.stroke(); } /** * 筆刷模式「筆刷」下的繪圖動作。 * @param {Event} event 與滑鼠動作相關的事件物件。 * @param {MouseStatus} status 滑鼠的狀態。 */ function NormalBrush_Drawing(event, status) { switch(status) { case MouseStatus.MouseDown: lastPosition = GetMousePosition(event); context.lineWidth = brushWidth; context.strokeStyle = strokeColor; FillCircle(lastPosition, brushWidth / 2, strokeColor); break; case MouseStatus.Drawing: curPosition = GetMousePosition(event); DrawLine(lastPosition, curPosition); FillCircle(curPosition, brushWidth / 2); lastPosition = curPosition; } } /** * 筆刷模式「滑筆」下的繪圖動作。 * @param {Event} event 與滑鼠動作相關的事件物件。 * @param {MouseStatus} status 滑鼠的狀態。 */ function SlideBrush_Drawing(event, status) { switch(status) { case MouseStatus.MouseDown: lastPosition = GetMousePosition(event); curPosition = lastPosition; context.lineWidth = brushWidth; context.strokeStyle = strokeColor; FillCircle(lastPosition, brushWidth / 2, strokeColor); setTimeout(SlideBrush_Chasing, 10); break; case MouseStatus.Drawing: curPosition = GetMousePosition(event); } } /** * 筆刷模式「滑筆」下的滑筆繪製動作。 */ function SlideBrush_Chasing() { let dx = (curPosition.X - lastPosition.X) / 100, dy = (curPosition.Y - lastPosition.Y) / 100; let nextPosition = {X: lastPosition.X + dx, Y: lastPosition.Y + dy}; DrawLine(lastPosition, nextPosition); FillCircle(nextPosition, brushWidth / 2); lastPosition = nextPosition; if (isMouseDown && DrawingMethod == SlideBrush_Drawing) setTimeout(SlideBrush_Chasing, 10); } let featherEnable = false; /** * 筆刷模式「燕筆」下的繪圖動作。 (燕尾筆) * @param {Event} event 與滑鼠動作相關的事件物件。 * @param {MouseStatus} status 滑鼠的狀態。 */ function FeatherBrush_Drawing(event, status) { switch(status) { case MouseStatus.MouseDown: lastPosition = GetMousePosition(event); context.strokeStyle = strokeColor; context.lineWidth = brushWidth; FillCircle(lastPosition, brushWidth / 2, strokeColor); featherEnable = true; break; case MouseStatus.Drawing: if (!featherEnable) break; curPosition = GetMousePosition(event); DrawLine(lastPosition, curPosition); FillCircle(curPosition, context.lineWidth / 2); lastPosition = curPosition; context.lineWidth -= 0.5; featherEnable = context.lineWidth - 0.5 > 0; } } /** * 筆刷模式「刺筆」下的繪圖動作。 * @param {Event} event 與滑鼠動作相關的事件物件。 * @param {MouseStatus} status 滑鼠的狀態。 */ function FurBrush_Drawing(event, status) { switch(status) { case MouseStatus.MouseDown: lastPosition = GetMousePosition(event); context.strokeStyle = strokeColor; context.lineWidth = brushWidth; FillCircle(lastPosition, brushWidth / 2, strokeColor); break; case MouseStatus.Drawing: curPosition = GetMousePosition(event); let dx = curPosition.X - lastPosition.X, dy = curPosition.Y - lastPosition.Y; dx = dx > 40 ? 2 : dx / 20; dy = dy > 40 ? 2 : dy / 20; DrawLine(lastPosition, curPosition); FillCircle(curPosition, brushWidth / 2); lastPosition.X += dx; lastPosition.Y += dy; } } /** * 筆刷模式「印筆」下的繪圖動作。 * @param {Event} event 與滑鼠動作相關的事件物件。 * @param {MouseStatus} status 滑鼠的狀態。 */ function StampBrush_Drawing(event, status) { switch(status) { case MouseStatus.MouseDown: context.strokeStyle = strokeColor; context.fillStyle = fillColor; context.lineWidth = brushWidth; StampCircle(GetMousePosition(event), fillWidth / 2); break; case MouseStatus.Drawing: StampCircle(GetMousePosition(event), fillWidth / 2); } }