您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhanced drawing tools and generative art effects for Drawaria.online (Requires separate bot script)
当前为
// ==UserScript== // @name Drawaria ideas and art generator (Drawing Tools Only) // @namespace http://tampermonkey.net/ // @version 1.0 // @description Enhanced drawing tools and generative art effects for Drawaria.online (Requires separate bot script) // @author YouTubeDrawaria // @match https://drawaria.online/* // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // @grant none // ==/UserScript== (() => { 'use strict'; const EL = (sel) => document.querySelector(sel); const ELL = (sel) => document.querySelectorAll(sel); // --- Globals for Drawing --- let drawing_active = false; let previewCanvas = document.createElement('canvas'); let originalCanvas = null; // Will be assigned later var data; // Stores image pixel data let cw = 0; // Canvas width let ch = 0; // Canvas height let executionLine = []; // Stores draw commands for image drawing // Note: This script relies on another script creating and managing window.___BOT // --- Utility Functions --- function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // This function is not used internally anymore after removing bot join, // but kept in case it's needed for compatibility or future features. function nullify(value = null) { return value == null ? null : String().concat('"', value, '"'); } function getRandomColor() { const r = Math.floor(Math.random() * 256); const g = Math.floor(Math.random() * 256); const b = Math.floor(Math.random() * 256); return `rgb(${r},${g},${b})`; } // Function to send a raw draw command message via the bot's socket function sendDrawCmd(socket, start, end, color, thickness, isEraser = false, algo = 0) { if (!socket || socket.readyState !== WebSocket.OPEN) { console.warn("Bot socket not open, cannot send draw command."); return false; // Indicate failure } // Ensure coordinates are normalized (0-1) const p1x = Math.max(0, Math.min(1, start[0])); const p1y = Math.max(0, Math.min(1, start[1])); const p2x = Math.max(0, Math.min(1, end[0])); const p2y = Math.max(0, Math.min(1, end[1])); const gameThickness = isEraser ? thickness : 0 - thickness; socket.send(`42["drawcmd",0,[${p1x},${p1y},${p2x},${p2y},${isEraser},${gameThickness},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`); return true; // Indicate success } // --- Image Drawing Logic --- function loadImage(url) { originalCanvas = document.getElementById('canvas'); // Ensure this is fresh if (!originalCanvas) { console.error("Original canvas not found!"); alert("Error: Game canvas not found. Cannot load image."); return; } var img = new Image(); img.onload = () => { previewCanvas.width = originalCanvas.width; previewCanvas.height = originalCanvas.height; cw = previewCanvas.width; ch = previewCanvas.height; var ctx = previewCanvas.getContext('2d'); let scaledWidth = img.width; let scaledHeight = img.height; const canvasAspect = cw / ch; const imgAspect = img.width / img.height; // Calculate scaling to fit image within canvas let scaleFactor = 1; if (img.width > cw || img.height > ch) { scaleFactor = Math.min(cw / img.width, ch / img.height); } scaledWidth = img.width * scaleFactor; scaledHeight = img.height * scaleFactor; const offsetX = (cw - scaledWidth) / 2; const offsetY = (ch - scaledHeight) / 2; ctx.clearRect(0, 0, cw, ch); // Clear preview // Draw image centered and scaled ctx.drawImage(img, offsetX, offsetY, scaledWidth, scaledHeight); try { var imgData = ctx.getImageData(0, 0, cw, ch); data = imgData.data; console.log('Image loaded and processed for drawing.'); alert('Image loaded and ready for drawing!'); } catch (e) { console.error("Error getting image data (CORS issue?):", e); alert("Error processing image. If loaded from a URL, it might be a CORS issue. Try downloading and selecting the file locally."); data = null; } }; img.onerror = () => { console.error("Failed to load image from URL:", url); alert("Failed to load image. Check the URL or file."); }; img.crossOrigin = 'anonymous'; // Attempt to handle CORS for URL loads img.src = url; } // Generates the drawing commands for the loaded image function generateImageDrawingCommands(pixelStep, brushThickness, offsetPercent, forceBW = false) { if (!data) { alert('No image data loaded. Please load an image first.'); return []; // Return empty array if no data } executionLine = []; const step = Math.max(1, parseInt(pixelStep, 10)); const thickness = parseInt(brushThickness, 10); // Calculate offset in canvas pixels, then normalize to 0-1 const offsetX = parseInt(offsetPercent.x, 10) / 100 * cw; const offsetY = parseInt(offsetPercent.y, 10) / 100 * ch; for (let y = 0; y < ch; y += step) { let lineStart = null; // Store the normalized start coordinate [0-1, 0-1] let lastColor = null; for (let x = 0; x < cw; x += step) { // Ensure we don't go out of bounds when checking pixels if (y >= ch || x >= cw) continue; let index = (y * cw + x) * 4; // Check if index is within data bounds if (index + 3 >= data.length) continue; let r = data[index]; let g = data[index + 1]; let b = data[index + 2]; let a = data[index + 3]; let currentColor = `rgb(${r},${g},${b})`; if (forceBW) { const grayscale = (r + g + b) / 3; currentColor = grayscale < 128 ? 'rgb(0,0,0)' : 'rgb(255,255,255)'; } const currentPointNormalized = [ (x + offsetX) / cw, // Normalize x with offset (y + offsetY) / ch // Normalize y with offset ]; if (a > 128) { // Consider pixel opaque enough if (!lineStart) { // Start of a new line segment lineStart = [...currentPointNormalized]; // Copy current point lastColor = currentColor; } else if (currentColor !== lastColor || x + step >= cw) { // Color changed or end of row // End the previous segment at the previous point const endPointX = ((x - step) + offsetX) / cw; const endPointY = (y + offsetY) / ch; executionLine.push({ pos1: lineStart, pos2: [endPointX, endPointY], color: lastColor, thickness: thickness, }); lineStart = [...currentPointNormalized]; // Start new segment with current pixel lastColor = currentColor; } // If it's the last pixel in the row and part of an ongoing line // Add condition: check if the next pixel is transparent let nextIndex = (y * cw + x + step) * 4; let nextIsTransparent = (x + step >= cw) || (nextIndex + 3 >= data.length) || data[nextIndex + 3] <= 128; if (x + step >= cw && lineStart && !nextIsTransparent && currentColor === lastColor) { // End at current point if it's the very last opaque pixel in the row executionLine.push({ pos1: lineStart, pos2: currentPointNormalized, color: lastColor, thickness: thickness, }); lineStart = null; // Reset for next row } } else { // Transparent pixel, end current line segment if (lineStart) { // End the segment at the previous point before the transparent one const endPointX = ((x - step) + offsetX) / cw; const endPointY = (y + offsetY) / ch; executionLine.push({ pos1: lineStart, pos2: [endPointX, endPointY], color: lastColor, thickness: thickness, }); lineStart = null; } } } // After the inner loop finishes a row, check if a line segment is still open if (lineStart) { // End the last segment at the last valid x coordinate in the row that was part of the line // This needs careful calculation... simplest is just ending at the last point checked const lastValidX = cw - (cw % step || step); // Find the last x coordinate we would have checked in the row const endPointX = (lastValidX + offsetX) / cw; const endPointY = (y + offsetY) / ch; executionLine.push({ pos1: lineStart, pos2: [endPointX, endPointY], color: lastColor, thickness: thickness, }); lineStart = null; // Reset for next row } } console.log(`Image processing complete. ${executionLine.length} draw commands generated.`); return executionLine; // Return the generated commands } // Executes a list of drawing commands (like the one generated by drawImageFromPreview) async function executeDrawingCommands(commands, socket) { if (!socket || socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected to a room. Cannot start drawing."); drawing_active = false; // Ensure flag is false return; } drawing_active = true; console.log(`Starting to draw ${commands.length} lines.`); const drawDelay = parseInt(EL('#engine_draw_delay').value, 10) || 10; for (let i = 0; i < commands.length; i++) { if (!drawing_active) { console.log("Drawing stopped by user."); break; } let currentLine = commands[i]; // Use sendDrawCmd which checks socket state if (!sendDrawCmd(socket, currentLine.pos1, currentLine.pos2, currentLine.color, currentLine.thickness)) { console.warn("Socket closed during drawing. Stopping."); break; // Stop if sending fails } if (drawDelay > 0) await delay(drawDelay); } drawing_active = false; console.log('Finished executing drawing commands.'); } // --- New Generative Art Functions --- // These functions now get the socket from window.___BOT and call executeDrawingCommands or sendDrawCmd async function floodDots() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } drawing_active = true; const socket = window.___BOT.conn.socket; const numDots = 2000; // Number of dots const dotSize = 2; // Thickness of dots const floodDelay = 2; // Milliseconds between dots console.log("Starting Flood of Dots..."); for (let i = 0; i < numDots; i++) { if (!drawing_active) break; const x = Math.random(); const y = Math.random(); const color = getRandomColor(); // A dot is a tiny line if (!sendDrawCmd(socket, [x, y], [x + 0.001, y + 0.001], color, dotSize)) break; // Stop if send fails if (floodDelay > 0) await delay(floodDelay); } drawing_active = false; console.log("Flood of Dots finished."); } async function explosionOfColors() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } drawing_active = true; const socket = window.___BOT.conn.socket; console.log("Starting Explosion of Colors..."); const centerX = 0.5, centerY = 0.5; const maxRadius = 0.7; // Max radius in normalized coords const radiusStep = 0.01; const pointsOnCircle = 36; // Lower for faster, more jagged const thickness = 10; const explosionDelay = 15; // ms per circle for (let r = 0.01; r < maxRadius; r += radiusStep) { if (!drawing_active) break; const color = getRandomColor(); let prevX = centerX + r * Math.cos(0); let prevY = centerY + r * Math.sin(0); for (let i = 1; i <= pointsOnCircle; i++) { if (!drawing_active) break; const angle = (i / pointsOnCircle) * 2 * Math.PI; const currentX = centerX + r * Math.cos(angle); const currentY = centerY + r * Math.sin(angle); if (!sendDrawCmd(socket, [prevX, prevY], [currentX, currentY], color, thickness)) break; // Stop if send fails prevX = currentX; prevY = currentY; // Small delay between segments to spread out commands within one circle step if (explosionDelay > 0) await delay(Math.max(1, Math.floor(explosionDelay / pointsOnCircle))); } if (!drawing_active) break; // Check again after drawing circle segments // Delay between full circles if (explosionDelay > 0) await delay(explosionDelay); if (!sendDrawCmd(socket, [prevX, prevY], [centerX + r * Math.cos(0), centerY + r * Math.sin(0)], color, thickness)) break; // Close the circle } drawing_active = false; console.log("Explosion of Colors finished."); } async function colorPartyCrash() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } drawing_active = true; const socket = window.___BOT.conn.socket; console.log("Starting Color Party (Attempting Crash)... Be careful!"); alert("Color Party is starting. This might be very disruptive or unstable!"); // Send a massive amount of commands with no delay. This is very likely to cause issues. const commandsToSend = 20000; // Increased amount for more impact console.log(`Attempting to send ${commandsToSend} commands rapidly.`); for (let i = 0; i < commandsToSend; i++) { if (!drawing_active) break; const x1 = Math.random(), y1 = Math.random(); const x2 = Math.random(), y2 = Math.random(); const color = getRandomColor(); const thickness = Math.random() * 50 + 5; if (!sendDrawCmd(socket, [x1, y1], [x2, y2], color, thickness)) break; // Stop if send fails // NO DELAY HERE! This is the "crash" part. } drawing_active = false; console.log("Color Party finished (or crashed)."); } async function colorFestival() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } drawing_active = true; const socket = window.___BOT.conn.socket; console.log("Starting Color Festival..."); const numShapes = 150; const festivalDelay = 50; // ms per shape for (let i = 0; i < numShapes; i++) { if (!drawing_active) break; const x = Math.random() * 0.8 + 0.1; // Avoid edges for full shape const y = Math.random() * 0.8 + 0.1; const size = Math.random() * 0.1 + 0.02; // Normalized size const color = getRandomColor(); const thickness = Math.floor(Math.random() * 15) + 3; const type = Math.floor(Math.random() * 3); let sentOk = true; if (type === 0) { // Rectangle sentOk = sentOk && sendDrawCmd(socket, [x - size/2, y - size/2], [x + size/2, y - size/2], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x + size/2, y - size/2], [x + size/2, y + size/2], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x + size/2, y + size/2], [x - size/2, y + size/2], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x - size/2, y + size/2], [x - size/2, y - size/2], color, thickness); } else if (type === 1) { // Triangle sentOk = sentOk && sendDrawCmd(socket, [x, y - size/2], [x + size/2, y + size/2], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x + size/2, y + size/2], [x - size/2, y + size/2], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x - size/2, y + size/2], [x, y - size/2], color, thickness); } else { // Cross sentOk = sentOk && sendDrawCmd(socket, [x - size/2, y], [x + size/2, y], color, thickness); if (!sentOk || !drawing_active) break; await delay(5); sentOk = sentOk && sendDrawCmd(socket, [x, y - size/2], [x, y + size/2], color, thickness); } if (!sentOk || !drawing_active) break; // Check again before main delay if(festivalDelay > 0) await delay(festivalDelay); } drawing_active = false; console.log("Color Festival finished."); } async function lightSpeedFireworks() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } drawing_active = true; const socket = window.___BOT.conn.socket; console.log("Starting Light Speed Fireworks..."); const numFireworks = 10; const fireworkBaseDelay = 500; // ms between fireworks for (let i = 0; i < numFireworks; i++) { if (!drawing_active) break; const startX = Math.random() * 0.6 + 0.2; // Launch from near bottom center const startY = 0.95; const peakX = startX + (Math.random() - 0.5) * 0.2; const peakY = Math.random() * 0.3 + 0.1; // Explode in upper part const launchColor = getRandomColor(); const launchThickness = 5; const particleCount = 30 + Math.floor(Math.random()*30); const particleThickness = 3 + Math.floor(Math.random()*3); const launchSteps = 20; const launchStepDelay = 5; // ms between launch segments const explosionParticleDelay = 10; // ms per particle // Launch rocket let sentOk = true; for(let s=0; s<launchSteps; s++){ if (!drawing_active) break; let y_progress = s / launchSteps; let next_y_progress = (s + 1) / launchSteps; let currentX = startX + (peakX - startX) * y_progress; let currentY = startY + (peakY - startY) * y_progress; let nextX = startX + (peakX - startX) * next_y_progress; let nextY = startY + (peakY - startY) * next_y_progress; sentOk = sentOk && sendDrawCmd(socket, [currentX, currentY], [nextX, nextY], launchColor, launchThickness); if (!sentOk) break; if(launchStepDelay > 0) await delay(launchStepDelay); } if (!sentOk || !drawing_active) break; // Explosion const explosionColor = getRandomColor(); for (let j = 0; j < particleCount; j++) { if (!drawing_active) break; const angle = Math.random() * 2 * Math.PI; const dist = Math.random() * 0.15 + 0.05; // Explosion radius const endX = peakX + dist * Math.cos(angle); const endY = peakY + dist * Math.sin(angle); sentOk = sentOk && sendDrawCmd(socket, [peakX, peakY], [endX, endY], explosionColor, particleThickness); if (!sentOk) break; if(explosionParticleDelay > 0) await delay(explosionParticleDelay); } if (!sentOk || !drawing_active) break; // Check before next firework delay if(fireworkBaseDelay > 0) await delay(fireworkBaseDelay); } drawing_active = false; console.log("Light Speed Fireworks finished."); } async function botClearCanvas() { if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket || window.___BOT.conn.socket.readyState !== WebSocket.OPEN) { alert("Bot is not connected. Join a room with another script first!"); return; } const socket = window.___BOT.conn.socket; console.log("Attempting to 'clear' canvas with a large white line using the bot."); // Draw a massive white line to simulate clear. // This is a whiteout, not a true clear. True clear requires the 'clear' command // and being the actual drawer. sendDrawCmd(socket, [0.01, 0.5], [0.99, 0.5], "#FFFFFF", 2000, false); // Huge horizontal line // Or maybe a big circle/dot in the middle // sendDrawCmd(socket, [0.5, 0.5], [0.501, 0.501], "#FFFFFF", 2000, false); // Huge dot } // --- UI Creation --- function addBoxIcons() { if (document.querySelector('link[href*="boxicons"]')) return; let boxicons = document.createElement('link'); boxicons.href = 'https://unpkg.com/[email protected]/css/boxicons.min.css'; boxicons.rel = 'stylesheet'; document.head.appendChild(boxicons); } function createStylesheet() { if (document.getElementById('drawaria-enhancer-style')) return; let style = document.createElement('style'); style.id = 'drawaria-enhancer-style'; style.innerHTML = ` #enhancer-menu { position: fixed; top: 70px; left: 10px; width: 320px; background-color: #333A44; /* Darker theme */ color: #E0E0E0; /* Light text */ border: 1px solid #555E69; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); z-index: 10001; font-family: 'Arial', sans-serif; font-size: 14px; display: none; /* Initially hidden */ } #enhancer-menu-header { padding: 10px; background-color: #4A525E; /* Slightly lighter header */ color: #FFF; cursor: move; border-top-left-radius: 7px; border-top-right-radius: 7px; display: flex; justify-content: space-between; align-items: center; } #enhancer-menu-header h3 { margin: 0; font-size: 16px; font-weight: bold; } #enhancer-menu-close { background: none; border: none; color: #FFF; font-size: 20px; cursor: pointer; } #enhancer-menu-body { padding: 10px; max-height: 70vh; overflow-y: auto; } .enhancer-section { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #4A525E; } .enhancer-section:last-child { border-bottom: none; } .enhancer-section h4 { margin-top: 0; margin-bottom: 8px; color: #A0D2EB; /* Accent color for headings */ font-size: 15px; } .enhancer-row { display: flex; margin-bottom: 8px; align-items: center; } .enhancer-row > label { flex-basis: 100px; margin-right: 5px; font-weight: normal; min-width: 80px; /* Prevent shrinking */ } .enhancer-row > input[type="text"], .enhancer-row > input[type="number"], .enhancer-row > input[type="file"], .enhancer-row > select { flex-grow: 1; padding: 6px; background-color: #2A3038; /* Dark input fields */ border: 1px solid #555E69; color: #E0E0E0; border-radius: 4px; box-sizing: border-box; } .enhancer-row > input[type="number"] { text-align: center; -moz-appearance: textfield; } .enhancer-row > input[type="number"]::-webkit-inner-spin-button, .enhancer-row > input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0;} .enhancer-button-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; } .enhancer-button { padding: 8px 10px; background-color: #4CAF50; /* Green accent */ color: white; border: none; border-radius: 4px; cursor: pointer; text-align: center; font-size: 13px; transition: background-color 0.2s; } .enhancer-button:hover { background-color: #45a049; } .enhancer-button.danger { background-color: #F44336; /* Red for dangerous actions */ } .enhancer-button.danger:hover { background-color: #da190b; } .enhancer-button.special { background-color: #2196F3; /* Blue for special */ } .enhancer-button.special:hover { background-color: #0b7dda; } #enhancer-toggle-button { /* Style for the main toggle button */ margin-left: 5px; /* Add some space from the chat input */ } details { margin-bottom: 8px; border: 1px solid #4A525E; border-radius: 4px; padding: 5px; } summary { cursor: pointer; font-weight: bold; color: #E0E0E0; margin-left: 5px; } details > div { margin-top: 8px; padding-top: 8px; border-top: 1px solid #4A525E; } /* Style for the Clear Canvas button - moved out of bot section */ .enhancer-clear-button { padding: 8px 10px; background-color: #ff9800; /* Orange */ color: white; border: none; border-radius: 4px; cursor: pointer; text-align: center; font-size: 13px; transition: background-color 0.2s; width: 100%; /* Make it full width */ margin-top: 8px; /* Add space above it */ } .enhancer-clear-button:hover { background-color: #f57c00; } `; document.head.appendChild(style); } function makeDraggable(menuElement, headerElement) { let offsetX, offsetY, isDragging = false; headerElement.onmousedown = (e) => { // Prevent dragging if clicking on interactive elements inside header if (e.target.closest('button, input, select, a')) return; isDragging = true; offsetX = e.clientX - menuElement.getBoundingClientRect().left; offsetY = e.clientY - menuElement.getBoundingClientRect().top; menuElement.style.userSelect = 'none'; // Prevent text selection while dragging menuElement.style.cursor = 'grabbing'; // Indicate dragging }; document.onmousemove = (e) => { if (!isDragging) return; // Calculate new position let newLeft = e.clientX - offsetX; let newTop = e.clientY - offsetY; // Optional: Keep menu within viewport bounds // const menuRect = menuElement.getBoundingClientRect(); // const viewportWidth = window.innerWidth; // const viewportHeight = window.innerHeight; // newLeft = Math.max(0, Math.min(newLeft, viewportWidth - menuRect.width)); // newTop = Math.max(0, Math.min(newTop, viewportHeight - menuRect.height)); menuElement.style.left = `${newLeft}px`; menuElement.style.top = `${newTop}px`; }; document.onmouseup = () => { if (isDragging) { isDragging = false; menuElement.style.userSelect = ''; // Restore text selection headerElement.style.cursor = 'move'; // Restore move cursor } }; headerElement.style.cursor = 'move'; // Set initial cursor } function buildMenu() { addBoxIcons(); createStylesheet(); const menu = document.createElement('div'); menu.id = 'enhancer-menu'; const header = document.createElement('div'); header.id = 'enhancer-menu-header'; header.innerHTML = `<h3>Drawaria Ideas & Art Generator</h3><button id="enhancer-menu-close" title="Close Menu"><i class='bx bx-x'></i></button>`; menu.appendChild(header); const body = document.createElement('div'); body.id = 'enhancer-menu-body'; body.innerHTML = ` <div class="enhancer-section"> <h4><i class='bx bxs-image-add'></i> Image Drawing</h4> <div class="enhancer-row"> <label for="img-file-input">Load Image:</label> <input type="file" id="img-file-input" accept="image/*" /> </div> <div class="enhancer-row"> <label for="img-url-input">Or Image URL:</label> <input type="text" id="img-url-input" placeholder="Enter image URL (CORS may apply)" /> </div> <details> <summary>Drawing Settings</summary> <div class="enhancer-row"> <label for="engine_imagesize" title="Detail factor. Smaller value = more detail/pixels. (Renamed from 'Image Size' for clarity)">Pixel Step:</label> <input type="number" id="engine_imagesize" min="1" max="50" value="5"> </div> <div class="enhancer-row"> <label for="engine_brushsize" title="Brush thickness for drawing">Brush Size:</label> <input type="number" id="engine_brushsize" min="1" max="100" value="3"> </div> <div class="enhancer-row"> <label for="engine_offset_x" title="Horizontal offset %">Offset X (%):</label> <input type="number" id="engine_offset_x" min="-100" max="100" value="0"> </div> <div class="enhancer-row"> <label for="engine_offset_y" title="Vertical offset %">Offset Y (%):</label> <input type="number" id="engine_offset_y" min="-100" max="100" value="0"> </div> <div class="enhancer-row"> <label for="engine_draw_delay" title="Delay (ms) between draw commands">Draw Delay:</label> <input type="number" id="engine_draw_delay" min="0" max="1000" value="10"> </div> </details> <div class="enhancer-button-grid" style="margin-top:10px;"> <button id="img-draw-start" class="enhancer-button"><i class='bx bx-play'></i> Draw Image</button> <button id="img-draw-stop" class="enhancer-button danger"><i class='bx bx-stop'></i> Stop Drawing</button> </div> </div> <div class="enhancer-section"> <h4><i class='bx bxs-magic-wand'></i> Generative Art Effects</h4> <div class="enhancer-button-grid"> <button id="effect-flood-dots" class="enhancer-button special"><i class='bx bx-dots-horizontal-rounded'></i> Flood o' Dots</button> <button id="effect-explosion" class="enhancer-button special"><i class='bx bxs-bomb'></i> Color Explosion</button> <button id="effect-color-festival" class="enhancer-button special"><i class='bx bxs-party'></i> Color Festival</button> <button id="effect-fireworks" class="enhancer-button special"><i class='bx bxs-hot'></i> Fireworks</button> <button id="effect-retro-machine" class="enhancer-button special" title="Draws loaded image in B&W with high pixel step. Load image first."><i class='bx bx-tv'></i> Retro Machine</button> <button id="effect-anime-girl" class="enhancer-button special" title="Uses image drawing. Load an anime girl image first."><i class='bx bx-face'></i> Anime Girl (Trace)</button> </div> <button id="bot-clear-canvas" class="enhancer-clear-button"><i class='bx bxs-eraser'></i> Bot Clear (Whiteout)</button> <button id="effect-color-party" class="enhancer-button danger" style="width:100%; margin-top: 8px;"><i class='bx bx-error-alt'></i> Color Party (CRASH)</button> </div> `; menu.appendChild(body); document.body.appendChild(menu); makeDraggable(menu, header); // --- Event Listeners for Menu Buttons --- EL('#enhancer-menu-close').onclick = () => menu.style.display = 'none'; // Image Drawing EL('#img-file-input').onchange = function() { if (!this.files || !this.files[0]) return; const FR = new FileReader(); FR.onload = (evt) => loadImage(evt.target.result); FR.readAsDataURL(this.files[0]); EL('#img-url-input').value = ''; // Clear URL input if file is chosen }; EL('#img-url-input').onchange = function() { if (this.value.trim() !== "") { loadImage(this.value.trim()); EL('#img-file-input').value = ''; // Clear file input } }; EL('#img-draw-start').onclick = () => { // Get settings from UI const pixelStep = EL('#engine_imagesize').value; const brushSize = EL('#engine_brushsize').value; const offsetX = EL('#engine_offset_x').value; const offsetY = EL('#engine_offset_y').value; // Generate commands based on current image data and settings const commands = generateImageDrawingCommands(pixelStep, brushSize, { x: offsetX, y: offsetY }); // Execute commands using the bot socket (if available) if (commands.length > 0 && window.___BOT && window.___BOT.conn && window.___BOT.conn.socket) { executeDrawingCommands(commands, window.___BOT.conn.socket); } else if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket) { alert("Bot is not connected. Join a room with another script first!"); } else { alert("No drawing commands generated from image."); } }; EL('#img-draw-stop').onclick = () => drawing_active = false; // Generative Art Effects & Clear Button (now independent of bot control section) EL('#bot-clear-canvas').onclick = botClearCanvas; EL('#effect-flood-dots').onclick = floodDots; EL('#effect-explosion').onclick = explosionOfColors; EL('#effect-color-festival').onclick = colorFestival; EL('#effect-fireworks').onclick = lightSpeedFireworks; EL('#effect-color-party').onclick = () => { if (confirm("WARNING: Color Party (CRASH) will send a massive number of commands very quickly. This can make the room unusable, crash your browser, or get you kicked. Proceed with caution?")) { colorPartyCrash(); } }; EL('#effect-retro-machine').onclick = () => { if (!data) { alert('Load an image first for Retro Machine!'); return; } // Use high pixel step for "blocky" retro look, and force B&W const pixelStep = 20; const brushSize = 5; const offsetX = 0; const offsetY = 0; const commands = generateImageDrawingCommands(pixelStep, brushSize, { x:offsetX, y:offsetY }, true); // forceBW=true if (commands.length > 0 && window.___BOT && window.___BOT.conn && window.___BOT.conn.socket) { executeDrawingCommands(commands, window.___BOT.conn.socket); } else if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket) { alert("Bot is not connected. Join a room with another script first!"); } else { alert("No drawing commands generated for Retro Machine."); } alert("Retro Machine will draw the loaded image in Black & White with a blocky style."); }; EL('#effect-anime-girl').onclick = () => { if (!data) { alert('Load an anime girl image first!'); return; } // Uses standard image drawing settings, user should adjust for line art // Trigger the normal image draw function after generating commands const pixelStep = EL('#engine_imagesize').value; const brushSize = EL('#engine_brushsize').value; const offsetX = EL('#engine_offset_x').value; const offsetY = EL('#engine_offset_y').value; const commands = generateImageDrawingCommands(pixelStep, brushSize, { x: offsetX, y: offsetY }, false); // Don't force BW if (commands.length > 0 && window.___BOT && window.___BOT.conn && window.___BOT.conn.socket) { executeDrawingCommands(commands, window.___BOT.conn.socket); } else if (!window.___BOT || !window.___BOT.conn || !window.___BOT.conn.socket) { alert("Bot is not connected. Join a room with another script first!"); } else { alert("No drawing commands generated for Anime Girl."); } alert("Anime Girl (Trace) uses the standard image drawing. Adjust settings for best results with line art."); }; // --- Create Toggle Button --- let chatInputTarget = EL('#chatbox_textinput'); if (chatInputTarget && !EL('#enhancer-toggle-button')) { let btnContainer = document.createElement('div'); btnContainer.className = 'input-group-append'; let toggleBtn = document.createElement('button'); toggleBtn.id = 'enhancer-toggle-button'; toggleBtn.className = 'btn btn-outline-secondary'; // Standard Drawaria button style toggleBtn.innerHTML = `<i class='bx bx-palette'></i>`; // Using a palette icon toggleBtn.title = "Toggle Art Generator Menu"; toggleBtn.onclick = (e) => { e.preventDefault(); menu.style.display = (menu.style.display === 'none' || menu.style.display === '') ? 'block' : 'none'; toggleBtn.classList.toggle('active', menu.style.display === 'block'); }; // Find the parent of the chat input and append the button container const chatInputParent = chatInputTarget.parentElement; if (chatInputParent) { chatInputParent.appendChild(btnContainer); btnContainer.appendChild(toggleBtn); } else { // Fallback if structure changes, append after chat input directly (less ideal) chatInputTarget.after(btnContainer); btnContainer.appendChild(toggleBtn); } } } // --- Initialization --- function initializeWhenReady() { originalCanvas = document.getElementById('canvas'); // Also check for the main chat input as a sign the UI is ready const chatInput = document.getElementById('chatbox_textinput'); if (!originalCanvas || !chatInput) { // console.log("Drawaria UI not ready, retrying..."); // Can be chatty setTimeout(initializeWhenReady, 500); return; } console.log("Drawaria Enhancer (Drawing Tools Only) initializing..."); // No bot creation here, just UI and functionality depending on external bot window['___ENGINE'] = { loadImage: loadImage, generateImageDrawingCommands: generateImageDrawingCommands, // Expose generate function executeDrawingCommands: executeDrawingCommands, // Expose execute function floodDots: floodDots, explosionOfColors: explosionOfColors, colorFestival: colorFestival, lightSpeedFireworks: lightSpeedFireworks, colorPartyCrash: colorPartyCrash, botClearCanvas: botClearCanvas // Expose clear function // Retro Machine and Anime Girl use combinations of the above }; buildMenu(); console.log("Drawaria Ideas & Art Generator (Drawing Tools Only) Loaded!"); alert("Drawaria Ideas & Art Generator (Drawing Tools Only) Loaded! Please use a separate script to connect a bot (creating window.___BOT) before using drawing functions."); } // Wait for the page to be fully loaded, especially the canvas element if (document.readyState === "complete" || document.readyState === "interactive") { initializeWhenReady(); } else { window.addEventListener('load', initializeWhenReady); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址