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();
}
});