From e453aec077c7110943ef9b9bba99f38f8b6e1224 Mon Sep 17 00:00:00 2001 From: Gregory Leeman Date: Sun, 18 Aug 2024 11:35:01 +0100 Subject: [PATCH] lazygit --- cool.js => archive/cool.js | 0 history.js => archive/history.js | 0 archive/temp.css | 25 - archive/temp.js | 774 +++++++++++++++++++++++++++---- temp2.js => archive/temp2.js | 0 render.js | 682 ++++++++++++++++++--------- temp.js | 724 ----------------------------- 7 files changed, 1158 insertions(+), 1047 deletions(-) rename cool.js => archive/cool.js (100%) rename history.js => archive/history.js (100%) delete mode 100644 archive/temp.css rename temp2.js => archive/temp2.js (100%) delete mode 100644 temp.js diff --git a/cool.js b/archive/cool.js similarity index 100% rename from cool.js rename to archive/cool.js diff --git a/history.js b/archive/history.js similarity index 100% rename from history.js rename to archive/history.js diff --git a/archive/temp.css b/archive/temp.css deleted file mode 100644 index 2383992..0000000 --- a/archive/temp.css +++ /dev/null @@ -1,25 +0,0 @@ -.puck { - width: 100px; - height: 120px; - padding: 2px; - background-color: #ddd; - border: 1px solid; - position: absolute; - display: flex; - flex-direction: column; -} - -.well { - width: 100%; - flex-grow: 1; - border: 1px solid; -} - -.puck-menu { - display: flex; - flex-direction: row; - justify-content: space-between; - padding-top: 2px; -} - - diff --git a/archive/temp.js b/archive/temp.js index 5076e70..fdb075f 100644 --- a/archive/temp.js +++ b/archive/temp.js @@ -1,124 +1,724 @@ -function createPuck(c) { - const puck = document.createElement('div'); - puck.className = 'puck'; +const colorPreview = document.createElement('div'); +colorPreview.id = 'color-preview'; +colorPreview.className = 'puck'; +colorPreview.style.backgroundColor = color; + +menuBar.appendChild(colorPreview); - const well = document.createElement('div'); - well.className = 'well'; - well.style.backgroundColor = c; - puck.appendChild(well); - const puckMenu = document.createElement('div'); - puckMenu.className = 'puck-menu'; - puck.appendChild(puckMenu); +// }}} - const selectHandle = document.createElement('div'); - selectHandle.className = 'select-handle'; - selectHandle.innerHTML = ''; - puckMenu.appendChild(selectHandle); +// helpers {{{ - const copyHandle = document.createElement('div'); - copyHandle.className = 'copy-handle'; - copyHandle.innerHTML = ''; - puckMenu.appendChild(copyHandle); - const deleteHandle = document.createElement('div'); - deleteHandle.className = 'delete-handle'; - deleteHandle.innerHTML = ''; - puckMenu.appendChild(deleteHandle); +function saveCanvas() { + const dataURL = canvas.toDataURL(); + const dimensions = `${canvas.width}x${canvas.height}`; + localStorage.setItem('mixxCanvas', dataURL); + localStorage.setItem('mixxDimensions', dimensions); + console.log('Canvas saved'); +} + +function loadCanvas() { + const dataURL = localStorage.getItem('mixxCanvas'); + const dimensions = localStorage.getItem('mixxDimensions'); + if (dataURL && dimensions) { + const img = new Image(); + img.src = dataURL; + img.onload = function() { + canvas.width = dimensions.split('x')[0]; + canvas.height = dimensions.split('x')[1]; + canvas.style.width = canvas.width * zoom + 'px'; + canvas.style.height = canvas.height * zoom + 'px'; + canvasWidth = canvas.width; + canvasHeight = canvas.height; + ctx.drawImage(img, 0, 0); + } + } else { + console.log('No saved canvas found'); + } +} - const updateHandle = document.createElement('div'); - updateHandle.className = 'update-handle'; - updateHandle.innerHTML = ''; - puckMenu.appendChild(updateHandle); +function clearCanvasFromLocalStorage() { + localStorage.removeItem('savedCanvas'); + localStorage.removeItem('canvasDimensions'); +} +function saveState() { + if (undoStack.length >= maxHistory) { + undoStack.shift(); // Remove the oldest state if the stack exceeds the limit + } - const dragHandle = document.createElement('div'); - dragHandle.className = 'drag-handle'; - dragHandle.innerHTML = ''; - puckMenu.appendChild(dragHandle); + undoStack.push({ + imageData: canvas.toDataURL(), + width: canvas.width, + height: canvas.height + }); - well.addEventListener('mousedown', (e) => { - let isMixing = true; - let startX = e.clientX; - let startY = e.clientY; + redoStack = []; // Clear the redo stack whenever a new action is performed +} - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); +function undo() { + if (undoStack.length > 0) { + const currentState = { + imageData: canvas.toDataURL(), + width: canvas.width, + height: canvas.height + }; + + redoStack.push(currentState); // Save current state to the redo stack + const lastState = undoStack.pop(); // Get the last state from the undo stack + + canvas.width = lastState.width; + canvas.height = lastState.height; + canvas.style.width = canvas.width * zoom + 'px'; + canvas.style.height = canvas.height * zoom + 'px'; + canvasWidth = canvas.width; + canvasHeight = canvas.height; + + const img = new Image(); + img.src = lastState.imageData; + img.onload = function() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0); + }; + } +} + +function redo() { + if (redoStack.length > 0) { + const currentState = { + imageData: canvas.toDataURL(), + width: canvas.width, + height: canvas.height + }; + + undoStack.push(currentState); // Save current state to the undo stack + const nextState = redoStack.pop(); // Get the last state from the redo stack + + canvas.width = nextState.width; + canvas.height = nextState.height; + canvas.style.width = canvas.width * zoom + 'px'; + canvas.style.height = canvas.height * zoom + 'px'; + canvasWidth = canvas.width; + canvasHeight = canvas.height; + + const img = new Image(); + img.src = nextState.imageData; + img.onload = function() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0); + }; + } +} - function onMouseMove(e) { - if (isMixing) { - const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2)); +function getPositionOnCanvas(e) { + const rect = canvas.getBoundingClientRect(); + return { + x: Math.round((e.clientX - rect.left) / zoom), + y: Math.round((e.clientY - rect.top) / zoom), + }; +} + +function drawCircle(x, y) { + ctx.beginPath(); + ctx.arc(x, y, brushSize / 2, 0, 2 * Math.PI, false); + ctx.fillStyle = color; + ctx.fill(); +} + +function drawLineWithCircles(x1, y1, x2, y2) { + const dx = x2 - x1; + const dy = y2 - y1; + const distance = Math.sqrt(dx * dx + dy * dy); + const steps = Math.ceil(distance / (brushSize / 5)); + + for (let i = 0; i <= steps; i++) { + const x = x1 + (dx * i) / steps; + const y = y1 + (dy * i) / steps; + drawCircle(x, y); + } +} - const t = Math.min(1, distance / 300); +function saveCanvasContents() { + tempCanvas = document.createElement('canvas'); + tempCanvas.width = canvas.width; + tempCanvas.height = canvas.height; + const tempCtx = tempCanvas.getContext('2d'); + tempCtx.drawImage(canvas, 0, 0); +} - const mixedColor = mixbox.lerp(color, well.style.backgroundColor, t); +function updateColorPreview() { + colorPreview.style.backgroundColor = color; +} - color = mixedColor; +function hexToRgbArray(hex) { + if (hex.startsWith('#')) { + hex = hex.slice(1); + } - startX = e.clientX; - startY = e.clientY; + if (hex.length === 3) { + hex = hex.split('').map(char => char + char).join(''); + } + + const bigint = parseInt(hex, 16); + return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; +} + +function floodFill(x, y, fillColor) { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + + const targetColor = getColorAtPixel(data, x, y); + const fillColorArray = hexToRgbArray(fillColor); + + if (colorsMatch(targetColor, fillColorArray)) { + return; // The clicked point is already the fill color + } + + const stack = [{x, y}]; + + while (stack.length > 0) { + const {x, y} = stack.pop(); + const currentColor = getColorAtPixel(data, x, y); + + if (colorsMatch(currentColor, targetColor)) { + setColorAtPixel(data, x, y, fillColorArray); + + if (x > 0) stack.push({x: x - 1, y}); + if (x < canvas.width - 1) stack.push({x: x + 1, y}); + if (y > 0) stack.push({x, y: y - 1}); + if (y < canvas.height - 1) stack.push({x, y: y + 1}); + } + } + + ctx.putImageData(imageData, 0, 0); +} + +function getColorAtPixel(data, x, y) { + const index = (y * canvas.width + x) * 4; + return [data[index], data[index + 1], data[index + 2], data[index + 3]]; +} + +function setColorAtPixel(data, x, y, color) { + const index = (y * canvas.width + x) * 4; + data[index] = color[0]; + data[index + 1] = color[1]; + data[index + 2] = color[2]; + data[index + 3] = 255; // Set alpha to fully opaque +} + +function colorsMatch(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; +} + +// }}} + +// mousedown {{{ + +canvasArea.addEventListener('mousedown', (e) => { + if (e.target.closest('.puck')) return; + + startX = e.clientX; + startY = e.clientY; + canvasStartX = getPositionOnCanvas(e).x; + canvasStartY = getPositionOnCanvas(e).y; + saveCanvasContents(); + isMouseDown = true; + + if ( + tool === 'brush' || + tool === 'content-move' || + tool === 'resize' || + tool === 'zoom' || + tool === 'bucket-fill' + ) { + saveState(); + } + + if (tool === 'brush') { + console.log('brush'); + drawCircle(canvasStartX, canvasStartY); + } else if (tool === 'bucket-fill') { + floodFill(canvasStartX, canvasStartY, color); + return; + } else if (tool === 'move') { + startX = e.clientX - canvasContainer.offsetLeft; + startY = e.clientY - canvasContainer.offsetTop; + } else if (tool === 'color-picker') { + const imageData = ctx.getImageData(canvasStartX, canvasStartY, 1, 1).data; + const pickedColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`; + color = pickedColor; + console.log('Picked Color:', pickedColor); + updateColorPreview(); + return; + } +}); + +// }}} + +// mousemove {{{ + +canvasArea.addEventListener('mousemove', (e) => { + + endX = e.clientX; + endY = e.clientY; + dX = endX - startX; + dY = endY - startY; + + canvasEndX = getPositionOnCanvas(e).x; + canvasEndY = getPositionOnCanvas(e).y; + canvasDX = canvasEndX - canvasStartX; + canvasDY = canvasEndY - canvasStartY; + + if (tool == 'brush-size') { + brushPreview.style.display = 'block'; + brushPreview.style.width = brushSize + 'px'; + brushPreview.style.height = brushSize + 'px'; + brushPreview.style.left = e.clientX - brushSize / 2 + 'px'; + brushPreview.style.top = e.clientY - brushSize / 2 + 'px'; + } + + if (isMouseDown) { + + if (tool === 'brush-size') { + brushSize += dX * dBrushSize; + if (brushSize < 1) brushSize = 1; + if (brushSize > maxBrushSize) brushSize = maxBrushSize; + startX = endX; + } else if (tool === 'brush') { + drawLineWithCircles(canvasStartX, canvasStartY, canvasEndX, canvasEndY); + + canvasStartX = canvasEndX; + canvasStartY = canvasEndY; + + } else if (tool === 'content-move') { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(tempCanvas, dX, dY); + } else if (tool === 'move') { + canvasContainer.style.left = dX + 'px'; + canvasContainer.style.top = dY + 'px'; + } else if (tool === 'zoom') { + zoom += dX * dZoom; + if (zoom < 0.1) zoom = 0.1; + canvas.style.height = canvasHeight * zoom + 'px'; + canvas.style.width = canvasWidth * zoom + 'px'; + startX = endX; + } else if (tool === 'resize') { + let newWidth = canvasWidth + dX / zoom; + let newHeight = canvasHeight + dY / zoom; + if (newWidth > 0 && newHeight > 0) { + canvas.width = newWidth; + canvas.height = newHeight; + canvas.style.width = newWidth * zoom + 'px'; + canvas.style.height = newHeight * zoom + 'px'; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(tempCanvas, 0, 0); } - } + } else if (tool === 'color-mix') { - function onMouseUp() { - isMixing = false; - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); + const imageData = ctx.getImageData(canvasEndX, canvasEndY, 1, 1).data; + const canvasColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`; + + const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2)); + const t = Math.min(1, distance / 300); + + const mixedColor = mixbox.lerp(color, canvasColor, t); + + color = mixedColor; + + startX = e.clientX; + startY = e.clientY; + + } + + } + + updateInfos(); + updateColorPreview(); +}); + +// }}} + +// mouseup {{{ + +canvasArea.addEventListener('mouseup', (e) => { + isMouseDown = false; + if (tool === 'brush') { + ctx.closePath(); + } else if (tool === 'resize') { + canvasWidth = canvas.width; + canvasHeight = canvas.height; + } + + updateColorPreview(); +}); + +// }}} + +// mouseleave {{{ + +canvasArea.addEventListener('mouseleave', (e) => { + isMouseDown = false; + brushPreview.style.display = 'none'; +}); + +// }}} + +// keybindings {{{ + +const toolKeyBindings = {} + +const functionKeyBindings = { +} + +document.addEventListener('keydown', (e) => { + if (keyDown) return; + + const newTool = toolKeyBindings[e.key.toLowerCase()] + if (newTool) { + prevTool = tool; + keyDown = true; + changeTool(newTool); + return; + } + + const func = functionKeyBindings[e.key]; + if (func) { + func(); + return; + } + +}); + +document.addEventListener('keyup', (e) => { + const currentTool = toolKeyBindings[e.key.toLowerCase()] + if (currentTool) { + keyDown = false; + if (e.key == e.key.toLowerCase()) { + changeTool(prevTool); + return; } + } +}); + +// }}} + +// tools {{{ + +var toolButtons = []; + +function changeTool(toolName) { + toolButtons.forEach(button => button.button.classList.remove('active')); + toolButtons.find(button => button.name === toolName).button.classList.add('active'); + tool = toolName; + brushPreview.style.display = 'none'; + updateInfos(); +} + +function createToolButton(toolName, displayName, icon, key=undefined) { + const button = document.createElement('div'); + button.classList.add('button'); + button.classList.add('tool'); + button.innerHTML = icon; + button.title = displayName; + button.addEventListener('click', () => { + changeTool(toolName); }); - dragHandle.addEventListener('mousedown', (e) => { - let isMovingPuck = true; - let startX = e.clientX; - let startY = e.clientY; - let left = puck.offsetLeft; - let top = puck.offsetTop; - document.addEventListener('mousemove', (e) => { - if (isMovingPuck) { - puck.style.left = left + e.clientX - startX + 'px'; - puck.style.top = top + e.clientY - startY + 'px'; + if (key) { + const keyHint = document.createElement('span'); + keyHint.className = 'key-hint'; + keyHint.innerHTML = key; + button.appendChild(keyHint); + toolKeyBindings[key] = toolName; + } + + toolBar.appendChild(button); + toolButtons.push({'name': toolName, 'button': button}); + return button; +} + +createToolButton('brush', 'Brush', '', 'b'); +createToolButton('content-move', 'Move Content', '', 'h'); +createToolButton('move', 'Move Canvas', '', 'm'); +createToolButton('zoom', 'Zoom', '', 'z'); +createToolButton('resize', 'Resize', '', 'r'); +createToolButton('color-picker', 'Color Picker', '', 'a'); +createToolButton('color-mix', 'Color Mix', '', 'x'); +createToolButton('brush-size', 'Brush Size', '', 'd'); +createToolButton('bucket-fill', 'Bucket Fill', '', 'k'); + +// }}} + +// menu functons {{{ + +function flipCanvasHorizontally(e) { + saveState(); + ctx.save(); + saveCanvasContents(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.scale(-1, 1); + ctx.translate(-canvas.width, 0); + ctx.drawImage(tempCanvas, 0, 0); + ctx.restore(); +} + +function flipCanvasVertically(e) { + saveState(); + ctx.save(); + saveCanvasContents(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.scale(1, -1); + ctx.translate(0, -canvas.height); + ctx.drawImage(tempCanvas, 0, 0); + ctx.restore(); +} + +function exportCanvas(e) { + const link = document.createElement('a'); + link.download = 'canvas.png'; + link.href = canvas.toDataURL(); + link.click(); +} + +function importCanvas(e) { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'image/*'; + input.onchange = (e) => { + const file = e.target.files[0]; + const reader = new FileReader(); + reader.onload = (e) => { + const img = new Image(); + img.onload = () => { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); } + img.src = e.target.result; + } + reader.readAsDataURL(file); + } + input.click(); +} + +function clearCanvas(e) { + saveState(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); +} + +function resetZoom(e) { + zoom = 1; + canvas.style.width = canvas.width * zoom + 'px'; + canvas.style.height = canvas.height * zoom + 'px'; + canvasWidth = canvas.width; + canvasHeight = canvas.height; + + canvasAreaRect = canvasArea.getBoundingClientRect(); + + canvasContainer.style.left = `${canvasAreaRect.left}px`; + canvasContainer.style.top = `${canvasAreaRect.top}px`; +} + +// }}} + +// menu {{{ + +var menuButtons = []; + +function createMenuButton(icon, name, clickFunction, key=undefined) { + const button = document.createElement('div'); + button.className = 'button'; + button.innerHTML = icon; + button.title = name; + if (clickFunction) { + button.addEventListener('click', () => { + clickFunction() + updateInfos(); }); - document.addEventListener('mouseup', () => { - isMovingPuck = false; - document.removeEventListener('mousemove', () => {}); - document.removeEventListener('mouseup', () => {}); + } + menuBar.appendChild(button); + if (key) { + const keyHint = document.createElement('span'); + keyHint.className = 'key-hint'; + keyHint.innerHTML = key; + button.appendChild(keyHint); + functionKeyBindings[key] = clickFunction; + } + return button; +} + +menuButtons.push(createMenuButton('', 'Save', saveCanvas, 's')); +menuButtons.push(createMenuButton('', 'Load', loadCanvas)); +menuButtons.push(createMenuButton('', 'Clear', clearCanvas)); +menuButtons.push(createMenuButton('', 'Export', exportCanvas)); +menuButtons.push(createMenuButton('', 'Import', importCanvas)); + + +menuButtons.push(createMenuButton('', 'Flip Horizontally', flipCanvasHorizontally, 'f')); +menuButtons.push(createMenuButton('', 'Flip Vertically', flipCanvasVertically, 'v')); +menuButtons.push(createMenuButton('', 'Undo', undo, 'u')); +menuButtons.push(createMenuButton('', 'Redo', redo, 'y')); +menuButtons.push(createMenuButton('', 'Clear', clearCanvas, 'c')); +menuButtons.push(createMenuButton('', 'Reset', resetZoom, 't')); +menuButtons.push(createMenuButton('', 'Add Color', createPuck)); + +// }}} + +// pucks {{{ + +function createPuck(c, editable=true, key=undefined) { + if (c === undefined) { + c = color; + } + + const puck = document.createElement('div'); + puck.className = 'puck'; + puck.style.backgroundColor = c; + + // const selectHandle = document.createElement('div'); + // selectHandle.className = 'select-handle'; + // selectHandle.innerHTML = ''; + // puck.appendChild(selectHandle); + + // selectHandle.addEventListener('click', () => { + // color = puck.style.backgroundColor; + // updateColorPreview(); + // updateInfos(); + // }); + + if (editable) { + // const updateHandle = document.createElement('div'); + // updateHandle.className = 'update-handle'; + // updateHandle.innerHTML = ''; + // puck.appendChild(updateHandle); + + // updateHandle.addEventListener('click', () => { + // puck.style.backgroundColor = color; + // }); + + const deleteHandle = document.createElement('div'); + deleteHandle.className = 'delete-handle'; + deleteHandle.innerHTML = ''; + puck.appendChild(deleteHandle); + + deleteHandle.addEventListener('click', () => { + console.log("test"); + puck.remove(); }); - }); + } - updateHandle.addEventListener('click', () => { - console.log('update'); - well.style.backgroundColor = color; - }); + if (key) { + const keyHint = document.createElement('div'); + keyHint.className = 'key-hint'; + keyHint.innerHTML = key; + puck.appendChild(keyHint); + } - selectHandle.addEventListener('click', () => { - console.log('select'); - color = well.style.backgroundColor; - console.log(color); - updateColorPreview(); - }); + function mixx(startTime) { + var interval = setInterval(() => { + const elapsedTime = Date.now() - startTime; + const t = Math.min(1, elapsedTime / 10000); + const mixedColor = mixbox.lerp(color, puck.style.backgroundColor, t); + color = mixedColor; + updateColorPreview(); + updateInfos(); + }, 50); + return interval; + } - copyHandle.addEventListener('click', () => { - pucks.push(createPuck(well.style.backgroundColor)); + puck.addEventListener('mousedown', (e) => { + const startTime = Date.now(); + var interval = mixx(startTime); + function onMouseUp() { + clearInterval(interval); + document.removeEventListener('mouseup', onMouseUp); + } + document.addEventListener('mouseup', onMouseUp); }); - deleteHandle.addEventListener('click', () => { - pucks = pucks.filter(p => p !== puck); - puck.remove(); + document.addEventListener('keydown', (e) => { + if (e.key == key) { + console.log(e.key); + const startTime = Date.now(); + var interval = mixx(startTime); + function onKeyUp() { + clearInterval(interval); + document.removeEventListener('keyup', onKeyUp); + } + document.addEventListener('keyup', onKeyUp); + } }); + menuBar.appendChild(puck); +} + +createPuck(c='rgb(0, 0, 0)', editable=false, key='1'); +createPuck(c='rgb(255, 0, 0)', editale=false, key='2'); +createPuck(c='rgb(0, 0, 255)', editale=false, key='3'); +createPuck(c='rgb(255, 255, 0)', editale=false, key='4'); +createPuck(c='rgb(99, 60, 22)', editale=false, key='5'); +createPuck(c='rgb(0, 255, 0)', editale=false, key='6'); +createPuck(c='rgb(255, 0, 255)', editale=false, key='7'); +createPuck(c='rgb(0, 255, 255)', editale=false, key='8'); +createPuck(c='rgb(255, 255, 255)', editale=false, key='9'); - canvasArea.appendChild(puck); - let canvasWidth = canvasArea.offsetWidth; - let canvasHeight = canvasArea.offsetHeight; +// }}} - let randonX = Math.floor(Math.random() * canvasWidth); - let randonY = Math.floor(Math.random() * canvasHeight); +// info {{{ - puck.style.left = randonX + 'px'; - puck.style.top = randonY + 'px'; +var infos = []; - return puck; +function createInfo(name, updateFunction) { + const info = document.createElement('span'); + info.className = 'info'; + const key = document.createElement('span'); + key.className = 'key'; + key.innerHTML = name + ':'; + const value = document.createElement('span'); + value.className = 'value'; + value.innerHTML = '0'; + info.appendChild(key); + info.appendChild(value); + infoBar.appendChild(info); + function update() { + let v = updateFunction(); + if (v === undefined) v = '?'; + value.innerHTML = v; + } + return update; } +infos.push(createInfo('zoom', function() { + var percent = zoom * 100; + return percent.toFixed(0) + '%'; +})); +infos.push(createInfo('brush', function() { return brushSize; })); +infos.push(createInfo('x', function() { return canvasEndX; })); +infos.push(createInfo('y', function() { return canvasEndY; })); +infos.push(createInfo('color', function() { return color; })); +infos.push(createInfo('width', function() { return canvas.width; })); +infos.push(createInfo('height', function() { return canvas.height; })); + +function updateInfos() { + infos.forEach(info => info()); +} + +// }}} + +// start {{{ + +ctx.fillStyle = backgroundColor; +ctx.fillRect(0, 0, canvas.width, canvas.height); +updateInfos(); +toolButtons[0]['button'].click(); + +// }}} diff --git a/temp2.js b/archive/temp2.js similarity index 100% rename from temp2.js rename to archive/temp2.js diff --git a/render.js b/render.js index 2534771..dda3d59 100644 --- a/render.js +++ b/render.js @@ -6,7 +6,6 @@ const layerControllersElement = document.getElementById('layer-controllers'); const studioElement = document.getElementById('studio'); const infoBarElement = document.getElementById('info-bar'); const easelElement = document.getElementById('easel'); -const brushPreviewElement = document.getElementById('brush-preview'); const dZoom = 0.001; const dBrushSize = 0.5; @@ -41,6 +40,7 @@ let isKeyDown = false; let isMouseDown = false; let interval; +var startTime; // }}} @@ -56,15 +56,14 @@ function disableImageSmoothing(ctx) { }; function hexToRgbArray(hex) { - hex = hex.replace(/^#/, ''); - const bigint = parseInt(hex, 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - return [r, g, b, 255]; // Add 255 for full opacity + const r = parseInt(hex.substring(1, 3), 16); + const g = parseInt(hex.substring(3, 5), 16); + const b = parseInt(hex.substring(5, 7), 16); + return [r, g, b, 255]; } function colorsMatch(color1, color2, tolerance = 0) { + return color1[0] === color2[0] && color1[1] === color2[1] && color1[2] === color2[2] && color1[3] === color2[3]; return Math.abs(color1[0] - color2[0]) <= tolerance && Math.abs(color1[1] - color2[1]) <= tolerance && Math.abs(color1[2] - color2[2]) <= tolerance && @@ -78,6 +77,10 @@ function makeIconElement(htmlString) { return iconElement; } +function closeRgbArray(color1, color2, tolerance) { + return Math.abs(color1[0] - color2[0]) <= tolerance && Math.abs(color1[1] - color2[1]) <= tolerance && Math.abs(color1[2] - color2[2]) <= tolerance; +} + function makeButtonElement({icon, name, func, key}) { if (!icon) throw new Error('No icon provided'); if (!name) throw new Error('No name provided'); @@ -118,10 +121,33 @@ function makeButtonElement({icon, name, func, key}) { function makeColor(rgb) { const color = {}; - color.color = rgb; + color.fromRgba = function(rgba) { + color.r = rgba.split(',')[0].split('(')[1]; + color.g = rgba.split(',')[1]; + color.b = rgba.split(',')[2]; + color.a = rgba.split(',')[3].split(')')[0]; + } + + color.toRgba = function() { + return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`; + } + + color.fromRgb = function(rgb) { + color.r = rgb.split(',')[0].split('(')[1]; + color.g = rgb.split(',')[1]; + color.b = rgb.split(',')[2].split(')')[0]; + color.a = 255; + } color.toRgb = function() { - return color.rgb; + return `rgb(${color.r}, ${color.g}, ${color.b})`; + } + + color.fromHex = function(hex) { + color.r = parseInt(hex.substring(1, 3), 16); + color.g = parseInt(hex.substring(3, 5), 16); + color.b = parseInt(hex.substring(5, 7), 16); + color.a = 255; } color.toHex = function() { @@ -131,17 +157,95 @@ function makeColor(rgb) { return `#${color.r.toString(16)}${color.g.toString(16)}${color.b.toString(16)}`; } - color.mix = function(color2, t) { - const color1 = color.color; - const newColor = mixbox.lerp(color1, color2, t); - color.color = newColor; - colorPreview.update(); + color.fromRgbaArray = function(array) { + color.r = array[0]; + color.g = array[1]; + color.b = array[2]; + color.a = array[3]; + } + + color.toRgbaArray = function() { + return [color.r, color.g, color.b, color.a]; + } + + color.fromRgbArray = function(array) { + color.r = array[0]; + color.g = array[1]; + color.b = array[2]; + color.a = 255; } + color.toRgbArray = function() { + return [color.r, color.g, color.b]; + } + + color.mixxRgbArray = function(color2RgbArray, t) { + const color1RgbArray = color.toRgbArray(); + if (color1RgbArray === color2RgbArray) { + return; + } + var newColorRgbArray = color2RgbArray; + if (!closeRgbArray(color1RgbArray, color2RgbArray, 2)) { + console.log('mixxing'); + newColorRgbArray = mixbox.lerp(color1RgbArray, color2RgbArray, t); + } + color.fromRgbArray(newColorRgbArray); + } + + color.mixxRgb = function(color2Rgb, t) { + const result = color2Rgb.match(/rgb\((\d+), (\d+), (\d+)\)/); + if (result) { + const color2RgbArray = [result[1], result[2], result[3]]; + color.mixxRgbArray(color2RgbArray, t); + } + } + + color.mixxRgbaArray = function(color2RgbaArray, t) { + const color2a = color2RgbaArray[3]; + if (color2a !== 225) { + return; + } + const color2RgbArray = color2RgbaArray.slice(0, 3); + color.mixxRgbArray(color2RgbArray, t); + } + + color.mixxRgba = function(color2Rgba, t) { + const result = color2Rgba.match(/rgba\((\d+), (\d+), (\d+), (\d+)\)/); + if (result) { + const color2RgbaArray = [result[1], result[2], result[3], result[4]]; + color.mixxRgbaArray(color2RgbaArray, t); + } + } + + color.mixx = function(color2, t) { + if (color2.a === 255) { + color.mixxRgbArray(color2.toRgbArray(), t); + } + } + + color.isOpaque = function() { + return color.a === 255; + } + + color.match = function(color2) { + return color.r === color2.r && color.g === color2.g && color.b === color2.b && color.a === color2.a; + } + + color.copy = function(color2) { + color.r = color2.r; + color.g = color2.g; + color.b = color2.b; + color.a = color2.a; + } + + color.fromRgb(rgb); + return color; } -const color = makeColor('rgb(0, 0, 0)'); +const brushColor = makeColor('rgb(0, 0, 0)'); +const canvasColor = makeColor('rgb(0, 0, 0)'); +const tempColor = makeColor('rgb(0, 0, 0)'); // }}} @@ -149,10 +253,11 @@ const color = makeColor('rgb(0, 0, 0)'); // FACTORY {{{ -function makeCanvas({height=600, width=800}) { // {{{ +function makeCanvas({height=600, width=800, background=false}) { // {{{ const canvas = document.createElement('canvas'); canvas.style.imageRendering = 'pixelated'; canvas.ctx = canvas.getContext('2d'); + canvas.background = background; canvas.tempCanvas = document.createElement('canvas'); canvas.tempCtx = canvas.tempCanvas.getContext('2d'); @@ -167,7 +272,9 @@ function makeCanvas({height=600, width=800}) { // {{{ } canvas.clearCanvas = function() { - canvas.ctx.clearRect(0, 0, canvas.width, canvas.height); + if (!canvas.background) { + canvas.ctx.clearRect(0, 0, canvas.width, canvas.height); + } } canvas.restoreCanvas = function(x=0, y=0) { @@ -185,9 +292,14 @@ function makeCanvas({height=600, width=800}) { // {{{ }; canvas.resize = function(width, height) { + canvas.saveCanvas(); + canvas.clearCanvas(); canvas.width = width; canvas.height = height; + canvas.style.width = width * zoom + 'px'; + canvas.style.height = height * zoom + 'px'; disableImageSmoothing(canvas.ctx); + canvas.restoreCanvas(); } canvas.getPositionOnCanvas = function(e) { @@ -199,87 +311,95 @@ function makeCanvas({height=600, width=800}) { // {{{ } canvas.drawPixel = function(x, y, color) { - canvas.ctx.fillStyle = color; - canvas.ctx.fillRect(x, y, 1, 1); + if (!canvas.background) { + canvas.ctx.fillStyle = color; + canvas.ctx.fillRect(x, y, 1, 1); + } } canvas.drawLineWithPixels = function(x1, y1, x2, y2, color) { - const dx = Math.abs(x2 - x1); - const dy = Math.abs(y2 - y1); - const sx = x1 < x2 ? 1 : -1; - const sy = y1 < y2 ? 1 : -1; - let err = dx - dy; - while (true) { - canvas.drawPixel(x1, y1, color); // Draw each pixel along the line - if (x1 === x2 && y1 === y2) break; - const e2 = err * 2; - if (e2 > -dy) { err -= dy; x1 += sx; } - if (e2 < dx) { err += dx; y1 += sy; } + if (!canvas.background) { + const dx = Math.abs(x2 - x1); + const dy = Math.abs(y2 - y1); + const sx = x1 < x2 ? 1 : -1; + const sy = y1 < y2 ? 1 : -1; + let err = dx - dy; + while (true) { + canvas.drawPixel(x1, y1, color); // Draw each pixel along the line + if (x1 === x2 && y1 === y2) break; + const e2 = err * 2; + if (e2 > -dy) { err -= dy; x1 += sx; } + if (e2 < dx) { err += dx; y1 += sy; } + } } } canvas.drawShape = function(x, y, shape, size, color) { - x = Math.round(x); - y = Math.round(y); + if (!canvas.background) { + x = Math.round(x); + y = Math.round(y); - if (size === 1) { - canvas.drawPixel(x, y, color); - return; - } - canvas.ctx.fillStyle = color; - - if (shape === 'square') { - canvas.ctx.fillRect(x - Math.floor(size / 2), y - Math.floor(size / 2), size, size); - } else if (shape === 'circle') { - let radius = Math.floor(size / 1); - let radiusSquared = radius * radius; - - for (let y1 = -radius; y1 <= radius; y1++) { - for (let x1 = -radius; x1 <= radius; x1++) { - // Adjust the condition to avoid the outcrop - if ((x1 * x1 + y1 * y1) <= radiusSquared - radius) { - canvas.ctx.fillRect(x + x1, y + y1, 1, 1); + if (size === 1) { + canvas.drawPixel(x, y, color); + return; + } + canvas.ctx.fillStyle = color; + + if (shape === 'square') { + canvas.ctx.fillRect(x - Math.floor(size / 2), y - Math.floor(size / 2), size, size); + } else if (shape === 'circle') { + let radius = Math.floor(size / 2); + let radiusSquared = radius * radius; + + for (let y1 = -radius; y1 <= radius; y1++) { + for (let x1 = -radius; x1 <= radius; x1++) { + // Adjust the condition to avoid the outcrop + if ((x1 * x1 + y1 * y1) <= radiusSquared - radius) { + canvas.ctx.fillRect(x + x1, y + y1, 1, 1); + } } } - } - } else if (shape === 'empty-circle') { - let radius = Math.floor(size / 2); - let x1 = radius; - let y1 = 0; - let radiusError = 1 - x1; - - while (x1 >= y1) { - // Draw the 8 octants of the circle - canvas.ctx.fillRect(x + x1, y + y1, 1, 1); - canvas.ctx.fillRect(x + y1, y + x1, 1, 1); - canvas.ctx.fillRect(x - y1, y + x1, 1, 1); - canvas.ctx.fillRect(x - x1, y + y1, 1, 1); - canvas.ctx.fillRect(x - x1, y - y1, 1, 1); - canvas.ctx.fillRect(x - y1, y - x1, 1, 1); - canvas.ctx.fillRect(x + y1, y - x1, 1, 1); - canvas.ctx.fillRect(x + x1, y - y1, 1, 1); - - y1++; - if (radiusError < 0) { - radiusError += 2 * y1 + 1; - } else { - x1--; - radiusError += 2 * (y1 - x1 + 1); + } else if (shape === 'empty-circle') { + let radius = Math.floor(size / 2); + let x1 = radius; + let y1 = 0; + let radiusError = 1 - x1; + + while (x1 >= y1) { + // Draw the 8 octants of the circle + canvas.ctx.fillRect(x + x1, y + y1, 1, 1); + canvas.ctx.fillRect(x + y1, y + x1, 1, 1); + canvas.ctx.fillRect(x - y1, y + x1, 1, 1); + canvas.ctx.fillRect(x - x1, y + y1, 1, 1); + canvas.ctx.fillRect(x - x1, y - y1, 1, 1); + canvas.ctx.fillRect(x - y1, y - x1, 1, 1); + canvas.ctx.fillRect(x + y1, y - x1, 1, 1); + canvas.ctx.fillRect(x + x1, y - y1, 1, 1); + + y1++; + if (radiusError < 0) { + radiusError += 2 * y1 + 1; + } else { + x1--; + radiusError += 2 * (y1 - x1 + 1); + } } } } } canvas.drawLineWithShape = function(x1, y1, x2, y2, shape, size, color) { - const dx = x2 - x1; - const dy = y2 - y1; - const distance = Math.sqrt(dx * dx + dy * dy); - const steps = Math.ceil(distance / (size / 2)); - - for (let i = 0; i <= steps; i++) { - const x = Math.round(x1 + (dx * i) / steps); - const y = Math.round(y1 + (dy * i) / steps); - canvas.drawShape(x, y, shape, size, color); + if (!canvas.background) { + const dx = x2 - x1; + const dy = y2 - y1; + const distance = Math.sqrt(dx * dx + dy * dy); + const steps = Math.ceil(distance / (size / 2)); + + for (let i = 0; i <= steps; i++) { + const x = Math.round(x1 + (dx * i) / steps); + const y = Math.round(y1 + (dy * i) / steps); + canvas.drawShape(x, y, shape, size, color); + } } } @@ -288,13 +408,13 @@ function makeCanvas({height=600, width=800}) { // {{{ canvas.ctx.fillRect(0, 0, canvas.width, canvas.height); } - canvas.getColorAtPixelData = function(x, y, data) { + canvas.getRgbaArrayColorAtPixelData = function(x, y, data) { const index = (y * canvas.width + x) * 4; const color = [data[index], data[index + 1], data[index + 2], data[index + 3]]; return color; } - canvas.setColorAtPixelData = function(x, y, color, data) { + canvas.setRgbaArrayColorAtPixelData = function(x, y, color, data) { const index = (y * canvas.width + x) * 4; data[index] = color[0]; data[index + 1] = color[1]; @@ -302,46 +422,53 @@ function makeCanvas({height=600, width=800}) { // {{{ data[index + 3] = color[3]; } - canvas.floodFill = function(x, y, color) { - const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; + canvas.floodFill = function(x, y, colorRgbaArray) { + if (!canvas.background) { + const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; - const targetColor = canvas.getColorAtPixelData(x, y, data); - const fillColorArray = hexToRgbArray(color); + const targetColor = canvas.getRgbaArrayColorAtPixelData(x, y, data); + const fillColorArray = colorRgbaArray; - if (colorsMatch(targetColor, fillColorArray, tolerance)) { - return; - } + if (colorsMatch(targetColor, fillColorArray, tolerance)) { + return; + } - const stack = [{x, y}]; + const stack = [{x, y}]; - while (stack.length > 0) { - const {x, y} = stack.pop(); - const currentColor = canvas.getColorAtPixelData(x, y, data); + while (stack.length > 0) { + const {x, y} = stack.pop(); + const currentColor = canvas.getRgbaArrayColorAtPixelData(x, y, data); - if (colorsMatch(currentColor, targetColor, tolerance)) { - canvas.setColorAtPixelData(x, y, fillColorArray, data); + if (colorsMatch(currentColor, targetColor, tolerance)) { + canvas.setRgbaArrayColorAtPixelData(x, y, fillColorArray, data); - if (x > 0) stack.push({x: x - 1, y}); - if (x < canvas.width - 1) stack.push({x: x + 1, y}); - if (y > 0) stack.push({x, y: y - 1}); - if (y < canvas.height - 1) stack.push({x, y: y + 1}); + if (x > 0) stack.push({x: x - 1, y}); + if (x < canvas.width - 1) stack.push({x: x + 1, y}); + if (y > 0) stack.push({x, y: y - 1}); + if (y < canvas.height - 1) stack.push({x, y: y + 1}); + } } - } - canvas.ctx.putImageData(imageData, 0, 0); + canvas.ctx.putImageData(imageData, 0, 0); + } } - canvas.getColorAtPixel = function(x, y) { + canvas.getRgbaColorArrayAtPixel = function(x, y) { const data = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height).data; - return canvas.getColorAtPixelData(x, y, data); + return canvas.getRgbaArrayColorAtPixelData(x, y, data); + } + + canvas.getRgbColorAtPixel = function(x, y) { + const color = canvas.getRgbaColorArrayAtPixel(x, y); + return `rgb(${color[0]}, ${color[1]}, ${color[2]})`; } canvas.setColorAtPixel = function(x, y, color) { const imageData = canvas.ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; - canvas.setColorAtPixelData(x, y, color, data); + canvas.setRgbaArrayColorAtPixelData(x, y, color, data); canvas.ctx.putImageData(new ImageData(data, canvas.width, canvas.height), 0, 0); } @@ -364,7 +491,9 @@ function makeCanvas({height=600, width=800}) { // {{{ } canvas.deleteCanvas = function() { - canvas.remove(); + if (!background) { + canvas.remove(); + } } canvas.setWidth(width); @@ -374,40 +503,17 @@ function makeCanvas({height=600, width=800}) { // {{{ } // }}} -function makeLayer({height=600, width=800}) { // {{{ +function makeLayer({height=600, width=800, background=undefined}) { // {{{ const layer = {} - layer.canvas = makeCanvas({height, width}); + layer.canvas = makeCanvas({height, width, background}); layer.active = false; layer.opacity = 1; + layer.background = background; layer.controllerElement = document.createElement('div'); layer.controllerElement.className = 'layer-controller'; layer.controllerElement.innerHTML = ''; - const moveUpHandle = document.createElement('div'); - moveUpHandle.classList.add('handle'); - moveUpHandle.classList.add('top-right'); - moveUpHandle.innerHTML = ''; - moveUpHandle.addEventListener('click', () => { - const index = layers.indexOf(layer); - if (index > 0) { - layers.move(layer, index - 1); - } - }); - layer.controllerElement.appendChild(moveUpHandle); - - const moveDownHandle = document.createElement('div'); - moveDownHandle.classList.add('handle'); - moveDownHandle.classList.add('bottom-right'); - moveDownHandle.innerHTML = ''; - moveDownHandle.addEventListener('click', () => { - const index = layers.indexOf(layer); - if (index < layers.length - 1) { - layers.move(layer, index + 1); - } - }); - layer.controllerElement.appendChild(moveDownHandle); - layer.controllerElement.addEventListener('click', () => { layers.setActive(layer); }); @@ -425,7 +531,7 @@ function makeLayer({height=600, width=800}) { // {{{ return layer; } // }}} -function makeLayers({height=600, width=800}) { // {{{ +function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)'}) { // {{{ const layers = []; layers.height = height; layers.width = width; @@ -439,6 +545,7 @@ function makeLayers({height=600, width=800}) { // {{{ layers.setHeight = function(height) { layers.height = height; + layers.forEach(layer => layer.canvas.setHeight(height)); easelElement.style.height = height + 2 + 'px'; } @@ -446,11 +553,30 @@ function makeLayers({height=600, width=800}) { // {{{ layers.setWidth = function(width) { layers.width = width; + layers.forEach(layer => layer.canvas.setWidth(width)); easelElement.style.width = width + 2 + 'px'; } layers.setWidth(width); + layers.resize = function(width, height) { + layers.height = height; + layers.width = width; + easelElement.style.height = height * zoom + 2 + 'px'; + easelElement.style.width = width * zoom + 2 + 'px'; + layers.forEach(layer => layer.canvas.resize(width, height)); + layers[0].canvas.fill(backgroundColor); + } + + layers.zoom = function(newZoom) { + easelElement.style.height = layers.height * zoom + 2 + 'px'; + easelElement.style.width = layers.width * zoom + 2 + 'px'; + layers.forEach(layer => { + layer.canvas.style.height = layers.height * zoom + 'px'; + layer.canvas.style.width = layers.width * zoom + 'px'; + }); + } + layers.resetPosition = function() { const studioRect = studioElement.getBoundingClientRect(); easelElement.style.left = `${studioRect.left}px`; @@ -488,19 +614,40 @@ function makeLayers({height=600, width=800}) { // {{{ } layers.delete = function(layer) { - layer.canvas.deleteCanvas(); - layers.splice(layers.indexOf(layer), 1); - layers.refresh(); + if (!layer.background) { + layer.canvas.deleteCanvas(); + layers.splice(layers.indexOf(layer), 1); + layers.refresh(); + } } layers.deleteAll = function() { - layers.forEach(layer => layer.deleteCanvas()); - // TODO + layers.forEach(function(layer) { + if (!layer.background) { + layer.canvas.deleteCanvas(); + layers.splice(layers.indexOf(layer), 1); + } + }); } - layers.move = function(layer, index) { - layers.splice(layers.indexOf(layer), 1); - layers.splice(index, 0, layer); + layers.moveUp = function(layer) { + if (layer.background) return; + if (layers.indexOf(layer) === layers.length - 1) return; + const index = layers.indexOf(layer); + const temp = layers[index + 1]; + layers[index + 1] = layer; + layers[index] = temp; + layers.refresh(); + } + + layers.moveDown = function(layer) { + if (layer.background) return; + if (layers.indexOf(layer) === 1) return; + const index = layers.indexOf(layer); + const temp = layers[index - 1]; + layers[index - 1] = layer; + layers[index] = temp; + layers.refresh(); } layers.setActive = function(layer) { @@ -512,6 +659,9 @@ function makeLayers({height=600, width=800}) { // {{{ return layers.find(layer => layer.active); } + layers.push(makeLayer({height, width, background: true})); + layers[0].canvas.fill(backgroundColor); + return layers; } // }}} @@ -519,8 +669,6 @@ function makeLayers({height=600, width=800}) { // {{{ const layers = makeLayers({height: initialHeight, width: initialWidth}); layers.add(); -layers.add(); -layers[0].canvas.fill('rgb(255, 255, 255)'); layers.setActive(layers[1]); // }}} @@ -532,10 +680,10 @@ function makeColorPreview() { colorPreview.element = document.createElement('div'); colorPreview.element.id = 'color-preview'; colorPreview.element.className = 'puck'; - colorPreview.element.style.backgroundColor = color.color; + colorPreview.element.style.backgroundColor = brushColor.toRgb(); commandBarElement.appendChild(colorPreview.element); colorPreview.update = function() { - colorPreview.element.style.backgroundColor = color.color; + colorPreview.element.style.backgroundColor = brushColor.toRgb(); } return colorPreview; @@ -543,6 +691,60 @@ function makeColorPreview() { const colorPreview = makeColorPreview(); +// }}} + +// BRUSH PREVIEW {{{ + +function makeBrushPreview() { + const brushPreview = {}; + brushPreview.element = document.createElement('div'); + brushPreview.element.id = 'brush-preview'; + brushPreview.element.style.width = brushSize + 'px'; + brushPreview.element.style.height = brushSize + 'px'; + brushPreview.element.style.position = 'absolute'; + brushPreview.element.style.display = 'none'; + brushPreview.element.style.pointerEvents = 'none'; + brushPreview.element.style.border = '1px solid black'; + brushPreview.element.style.zIndex = '1000'; + + document.body.appendChild(brushPreview.element); + + brushPreview.update = function() { + brushPreview.element.style.width = brushSize * zoom + 'px'; + brushPreview.element.style.height = brushSize * zoom + 'px'; + if (brushShape === 'circle') { + brushPreview.element.style.borderRadius = '50%'; + } else { + brushPreview.element.style.borderRadius = '0'; + } + if (brushSize < 3) { + brushPreview.element.style.visibility = 'hidden'; + } else { + brushPreview.element.style.visibility = 'visible'; + } + } + + brushPreview.setPosition = function(x, y) { + brushPreview.element.style.left = x - brushSize * zoom / 2 + 'px'; + brushPreview.element.style.top = y - brushSize * zoom / 2 + 'px'; + } + + brushPreview.show = function() { + brushPreview.element.style.display = 'block'; + } + + brushPreview.hide = function() { + brushPreview.element.style.display = 'none'; + } + + brushPreview.update(); + + return brushPreview; +} + +const brushPreview = makeBrushPreview(); + + // }}} // COMMANDS {{{ @@ -693,16 +895,28 @@ commands.add({ // change-shape {{{ func: function changeShape() { const currentIndex = shapes.indexOf(brushShape); brushShape = shapes[(currentIndex + 1) % shapes.length]; + brushPreview.update(); } }); // }}} +commands.add({ + name: 'reset', + key: 'r', + icon: '', + func: function resetCanvas() { + zoom = 1; + layers.zoom(); + layers.resetPosition(); + } +}); + // }}} // TOOLS {{{ // FACTORY {{{ -function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) { +function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave}) { if (!name) throw new Error('No name provided'); if (!key) throw new Error('No key provided'); if (!icon) throw new Error('No icon provided'); @@ -714,6 +928,8 @@ function makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}) { tool.mouseDown = mouseDown; tool.mouseMove = mouseMove; tool.mouseUp = mouseUp; + tool.mouseDrag = mouseDrag; + tool.mouseLeave = mouseLeave; tool.active = false; tool.activate = function() { @@ -745,8 +961,8 @@ function makeTools() { tools.prevToolName = 'na'; - tools.add = function({name, key, icon, mouseDown, mouseMove, mouseUp}) { - const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp}); + tools.add = function({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave}) { + const tool = makeTool({name, key, icon, mouseDown, mouseMove, mouseUp, mouseDrag, mouseLeave}); tools.push(tool); } @@ -788,21 +1004,28 @@ tools.add({ // brush {{{ mouseDown: function(e) { const canvas = layers.getActive().canvas; if (brushSize === 1) { - canvas.drawPixel(canvasStartX, canvasStartY, color.color); + canvas.drawPixel(canvasStartX, canvasStartY, brushColor.toRgb()); } else { - canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, color.color); + canvas.drawShape(canvasStartX, canvasStartY, brushShape, brushSize, brushColor.toRgb()); } }, mouseMove: function(e) { + brushPreview.show(); + brushPreview.setPosition(e.clientX, e.clientY); + }, + mouseDrag: function(e) { const canvas = layers.getActive().canvas; if (brushSize === 1) { - canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, color.color); + canvas.drawLineWithPixels(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushColor.toRgb()); } else { - canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, color.color); + canvas.drawLineWithShape(canvasStartX, canvasStartY, canvasEndX, canvasEndY, brushShape, brushSize, brushColor.toRgb()); } canvasStartX = canvasEndX; canvasStartY = canvasEndY; }, + mouseLeave: function(e) { + brushPreview.hide(); + } }); // }}} tools.add({ // content-move {{{ @@ -813,7 +1036,7 @@ tools.add({ // content-move {{{ const canvas = layers.getActive().canvas; canvas.saveCanvas(); }, - mouseMove: function(e) { + mouseDrag: function(e) { const canvas = layers.getActive().canvas; canvas.clearCanvas(); canvas.restoreCanvas(dX, dY); @@ -828,7 +1051,7 @@ tools.add({ // move {{{ startX = e.clientX - easelElement.offsetLeft; startY = e.clientY - easelElement.offsetTop; }, - mouseMove: function(e) { + mouseDrag: function(e) { easelElement.style.left = dX + 'px'; easelElement.style.top = dY + 'px'; }, @@ -838,13 +1061,11 @@ tools.add({ // zoom {{{ name: 'zoom', key: 'z', icon: '', - mouseMove: function(e) { - // TODO all canvases - // const canvas = layers.getActive().canvas; + mouseDrag: function(e) { zoom += dX * dZoom; if (zoom < 0.1) zoom = 0.1; - // canvas.style.height = canvasHeight * zoom + 'px'; - // canvas.style.width = canvasWidth * zoom + 'px'; + layers.zoom(); + brushPreview.update(); startX = endX; } }); // }}} @@ -855,7 +1076,7 @@ tools.add({ // bucket-fill {{{ icon: '', mouseDown: function(e) { const canvas = layers.getActive().canvas; - canvas.floodFill(canvasStartX, canvasStartY, color.color); + canvas.floodFill(canvasStartX, canvasStartY, brushColor.toRgbaArray()); } }); // }}} @@ -867,7 +1088,7 @@ tools.add({ // color-picker {{{ const canvas = layers.getActive().canvas; const imageData = canvas.ctx.getImageData(canvasStartX, canvasStartY, 1, 1).data; const pickedColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`; - color.color = pickedColor; + brushColor.fromRgb(pickedColor); colorPreview.update(); } }); // }}} @@ -877,10 +1098,18 @@ tools.add({ // brush-size {{{ key: 'd', icon: '', mouseMove: function(e) { + brushPreview.show(); + brushPreview.setPosition(e.clientX, e.clientY); + }, + mouseDrag: function(e) { brushSize += dX * dBrushSize; if (brushSize < 1) brushSize = 1; if (brushSize > maxBrushSize) brushSize = maxBrushSize; startX = endX; + brushPreview.update(); + }, + mouseLeave: function(e) { + brushPreview.hide(); } }); // }}} @@ -888,37 +1117,39 @@ tools.add({ // resize {{{ name: 'resize', key: 'r', icon: '', - mouseMove: function(e) { - // const canvas = layers.getActive().canvas; - // let newWidth = canvasWidth + dX / zoom; - // let newHeight = canvasHeight + dY / zoom; - // if (newWidth > 0 && newHeight > 0) { - // canvas.setWidth(newWidth); - // canvas.setHeight(newHeight); - // canvas.style.width = newWidth * zoom + 'px'; - // canvas.style.height = newHeight * zoom + 'px'; - // canvas.ctx.clearRect(0, 0, canvas.width, canvas.height); - // canvas.ctx.fillStyle = backgroundColor; - // canvas.ctx.fillRect(0, 0, canvas.width, canvas.height); - // canvas.ctx.drawImage(tempCanvas, 0, 0); - // } + mouseDrag: function(e) { + let newWidth = layers.width + dX / zoom; + let newHeight = layers.height + dY / zoom; + layers.resize(newWidth, newHeight); + startX = endX; + startY = endY; } }); // }}} - tools.add({ // color-mix {{{ name: 'color-mix', key: 'x', icon: '', mouseDown: function(e) { - const startTime = Date.now(); - const canvas = layers.getActive().canvas; + tempColor.copy(canvasColor); + startTime = Date.now(); interval = setInterval(() => { - let canvasColor = canvas.getColorAtPixel(canvasEndX, canvasEndX); - console.log({canvasEndX, canvasEndY, canvasColor}); - const elapsedTime = Date.now() - startTime; - const t = Math.min(1, elapsedTime / 10000); - color.mix(canvasColor, t); + if (!tempColor.match(canvasColor)) { + startTime = Date.now(); + tempColor.copy(canvasColor); + } + if (!canvasColor.isOpaque()) { + startTime = Date.now(); + } else { + const elapsedTime = Date.now() - startTime; + const t = Math.min(1, elapsedTime / 10000); + brushColor.mixx(canvasColor, t); + colorPreview.update(); + if (!isMouseDown) { + clearInterval(interval); + startTime = Date.now(); + } + } }, 50); }, mouseUp: function(e) { @@ -927,7 +1158,6 @@ tools.add({ // color-mix {{{ mouseLeave: function(e) { clearInterval(interval); } - }); // }}} // }}} @@ -963,13 +1193,13 @@ function makePuck({puckColor, key, editable=true}) { puck.element.appendChild(keyHint); } - puck.element.addEventListener('mousedown', (e) => { const startTime = Date.now(); interval = setInterval(() => { const elapsedTime = Date.now() - startTime; const t = Math.min(1, elapsedTime / 10000); - color.mix(puck.element.style.backgroundColor, t); + brushColor.mixxRgb(puck.element.style.backgroundColor, t); + colorPreview.update(); }, 50); }); @@ -977,12 +1207,17 @@ function makePuck({puckColor, key, editable=true}) { clearInterval(interval); }); + puck.element.addEventListener('mouseleave', (e) => { + clearInterval(interval); + }); + puck.keydown = function(e) { - const startTime = Date.now(); + startTime = Date.now(); var interval = setInterval(() => { const elapsedTime = Date.now() - startTime; const t = Math.min(1, elapsedTime / 10000); - color.mix(puck.element.style.backgroundColor, t); + brushColor.mixxRgb(puck.element.style.backgroundColor, t); + colorPreview.update(); }, 50); function onKeyUp() { clearInterval(interval); @@ -1012,36 +1247,53 @@ function makePucks() { const pucks = makePucks(); -pucks.add({ +pucks.add({ // black puckColor: 'rgb(0, 0, 0)', key: '1', editable: false, }); -pucks.add({ +pucks.add({ // white puckColor: 'rgb(255, 255, 255)', key: '2', editable: false, }); -pucks.add({ +pucks.add({ // red puckColor: 'rgb(255, 0, 0)', key: '3', editable: false, }); -pucks.add({ - puckColor: 'rgb(0, 255, 0)', - key: '4', +pucks.add({ // blue + puckColor: 'rgb(0, 0, 255)', + key: '5', editable: false, }); -pucks.add({ - puckColor: 'rgb(0, 0, 255)', - key: '5', +pucks.add({ // yellow + puckColor: 'rgb(255, 255, 0)', + key: '6', + editable: false, +}); + +pucks.add({ // cyan + puckColor: 'rgb(0, 255, 255)', + key: '7', editable: false, }); +pucks.add({ // magenta + puckColor: 'rgb(255, 0, 255)', + key: '8', + editable: false, +}); + +pucks.add({ // green + puckColor: 'rgb(0, 255, 0)', + key: '4', + editable: false, +}); // }}} @@ -1134,7 +1386,7 @@ infos.add({ infos.add({ name: 'color', updateFunction: function() { - return color.color; + return brushColor.toRgb(); } }); @@ -1166,6 +1418,13 @@ infos.add({ } }); +infos.add({ + name: 'cursor-color', + updateFunction: function() { + return canvasColor; + } +}); + // }}} // MOUSE EVENT LISTENERS {{{ @@ -1179,13 +1438,13 @@ studioElement.addEventListener('mousedown', (e) => { canvasStartY = canvas.getPositionOnCanvas(e).y; canvasEndX = canvas.getPositionOnCanvas(e).x; canvasEndX = canvas.getPositionOnCanvas(e).y; + canvasColor.fromRgbaArray(canvas.getRgbaColorArrayAtPixel(canvasStartX, canvasStartY)); for (var i = 0; i < tools.length; i++) { var tool = tools[i]; if (tool.active) { if (tool.mouseDown) { tool.mouseDown(e); - break; } } } @@ -1204,22 +1463,23 @@ studioElement.addEventListener('mousemove', (e) => { canvasEndY = canvas.getPositionOnCanvas(e).y; canvasDX = canvasEndX - canvasStartX; canvasDY = canvasEndY - canvasStartY; + canvasColor.fromRgbaArray(canvas.getRgbaColorArrayAtPixel(canvasEndX, canvasEndY)); - if (tools.getActive().name === 'brush-size') { - brushPreviewElement.style.display = 'block'; - brushPreviewElement.style.width = brushSize + 'px'; - brushPreviewElement.style.height = brushSize + 'px'; - brushPreviewElement.style.left = e.clientX - brushSize / 2 + 'px'; - brushPreviewElement.style.top = e.clientY - brushSize / 2 + 'px'; + for (var i = 0; i < tools.length; i++) { + var tool = tools[i]; + if (tool.active) { + if (tool.mouseMove) { + tool.mouseMove(e); + } + } } if (isMouseDown) { for (var i = 0; i < tools.length; i++) { var tool = tools[i]; if (tool.active) { - if (tool.mouseMove) { - tool.mouseMove(e); - break; + if (tool.mouseDrag) { + tool.mouseDrag(e); } } } @@ -1229,6 +1489,7 @@ studioElement.addEventListener('mousemove', (e) => { }); + studioElement.addEventListener('mouseup', () => { isMouseDown = false; @@ -1258,7 +1519,6 @@ studioElement.addEventListener('mouseleave', () => { } } - brushPreviewElement.style.display = 'none'; infos.update(); }); diff --git a/temp.js b/temp.js deleted file mode 100644 index fdb075f..0000000 --- a/temp.js +++ /dev/null @@ -1,724 +0,0 @@ -const colorPreview = document.createElement('div'); -colorPreview.id = 'color-preview'; -colorPreview.className = 'puck'; -colorPreview.style.backgroundColor = color; - -menuBar.appendChild(colorPreview); - - -// }}} - -// helpers {{{ - - -function saveCanvas() { - const dataURL = canvas.toDataURL(); - const dimensions = `${canvas.width}x${canvas.height}`; - localStorage.setItem('mixxCanvas', dataURL); - localStorage.setItem('mixxDimensions', dimensions); - console.log('Canvas saved'); -} - -function loadCanvas() { - const dataURL = localStorage.getItem('mixxCanvas'); - const dimensions = localStorage.getItem('mixxDimensions'); - if (dataURL && dimensions) { - const img = new Image(); - img.src = dataURL; - img.onload = function() { - canvas.width = dimensions.split('x')[0]; - canvas.height = dimensions.split('x')[1]; - canvas.style.width = canvas.width * zoom + 'px'; - canvas.style.height = canvas.height * zoom + 'px'; - canvasWidth = canvas.width; - canvasHeight = canvas.height; - ctx.drawImage(img, 0, 0); - } - } else { - console.log('No saved canvas found'); - } -} - -function clearCanvasFromLocalStorage() { - localStorage.removeItem('savedCanvas'); - localStorage.removeItem('canvasDimensions'); -} - -function saveState() { - if (undoStack.length >= maxHistory) { - undoStack.shift(); // Remove the oldest state if the stack exceeds the limit - } - - undoStack.push({ - imageData: canvas.toDataURL(), - width: canvas.width, - height: canvas.height - }); - - redoStack = []; // Clear the redo stack whenever a new action is performed -} - -function undo() { - if (undoStack.length > 0) { - const currentState = { - imageData: canvas.toDataURL(), - width: canvas.width, - height: canvas.height - }; - - redoStack.push(currentState); // Save current state to the redo stack - const lastState = undoStack.pop(); // Get the last state from the undo stack - - canvas.width = lastState.width; - canvas.height = lastState.height; - canvas.style.width = canvas.width * zoom + 'px'; - canvas.style.height = canvas.height * zoom + 'px'; - canvasWidth = canvas.width; - canvasHeight = canvas.height; - - const img = new Image(); - img.src = lastState.imageData; - img.onload = function() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(img, 0, 0); - }; - } -} - -function redo() { - if (redoStack.length > 0) { - const currentState = { - imageData: canvas.toDataURL(), - width: canvas.width, - height: canvas.height - }; - - undoStack.push(currentState); // Save current state to the undo stack - const nextState = redoStack.pop(); // Get the last state from the redo stack - - canvas.width = nextState.width; - canvas.height = nextState.height; - canvas.style.width = canvas.width * zoom + 'px'; - canvas.style.height = canvas.height * zoom + 'px'; - canvasWidth = canvas.width; - canvasHeight = canvas.height; - - const img = new Image(); - img.src = nextState.imageData; - img.onload = function() { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(img, 0, 0); - }; - } -} - -function getPositionOnCanvas(e) { - const rect = canvas.getBoundingClientRect(); - return { - x: Math.round((e.clientX - rect.left) / zoom), - y: Math.round((e.clientY - rect.top) / zoom), - }; -} - -function drawCircle(x, y) { - ctx.beginPath(); - ctx.arc(x, y, brushSize / 2, 0, 2 * Math.PI, false); - ctx.fillStyle = color; - ctx.fill(); -} - -function drawLineWithCircles(x1, y1, x2, y2) { - const dx = x2 - x1; - const dy = y2 - y1; - const distance = Math.sqrt(dx * dx + dy * dy); - const steps = Math.ceil(distance / (brushSize / 5)); - - for (let i = 0; i <= steps; i++) { - const x = x1 + (dx * i) / steps; - const y = y1 + (dy * i) / steps; - drawCircle(x, y); - } -} - -function saveCanvasContents() { - tempCanvas = document.createElement('canvas'); - tempCanvas.width = canvas.width; - tempCanvas.height = canvas.height; - const tempCtx = tempCanvas.getContext('2d'); - tempCtx.drawImage(canvas, 0, 0); -} - -function updateColorPreview() { - colorPreview.style.backgroundColor = color; -} - -function hexToRgbArray(hex) { - if (hex.startsWith('#')) { - hex = hex.slice(1); - } - - if (hex.length === 3) { - hex = hex.split('').map(char => char + char).join(''); - } - - const bigint = parseInt(hex, 16); - return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255]; -} - -function floodFill(x, y, fillColor) { - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - const data = imageData.data; - - const targetColor = getColorAtPixel(data, x, y); - const fillColorArray = hexToRgbArray(fillColor); - - if (colorsMatch(targetColor, fillColorArray)) { - return; // The clicked point is already the fill color - } - - const stack = [{x, y}]; - - while (stack.length > 0) { - const {x, y} = stack.pop(); - const currentColor = getColorAtPixel(data, x, y); - - if (colorsMatch(currentColor, targetColor)) { - setColorAtPixel(data, x, y, fillColorArray); - - if (x > 0) stack.push({x: x - 1, y}); - if (x < canvas.width - 1) stack.push({x: x + 1, y}); - if (y > 0) stack.push({x, y: y - 1}); - if (y < canvas.height - 1) stack.push({x, y: y + 1}); - } - } - - ctx.putImageData(imageData, 0, 0); -} - -function getColorAtPixel(data, x, y) { - const index = (y * canvas.width + x) * 4; - return [data[index], data[index + 1], data[index + 2], data[index + 3]]; -} - -function setColorAtPixel(data, x, y, color) { - const index = (y * canvas.width + x) * 4; - data[index] = color[0]; - data[index + 1] = color[1]; - data[index + 2] = color[2]; - data[index + 3] = 255; // Set alpha to fully opaque -} - -function colorsMatch(a, b) { - return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; -} - -// }}} - -// mousedown {{{ - -canvasArea.addEventListener('mousedown', (e) => { - if (e.target.closest('.puck')) return; - - startX = e.clientX; - startY = e.clientY; - canvasStartX = getPositionOnCanvas(e).x; - canvasStartY = getPositionOnCanvas(e).y; - saveCanvasContents(); - isMouseDown = true; - - if ( - tool === 'brush' || - tool === 'content-move' || - tool === 'resize' || - tool === 'zoom' || - tool === 'bucket-fill' - ) { - saveState(); - } - - if (tool === 'brush') { - console.log('brush'); - drawCircle(canvasStartX, canvasStartY); - } else if (tool === 'bucket-fill') { - floodFill(canvasStartX, canvasStartY, color); - return; - } else if (tool === 'move') { - startX = e.clientX - canvasContainer.offsetLeft; - startY = e.clientY - canvasContainer.offsetTop; - } else if (tool === 'color-picker') { - const imageData = ctx.getImageData(canvasStartX, canvasStartY, 1, 1).data; - const pickedColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`; - color = pickedColor; - console.log('Picked Color:', pickedColor); - updateColorPreview(); - return; - } -}); - -// }}} - -// mousemove {{{ - -canvasArea.addEventListener('mousemove', (e) => { - - endX = e.clientX; - endY = e.clientY; - dX = endX - startX; - dY = endY - startY; - - canvasEndX = getPositionOnCanvas(e).x; - canvasEndY = getPositionOnCanvas(e).y; - canvasDX = canvasEndX - canvasStartX; - canvasDY = canvasEndY - canvasStartY; - - if (tool == 'brush-size') { - brushPreview.style.display = 'block'; - brushPreview.style.width = brushSize + 'px'; - brushPreview.style.height = brushSize + 'px'; - brushPreview.style.left = e.clientX - brushSize / 2 + 'px'; - brushPreview.style.top = e.clientY - brushSize / 2 + 'px'; - } - - if (isMouseDown) { - - if (tool === 'brush-size') { - brushSize += dX * dBrushSize; - if (brushSize < 1) brushSize = 1; - if (brushSize > maxBrushSize) brushSize = maxBrushSize; - startX = endX; - } else if (tool === 'brush') { - drawLineWithCircles(canvasStartX, canvasStartY, canvasEndX, canvasEndY); - - canvasStartX = canvasEndX; - canvasStartY = canvasEndY; - - } else if (tool === 'content-move') { - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(tempCanvas, dX, dY); - } else if (tool === 'move') { - canvasContainer.style.left = dX + 'px'; - canvasContainer.style.top = dY + 'px'; - } else if (tool === 'zoom') { - zoom += dX * dZoom; - if (zoom < 0.1) zoom = 0.1; - canvas.style.height = canvasHeight * zoom + 'px'; - canvas.style.width = canvasWidth * zoom + 'px'; - startX = endX; - } else if (tool === 'resize') { - let newWidth = canvasWidth + dX / zoom; - let newHeight = canvasHeight + dY / zoom; - if (newWidth > 0 && newHeight > 0) { - canvas.width = newWidth; - canvas.height = newHeight; - canvas.style.width = newWidth * zoom + 'px'; - canvas.style.height = newHeight * zoom + 'px'; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(tempCanvas, 0, 0); - } - } else if (tool === 'color-mix') { - - const imageData = ctx.getImageData(canvasEndX, canvasEndY, 1, 1).data; - const canvasColor = `rgb(${imageData[0]}, ${imageData[1]}, ${imageData[2]})`; - - const distance = Math.sqrt(Math.pow(e.clientX - startX, 2) + Math.pow(e.clientY - startY, 2)); - const t = Math.min(1, distance / 300); - - const mixedColor = mixbox.lerp(color, canvasColor, t); - - color = mixedColor; - - startX = e.clientX; - startY = e.clientY; - - } - - } - - updateInfos(); - updateColorPreview(); -}); - -// }}} - -// mouseup {{{ - -canvasArea.addEventListener('mouseup', (e) => { - isMouseDown = false; - if (tool === 'brush') { - ctx.closePath(); - } else if (tool === 'resize') { - canvasWidth = canvas.width; - canvasHeight = canvas.height; - } - - updateColorPreview(); -}); - -// }}} - -// mouseleave {{{ - -canvasArea.addEventListener('mouseleave', (e) => { - isMouseDown = false; - brushPreview.style.display = 'none'; -}); - -// }}} - -// keybindings {{{ - -const toolKeyBindings = {} - -const functionKeyBindings = { -} - -document.addEventListener('keydown', (e) => { - if (keyDown) return; - - const newTool = toolKeyBindings[e.key.toLowerCase()] - if (newTool) { - prevTool = tool; - keyDown = true; - changeTool(newTool); - return; - } - - const func = functionKeyBindings[e.key]; - if (func) { - func(); - return; - } - -}); - -document.addEventListener('keyup', (e) => { - const currentTool = toolKeyBindings[e.key.toLowerCase()] - if (currentTool) { - keyDown = false; - if (e.key == e.key.toLowerCase()) { - changeTool(prevTool); - return; - } - } -}); - -// }}} - -// tools {{{ - -var toolButtons = []; - -function changeTool(toolName) { - toolButtons.forEach(button => button.button.classList.remove('active')); - toolButtons.find(button => button.name === toolName).button.classList.add('active'); - tool = toolName; - brushPreview.style.display = 'none'; - updateInfos(); -} - -function createToolButton(toolName, displayName, icon, key=undefined) { - const button = document.createElement('div'); - button.classList.add('button'); - button.classList.add('tool'); - button.innerHTML = icon; - button.title = displayName; - button.addEventListener('click', () => { - changeTool(toolName); - }); - - if (key) { - const keyHint = document.createElement('span'); - keyHint.className = 'key-hint'; - keyHint.innerHTML = key; - button.appendChild(keyHint); - toolKeyBindings[key] = toolName; - } - - toolBar.appendChild(button); - toolButtons.push({'name': toolName, 'button': button}); - return button; -} - -createToolButton('brush', 'Brush', '', 'b'); -createToolButton('content-move', 'Move Content', '', 'h'); -createToolButton('move', 'Move Canvas', '', 'm'); -createToolButton('zoom', 'Zoom', '', 'z'); -createToolButton('resize', 'Resize', '', 'r'); -createToolButton('color-picker', 'Color Picker', '', 'a'); -createToolButton('color-mix', 'Color Mix', '', 'x'); -createToolButton('brush-size', 'Brush Size', '', 'd'); -createToolButton('bucket-fill', 'Bucket Fill', '', 'k'); - -// }}} - -// menu functons {{{ - -function flipCanvasHorizontally(e) { - saveState(); - ctx.save(); - saveCanvasContents(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.scale(-1, 1); - ctx.translate(-canvas.width, 0); - ctx.drawImage(tempCanvas, 0, 0); - ctx.restore(); -} - -function flipCanvasVertically(e) { - saveState(); - ctx.save(); - saveCanvasContents(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.scale(1, -1); - ctx.translate(0, -canvas.height); - ctx.drawImage(tempCanvas, 0, 0); - ctx.restore(); -} - -function exportCanvas(e) { - const link = document.createElement('a'); - link.download = 'canvas.png'; - link.href = canvas.toDataURL(); - link.click(); -} - -function importCanvas(e) { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/*'; - input.onchange = (e) => { - const file = e.target.files[0]; - const reader = new FileReader(); - reader.onload = (e) => { - const img = new Image(); - img.onload = () => { - canvas.width = img.width; - canvas.height = img.height; - ctx.drawImage(img, 0, 0); - } - img.src = e.target.result; - } - reader.readAsDataURL(file); - } - input.click(); -} - -function clearCanvas(e) { - saveState(); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = backgroundColor; - ctx.fillRect(0, 0, canvas.width, canvas.height); -} - -function resetZoom(e) { - zoom = 1; - canvas.style.width = canvas.width * zoom + 'px'; - canvas.style.height = canvas.height * zoom + 'px'; - canvasWidth = canvas.width; - canvasHeight = canvas.height; - - canvasAreaRect = canvasArea.getBoundingClientRect(); - - canvasContainer.style.left = `${canvasAreaRect.left}px`; - canvasContainer.style.top = `${canvasAreaRect.top}px`; -} - -// }}} - -// menu {{{ - -var menuButtons = []; - -function createMenuButton(icon, name, clickFunction, key=undefined) { - const button = document.createElement('div'); - button.className = 'button'; - button.innerHTML = icon; - button.title = name; - if (clickFunction) { - button.addEventListener('click', () => { - clickFunction() - updateInfos(); - }); - } - menuBar.appendChild(button); - if (key) { - const keyHint = document.createElement('span'); - keyHint.className = 'key-hint'; - keyHint.innerHTML = key; - button.appendChild(keyHint); - functionKeyBindings[key] = clickFunction; - } - return button; -} - -menuButtons.push(createMenuButton('', 'Save', saveCanvas, 's')); -menuButtons.push(createMenuButton('', 'Load', loadCanvas)); -menuButtons.push(createMenuButton('', 'Clear', clearCanvas)); -menuButtons.push(createMenuButton('', 'Export', exportCanvas)); -menuButtons.push(createMenuButton('', 'Import', importCanvas)); - - -menuButtons.push(createMenuButton('', 'Flip Horizontally', flipCanvasHorizontally, 'f')); -menuButtons.push(createMenuButton('', 'Flip Vertically', flipCanvasVertically, 'v')); -menuButtons.push(createMenuButton('', 'Undo', undo, 'u')); -menuButtons.push(createMenuButton('', 'Redo', redo, 'y')); -menuButtons.push(createMenuButton('', 'Clear', clearCanvas, 'c')); -menuButtons.push(createMenuButton('', 'Reset', resetZoom, 't')); -menuButtons.push(createMenuButton('', 'Add Color', createPuck)); - -// }}} - -// pucks {{{ - -function createPuck(c, editable=true, key=undefined) { - if (c === undefined) { - c = color; - } - - const puck = document.createElement('div'); - puck.className = 'puck'; - puck.style.backgroundColor = c; - - // const selectHandle = document.createElement('div'); - // selectHandle.className = 'select-handle'; - // selectHandle.innerHTML = ''; - // puck.appendChild(selectHandle); - - // selectHandle.addEventListener('click', () => { - // color = puck.style.backgroundColor; - // updateColorPreview(); - // updateInfos(); - // }); - - if (editable) { - // const updateHandle = document.createElement('div'); - // updateHandle.className = 'update-handle'; - // updateHandle.innerHTML = ''; - // puck.appendChild(updateHandle); - - // updateHandle.addEventListener('click', () => { - // puck.style.backgroundColor = color; - // }); - - const deleteHandle = document.createElement('div'); - deleteHandle.className = 'delete-handle'; - deleteHandle.innerHTML = ''; - puck.appendChild(deleteHandle); - - deleteHandle.addEventListener('click', () => { - console.log("test"); - puck.remove(); - }); - } - - if (key) { - const keyHint = document.createElement('div'); - keyHint.className = 'key-hint'; - keyHint.innerHTML = key; - puck.appendChild(keyHint); - } - - function mixx(startTime) { - var interval = setInterval(() => { - const elapsedTime = Date.now() - startTime; - const t = Math.min(1, elapsedTime / 10000); - const mixedColor = mixbox.lerp(color, puck.style.backgroundColor, t); - color = mixedColor; - updateColorPreview(); - updateInfos(); - }, 50); - return interval; - } - - puck.addEventListener('mousedown', (e) => { - const startTime = Date.now(); - var interval = mixx(startTime); - function onMouseUp() { - clearInterval(interval); - document.removeEventListener('mouseup', onMouseUp); - } - document.addEventListener('mouseup', onMouseUp); - }); - - document.addEventListener('keydown', (e) => { - if (e.key == key) { - console.log(e.key); - const startTime = Date.now(); - var interval = mixx(startTime); - function onKeyUp() { - clearInterval(interval); - document.removeEventListener('keyup', onKeyUp); - } - document.addEventListener('keyup', onKeyUp); - } - }); - - menuBar.appendChild(puck); -} - -createPuck(c='rgb(0, 0, 0)', editable=false, key='1'); -createPuck(c='rgb(255, 0, 0)', editale=false, key='2'); -createPuck(c='rgb(0, 0, 255)', editale=false, key='3'); -createPuck(c='rgb(255, 255, 0)', editale=false, key='4'); -createPuck(c='rgb(99, 60, 22)', editale=false, key='5'); -createPuck(c='rgb(0, 255, 0)', editale=false, key='6'); -createPuck(c='rgb(255, 0, 255)', editale=false, key='7'); -createPuck(c='rgb(0, 255, 255)', editale=false, key='8'); -createPuck(c='rgb(255, 255, 255)', editale=false, key='9'); - - -// }}} - -// info {{{ - -var infos = []; - -function createInfo(name, updateFunction) { - const info = document.createElement('span'); - info.className = 'info'; - const key = document.createElement('span'); - key.className = 'key'; - key.innerHTML = name + ':'; - const value = document.createElement('span'); - value.className = 'value'; - value.innerHTML = '0'; - info.appendChild(key); - info.appendChild(value); - infoBar.appendChild(info); - function update() { - let v = updateFunction(); - if (v === undefined) v = '?'; - value.innerHTML = v; - } - return update; -} - -infos.push(createInfo('zoom', function() { - var percent = zoom * 100; - return percent.toFixed(0) + '%'; -})); -infos.push(createInfo('brush', function() { return brushSize; })); -infos.push(createInfo('x', function() { return canvasEndX; })); -infos.push(createInfo('y', function() { return canvasEndY; })); -infos.push(createInfo('color', function() { return color; })); -infos.push(createInfo('width', function() { return canvas.width; })); -infos.push(createInfo('height', function() { return canvas.height; })); - -function updateInfos() { - infos.forEach(info => info()); -} - -// }}} - -// start {{{ - -ctx.fillStyle = backgroundColor; -ctx.fillRect(0, 0, canvas.width, canvas.height); -updateInfos(); -toolButtons[0]['button'].click(); - -// }}}