@nkzw/palette-swap
v4.0.1
Published
Image palette swapping and color replacement tool based on canvas for Node.js and browsers.
Readme
@nkzw/palette-swap
Image palette swapping and color replacement tool based on canvas for Node.js and browsers to create variants from a base image.

Installation
npm install @nkzw/palette-swapThe package selects the browser or Node.js entry point automatically. Browser bundles use native HTMLCanvasElement and ImageData and do not include the Node backend. Node.js uses @napi-rs/canvas, whose platform binaries are installed as optional dependencies; keep optional dependencies enabled.
For Node.js applications that also load images or create canvases, add the backend as a direct dependency:
npm install @napi-rs/canvasUsage
paletteSwap()
Here is an example of how to use paletteSwap() to create two variants from a base image:
import { loadImage } from '@napi-rs/canvas';
import { writeFileSync } from 'node:fs';
import paletteSwap from '@nkzw/palette-swap';
const results = paletteSwap(
await loadImage('./Yoshi.png'),
new Map([
[
2,
new Map([
['#006000', '#570061'],
['#00a800', '#8c00a8'],
['#00f800', '#de00f8'],
]),
],
[3, 220],
]),
new Set(['#000000', '#903020', '#f84020', '#f89000', '#ff0000', '#f8c0a8', '#f8e0d0', '#f8f8f8']),
);
for (const [variant, canvas] of results) {
writeFileSync(`./Yoshi-${variant}.png`, canvas.toBuffer('image/png'));
}Browser
Use the same import and palette definitions with a loaded browser image:
import paletteSwap from '@nkzw/palette-swap';
const image = new Image();
image.src = './Yoshi.png';
await image.decode();
const variants = new Map([[2, new Map([['#006000', '#570061']])]]);
for (const canvas of paletteSwap(image, variants).values()) {
document.body.append(canvas);
}Modern bundlers select the browser export automatically. The browser fallback also supports bundlers using the browser field. Each entry point has matching TypeScript declarations. With TypeScript's bundler module resolution, set customConditions: ["browser"] to select the browser declarations explicitly.
The browser build also accepts ImageData. Read pixels once with getImageData() and reuse them across calls when generating variants in batches.
Migrating from node-canvas
Browser callers keep the same API. Node.js callers should create and load images using @napi-rs/canvas instead of canvas, and use canvas.toBuffer('image/png') to encode results. Returned Node canvases are @napi-rs/canvas objects; native objects from the old canvas package are not interchangeable.
Parameters
image: Image
The base image that is used for generating each variant.
variants: ReadonlyMap<VariantName, ReadonlyMap<HEX, HEX> | Hue>
The color definitions for each variant. You can either provide a list of mappings for each color, a hue for all colors or a hue for individual colors:
const variantMap = new Map([
// Map each color to another hex value.
[
'Variant',
new Map([
['#ff0000', '#00ff00'],
['#ffff00', '#0000ff'],
]),
],
// Change the hue of each color.
['Variant2', 130],
]);staticColors?: Set<HEX>
A Set with a list of HEX values (for example #000000) to lock certain colors to the same values. Colors in this set will not be replaced when generating variants. For example when you are palette swapping an image of a car you might want to change the color of the chassis but not the color of the tires and the outline. If this parameter is omitted it will copy any colors from the base image that are not explicitly listed in the variants definition.
images?: ReadonlyMap<VariantName, Image>
If you want to avoid returning variants for images that have not changed, you can add an optional images parameter with the current image for each variant. If the image hasn't changed, the variant will not be returned in the results.
// Optionally you can provide existing variants
const results = paletteSwap(
await loadImage('MyImage.png'),
new Map([
[2, new Map([])],
[3, 130],
]),
null,
new Map([
[2, await loadImage('MyImage-2.png')],
[3, await loadImage('MyImage-3.png')],
]),
);options?: { imageName?: string; ignoreMissing?: boolean; }
By default paletteSwap will throw an error for any missing color mapping. Options can be specified to ignore missing color mappings or to provide an image name used for the error message when a color mapping is missing.
Development
pnpm install
pnpm exec playwright install chromium
pnpm testTests build the published entry points, verify JavaScript and TypeScript export resolution, reject native dependencies in browser bundles, and compare browser and Node pixels, including transparent edges. The existing example pixel hashes remain regression checks.
