/** * AKSRT Color Extract * * Extracts dominant colors from images and applies them as dynamic accents. * Uses canvas-based color sampling — no external dependencies. * * Usage: * * * * @package AKSRT */ (function () { 'use strict'; var canvas = null; var ctx = null; function getCanvas() { if (!canvas) { canvas = document.createElement('canvas'); canvas.width = 40; canvas.height = 40; ctx = canvas.getContext('2d', { willReadFrequently: true }); } return { canvas: canvas, ctx: ctx }; } function extractColor(img) { try { var c = getCanvas(); c.ctx.drawImage(img, 0, 0, 40, 40); var data = c.ctx.getImageData(0, 0, 40, 40).data; var r = 0, g = 0, b = 0, count = 0; var step = 4; // sample every 4th pixel for speed for (var i = 0; i < data.length; i += 4 * step) { var pr = data[i], pg = data[i + 1], pb = data[i + 2]; // Skip very dark or very light pixels var brightness = (pr + pg + pb) / 3; if (brightness < 20 || brightness > 240) continue; // Skip near-transparent if (data[i + 3] < 128) continue; r += pr; g += pg; b += pb; count++; } if (count === 0) return null; r = Math.round(r / count); g = Math.round(g / count); b = Math.round(b / count); return { r: r, g: g, b: b, hex: rgbToHex(r, g, b) }; } catch (e) { // CORS or tainted canvas — silently fail return null; } } function rgbToHex(r, g, b) { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } function lighten(r, g, b, amount) { return rgbToHex( Math.min(255, r + amount), Math.min(255, g + amount), Math.min(255, b + amount) ); } function darken(r, g, b, amount) { return rgbToHex( Math.max(0, r - amount), Math.max(0, g - amount), Math.max(0, b - amount) ); } function applyColor(target, color) { if (!target || !color) return; var r = color.r, g = color.g, b = color.b; target.style.setProperty('--aksrt-dynamic-color', color.hex); target.style.setProperty('--aksrt-dynamic-color-rgb', r + ',' + g + ',' + b); target.style.setProperty('--aksrt-dynamic-color-light', lighten(r, g, b, 60)); target.style.setProperty('--aksrt-dynamic-color-dark', darken(r, g, b, 40)); // Add a subtle tinted background if target doesn't already have one if (!target.style.background && !target.style.backgroundColor) { target.style.setProperty('--aksrt-dynamic-bg', 'rgba(' + r + ',' + g + ',' + b + ',0.05)'); } } function processImage(img) { if (img.dataset.aksrtColorDone || img.dataset.aksrtColorBound) return; img.dataset.aksrtColorBound = '1'; // Need crossOrigin for canvas access if (!img.crossOrigin && img.src.indexOf(window.location.origin) !== 0) { img.crossOrigin = 'anonymous'; } function extract() { var color = extractColor(img); if (!color) return; img.dataset.aksrtColorDone = '1'; var targetSelector = img.dataset.aksrtColorTarget; var target = targetSelector ? img.closest(targetSelector) || document.querySelector(targetSelector) : img.parentElement; applyColor(target, color); } if (img.complete && img.naturalWidth > 0) { extract(); } else { img.addEventListener('load', extract, { once: true }); } } function init(root, reason) { var scope = root && root.querySelectorAll ? root : document; if (scope.nodeType === 1 && scope.matches('[data-aksrt-color-extract]')) processImage(scope); scope.querySelectorAll('[data-aksrt-color-extract]').forEach(processImage); } // Public API window.AksrtColorExtract = { extract: extractColor, apply: applyColor, process: processImage, init: init }; window.AksrtFrontend.register('aksrt-color-extract', init); })();