Newer
Older
vmk-ui-kit / scripts / report-figma-diff.js
const fs = require("fs");
const path = require("path");
const pixelmatch = (require("pixelmatch").default || require("pixelmatch"));
const sharp = require("sharp");

const BASELINES_DIR = path.join(__dirname, "..", "tests", "figma-baselines");
const SCREENSHOTS_DIR = path.join(__dirname, "..", "tests", "figma-screenshots");

const comparisons = [
  { name: "buttons", viewport: { width: 1200, height: 2372 }, background: "#ffffff" },
  { name: "alerts", viewport: { width: 1200, height: 1363 }, background: "#ffffff" },
  { name: "badges", viewport: { width: 1200, height: 743 }, background: "#ffffff" },
  { name: "forms", viewport: { width: 1200, height: 2648 }, background: "#ffffff" },
  { name: "breadcrumbs", viewport: { width: 1200, height: 567 }, background: "#000000" },
  { name: "divider", viewport: { width: 1200, height: 120 }, background: "#ffffff" },
  { name: "attached-file", viewport: { width: 560, height: 2000 }, background: "#000000" },
];

async function normalizeToCanvas(imagePath, targetWidth, targetHeight, padColor) {
  const { data, info } = await sharp(imagePath)
    .ensureAlpha()
    .resize(targetWidth, targetHeight, { fit: "inside" })
    .raw()
    .toBuffer({ resolveWithObject: true });
  const { width, height } = info;
  const canvas = Buffer.alloc(targetWidth * targetHeight * 4);
  const hex = padColor.replace("#", "");
  const pr = parseInt(hex.substring(0, 2), 16);
  const pg = parseInt(hex.substring(2, 4), 16);
  const pb = parseInt(hex.substring(4, 6), 16);
  for (let i = 0; i < targetWidth * targetHeight; i++) {
    canvas[i * 4] = pr;
    canvas[i * 4 + 1] = pg;
    canvas[i * 4 + 2] = pb;
    canvas[i * 4 + 3] = 255;
  }
  const offsetX = Math.round((targetWidth - width) / 2);
  const offsetY = Math.round((targetHeight - height) / 2);
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const srcIdx = (y * width + x) * 4;
      const dstIdx = ((y + offsetY) * targetWidth + (x + offsetX)) * 4;
      const srcA = data[srcIdx + 3] / 255;
      const invA = 1 - srcA;
      canvas[dstIdx] = Math.round(pr * invA + data[srcIdx] * srcA);
      canvas[dstIdx + 1] = Math.round(pg * invA + data[srcIdx + 1] * srcA);
      canvas[dstIdx + 2] = Math.round(pb * invA + data[srcIdx + 2] * srcA);
      canvas[dstIdx + 3] = 255;
    }
  }
  return canvas;
}

async function main() {
  for (const c of comparisons) {
    const screenshotPath = path.join(SCREENSHOTS_DIR, `${c.name}.png`);
    const baselinePath = path.join(BASELINES_DIR, c.baseline || `${c.name}-baseline.png`);
    if (!fs.existsSync(screenshotPath) || !fs.existsSync(baselinePath)) {
      console.log(`${c.name}: missing files`);
      continue;
    }
    const tw = c.viewport.width;
    const th = c.viewport.height;
    const screenshot = await normalizeToCanvas(screenshotPath, tw, th, c.background);
    const baseline = await normalizeToCanvas(baselinePath, tw, th, c.background);
    const diff = Buffer.alloc(tw * th * 4);
    const mismatched = pixelmatch(screenshot, baseline, diff, tw, th, { threshold: 0.1, includeAA: false });
    console.log(`${c.name}: ${(mismatched / (tw * th) * 100).toFixed(2)}%`);
  }
}

main().catch(err => { console.error(err); process.exit(1); });