npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@compare-ui/core

v0.1.0

Published

Core image utilities for design comparison workflows.

Readme

@compare-ui/core

PNG comparison primitives for design-review workflows.

This package gives you the low-level building blocks to:

  • load reference and actual images
  • crop images by edge offsets
  • compare two images
  • generate review artifacts such as overlays and grid rows
  • encode the generated images back to PNG buffers

It stays low-level and permissive. Environment-specific behavior belongs in higher-level packages such as the runner, Storybook adapter, and Playwright fixture.

Installation

pnpm add @compare-ui/core

This package has no peer dependencies.

What it exports

Main functions:

  • loadImageSource(...)
  • decodePng(...)
  • encodePng(...)
  • prepareImageSource(...)
  • applyCropToSize(...)
  • normalizeImageSize(...)
  • flattenImageBackground(...)
  • createArtifactSet(...)
  • runComparePhase(...)
  • runArtifactInstructions(...)
  • getComparisonMetrics(...)
  • evaluateAcceptance(...)
  • registerImageResourceResolver(...)

Artifact helpers:

  • artifactRefs
  • overlayArtifact(...)
  • gridArtifact(...)
  • gridRowArtifact(...)
  • joinArtifact(...)

Browser-safe config entrypoint:

  • @compare-ui/core/config

Published entrypoints:

  • @compare-ui/core
  • @compare-ui/core/config

Those entrypoints are intended to resolve from the published build output, and the workspace verifies them through a tarball smoke test.

TypeScript resolver compatibility

The package exports are designed for modern resolver modes such as:

  • moduleResolution: "bundler"
  • moduleResolution: "node16"
  • moduleResolution: "nodenext"

Older moduleResolution: "node" projects may still run successfully at runtime but can hit TypeScript friction for package subpaths such as @compare-ui/core/config.

If TypeScript reports import-resolution errors for those subpaths, switch to one of the resolver modes above.

Supported image sources

Built-in source types:

  • fs
  • url
  • buffer

Example:

type ImageResource =
  | {
      type: 'fs';
      path: string;
      crop?: CropOffsets;
    }
  | {
      type: 'url';
      url: string;
      crop?: CropOffsets;
    }
  | {
      type: 'buffer';
      buffer: Buffer;
      crop?: CropOffsets;
    };

Custom source types can be added by augmenting DesignComparisonResourceMap and registering a resolver.

Cropping

Cropping is offset-based and lossless.

Supported offsets:

  • top
  • right
  • bottom
  • left

Example:

const preparedImage = await prepareImageSource({
  type: 'fs',
  path: './figma-screen.png',
  crop: {
    bottom: 20,
  },
});

If the original image is 320x640, the cropped result becomes 320x620.

Size normalization

Size normalization is an explicit preprocessing step.

Use it when:

  • the reference and actual represent the same design at different pixel sizes
  • you want to resize before comparison instead of failing on size mismatch

Example:

const normalizedActual = normalizeImageSize(actual.image, {
  targetSize: reference.finalSize,
  algorithm: 'bilinear',
});

Important notes:

  • size normalization is opt-in
  • size normalization is lossy
  • if you do not normalize first, runComparePhase(...) still expects the images to have identical dimensions

Transparent PNG normalization

Transparent PNG handling is also available as an explicit preprocessing step.

Use flattenImageBackground(...) when:

  • the reference image contains transparency
  • the browser screenshot is visually composited on a known surface
  • you want both images flattened onto the same background before comparison

Example:

const flattenedReference = flattenImageBackground(reference.image, {
  color: [255, 255, 255, 255],
});

const flattenedActual = flattenImageBackground(actual.image, {
  color: [255, 255, 255, 255],
});

Important notes:

  • flattening is opt-in
  • flattening should use the same background color for both images when visual parity is the goal
  • if you do not flatten first, transparent regions are compared as raw RGBA pixels

Comparison

runComparePhase(...) compares reference and actual and returns:

  • comparison status
  • an updated artifact set
  • metrics
  • acceptance result
  • a diff image when compare is enabled
  • accumulated errors

Example compare config:

type AcceptanceThreshold = {
  maxDiffPercent: number;
};

type CompareConfig = {
  threshold?: number;
  includeAntiAliasedPixels?: boolean;
  acceptance?: AcceptanceThreshold;
  writeDiffArtifact?: boolean;
  diffArtifact?: {
    alpha?: number;
    diffMask?: boolean;
    diffColor?: [number, number, number];
    diffColorAlt?: [number, number, number];
    antiAliasedPixelsColor?: [number, number, number];
  };
};

type ComparePhaseStatus = 'passed' | 'failed' | 'error';

type ComparePhaseResult = {
  status: ComparePhaseStatus;
  artifactSet: {
    reference: ImageArtifact;
    actual: ImageArtifact;
    diff?: ImageArtifact;
    overlay?: ImageArtifact;
  };
  metrics?: ComparisonMetrics;
  acceptance?: AcceptanceResult;
  errors: string[];
};

