Paint-Simulator / index.html
Open-Source-Lab's picture
Update index.html
8317a01 verified
<html>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
.container {
display: flex;
flex-direction: column;
gap: 15px;
max-width: 800px;
width: 100%;
}
.canvas-container {
position: relative;
width: 100%;
}
canvas {
border: 1px solid #000;
cursor: crosshair;
background-color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
width: 100%;
height: auto;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
background-color: #ffffff;
padding: 12px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.tool-group {
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
border-radius: 4px;
background-color: #f7f7f7;
flex-wrap: wrap;
}
button {
padding: 8px 12px;
font-size: 14px;
border: none;
border-radius: 4px;
background-color: #4a90e2;
color: white;
cursor: pointer;
transition: all 0.2s;
}
button:hover {
background-color: #357abd;
transform: translateY(-1px);
}
button:active {
transform: translateY(0);
}
button.active {
background-color: #2c5990;
}
button.danger {
background-color: #e74c3c;
}
button.danger:hover {
background-color: #c0392b;
}
button.secondary {
background-color: #95a5a6;
}
button.secondary:hover {
background-color: #7f8c8d;
}
input[type="color"] {
width: 40px;
height: 40px;
padding: 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type="range"] {
width: 100px;
}
.color-preview {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid #ddd;
}
.tool-option {
display: flex;
align-items: center;
gap: 5px;
}
label {
font-size: 14px;
}
.size-display {
min-width: 30px;
text-align: center;
}
.layer-panel {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
background-color: #ffffff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.layer-list {
display: flex;
flex-direction: column;
gap: 5px;
}
.layer-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px;
background-color: #f7f7f7;
border-radius: 4px;
}
.layer-item.active {
background-color: #d3e8ff;
}
.layer-buttons {
display: flex;
gap: 5px;
}
.status-bar {
display: flex;
justify-content: space-between;
width: 100%;
padding: 6px 12px;
background-color: #ffffff;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
font-size: 12px;
color: #555;
}
#colorPalette {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.palette-color {
width: 24px;
height: 24px;
border-radius: 4px;
cursor: pointer;
border: 1px solid #ddd;
}
.palette-color:hover {
transform: scale(1.1);
}
.brush-preview {
width: 40px;
height: 40px;
border-radius: 4px;
background-color: #f7f7f7;
position: relative;
}
.brush-preview-dot {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
background-color: #000;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
max-width: 400px;
width: 100%;
}
.modal-title {
margin-top: 0;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.image-preview {
max-width: 100%;
margin-top: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.loading:after {
content: " ";
display: block;
width: 24px;
height: 24px;
border-radius: 50%;
border: 6px solid #5D5CDE;
border-color: #5D5CDE transparent #5D5CDE transparent;
animation: loading 1.2s linear infinite;
}
@keyframes loading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.transform-modal {
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.transform-preview {
text-align: center;
margin-top: 15px;
}
.transform-preview img {
max-width: 100%;
max-height: 60vh;
border-radius: 4px;
border: 1px solid #ddd;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #181818;
color: #e0e0e0;
}
.toolbar, .status-bar, .modal-content, .layer-panel {
background-color: #292929;
color: #e0e0e0;
}
.tool-group, .layer-item {
background-color: #383838;
}
.layer-item.active {
background-color: #3a4d64;
}
canvas {
border-color: #444;
}
.palette-color {
border-color: #444;
}
}
</style>
</head>
<body>
<div class="container">
<div class="toolbar">
<div class="tool-group">
<button id="pencilTool" class="active" title="Pencil (P)">Pencil</button>
<button id="brushTool" title="Brush (B)">Brush</button>
<button id="eraserTool" title="Eraser (E)">Eraser</button>
<button id="fillTool" title="Fill (F)">Fill</button>
<button id="lineTool" title="Line (L)">Line</button>
<button id="rectangleTool" title="Rectangle (R)">Rectangle</button>
<button id="circleTool" title="Circle (C)">Circle</button>
</div>
<div class="tool-group">
<div class="tool-option">
<input type="color" id="colorPicker" value="#000000">
<div id="currentColor" class="color-preview" style="background-color: #000000;"></div>
</div>
<div class="tool-option">
<label for="brushSize">Size:</label>
<input type="range" id="brushSize" min="1" max="50" value="5">
<span id="sizeDisplay" class="size-display">5px</span>
</div>
<div class="tool-option">
<label for="opacityRange">Opacity:</label>
<input type="range" id="opacityRange" min="1" max="100" value="100">
<span id="opacityDisplay" class="size-display">100%</span>
</div>
</div>
</div>
<div class="canvas-container">
<canvas id="paintCanvas" width="800" height="600"></canvas>
</div>
<div class="toolbar">
<div class="tool-group">
<button id="undoButton" title="Undo (Ctrl+Z)">Undo</button>
<button id="redoButton" title="Redo (Ctrl+Y)">Redo</button>
<button id="clearButton" class="danger" title="Clear">Clear</button>
</div>
<div class="tool-group">
<button id="saveButton" title="Save (Ctrl+S)">Save</button>
<button id="loadButton" title="Load">Load</button>
<button id="exportButton" title="Export as PNG">Export</button>
<button id="downloadButton" title="Download Image">Download Image</button>
<button id="transformButton" title="Transform with Image-Photo">Transform Image</button>
</div>
<div class="tool-group">
<div id="colorPalette">
<div class="palette-color" style="background-color: #000000;"></div>
<div class="palette-color" style="background-color: #ffffff;"></div>
<div class="palette-color" style="background-color: #ff0000;"></div>
<div class="palette-color" style="background-color: #00ff00;"></div>
<div class="palette-color" style="background-color: #0000ff;"></div>
<div class="palette-color" style="background-color: #ffff00;"></div>
</div>
</div>
</div>
<div class="status-bar">
<span id="positionDisplay">Position: 0, 0</span>
<span id="toolInfo">Pencil Tool | Size: 5px | Color: #000000</span>
</div>
</div>
<div id="saveModal" class="modal">
<div class="modal-content">
<h3 class="modal-title">Save Drawing</h3>
<div>
<label for="saveFilename">Filename:</label>
<input type="text" id="saveFilename" value="my-drawing" style="width: 100%; margin-top: 5px; padding: 5px;">
</div>
<div class="modal-footer">
<button id="cancelSave" class="secondary">Cancel</button>
<button id="confirmSave">Save</button>
</div>
</div>
</div>
<div id="transformModal" class="modal">
<div class="modal-content transform-modal">
<h3 class="modal-title">Transform Image</h3>
<div>
<label for="transformPrompt">Enter a prompt for @Image-Photo:</label>
<input type="text" id="transformPrompt" placeholder="Describe how to transform the image..." style="width: 100%; margin-top: 5px; padding: 5px; font-size: 16px;">
</div>
<div id="transformLoading" class="loading" style="display: none;"></div>
<div id="transformPreview" class="transform-preview"></div>
<div class="modal-footer">
<button id="cancelTransform" class="secondary">Cancel</button>
<button id="confirmTransform">Send to Image-Photo</button>
<button id="downloadTransformed" style="display: none;">Download Transformed</button>
</div>
</div>
</div>
<script>
// Check if dark mode is preferred
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
if (event.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
});
const canvas = document.getElementById('paintCanvas');
const ctx = canvas.getContext('2d');
const colorPicker = document.getElementById('colorPicker');
const currentColor = document.getElementById('currentColor');
const brushSize = document.getElementById('brushSize');
const sizeDisplay = document.getElementById('sizeDisplay');
const opacityRange = document.getElementById('opacityRange');
const opacityDisplay = document.getElementById('opacityDisplay');
const positionDisplay = document.getElementById('positionDisplay');
const toolInfo = document.getElementById('toolInfo');
const pencilTool = document.getElementById('pencilTool');
const brushTool = document.getElementById('brushTool');
const eraserTool = document.getElementById('eraserTool');
const fillTool = document.getElementById('fillTool');
const lineTool = document.getElementById('lineTool');
const rectangleTool = document.getElementById('rectangleTool');
const circleTool = document.getElementById('circleTool');
const undoButton = document.getElementById('undoButton');
const redoButton = document.getElementById('redoButton');
const clearButton = document.getElementById('clearButton');
const saveButton = document.getElementById('saveButton');
const loadButton = document.getElementById('loadButton');
const exportButton = document.getElementById('exportButton');
const downloadButton = document.getElementById('downloadButton');
const transformButton = document.getElementById('transformButton');
const saveModal = document.getElementById('saveModal');
const saveFilename = document.getElementById('saveFilename');
const cancelSave = document.getElementById('cancelSave');
const confirmSave = document.getElementById('confirmSave');
const transformModal = document.getElementById('transformModal');
const transformPrompt = document.getElementById('transformPrompt');
const transformLoading = document.getElementById('transformLoading');
const transformPreview = document.getElementById('transformPreview');
const cancelTransform = document.getElementById('cancelTransform');
const confirmTransform = document.getElementById('confirmTransform');
const downloadTransformed = document.getElementById('downloadTransformed');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let currentTool = 'pencil';
let undoStack = [];
let redoStack = [];
let startX = 0;
let startY = 0;
let transformedImageUrl = null;
function initCanvas() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
saveState();
}
function saveState() {
redoStack = [];
undoStack.push(canvas.toDataURL());
updateButtons();
}
function updateButtons() {
undoButton.disabled = undoStack.length <= 1;
redoButton.disabled = redoStack.length === 0;
}
function undo() {
if (undoStack.length <= 1) return;
redoStack.push(undoStack.pop());
const img = new Image();
img.src = undoStack[undoStack.length - 1];
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
updateButtons();
};
}
function redo() {
if (redoStack.length === 0) return;
const img = new Image();
img.src = redoStack.pop();
img.onload = () => {
undoStack.push(img.src);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
updateButtons();
};
}
function clearCanvas() {
saveState();
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
function startDrawing(e) {
isDrawing = true;
const rect = canvas.getBoundingClientRect();
lastX = e.clientX - rect.left;
lastY = e.clientY - rect.top;
startX = lastX;
startY = lastY;
if (['line', 'rectangle', 'circle'].includes(currentTool)) {
saveState();
return;
}
ctx.beginPath();
if (currentTool === 'fill') {
floodFill(Math.floor(lastX), Math.floor(lastY), hexToRgb(colorPicker.value));
isDrawing = false;
saveState();
return;
}
setupContext();
if (['pencil', 'brush'].includes(currentTool)) {
ctx.arc(lastX, lastY, 0.5, 0, Math.PI * 2);
ctx.fillStyle = ctx.strokeStyle;
ctx.fill();
}
saveState();
}
function draw(e) {
if (!isDrawing) return;
const rect = canvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
positionDisplay.textContent = `Position: ${Math.floor(currentX)}, ${Math.floor(currentY)}`;
if (currentTool === 'pencil' || currentTool === 'brush' || currentTool === 'eraser') {
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(currentX, currentY);
ctx.stroke();
lastX = currentX;
lastY = currentY;
} else if (['line', 'rectangle', 'circle'].includes(currentTool)) {
const img = new Image();
img.src = undoStack[undoStack.length - 1];
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
setupContext();
if (currentTool === 'line') {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(currentX, currentY);
ctx.stroke();
} else if (currentTool === 'rectangle') {
const width = currentX - startX;
const height = currentY - startY;
ctx.strokeRect(startX, startY, width, height);
} else if (currentTool === 'circle') {
const radius = Math.sqrt(Math.pow(currentX - startX, 2) + Math.pow(currentY - startY, 2));
ctx.beginPath();
ctx.arc(startX, startY, radius, 0, Math.PI * 2);
ctx.stroke();
}
}
}
function endDrawing() {
if (!isDrawing) return;
isDrawing = false;
if (['line', 'rectangle', 'circle'].includes(currentTool)) {
saveState();
}
}
function setupContext() {
const size = parseInt(brushSize.value);
const opacity = parseInt(opacityRange.value) / 100;
ctx.lineWidth = size;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
if (currentTool === 'eraser') {
ctx.globalCompositeOperation = 'destination-out';
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
} else {
ctx.globalCompositeOperation = 'source-over';
const color = colorPicker.value;
ctx.strokeStyle = `${color}${Math.round(opacity * 255).toString(16).padStart(2, '0')}`;
}
}
function floodFill(x, y, targetColor) {
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const width = canvas.width;
const height = canvas.height;
const index = (y * width + x) * 4;
const clickedColor = { r: data[index], g: data[index + 1], b: data[index + 2], a: data[index + 3] };
if (colorMatch(clickedColor, targetColor)) return;
const stack = [{x, y}];
while (stack.length > 0) {
const pixel = stack.pop();
const px = pixel.x;
const py = pixel.y;
if (px < 0 || px >= width || py < 0 || py >= height) continue;
const currentIndex = (py * width + px) * 4;
const currentColor = {
r: data[currentIndex],
g: data[currentIndex + 1],
b: data[currentIndex + 2],
a: data[currentIndex + 3]
};
if (colorMatch(currentColor, clickedColor)) {
data[currentIndex] = targetColor.r;
data[currentIndex + 1] = targetColor.g;
data[currentIndex + 2] = targetColor.b;
data[currentIndex + 3] = 255;
stack.push({x: px + 1, y: py});
stack.push({x: px - 1, y: py});
stack.push({x: px, y: py + 1});
stack.push({x: px, y: py - 1});
}
}
ctx.putImageData(imageData, 0, 0);
}
function colorMatch(color1, color2, tolerance = 10) {
return Math.abs(color1.r - color2.r) <= tolerance &&
Math.abs(color1.g - color2.g) <= tolerance &&
Math.abs(color1.b - color2.b) <= tolerance;
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : {r: 0, g: 0, b: 0};
}
function saveDrawing() {
const filename = saveFilename.value.trim() || 'my-drawing';
const dataURL = canvas.toDataURL();
try {
localStorage.setItem(`painting-${filename}`, dataURL);
alert(`Drawing saved as "${filename}"`);
} catch (e) {
alert('Error saving drawing. Local storage might be full or disabled.');
}
saveModal.style.display = 'none';
}
function loadDrawing() {
const savedDrawings = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('painting-')) {
savedDrawings.push(key.replace('painting-', ''));
}
}
if (savedDrawings.length === 0) {
alert('No saved drawings found.');
return;
}
const drawing = prompt(`Enter filename to load (available: ${savedDrawings.join(', ')})`);
if (!drawing) return;
const dataURL = localStorage.getItem(`painting-${drawing}`);
if (!dataURL) {
alert(`Drawing "${drawing}" not found.`);
return;
}
const img = new Image();
img.src = dataURL;
img.onload = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
saveState();
};
}
function exportDrawing() {
const link = document.createElement('a');
link.download = 'drawing.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
// New function for downloading image
function downloadImage() {
const timestamp = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
const link = document.createElement('a');
link.download = `drawing-${timestamp}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
}
// New function to open transform modal
function openTransformModal() {
transformPrompt.value = '';
transformPreview.innerHTML = '';
downloadTransformed.style.display = 'none';
transformModal.style.display = 'flex';
}
// New function to transform image with Image-Photo
async function transformImage() {
const prompt = transformPrompt.value.trim();
if (!prompt) {
alert('Please enter a prompt for the transformation');
return;
}
// Show loading state
transformLoading.style.display = 'block';
confirmTransform.disabled = true;
transformPreview.innerHTML = '';
downloadTransformed.style.display = 'none';
transformedImageUrl = null;
try {
// Convert canvas to File
const dataUrl = canvas.toDataURL('image/png');
const res = await fetch(dataUrl);
const blob = await res.blob();
const file = new File([blob], 'drawing.png', { type: 'image/png' });
// Register handler for Image-Photo response
const handlerId = `image-transform-${Date.now()}`;
window.Poe.registerHandler(handlerId, (result) => {
const response = result.responses[0];
if (response.status === "error") {
transformLoading.style.display = 'none';
transformPreview.innerHTML = `<p style="color: red;">Error: ${response.statusText || 'Failed to transform image'}</p>`;
confirmTransform.disabled = false;
}
else if (response.status === "complete") {
transformLoading.style.display = 'none';
confirmTransform.disabled = false;
if (response.attachments?.length > 0) {
const imageAttachment = response.attachments[0];
transformedImageUrl = imageAttachment.url;
transformPreview.innerHTML = `
<img src="${imageAttachment.url}" alt="Transformed image">
<p>${response.content}</p>
`;
downloadTransformed.style.display = 'inline-block';
} else {
transformPreview.innerHTML = `<p>${response.content}</p>
<p style="color: #f59e0b;">No image was returned. Try a different prompt.</p>`;
}
}
});
// Send message to Image-Photo
await window.Poe.sendUserMessage(
"@Image-Photo " + prompt,
{
handler: handlerId,
stream: false,
openChat: false,
attachments: [file]
}
);
await window.Poe.sendUserMessage(
"@Free-Thinking-Server " + prompt,
{
handler: handlerId,
stream: false,
openChat: false,
attachments: [file]
}
);
} catch (err) {
transformLoading.style.display = 'none';
confirmTransform.disabled = false;
transformPreview.innerHTML = `<p style="color: red;">Error: ${err.message || 'Failed to send image'}</p>`;
console.error("Error:", err);
}
}
// Function to download transformed image
function downloadTransformedImage() {
if (!transformedImageUrl) return;
const link = document.createElement('a');
link.href = transformedImageUrl;
link.download = `transformed-${Date.now()}.png`;
link.click();
}
function setCurrentTool(tool) {
currentTool = tool;
document.querySelectorAll('.tool-group button').forEach(btn => {
btn.classList.remove('active');
});
document.getElementById(`${tool}Tool`).classList.add('active');
updateToolInfo();
}
function updateToolInfo() {
const size = brushSize.value;
const color = colorPicker.value;
const opacity = opacityRange.value;
let toolName;
switch (currentTool) {
case 'pencil': toolName = 'Pencil'; break;
case 'brush': toolName = 'Brush'; break;
case 'eraser': toolName = 'Eraser'; break;
case 'fill': toolName = 'Fill'; break;
case 'line': toolName = 'Line'; break;
case 'rectangle': toolName = 'Rectangle'; break;
case 'circle': toolName = 'Circle'; break;
default: toolName = 'Unknown';
}
toolInfo.textContent = `${toolName} Tool | Size: ${size}px | Color: ${color} | Opacity: ${opacity}%`;
}
function handleKeyDown(e) {
if (document.activeElement.tagName === 'INPUT') return;
if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
undo();
} else if (e.key === 'y' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
redo();
} else if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
saveModal.style.display = 'flex';
} else if (e.key === 'p' || e.key === 'b' || e.key === 'e' || e.key === 'f' || e.key === 'l' || e.key === 'r' || e.key === 'c') {
switch (e.key) {
case 'p': setCurrentTool('pencil'); break;
case 'b': setCurrentTool('brush'); break;
case 'e': setCurrentTool('eraser'); break;
case 'f': setCurrentTool('fill'); break;
case 'l': setCurrentTool('line'); break;
case 'r': setCurrentTool('rectangle'); break;
case 'c': setCurrentTool('circle'); break;
}
}
}
function trackMousePosition(e) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
positionDisplay.textContent = `Position: ${Math.floor(x)}, ${Math.floor(y)}`;
}
function rgbToHex(rgb) {
const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (match) {
return "#" + ((1 << 24) + (parseInt(match[1]) << 16) + (parseInt(match[2]) << 8) + parseInt(match[3])).toString(16).slice(1);
}
return rgb;
}
// Event listeners
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', endDrawing);
canvas.addEventListener('mouseout', endDrawing);
canvas.addEventListener('mousemove', trackMousePosition);
pencilTool.addEventListener('click', () => setCurrentTool('pencil'));
brushTool.addEventListener('click', () => setCurrentTool('brush'));
eraserTool.addEventListener('click', () => setCurrentTool('eraser'));
fillTool.addEventListener('click', () => setCurrentTool('fill'));
lineTool.addEventListener('click', () => setCurrentTool('line'));
rectangleTool.addEventListener('click', () => setCurrentTool('rectangle'));
circleTool.addEventListener('click', () => setCurrentTool('circle'));
colorPicker.addEventListener('input', () => {
currentColor.style.backgroundColor = colorPicker.value;
updateToolInfo();
});
brushSize.addEventListener('input', () => {
sizeDisplay.textContent = `${brushSize.value}px`;
updateToolInfo();
});
opacityRange.addEventListener('input', () => {
opacityDisplay.textContent = `${opacityRange.value}%`;
updateToolInfo();
});
undoButton.addEventListener('click', undo);
redoButton.addEventListener('click', redo);
clearButton.addEventListener('click', clearCanvas);
saveButton.addEventListener('click', () => saveModal.style.display = 'flex');
loadButton.addEventListener('click', loadDrawing);
exportButton.addEventListener('click', exportDrawing);
// New button event listeners
downloadButton.addEventListener('click', downloadImage);
transformButton.addEventListener('click', openTransformModal);
cancelTransform.addEventListener('click', () => transformModal.style.display = 'none');
confirmTransform.addEventListener('click', transformImage);
downloadTransformed.addEventListener('click', downloadTransformedImage);
cancelSave.addEventListener('click', () => saveModal.style.display = 'none');
confirmSave.addEventListener('click', saveDrawing);
document.querySelectorAll('.palette-color').forEach(colorEl => {
colorEl.addEventListener('click', () => {
const color = colorEl.style.backgroundColor;
const hex = rgbToHex(color);
colorPicker.value = hex;
currentColor.style.backgroundColor = color;
updateToolInfo();
});
});
document.addEventListener('keydown', handleKeyDown);
initCanvas();
updateButtons();
</script>
</html>