/*
Estrattore colore dominante veloce e robusto (no librerie esterne).
- Cambia imageContainerId e targetContainerId se necessario.
- Funziona meglio se l'immagine è same-origin o il server fornisce Access-Control-Allow-Origin.
*/
(async function() {
const imageContainerId = 'myImage'; // elemento che contiene
const targetContainerId = 'targetDiv'; // container Elementor da colorare
// Parametri (regolabili)
const SAMPLE_SIZE = 200; // lato massiccio del canvas di sampling (200x200 mantiene qualità/speed)
const QUANTIZE_LEVEL = 24; // quantizzazione colore (più basso = più bin)
const MIN_PIXEL_COUNT = 20; // ignora cluster troppo piccoli
const IGNORE_BRIGHTNESS_ABOVE = 0.94; // ignora near-white
const IGNORE_BRIGHTNESS_BELOW = 0.03; // ignora near-black
function rgbToHex(r,g,b) {
return '#' + [r,g,b].map(v => v.toString(16).padStart(2,'0')).join('');
}
function rgbToHsl(r,g,b) {
r/=255; g/=255; b/=255;
const max = Math.max(r,g,b), 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 rgb (minima) ---
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
} else {
const hue2rgb = (p, q, t) => {
if (t 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l v/255).map(v => v {
// se l'elemento esistente è same-origin e già caricato, lo usiamo
try {
const urlObj = new URL(url, location.href);
if (urlObj.origin === location.origin && img.complete && img.naturalWidth > 0) {
return resolve(img);
}
} catch(e) { /* ignore */ }
const i = new Image();
i.crossOrigin = 'anonymous'; // deve essere impostato PRIMA di src
const t = setTimeout(() => reject(new Error('Timeout image load')), timeout);
i.onload = () => { clearTimeout(t); resolve(i); };
i.onerror = (e) => { clearTimeout(t); reject(new Error('Errore caricamento immagine')); };
i.src = url;
});
}
// quantizzazione semplice per raggruppare colori
function quantizeKey(r,g,b,level) {
// mappa 0..255 su 0..level-1
const q = v => Math.floor(v * (level-1) / 255);
return `${q(r)}_${q(g)}_${q(b)}`;
}
// main sampling + clustering
async function computeDominant(imgEl) {
// canvas dimension keeping aspect ratio
const w = imgEl.naturalWidth || imgEl.width;
const h = imgEl.naturalHeight || imgEl.height;
if (!w || !h) throw new Error('Immagine senza dimensioni');
// dimensione canvas di sampling: manteniamo lato massimo SAMPLE_SIZE
const scale = Math.min(1, SAMPLE_SIZE / Math.max(w,h));
const cw = Math.max(1, Math.round(w * scale));
const ch = Math.max(1, Math.round(h * scale));
const canvas = document.createElement('canvas');
canvas.width = cw;
canvas.height = ch;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgEl, 0, 0, cw, ch);
const data = ctx.getImageData(0,0,cw,ch).data;
const map = new Map();
let totalCount = 0;
// optionally exclude big white margins by focusing sampling area in center
const cx0 = Math.floor(cw * 0.08), cy0 = Math.floor(ch * 0.08);
const cx1 = Math.ceil(cw * 0.92), cy1 = Math.ceil(ch * 0.92);
for (let y = cy0; y < cy1; y++) {
for (let x = cx0; x < cx1; x++) {
const idx = (y * cw + x) * 4;
const r = data[idx], g = data[idx+1], b = data[idx+2], a = data[idx+3];
if (a ignora
// filtro luminosità per scartare sfondi quasi bianchi/neri
const [hue, sat, lum] = rgbToHsl(r,g,b);
if (lum > IGNORE_BRIGHTNESS_ABOVE || lum < IGNORE_BRIGHTNESS_BELOW) continue;
// quantizza
const key = quantizeKey(r,g,b,QUANTIZE_LEVEL);
let entry = map.get(key);
if (!entry) {
entry = {count:0, rSum:0, gSum:0, bSum:0};
map.set(key, entry);
}
entry.count++;
entry.rSum += r;
entry.gSum += g;
entry.bSum += b;
totalCount++;
}
}
if (totalCount === 0) throw new Error('Nessun pixel valido (forse immagine sfondo bianco o CORS)');
// crea array di cluster medi
const clusters = [];
for (const [k,e] of map.entries()) {
if (e.count {
const lumFactor = 1 - Math.min(0.5, Math.abs(c.l - 0.5)); // 0..0.5 -> 0.5..1
c.score = c.count * (0.5 + c.s) * (0.5 + lumFactor);
});
clusters.sort((a,b) => b.score - a.score);
// top cluster
const top = clusters[0];
return {
hex: rgbToHex(top.r, top.g, top.b),
rgb: [top.r, top.g, top.b],
count: top.count,
score: top.score,
clusters // utile per debug / scelta dei top 2
};
}
// esegui flusso
let loaded;
try {
loaded = await loadImage(src);
} catch (e) {
// fallback: se immagine originale è caricata e same-origin, usala
if (img.complete && img.naturalWidth > 0) {
loaded = img;
} else {
console.error('Impossibile caricare immagine per analisi:', e);
return;
}
}
let result;
try {
result = await computeDominant(loaded);
} catch (e) {
console.error('Errore computazione dominante:', e);
return;
}
// --- Modifica: prendiamo due colori e applichiamo gradiente ---
const top1 = result.clusters && result.clusters[0];
const top2 = result.clusters && result.clusters[1];
let hex1 = result.hex;
let rgb1 = result.rgb;
let hex2, rgb2;
if (top2) {
rgb2 = [top2.r, top2.g, top2.b];
hex2 = rgbToHex(...rgb2);
} else {
// se non c'è un secondo cluster, generiamo un colore "complementare leggermente ruotato" del primo
const [h, s, l] = rgbToHsl(...rgb1);
const newH = (h + 0.08) % 1; // ruota la tinta di ~29° (0.08*360)
rgb2 = hslToRgb(newH, s, Math.min(0.9, Math.max(0.1, l))); // mantieni s/l in range
hex2 = rgbToHex(...rgb2);
}
// applica gradiente da alto-sinistra a basso-destra (to bottom right)
target.style.backgroundImage = `linear-gradient(to bottom right, ${hex1}, ${hex2})`;
// rimuoviamo backgroundColor per evitare conflitti
target.style.backgroundColor = 'transparent';
// scegli colore testo in base al contrast ratio approssimativo (lum media)
const lumAvg = (getLuminance(...rgb1) + getLuminance(...rgb2)) / 2;
target.style.color = (lumAvg > 0.55) ? '#000' : '#fff';
// opzionale: se il tuo contenitore ha elementi con colori fissi (pulsanti) potresti voler cambiare solo una overlay
console.log('Dominant colors chosen:', hex1, hex2, 'details:', {result, rgb2});
})();