When writeDiffArtifact: true, the returned artifactSet includes diff. That makes the compare-produced diff available both for encoding and for later artifact instructions.

runComparePhase(...) still compares same-size images. If you want to compare different sizes or normalize transparent edges onto a shared background, do that first with the explicit preprocessing helpers.

Artifact generation

Artifacts are configured separately from comparison.

Available helpers:

  • overlayArtifact()
  • gridArtifact(...)
  • gridRowArtifact(...)
  • joinArtifact(...)

Public artifact refs:

  • artifactRefs.reference
  • artifactRefs.actual
  • artifactRefs.diff
  • artifactRefs.overlay

runArtifactInstructions(...) returns only the artifacts created by the provided instructions. It does not duplicate compare-produced artifacts such as diff; those remain available on the compare result's artifactSet.

Grid configuration supports:

  • size
  • offsetX
  • offsetY
  • color
  • alpha
  • lineWidth

Example:

gridRowArtifact({
  items: [artifactRefs.overlay, artifactRefs.actual, artifactRefs.reference],
  size: 16,
  lineWidth: 2,
});

Stable default artifact names:

  • diff
  • overlay
  • grid
  • grid-row
  • join

Those names become file names such as diff.png, overlay.png, and grid-row.png when you encode or persist the images.

grid-row.png is a composite debugging artifact, not source truth. When a seam or corner looks suspicious in the composite, inspect these in order:

  1. actual.png
  2. reference.png
  3. overlay.png or grid-row.png

That avoids mistaking artifact chrome for a real UI defect.

Basic example

import {
  artifactRefs,
  createArtifactSet,
  encodePng,
  flattenImageBackground,
  gridRowArtifact,
  normalizeImageSize,
  overlayArtifact,
  prepareImageSource,
  runArtifactInstructions,
  runComparePhase,
} from '@compare-ui/core';

const reference = await prepareImageSource({
  type: 'fs',
  path: './reference.png',
  crop: {
    bottom: 20,
  },
});

const actual = await prepareImageSource({
  type: 'fs',
  path: './actual.png',
});

const flattenedReference = flattenImageBackground(reference.image, {
  color: [255, 255, 255, 255],
});

const flattenedActual = flattenImageBackground(actual.image, {
  color: [255, 255, 255, 255],
});

const normalizedActual = normalizeImageSize(flattenedActual, {
  targetSize: reference.finalSize,
  algorithm: 'bilinear',
});

const artifactSet = createArtifactSet({
  reference: flattenedReference,
  actual: normalizedActual,
});

const comparePhaseResult = runComparePhase(artifactSet, {
  threshold: 0.1,
  includeAntiAliasedPixels: false,
  writeDiffArtifact: true,
  acceptance: {
    maxDiffPercent: 0.5,
  },
});

const artifactPhaseResult = runArtifactInstructions(comparePhaseResult.artifactSet, [
  overlayArtifact(),
  gridRowArtifact({
    items: [artifactRefs.diff, artifactRefs.overlay, artifactRefs.actual, artifactRefs.reference],
    size: 16,
    lineWidth: 2,
  }),
]);

const diffBytes = comparePhaseResult.artifactSet.diff ? encodePng(comparePhaseResult.artifactSet.diff) : undefined;

const gridRow = artifactPhaseResult.artifacts.find((artifact) => artifact.name === 'grid-row');

const gridRowBytes = gridRow ? encodePng(gridRow.image) : undefined;

Custom resource loaders

If you need another source type, extend the resource map and register a resolver.

There are two parts to this:

  • type registration in TypeScript through module augmentation
  • runtime registration through registerImageResourceResolver(...)

Type registration makes the new type valid in config and gives you autocomplete. Runtime registration tells @compare-ui/core how to load that resource.

Step 1. Define a resource type:

type S3ImageResourceDefinition = {
  bucket: string;
  key: string;
  region?: string;
  crop?: CropOffsets;
};

Step 2. Augment DesignComparisonResourceMap:

declare module '@compare-ui/core' {
  interface DesignComparisonResourceMap {
    s3: S3ImageResourceDefinition;
  }
}

Step 3. Register a resolver for that resource type:

registerImageResourceResolver({
  type: 's3',
  async resolve(resource) {
    return {
      resourceType: 's3',
      bytes: await loadFromS3(resource),
      sourceLabel: `${resource.bucket}/${resource.key}`,
    };
  },
});

After registration, type: 's3' becomes a valid resource in the same places where built-in resources are accepted.

If you are publishing a separate package, that package should:

  • export the resource type
  • include the module augmentation in its type declarations
  • export a resolver or resolver factory for runtime registration

This pattern is intended for:

  • internal asset services
  • cloud storage
  • vendor integrations
  • package-based extensions such as @compare-ui/figma

Notes

  • This package works with PNG images.
  • It returns in-memory results and encoded buffers.
  • Use @compare-ui/core/config in browser-side code such as Storybook stories when you only need config helpers like artifactRefs, overlayArtifact(...), or gridRowArtifact(...).
  • It is intended to be used directly or through higher-level packages such as the runner, Playwright fixture, and Storybook integration.