document.addEventListener('DOMContentLoaded', function() { // ===== DOM Elements ===== const imageUpload = document.getElementById('imageUpload'); const imagePreview = document.getElementById('imagePreview'); const analyzeBtn = document.getElementById('analyzeBtn'); const downloadBtn = document.getElementById('downloadBtn'); const outputContainer = document.getElementById('outputContainer'); const outputCanvas = document.getElementById('outputCanvas'); const uploadContainer = document.querySelector('.upload-container'); const orientationWarning = document.getElementById('orientationWarning'); const loadingIndicator = document.getElementById('loadingIndicator'); const colorThief = new ColorThief(); let uploadedImage = null; let currentPalette = []; // ===== Constants ===== const CANVAS_WIDTH = 1080; const CANVAS_HEIGHT = 1350; const SWATCH_RADIUS = 70; const ROW_SPACING = 180; const ROWS_Y_START = 820; const LABEL_OFFSET = 40; const SWATCH_SPACING = 80; const SWATCHES_PER_ROW = 3; const TOTAL_COLORS = 9; const LABEL_COLORS = { light: '#2c3e50', // Dark gray for light backgrounds dark: '#ffffff' // White for dark backgrounds }; const FONT_STYLE = 'bold 36px Permanent Marker'; // ===== Event Listeners ===== imageUpload.addEventListener('change', handleFileUpload); analyzeBtn.addEventListener('click', analyzeImage); downloadBtn.addEventListener('click', downloadImage); // Drag and drop events uploadContainer.addEventListener('dragover', handleDragOver); uploadContainer.addEventListener('dragleave', handleDragLeave); uploadContainer.addEventListener('drop', handleDrop); // ===== Main Functions ===== function handleFileUpload(e) { const file = e.target.files?.[0]; if (!file) return; if (!file.type.match('image.*')) { showError('Please upload an image file'); return; } loadingIndicator.style.display = 'block'; analyzeBtn.disabled = true; const reader = new FileReader(); reader.onload = function(event) { imagePreview.onload = function() { loadingIndicator.style.display = 'none'; uploadedImage = imagePreview; analyzeBtn.disabled = false; orientationWarning.style.display = 'block'; }; imagePreview.src = event.target.result; outputContainer.style.display = 'none'; }; reader.onerror = () => showError('Error reading file'); reader.readAsDataURL(file); } function analyzeImage() { if (!uploadedImage?.complete) { showError('Image not fully loaded yet'); return; } loadingIndicator.style.display = 'block'; outputContainer.style.display = 'none'; // Use setTimeout to allow UI to update before heavy processing setTimeout(() => { try { processImage(); } catch (error) { showError('Analysis failed. Try another image.'); console.error('Analysis error:', error); } finally { loadingIndicator.style.display = 'none'; } }, 100); } function processImage() { currentPalette = colorThief.getPalette(uploadedImage, TOTAL_COLORS) || []; if (currentPalette.length === 0) { throw new Error('Could not extract colors'); } createOutputImage(); outputContainer.style.display = 'block'; downloadBtn.disabled = false; } function createOutputImage() { const ctx = outputCanvas.getContext('2d'); // Set canvas dimensions (important for high quality exports) outputCanvas.width = CANVAS_WIDTH; outputCanvas.height = CANVAS_HEIGHT; // Fill background ctx.fillStyle = '#EDD286'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); // Draw image with proper cropping drawCroppedImage(ctx); // Determine text color based on image brightness const textColor = getOptimalTextColor(); // Draw color swatches drawColorSwatches(ctx, textColor); } function drawCroppedImage(ctx) { const imgRatio = uploadedImage.naturalWidth / uploadedImage.naturalHeight; const canvasRatio = CANVAS_WIDTH / CANVAS_HEIGHT; let srcX = 0, srcY = 0, srcWidth = uploadedImage.naturalWidth, srcHeight = uploadedImage.naturalHeight; if (imgRatio > canvasRatio) { const scale = CANVAS_HEIGHT / uploadedImage.naturalHeight; srcX = (uploadedImage.naturalWidth - (CANVAS_WIDTH / scale)) / 2; srcWidth = CANVAS_WIDTH / scale; } else { const scale = CANVAS_WIDTH / uploadedImage.naturalWidth; srcY = (uploadedImage.naturalHeight - (CANVAS_HEIGHT / scale)) / 2; srcHeight = CANVAS_HEIGHT / scale; } ctx.drawImage(uploadedImage, srcX, srcY, srcWidth, srcHeight, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT ); } function drawColorSwatches(ctx, textColor) { const toneRows = [ { label: "SHADOWS", yPos: ROWS_Y_START }, { label: "MIDTONES", yPos: ROWS_Y_START + ROW_SPACING }, { label: "HIGHLIGHTS", yPos: ROWS_Y_START + ROW_SPACING * 2 } ]; toneRows.forEach((row, rowIndex) => { const rowColors = currentPalette.slice(rowIndex * SWATCHES_PER_ROW, (rowIndex + 1) * SWATCHES_PER_ROW); if (rowColors.length === 0) return; // Draw label ctx.font = FONT_STYLE; ctx.textAlign = 'center'; ctx.fillStyle = textColor; ctx.shadowColor = 'rgba(0,0,0,0.5)'; ctx.shadowBlur = 5; ctx.fillText(row.label, CANVAS_WIDTH/2, row.yPos - SWATCH_RADIUS - LABEL_OFFSET); ctx.shadowBlur = 0; // Calculate swatch positions const totalSwatchWidth = rowColors.length * (SWATCH_RADIUS * 2) + (rowColors.length - 1) * SWATCH_SPACING; const startX = (CANVAS_WIDTH - totalSwatchWidth) / 2; // Draw each swatch rowColors.forEach((color, i) => { const x = startX + i * (SWATCH_RADIUS * 2 + SWATCH_SPACING) + SWATCH_RADIUS; const hex = rgbToHex(color[0], color[1], color[2]); // Draw glow effect const gradient = ctx.createRadialGradient( x, row.yPos, SWATCH_RADIUS * 0.7, x, row.yPos, SWATCH_RADIUS * 1.3 ); gradient.addColorStop(0, hex); gradient.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(x, row.yPos, SWATCH_RADIUS * 1.3, 0, Math.PI * 2); ctx.fill(); // Draw color circle ctx.beginPath(); ctx.arc(x, row.yPos, SWATCH_RADIUS, 0, Math.PI * 2); ctx.fillStyle = hex; ctx.fill(); // Add outline ctx.lineWidth = 4; ctx.strokeStyle = '#2c3e50'; ctx.stroke(); // Optional: Add hex code below swatch ctx.font = '18px Arial'; ctx.fillStyle = textColor; ctx.fillText(hex, x, row.yPos + SWATCH_RADIUS + 30); }); }); } function getOptimalTextColor() { // Simple brightness check from the top-left corner const tempCanvas = document.createElement('canvas'); tempCanvas.width = tempCanvas.height = 1; const tempCtx = tempCanvas.getContext('2d'); tempCtx.drawImage(uploadedImage, 0, 0, 1, 1); const pixel = tempCtx.getImageData(0, 0, 1, 1).data; const brightness = (pixel[0] * 299 + pixel[1] * 587 + pixel[2] * 114) / 1000; return brightness > 128 ? LABEL_COLORS.light : LABEL_COLORS.dark; } // ===== Helper Functions ===== function showError(message) { alert(message); // In production, replace with a nicer UI element loadingIndicator.style.display = 'none'; } function rgbToHex(r, g, b) { return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join(''); } // ===== UI Event Handlers ===== function handleDragOver(e) { e.preventDefault(); uploadContainer.classList.add('dragover'); } function handleDragLeave() { uploadContainer.classList.remove('dragover'); } function handleDrop(e) { e.preventDefault(); handleDragLeave(); if (e.dataTransfer.files?.length) { imageUpload.files = e.dataTransfer.files; imageUpload.dispatchEvent(new Event('change')); } } function downloadImage() { const link = document.createElement('a'); link.download = `color-palette-${new Date().toISOString().slice(0,10)}.png`; link.href = outputCanvas.toDataURL('image/png', 1.0); link.click(); } });