VecAI_SVG2PSD / src /colorUtils.js
yeq6x's picture
Rewrite as Vite + React app with structural SVG color grouping
d7da259
// sRGB → Linear
function srgbToLinear(c) {
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}
// RGB [0-255] → XYZ
function rgbToXyz(r, g, b) {
const rl = srgbToLinear(r / 255);
const gl = srgbToLinear(g / 255);
const bl = srgbToLinear(b / 255);
return [
0.4124564 * rl + 0.3575761 * gl + 0.1804375 * bl,
0.2126729 * rl + 0.7151522 * gl + 0.0721750 * bl,
0.0193339 * rl + 0.1191920 * gl + 0.9503041 * bl,
];
}
// XYZ → CIELAB
function xyzToLab(x, y, z) {
const Xn = 0.95047, Yn = 1.0, Zn = 1.08883;
const f = (v) => (v > 0.008856 ? Math.cbrt(v) : 7.787 * v + 16 / 116);
const fx = f(x / Xn), fy = f(y / Yn), fz = f(z / Zn);
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
}
export function rgbToLab(r, g, b) {
const [x, y, z] = rgbToXyz(r, g, b);
return xyzToLab(x, y, z);
}
export function labDist(a, b) {
const dL = a[0] - b[0], da = a[1] - b[1], db = a[2] - b[2];
return Math.sqrt(dL * dL + da * da + db * db);
}
// Parse any CSS color string → { r, g, b, hex }
const parseCtx = typeof document !== 'undefined'
? document.createElement('canvas').getContext('2d')
: null;
export function parseColor(str) {
if (!str || str === 'none' || str === 'transparent' || str === 'inherit' || str === 'currentColor') return null;
if (str.startsWith('url(')) return null;
if (!parseCtx) return null;
parseCtx.fillStyle = '#000000';
parseCtx.fillStyle = str;
const hex = parseCtx.fillStyle;
if (hex.startsWith('#')) {
const n = parseInt(hex.slice(1), 16);
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255, hex };
}
const m = hex.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (m) {
const r = +m[1], g = +m[2], b = +m[3];
const h = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
return { r, g, b, hex: h };
}
return null;
}
// Extract unique colors from SVG element (all elements including <g>)
export function extractColorsFromSVG(svgEl) {
const colorMap = new Map();
const elements = svgEl.querySelectorAll('*');
for (const el of elements) {
const fill = el.getAttribute('fill');
const stroke = el.getAttribute('stroke');
const style = el.getAttribute('style') || '';
const styleFill = style.match(/fill\s*:\s*([^;]+)/);
const styleStroke = style.match(/stroke\s*:\s*([^;]+)/);
for (const raw of [fill, stroke, styleFill?.[1], styleStroke?.[1]]) {
if (!raw) continue;
const c = parseColor(raw.trim());
if (c) colorMap.set(c.hex, c);
}
}
return Array.from(colorMap.values());
}
// Extract structural color groups from SVG:
// - Fill anchor colors from <g fill="..."> elements
// - Stroke colors from individual paths
// Returns { anchors: [{hex, r, g, b}], strokes: [{hex, r, g, b}] } or null if no structure
export function extractStructuralGroups(svgEl) {
const anchors = new Map(); // fill colors on <g> elements
const strokes = new Map(); // stroke colors on leaf elements
const gElements = svgEl.querySelectorAll('g');
for (const g of gElements) {
const fill = g.getAttribute('fill');
if (fill) {
const c = parseColor(fill.trim());
if (c) anchors.set(c.hex, c);
}
}
// If no <g fill> structure found, return null
if (anchors.size < 2) return null;
const allElements = svgEl.querySelectorAll('*');
for (const el of allElements) {
if (el.tagName === 'g' || el.tagName === 'svg' || el.tagName === 'defs') continue;
const stroke = el.getAttribute('stroke');
if (stroke) {
const c = parseColor(stroke.trim());
if (c && !anchors.has(c.hex)) strokes.set(c.hex, c);
}
const fill = el.getAttribute('fill');
if (fill) {
const c = parseColor(fill.trim());
if (c && !anchors.has(c.hex)) strokes.set(c.hex, c);
}
}
return {
anchors: Array.from(anchors.values()),
strokes: Array.from(strokes.values()),
};
}
// Get the color directly set on this element (no inheritance)
export function getOwnColor(el, attr) {
const style = el.getAttribute('style') || '';
const styleMatch = style.match(new RegExp(attr + '\\s*:\\s*([^;]+)'));
if (styleMatch) {
const c = parseColor(styleMatch[1].trim());
return c ? c.hex : null;
}
const val = el.getAttribute(attr);
if (val) {
const c = parseColor(val.trim());
return c ? c.hex : null;
}
return null;
}
// Get effective color including inheritance from parent <g> elements
export function getEffectiveColor(el, attr) {
let node = el;
while (node && node.nodeType === 1) {
const color = getOwnColor(node, attr);
if (color) return color;
const val = node.getAttribute(attr);
if (val === 'none') return null;
node = node.parentNode;
}
return null;
}