design-token-compiler.mjs 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import sharp from "sharp";
  2. const hex = (rgb) => `#${rgb.map((value) => Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
  3. const mix = (a, b, ratio) => a.map((value, index) => value * (1 - ratio) + b[index] * ratio);
  4. export async function compileDesignTokens(productBuffer) {
  5. const { data, info } = await sharp(productBuffer).resize({ width: 160 }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
  6. const buckets = new Map();
  7. for (let index = 0; index < data.length; index += info.channels) {
  8. if (data[index + 3] < 180) continue;
  9. const rgb = [data[index], data[index + 1], data[index + 2]];
  10. const max = Math.max(...rgb); const min = Math.min(...rgb);
  11. if (max > 242 && max - min < 12) continue;
  12. const key = rgb.map((value) => Math.floor(value / 32) * 32).join(",");
  13. buckets.set(key, (buckets.get(key) || 0) + 1);
  14. }
  15. const ranked = [...buckets.entries()].map(([key, count]) => {
  16. const rgb = key.split(",").map(Number).map((value) => value + 16);
  17. const saturation = Math.max(...rgb) - Math.min(...rgb);
  18. const luminance = rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722;
  19. return { rgb, count, saturation, luminance, score: count * (1 + saturation / 55) * (luminance < 210 ? 1.25 : 0.5) };
  20. }).sort((a, b) => b.score - a.score);
  21. const primary = ranked.find((item) => item.saturation >= 18 && item.luminance < 190)?.rgb
  22. || ranked[0]?.rgb
  23. || [38, 49, 38];
  24. const accentCandidate = ranked.find((item) => (
  25. item.saturation > 24
  26. && item.rgb[0] > 120
  27. && item.rgb[0] >= item.rgb[1] * 0.95
  28. && item.rgb[1] > item.rgb[2] * 1.12
  29. ));
  30. const accent = accentCandidate?.rgb || mix(primary, [218, 160, 72], 0.58);
  31. return {
  32. primary: hex(primary),
  33. primarySoft: hex(mix(primary, [255, 255, 255], 0.72)),
  34. primaryDark: hex(mix(primary, [15, 20, 16], 0.42)),
  35. surface: hex(mix(primary, [250, 247, 239], 0.88)),
  36. accent: hex(accent),
  37. ink: "#1D2922",
  38. lightText: "#FAF7EF",
  39. };
  40. }