chickenboard - commit 498829b
Initial Commit498829bb8991ea2077cbeaf7b470920b57da0f24LukeJ<152485046+LukePJ25@users.noreply.github.com>2024-12-09T19:03:27Zdiff --git a/index.html b/index.html
new file mode 100644
index 0000000..ccbdf19
--- /dev/null
+++ b/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<head>
+ <title>ChickenBoard</title>
+ <link rel="stylesheet" href="style.css">
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
+</head>
+<body>
+ <div class="container" id="main_container">
+ <h1>ChickenBoard</h1>
+ <div id="canvas_container">
+ <canvas id="drawing_canvas" width="600" height="500"></canvas>
+ <canvas id="overlay_canvas" width="600" height="500"></canvas>
+ </div>
+ <div id="canvas_controls">
+ <button class="control_button" onclick="clearCanvasRequest(gtx, '#fff')">Clear Canvas</button>
+ <button class="control_button" onclick="saveAsImage()">Save Image</button>
+ <button class="control_button" onclick="document.getElementById('fileInput').click()">Load Image</button>
+ <input type="file" id="fileInput" accept="image/*" style="display: none;" onchange="loadInImage()">
+ <div class="vertical_separator"></div>
+ <div id="tools_container">
+ <button aria-label="Brush" class="tool_button" id="button_brush" onclick="changeTool(0);"></button>
+ <button aria-label="Eraser" class="tool_button" id="button_eraser" onclick="changeTool(1);"></button>
+ <button aria-label="Pencil" class="tool_button" id="button_pencil" onclick="changeTool(2);"></button>
+ <button aria-label="Spray" class="tool_button" id="button_spray" onclick="changeTool(3);"></button>
+ <button aria-label="Line" class="tool_button" id="button_line" onclick="changeTool(4);"></button>
+ <button aria-label="Picker" class="tool_button" id="button_picker" onclick="changeTool(5);"></button>
+ </div>
+ <div class="vertical_separator"></div>
+ <div id="tools_settings_container">
+ <div id="tool_size_selector_container">
+ <label for="tool_size_selector">Size</label>
+ <input class="tool_setting" type="number" id="tool_size_selector" min="1" max="256" value="8">
+ </div>
+ <div id="spray_settings_container">
+ <button class="tool_button" id="spray_strength_1" onclick="sprayStrength(1);"></button>
+ <button class="tool_button" id="spray_strength_2" onclick="sprayStrength(2);"></button>
+ <button class="tool_button" id="spray_strength_3" onclick="sprayStrength(3);"></button>
+ </div>
+ </div>
+ <div class="vertical_separator"></div>
+ <div id="colour_container">
+ <label for="colour_selector">Colour</label>
+ <input type="color" id="colour_selector" value="#000">
+ </div>
+ </div>
+ <script src="js/drawing.js"></script>
+ </div>
+</body>
\ No newline at end of file
diff --git a/js/drawing.js b/js/drawing.js
new file mode 100644
index 0000000..25db3b9
--- /dev/null
+++ b/js/drawing.js
@@ -0,0 +1,465 @@
+/**
+ * The source for ChickenBoard
+ * Written by Luke Jones
+ *
+ * ██ ██ ██ ██ ██ ████████ ████████ ██ ██ ██ ██ ██ ██ ████████ ██ ██ ████████ ████████
+ * ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ████ ██ ██ ████ ██ ██ ██
+ * ██ ██ ██ ████ ████████ ██ ██ ██ ████ ██ ██ ██ ████ ████████ ██ ████ ████████ ██
+ * ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
+ * ████████ ████████ ██ ██ ████████ ████████ ██ ██ ████████ ██ ██ ██ ████████ ██ ██ ██ ████████ ██
+ * https://www.lukeonline.net/ChickenBoard
+ * V1.0
+ */
+
+/*
+Math Functions
+ */
+const MathL = {
+ clamp: function (v, mn, mx) {
+ return Math.max(mn, Math.min(v, mx));
+ },
+
+ // Check if value is in range
+ rangeCheck: function (v, mn, mx) {
+ return (v <= mx && v >= mn);
+ },
+
+ // Linear Interpolation
+ lerp: function (va, vb, ratio) {
+ return va + ratio * (vb - va);
+ }
+}
+
+// Initialise Program
+const canvas = document.getElementById("drawing_canvas");
+const overlay = document.getElementById("overlay_canvas");
+const gtx = canvas.getContext("2d");
+const otx = overlay.getContext("2d");
+const canv_width = canvas.width, canv_height = canvas.height;
+const size_selector = document.getElementById("tool_size_selector");
+const size_selector_container = document.getElementById("tool_size_selector_container");
+const spray_strength_selector = document.getElementById("spray_settings_container");
+const colour_selector = document.getElementById("colour_selector");
+let isDrawingLine = false, lineA = [], lineB = [];
+let canvasBounds;
+
+// Initialise usage Variables
+const tools = { // Tool types
+ BRUSH: 'BRUSH',
+ ERASER: 'ERASER',
+ PENCIL: 'PENCIL',
+ SPRAY: 'SPRAY',
+ LINE: 'LINE',
+ PICKER: 'PICKER',
+ FILL: 'FILL'
+}
+let toolSize = 8; // Tool size, in pixels
+let primaryColour = "#000"; // Current selected colour
+let secondaryColour = "#fff"; // Secondary colour (unused)
+let spray_strength = 1; // Spray strength, either 1, 2 or 3
+
+let mouseHeld = false; // Is mouse held?
+let mouseClicked = false; // Has mouse just been clicked?
+let mousepos = [0,0]; // Current mouse position (x, y)
+let prevMouseMos = [0,0]; // Mouse position in the last frame (x, y)
+
+/*
+ Initialise board appearance
+ */
+clearCanvas(gtx, "#fff");
+clearCanvas(otx, "rgba(0,0,0,0)");
+
+/*
+ Checks to see the mouse and engine state before
+ beginning the action chain, if needed.
+ */
+function runMotion() {
+ // Make sure tool size is up to date
+ toolSize = MathL.clamp(size_selector.value,1,256);
+ setPrimaryColour(colour_selector.value);
+ updateOverlay();
+
+ if(mouseHeld) {
+ if(mouseInCanvas()){
+ startAction();
+ }
+ }
+}
+
+/*
+ Executes the beginning of the "action" chain where what happens under the users
+ pointer is determined by current values, and is executed accordingly.
+ Called when mouse is pressed
+ */
+function startAction() {
+ if (currentTool === tools.LINE) {
+ if (!mouseClicked) return;
+
+ if (!isDrawingLine) {
+ isDrawingLine = true;
+ lineA = [...mousepos];
+ } else {
+ isDrawingLine = false;
+ lineB = [...mousepos];
+
+ gtx.beginPath();
+ gtx.lineWidth = toolSize;
+ gtx.lineCap = "round";
+ gtx.moveTo(lineA[0], lineA[1]);
+ gtx.lineTo(lineB[0], lineB[1]);
+ gtx.stroke();
+
+ lineA = [];
+ lineB = [];
+ }
+ mouseClicked = false;
+ return;
+ }
+
+ if (currentTool === tools.ERASER) {
+ gtx.fillStyle = secondaryColour;
+ gtx.strokeStyle = secondaryColour;
+ } else {
+ gtx.fillStyle = primaryColour;
+ gtx.strokeStyle = primaryColour;
+ }
+
+ function act() {
+ switch (currentTool) {
+ case tools.PENCIL:
+ gtx.fillRect(mousepos[0] - toolSize / 2, mousepos[1] - toolSize / 2, toolSize, toolSize);
+ break;
+ case tools.ERASER:
+ case tools.BRUSH:
+ gtx.beginPath();
+ gtx.lineWidth = toolSize;
+ gtx.lineCap = "round";
+ gtx.moveTo(prevMouseMos[0], prevMouseMos[1]);
+ gtx.lineTo(mousepos[0], mousepos[1]);
+ gtx.stroke();
+ break;
+ case tools.SPRAY:
+ for (let s = 0; s <= Math.pow(spray_strength, 3); s++) {
+ let dist = Math.random() * (toolSize / 2);
+ let ang = Math.random() * 2 * Math.PI;
+ let dot = [
+ mousepos[0] + dist * Math.cos(ang),
+ mousepos[1] + dist * Math.sin(ang),
+ ];
+ gtx.fillRect(dot[0], dot[1], 1, 1);
+ }
+ break;
+ case tools.PICKER:
+ setColourPicker(rgbaToHex(getColorAt(mousepos[0], mousepos[1])));
+ break;
+ default:
+ break;
+ }
+ }
+ act();
+ mouseClicked = false;
+}
+
+/*
+Queries the user before clearing the canvas.
+- context is the canvas context to clear
+- col is the colour to clear it with
+ */
+function clearCanvasRequest(context, col) {
+ if(confirm("Clear the Canvas? This cannot be undone!")) {
+ clearCanvas(context, col);
+ }
+}
+
+/*
+Clearing the given canvas with the given colour.
+- context is the canvas context to clear
+- col is the colour to clear it with. If transparent (rgba(0,0,0,0)) it will just wipe it clear.
+ */
+function clearCanvas(context, col) {
+ context.fillStyle = col;
+ if(col === "rgba(0,0,0,0)") {
+ context.clearRect(0,0,canv_width,canv_height);
+ } else {
+ context.fillRect(0,0,canv_width,canv_height);
+ }
+}
+
+/*
+Updates the overlay canvas when called. Used for things like paint
+previews, brush position, e.g.
+ */
+function updateOverlay() {
+ clearCanvas(otx, "rgba(0,0,0,0)")
+ otx.strokeStyle = "#666";
+ otx.lineWidth = 2;
+ switch (currentTool) {
+ case "ERASER":
+ case "BRUSH":
+ case "SPRAY":
+ otx.beginPath();
+ otx.arc(mousepos[0], mousepos[1], toolSize/2, 0, 2 * Math.PI);
+ otx.stroke();
+ break;
+ case "PENCIL":
+ otx.strokeRect(mousepos[0]-(toolSize/2), mousepos[1]-(toolSize/2), toolSize, toolSize);
+ break;
+ case "LINE":
+ if(isDrawingLine){
+ otx.lineWidth = toolSize;
+ otx.strokeStyle = "rgba(128,128,128,0.5)";
+ otx.beginPath();
+ otx.lineCap = "round";
+ otx.moveTo(lineA[0],lineA[1]);
+ otx.lineTo(mousepos[0],mousepos[1]);
+ otx.stroke();
+ otx.strokeStyle = "#666";
+ otx.lineWidth = 2;
+ }
+ case "PICKER":
+ case "FILL":
+ otx.beginPath();
+ otx.moveTo(mousepos[0]-12, mousepos[1]);
+ otx.lineTo(mousepos[0]+12, mousepos[1]);
+ otx.stroke();
+ otx.beginPath();
+ otx.moveTo(mousepos[0], mousepos[1]-12);
+ otx.lineTo(mousepos[0], mousepos[1]+12);
+ otx.stroke();
+ break;
+ default:
+ break;
+
+ }
+}
+
+/*
+Updates the sprayStrength to a set value
+- str is the new spray strength, either 1, 2 or 3 - weak, light and heavy respectively
+ */
+function sprayStrength(str) {
+ spray_strength = MathL.clamp(str,1,3);
+}
+
+/*
+ Returns array [R,G,B,A] of pixel at xy
+ - x position of the pixel to query
+ - y position of the pixel to query
+ */
+function getColorAt(x, y) {
+ const data = gtx.getImageData(x, y, 1, 1).data;
+ return { r: data[0], g: data[1], b: data[2], a: data[3] };
+}
+/*
+Returns an rgba value as a hex colour value.
+- rgba is the colour to convert to hex.
+ */
+function rgbaToHex(rgba) {
+ const { r, g, b } = rgba;
+ const toHex = (value) => value.toString(16).padStart(2, '0');
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+}
+
+/*
+Checks to see if two colours match.
+ */
+function colorsMatch(c1, c2) {
+ return c1.r === c2.r && c1.g === c2.g && c1.b === c2.b && c1.a === c2.a;
+}
+
+/*
+Sets the colour picker selector to a given colour value
+- colour is the colour to set the colour picker to.
+ */
+function setColourPicker(colour){
+ colour_selector.value = colour;
+}
+
+/*
+Sets the primary colour to a given colour value
+- colour is the colour to set the primary colour to.
+ */
+function setPrimaryColour (colour) {
+ primaryColour = colour;
+}
+
+/*
+- - UNUSED
+Starts the fill algorithm
+- x is the x position of the pixel to fill from
+- y is the y position of the pixel to fill from
+ */
+function startFill(x, y) {
+ let startColor = getColorAt(x, y);
+ let visited = new Set();
+ let queue = [];
+ queue.push([x, y]);
+ while (queue.length > 0) {
+ let [startX, startY] = queue.shift();
+ if (visited.has(`${startX},${startY}`)) continue;
+ visited.add(`${startX},${startY}`);
+ if (!MathL.rangeCheck(startX, 0, canv_width) || !MathL.rangeCheck(startY, 0, canv_height)) continue;
+ let currentColor = getColorAt(startX, startY);
+ if (!colorsMatch(currentColor, startColor)) continue;
+ gtx.fillRect(startX, startY, 1, 1);
+ let left = startX;
+ while (left >= 0 && colorsMatch(getColorAt(left, startY), startColor)) {
+ visited.add(`${left},${startY}`);
+ gtx.fillRect(left, startY, 1, 1);
+ left--;
+ }
+ let right = startX + 1;
+ while (right < canv_width && colorsMatch(getColorAt(right, startY), startColor)) {
+ visited.add(`${right},${startY}`);
+ gtx.fillRect(right, startY, 1, 1);
+ right++;
+ }
+ if (startY - 1 >= 0) {
+ let newLeft = left + 1;
+ while (newLeft < right) {
+ if (colorsMatch(getColorAt(newLeft, startY - 1), startColor) && !visited.has(`${newLeft},${startY - 1}`)) {
+ queue.push([newLeft, startY - 1]);
+ }
+ newLeft++;
+ }
+ }
+ if (startY + 1 < canv_height) {
+ let newLeft = left + 1;
+ while (newLeft < right) {
+ if (colorsMatch(getColorAt(newLeft, startY + 1), startColor) && !visited.has(`${newLeft},${startY + 1}`)) {
+ queue.push([newLeft, startY + 1]);
+ }
+ newLeft++;
+ }
+ }
+ }
+}
+
+/*
+Sets the tool to the given tool value and updates the interface and
+functionality accordingly
+- tool is the tool to set the currentTool to.
+ */
+function changeTool(tool) {
+ size_selector_container.style.display = "none";
+ spray_strength_selector.style.display = "none";
+ $(".tool_button").css = "";
+ let selected;
+ switch (tool) {
+ case 0:
+ currentTool = tools.BRUSH;
+ size_selector_container.style.display = "inline-block";
+ selected = document.getElementById("button_brush");
+ break;
+ case 1:
+ currentTool = tools.ERASER;
+ size_selector_container.style.display = "inline-block";
+ selected = document.getElementById("button_eraser");
+ break;
+ case 2:
+ currentTool = tools.PENCIL;
+ size_selector_container.style.display = "inline-block";
+ selected = document.getElementById("button_pencil");
+ break;
+ case 3:
+ currentTool = tools.SPRAY;
+ size_selector_container.style.display = "inline-block";
+ spray_strength_selector.style.display = "inline-block";
+ selected = document.getElementById("button_spray");
+ break;
+ case 4:
+ currentTool = tools.LINE;
+ size_selector_container.style.display = "inline-block";
+ selected = document.getElementById("button_line");
+ break;
+ case 5:
+ currentTool = tools.PICKER;
+ selected = document.getElementById("button_picker");
+ break;
+ /*case 6:
+ currentTool = tools.FILL;
+ break;*/
+ default:
+ break;
+ }
+ selected.css = "border: '2px inset #bbccff'; borderRadius: '4px'; background: '#779'";
+}
+
+/*
+Loads any bitmap image into the canvas
+ */
+function loadInImage() {
+ const fileInput = document.getElementById('fileInput');
+ const file = fileInput.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ const img = new Image();
+ img.src = e.target.result;
+ img.onload = function () {
+ gtx.drawImage(img, 0,0,canv_width,canv_height);
+ };
+
+ };
+
+ reader.readAsDataURL(file);
+ }
+}
+
+/*
+Provides a download dialog for the current canvas viewport
+ */
+function saveAsImage() {
+ if (canvas) {
+ const dataURL = canvas.toDataURL("image/png");
+ const link = document.createElement("a");
+ link.href = dataURL;
+ link.download = "canvas.png";
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ } else {
+ console.error("Canvas element not found!");
+ }
+}
+
+/*
+ Check to ensure the mouse is inside the canvas
+ */
+function mouseInCanvas() {
+ return (
+ MathL.rangeCheck(mousepos[0], 0, canv_width) &&
+ MathL.rangeCheck(mousepos[1], 0, canv_height)
+ );
+}
+
+/*
+ Update mouse variables on movement
+ */
+document.onmousemove = function (event) {
+ prevMouseMos = mousepos;
+ canvasBounds = canvas.getBoundingClientRect();
+ mousepos = [Math.trunc(event.clientX - canvasBounds.left), Math.trunc(event.clientY - canvasBounds.top)];
+
+ runMotion();
+ if (mouseHeld && mouseInCanvas()) {
+ startAction();
+ }
+};
+document.onmousedown = function (event) {
+ mouseHeld = true;
+ mouseClicked = true;
+ prevMouseMos = mousepos;
+ canvasBounds = canvas.getBoundingClientRect();
+ mousepos = [Math.trunc(event.clientX - canvasBounds.left), Math.trunc(event.clientY - canvasBounds.top)];
+ if (mouseInCanvas()) {
+ startAction();
+ }
+};
+document.onmouseup = function () {
+ mouseHeld = false;
+};
+
+
+changeTool(0);
+
diff --git a/res/res.gif b/res/res.gif
new file mode 100644
index 0000000000000000000000000000000000000000..9520f32ffb855939e316144b8aa37d3900120344
GIT binary patch
literal 1841
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
literal 0
HcmV?d00001
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..0e41c21
--- /dev/null
+++ b/style.css
@@ -0,0 +1,131 @@
+html {
+ background-color: #203450;
+ font-size: 14px;
+ font-family: Arial, "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+ color: #000000;
+}
+
+.container {
+ background-color: #5e8ad0;
+ margin: auto;
+ padding: 20px;
+ border: 3px outset #6a99e8;
+ border-radius: 20px;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
+}
+
+
+#image_input_container {
+ width: auto;
+ margin: auto;
+ z-index: 3;
+ display: none;
+ position: absolute;
+}
+
+#main_container {
+ width: 600px;
+}
+
+canvas {
+ position: absolute;
+ margin: auto;
+ border: 3px inset #6a99e8;
+ border-radius: 8px;
+}
+#canvas_container {
+ padding-bottom: 510px;
+}
+
+#drawing_canvas {
+ z-index: 1;
+ image-rendering: pixelated;
+}
+#overlay_canvas {
+ z-index: 2;
+ background-color: rgba(0, 0, 0, 0);
+}
+#overlay_canvas:hover {
+ cursor: none;
+}
+
+#canvas_controls {
+ display: inline-block;
+ width: 100%;
+}
+
+button {
+ border: 2px outset #bce;
+ border-radius: 4px;
+ background-color: #bce;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
+ font-family: inherit;
+}
+button:hover {
+ border: 2px outset #cdf;
+ background-color: #ddf;
+}
+button:active {
+ border: 2px inset #68c;
+ background-color: #68c;
+}
+
+.control_button {
+ padding: 2px 4px;
+}
+.tool_button {
+ width: 32px;
+ height: 32px;
+ padding: 16px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ image-rendering: pixelated;
+}
+.tool_button#button_pencil {
+ background: #bce url("res/res.gif") 0 0;
+}
+.tool_button#button_eraser {
+ background: #bce url("res/res.gif") -32px 0;
+}
+.tool_button#button_brush {
+ background: #bce url("res/res.gif") -64px 0;
+}
+.tool_button#button_spray {
+ background: #bce url("res/res.gif") -96px 0;
+}
+.tool_button#button_line {
+ background: #bce url("res/res.gif") -130px 0;
+}
+.tool_button#button_picker {
+ background: #bce url("res/res.gif") -162px 0;
+}
+.tool_button#button_fill {
+ background: #bce url("res/res.gif") -164px 0;
+}
+.tool_button#spray_strength_1 {
+ background: #bce url("res/res.gif") 0 -32px;
+}
+.tool_button#spray_strength_2 {
+ background: #bce url("res/res.gif") -32px -32px;
+}
+.tool_button#spray_strength_3 {
+ background: #bce url("res/res.gif") -64px -32px;
+}
+.tool_setting {
+ width: 64px;
+ font-family: inherit;
+}
+
+h1 {
+ color: #fff;
+ text-shadow: #212121 2px 2px 4px;
+}
+
+.vertical_separator {
+ margin: 4px 0;
+ height: 1px;
+ width: 100%;
+ border: 3px outset #6a99e8;
+ border-radius: 4px;
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
+}
\ No newline at end of file