You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg) {
/**
* Finds a music soundtrack genre based on the photo's average color mood.
* This is an artistic interpretation and not a scientifically accurate process.
* It works by analyzing the average Hue, Saturation, and Lightness of the image.
*/
// Helper function to convert an RGB color value to HSL.
// Handles R, G, and B values from 0 to 255 and returns h, s, and l in the range [0, 1].
const rgbToHsl = (r, g, b) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0, s = 0, l = (max + min) / 2;
if (max !== min) {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
};
// Create the final output canvas which will be returned.
const outputCanvas = document.createElement('canvas');
const ctx = outputCanvas.getContext('2d');
const infoHeight = 80;
// Ensure the canvas is wide enough for the text, but not smaller than the image.
const canvasWidth = Math.max(originalImg.width, 400);
const canvasHeight = originalImg.height + infoHeight;
outputCanvas.width = canvasWidth;
outputCanvas.height = canvasHeight;
// A function to draw the final result on the canvas.
const drawResult = (soundtrack, isError = false) => {
// Draw the original image. A white background is drawn first for transparent images.
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvasWidth, originalImg.height);
ctx.drawImage(originalImg, 0, 0);
// Draw the bottom info panel background.
ctx.fillStyle = isError ? '#8B0000' : '#181818'; // Dark red for errors
ctx.fillRect(0, originalImg.height, canvasWidth, infoHeight);
// If it's an error, just display the message and stop.
if (isError) {
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 16px "Segoe UI", Roboto, Arial, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(soundtrack, canvasWidth / 2, originalImg.height + infoHeight / 2);
return;
}
// Draw a divider line.
ctx.fillStyle = '#444444';
ctx.fillRect(0, originalImg.height, canvasWidth, 2);
// Draw a music icon.
ctx.fillStyle = '#FFFFFF';
ctx.font = '30px Arial';
ctx.textBaseline = 'middle';
ctx.fillText('🎵', 25, originalImg.height + infoHeight / 2);
// Draw the text labels.
const textX = 75;
ctx.fillStyle = '#CCCCCC';
ctx.font = '16px "Segoe UI", Roboto, Arial, sans-serif';
ctx.fillText('Suggested Soundtrack:', textX, originalImg.height + 30);
ctx.fillStyle = '#FFFFFF';
ctx.font = 'bold 22px "Segoe UI", Roboto, Arial, sans-serif';
ctx.fillText(soundtrack, textX, originalImg.height + 58);
};
// Use a temporary, small canvas for faster image analysis.
const analysisCanvas = document.createElement('canvas');
const analysisCtx = analysisCanvas.getContext('2d', { willReadFrequently: true });
const MAX_ANALYSIS_DIMENSION = 100;
let analysisWidth = originalImg.width;
let analysisHeight = originalImg.height;
if (analysisWidth > MAX_ANALYSIS_DIMENSION || analysisHeight > MAX_ANALYSIS_DIMENSION) {
if (analysisWidth > analysisHeight) {
analysisHeight = Math.round((MAX_ANALYSIS_DIMENSION / analysisWidth) * analysisHeight);
analysisWidth = MAX_ANALYSIS_DIMENSION;
} else {
analysisWidth = Math.round((MAX_ANALYSIS_DIMENSION / analysisHeight) * analysisWidth);
analysisHeight = MAX_ANALYSIS_DIMENSION;
}
}
analysisCanvas.width = analysisWidth;
analysisCanvas.height = analysisHeight;
analysisCtx.drawImage(originalImg, 0, 0, analysisWidth, analysisHeight);
// Get pixel data from the temporary canvas.
let imageData;
try {
imageData = analysisCtx.getImageData(0, 0, analysisWidth, analysisHeight);
} catch (e) {
// This error often happens with cross-origin images without CORS headers.
console.error("Image analysis failed:", e);
drawResult("Error: Cannot analyze cross-origin image.", true);
return outputCanvas;
}
const data = imageData.data;
let totalH = 0, totalS = 0, totalL = 0;
const pixelCount = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
const [h, s, l] = rgbToHsl(data[i], data[i + 1], data[i + 2]);
totalH += h;
totalS += s;
totalL += l;
}
const avgH = totalH / pixelCount; // Average Hue (0-1)
const avgS = totalS / pixelCount; // Average Saturation (0-1)
const avgL = totalL / pixelCount; // Average Lightness (0-1)
// A simplified logic to map color properties to music genres.
let soundtrack = "Ambient Chill"; // A safe default
if (avgS < 0.15) { // Low saturation (grayscale-like images)
soundtrack = avgL < 0.4 ? "Somber Classical Piano" : "Nostalgic Film Score";
} else { // Colored images
const hueDegrees = avgH * 360;
if (avgL < 0.4) { // Dark images
soundtrack = (hueDegrees > 180 && hueDegrees < 280) ? "Dark Synthwave" : "Lofi Hip Hop / Chillhop";
} else if (avgL > 0.65) { // Bright images
soundtrack = (hueDegrees > 45 && hueDegrees < 150) ? "Happy Acoustic Folk" : "Upbeat Summer Pop";
} else { // Mid-lightness images with distinct colors
if (hueDegrees >= 0 && hueDegrees < 60) { // Reds/Oranges
soundtrack = "Energetic Indie Rock";
} else if (hueDegrees >= 60 && hueDegrees < 160) { // Yellows/Greens
soundtrack = "Peaceful Nature Ambience";
} else if (hueDegrees >= 160 && hueDegrees < 260) { // Blues
soundtrack = "Relaxing Deep House";
} else { // Purples/Pinks
soundtrack = "Dreamy Indie Pop";
}
}
}
drawResult(soundtrack);
return outputCanvas;
}
Apply Changes