@nkzw/palette-swap 3.2.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,15 @@ Image palette swapping and color replacement tool based on canvas for Node.js an
9
9
  ## Installation
10
10
 
11
11
  ```
12
- npm install @nkzw/palette-swap canvas
12
+ npm install @nkzw/palette-swap
13
+ ```
14
+
15
+ The 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.
16
+
17
+ For Node.js applications that also load images or create canvases, add the backend as a direct dependency:
18
+
19
+ ```sh
20
+ npm install @napi-rs/canvas
13
21
  ```
14
22
 
15
23
  ## Usage
@@ -19,9 +27,9 @@ npm install @nkzw/palette-swap canvas
19
27
  Here is an example of how to use `paletteSwap()` to create two variants from a base image:
20
28
 
21
29
  ```js
22
- import { loadImage } from 'canvas';
30
+ import { loadImage } from '@napi-rs/canvas';
23
31
  import { writeFileSync } from 'node:fs';
24
- import paletteSwap from 'paletteSwap';
32
+ import paletteSwap from '@nkzw/palette-swap';
25
33
 
26
34
  const results = paletteSwap(
27
35
  await loadImage('./Yoshi.png'),
@@ -36,23 +44,39 @@ const results = paletteSwap(
36
44
  ],
37
45
  [3, 220],
38
46
  ]),
39
- new Set([
40
- '#000000',
41
- '#903020',
42
- '#f84020',
43
- '#f89000',
44
- '#ff0000',
45
- '#f8c0a8',
46
- '#f8e0d0',
47
- '#f8f8f8',
48
- ]),
47
+ new Set(['#000000', '#903020', '#f84020', '#f89000', '#ff0000', '#f8c0a8', '#f8e0d0', '#f8f8f8']),
49
48
  );
50
49
 
51
50
  for (const [variant, canvas] of results) {
52
- writeFileSync(`./Yoshi-${variant}.png`, canvas.toBuffer());
51
+ writeFileSync(`./Yoshi-${variant}.png`, canvas.toBuffer('image/png'));
53
52
  }
54
53
  ```
55
54
 
55
+ ### Browser
56
+
57
+ Use the same import and palette definitions with a loaded browser image:
58
+
59
+ ```js
60
+ import paletteSwap from '@nkzw/palette-swap';
61
+
62
+ const image = new Image();
63
+ image.src = './Yoshi.png';
64
+ await image.decode();
65
+
66
+ const variants = new Map([[2, new Map([['#006000', '#570061']])]]);
67
+ for (const canvas of paletteSwap(image, variants).values()) {
68
+ document.body.append(canvas);
69
+ }
70
+ ```
71
+
72
+ 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.
73
+
74
+ The browser build also accepts `ImageData`. Read pixels once with `getImageData()` and reuse them across calls when generating variants in batches.
75
+
76
+ ### Migrating from node-canvas
77
+
78
+ 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.
79
+
56
80
  ## Parameters
57
81
 
58
82
  ### `image: Image`
@@ -105,3 +129,13 @@ const results = paletteSwap(
105
129
  ### `options?: { imageName?: string; ignoreMissing?: boolean; }`
106
130
 
107
131
  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.
132
+
133
+ ## Development
134
+
135
+ ```sh
136
+ pnpm install
137
+ pnpm exec playwright install chromium
138
+ pnpm test
139
+ ```
140
+
141
+ Tests 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.
@@ -0,0 +1,7 @@
1
+ import { n as HSL, r as VariantName, t as HEX } from "./palette-swap-CpUFcX2w.js";
2
+ export type Image = HTMLImageElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas;
3
+ declare const _default: (image: ImageData | Image, inputVariants: ReadonlyMap<VariantName, ReadonlyMap<HEX, HEX> | number>, staticColors?: Set<HEX> | null, images?: ReadonlyMap<VariantName, ImageData | Image> | null | undefined, options?: {
4
+ imageName?: string;
5
+ ignoreMissing?: boolean;
6
+ } | undefined) => ReadonlyMap<VariantName, HTMLCanvasElement>;
7
+ export { type HEX, type HSL, type VariantName, _default as default };
package/lib/browser.js ADDED
@@ -0,0 +1,31 @@
1
+ import { t as createPaletteSwap } from "./palette-swap-DnkKCKpC.js";
2
+ // src/browser.ts
3
+ var browser_default = createPaletteSwap({
4
+ readImage(image, width = image.width, height = image.height) {
5
+ const isImageData = image instanceof ImageData;
6
+ if (isImageData && image.width === width && image.height === height && image.colorSpace === "srgb" && image.data.BYTES_PER_ELEMENT === 1) return image;
7
+ const canvas = document.createElement("canvas");
8
+ canvas.width = image.width;
9
+ canvas.height = image.height;
10
+ try {
11
+ const context = canvas.getContext("2d");
12
+ if (!context) throw new Error("paletteSwap: Could not create a canvas context.");
13
+ if (isImageData) context.putImageData(image, 0, 0);
14
+ else context.drawImage(image, 0, 0);
15
+ return context.getImageData(0, 0, width, height);
16
+ } finally {
17
+ canvas.width = 0;
18
+ canvas.height = 0;
19
+ }
20
+ },
21
+ writeImage({ data, height, width }) {
22
+ const canvas = document.createElement("canvas");
23
+ canvas.width = width;
24
+ canvas.height = height;
25
+ const context = canvas.getContext("2d");
26
+ if (!context) throw new Error("paletteSwap: Could not create a canvas context.");
27
+ context.putImageData(new ImageData(data, width, height), 0, 0);
28
+ return canvas;
29
+ }
30
+ });
31
+ export { browser_default as default };
package/lib/node.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { n as HSL, r as VariantName, t as HEX } from "./palette-swap-CpUFcX2w.js";
2
+ import { Canvas, Image as Image$1 } from "@napi-rs/canvas";
3
+ export type Image = Image$1 | Canvas;
4
+ declare const _default: (image: Image, inputVariants: ReadonlyMap<VariantName, ReadonlyMap<HEX, HEX> | number>, staticColors?: Set<HEX> | null, images?: ReadonlyMap<VariantName, Image> | null | undefined, options?: {
5
+ imageName?: string;
6
+ ignoreMissing?: boolean;
7
+ } | undefined) => ReadonlyMap<VariantName, Canvas>;
8
+ export { type HEX, type HSL, type VariantName, _default as default };
package/lib/node.js ADDED
@@ -0,0 +1,16 @@
1
+ import { t as createPaletteSwap } from "./palette-swap-DnkKCKpC.js";
2
+ import { ImageData, createCanvas } from "@napi-rs/canvas";
3
+ // src/node.ts
4
+ var node_default = createPaletteSwap({
5
+ readImage(image, width = image.width, height = image.height) {
6
+ const context = createCanvas(image.width, image.height).getContext("2d");
7
+ context.drawImage(image, 0, 0);
8
+ return context.getImageData(0, 0, width, height);
9
+ },
10
+ writeImage({ data, height, width }) {
11
+ const canvas = createCanvas(width, height);
12
+ canvas.getContext("2d").putImageData(new ImageData(data, width, height), 0, 0);
13
+ return canvas;
14
+ }
15
+ });
16
+ export { node_default as default };
@@ -0,0 +1,4 @@
1
+ type HEX = `#${string}`;
2
+ type HSL = [hue: number, saturation: number, lightness: number];
3
+ type VariantName = number | string;
4
+ export { HSL as n, VariantName as r, HEX as t };
@@ -0,0 +1,198 @@
1
+ // src/palette-swap.ts
2
+ const hexToUint32LE = (hex) => {
3
+ const n = Number.parseInt(hex.slice(1), 16) & 16777215;
4
+ const [r, g, b] = int32ToRgb(n);
5
+ return b << 16 | g << 8 | r;
6
+ };
7
+ const int32ToRgb = (color) => {
8
+ return [
9
+ color >> 16 & 255,
10
+ color >> 8 & 255,
11
+ color & 255
12
+ ];
13
+ };
14
+ const hex = (value) => Math.round(value).toString(16).padStart(2, "0");
15
+ const rgbToHex = ([r, g, b]) => `#${hex(r)}${hex(g)}${hex(b)}`;
16
+ const int32ToHex = (color) => {
17
+ const [r, g, b] = int32ToRgb(color);
18
+ return rgbToHex([
19
+ b,
20
+ g,
21
+ r
22
+ ]);
23
+ };
24
+ const applyHue = (color, hue) => {
25
+ const h = hue / 360;
26
+ const r = (color & 255) / 255;
27
+ const g = (color >> 8 & 255) / 255;
28
+ const b = (color >> 16 & 255) / 255;
29
+ const min = Math.min(r, g, b);
30
+ const max = Math.max(r, g, b);
31
+ const delta = max - min;
32
+ let s;
33
+ const l = (min + max) / 2;
34
+ if (max === min) s = 0;
35
+ else if (l <= .5) s = delta / (max + min);
36
+ else s = delta / (2 - max - min);
37
+ let t2;
38
+ let t3;
39
+ let val;
40
+ if (s === 0) {
41
+ val = l * 255;
42
+ return val << 16 | val << 8 | val;
43
+ }
44
+ if (l < .5) t2 = l * (1 + s);
45
+ else t2 = l + s - l * s;
46
+ const t1 = 2 * l - t2;
47
+ let result0 = 0;
48
+ let result1 = 0;
49
+ let result2 = 0;
50
+ for (let i = 0; i < 3; i++) {
51
+ t3 = h + 1 / 3 * -(i - 1);
52
+ if (t3 < 0) t3++;
53
+ if (t3 > 1) t3--;
54
+ if (6 * t3 < 1) val = t1 + (t2 - t1) * 6 * t3;
55
+ else if (2 * t3 < 1) val = t2;
56
+ else if (3 * t3 < 2) val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
57
+ else val = t1;
58
+ if (i === 0) result0 = val * 255;
59
+ else if (i === 1) result1 = val * 255;
60
+ else result2 = val * 255;
61
+ }
62
+ return result2 << 16 | result1 << 8 | result0;
63
+ };
64
+ const equals = (a, b) => {
65
+ if (a.width !== b.width || a.height !== b.height) return false;
66
+ for (let i = 0; i < a.data.length; i++) if (a.data[i] !== b.data[i]) return false;
67
+ return true;
68
+ };
69
+ const colorCacheSlot = (keys, key, mask) => {
70
+ let slot = Math.imul(key, 2654435761) & mask;
71
+ let cachedKey = keys[slot];
72
+ while (cachedKey !== 0 && cachedKey !== key) {
73
+ slot = slot + 1 & mask;
74
+ cachedKey = keys[slot];
75
+ }
76
+ return slot;
77
+ };
78
+ const maxCachedColors = 4096;
79
+ function createPaletteSwap({ readImage, writeImage }) {
80
+ return function paletteSwap(image, inputVariants, staticColors, images, options) {
81
+ const pixels = readImage(image);
82
+ const { height, width } = pixels;
83
+ const size = width * height;
84
+ const imageData = new Uint32Array(pixels.data.buffer, pixels.data.byteOffset, size);
85
+ const results = /* @__PURE__ */ new Map();
86
+ const variantNames = [];
87
+ const variantImageData = [];
88
+ const variantBuffers = [];
89
+ const variantHues = [];
90
+ let i = 0;
91
+ const palettes = /* @__PURE__ */ new Map();
92
+ for (const [variant, palette] of inputVariants) {
93
+ const newImageData = {
94
+ data: new Uint8ClampedArray(size * 4),
95
+ height,
96
+ width
97
+ };
98
+ if (typeof palette !== "number") for (const [key, value] of palette) {
99
+ const k = hexToUint32LE(key);
100
+ const v = palettes.get(k) || [];
101
+ v[i] = hexToUint32LE(value);
102
+ palettes.set(k, v);
103
+ }
104
+ variantNames.push(variant);
105
+ variantImageData.push(newImageData);
106
+ variantBuffers.push(new Uint32Array(newImageData.data.buffer));
107
+ variantHues.push(typeof palette === "number" ? palette : null);
108
+ i++;
109
+ }
110
+ const variantCount = variantNames.length;
111
+ const staticColorNumbers = staticColors ? new Set([...staticColors].map(hexToUint32LE)) : null;
112
+ const trackMissing = !options?.ignoreMissing;
113
+ const missing = /* @__PURE__ */ new Set();
114
+ const colorCacheKeys = new Uint32Array(maxCachedColors * 2);
115
+ const colorCacheValues = new Uint32Array(colorCacheKeys.length * variantCount);
116
+ const colorCacheMask = colorCacheKeys.length - 1;
117
+ let colorCacheSize = 0;
118
+ const uncachedColors = new Uint32Array(variantCount);
119
+ const buffer0 = variantBuffers[0];
120
+ const buffer1 = variantBuffers[1];
121
+ const buffer2 = variantBuffers[2];
122
+ const buffer3 = variantBuffers[3];
123
+ const buffer4 = variantBuffers[4];
124
+ const buffer5 = variantBuffers[5];
125
+ const buffer6 = variantBuffers[6];
126
+ const buffer7 = variantBuffers[7];
127
+ for (let index = 0; index < size; index++) {
128
+ const originalColor = imageData[index];
129
+ const a = originalColor >> 24 & 255;
130
+ if (a === 0) continue;
131
+ const key = originalColor >>> 0;
132
+ const slot = colorCacheSlot(colorCacheKeys, key, colorCacheMask);
133
+ const cachedKey = colorCacheKeys[slot];
134
+ const canCache = cachedKey === key || colorCacheSize < maxCachedColors;
135
+ const colors = canCache ? colorCacheValues : uncachedColors;
136
+ const colorOffset = canCache ? slot * variantCount : 0;
137
+ if (cachedKey !== key) {
138
+ if (canCache) {
139
+ colorCacheKeys[slot] = key;
140
+ colorCacheSize++;
141
+ }
142
+ const color = originalColor & 16777215;
143
+ const isStatic = staticColorNumbers?.has(color);
144
+ const paletteColors = !isStatic ? palettes.get(color) : null;
145
+ for (let i = 0; i < variantCount; i++) {
146
+ if (isStatic) {
147
+ colors[colorOffset + i] = originalColor;
148
+ continue;
149
+ }
150
+ const hue = variantHues[i];
151
+ if (hue) colors[colorOffset + i] = a << 24 | applyHue(color, hue);
152
+ else if (paletteColors?.[i]) colors[colorOffset + i] = a << 24 | paletteColors[i];
153
+ else if (!staticColorNumbers) colors[colorOffset + i] = originalColor;
154
+ else {
155
+ colors[colorOffset + i] = 0;
156
+ if (trackMissing) missing.add([variantNames[i], color]);
157
+ }
158
+ }
159
+ }
160
+ if (variantCount === 8) {
161
+ const color0 = colors[colorOffset];
162
+ const color1 = colors[colorOffset + 1];
163
+ const color2 = colors[colorOffset + 2];
164
+ const color3 = colors[colorOffset + 3];
165
+ const color4 = colors[colorOffset + 4];
166
+ const color5 = colors[colorOffset + 5];
167
+ const color6 = colors[colorOffset + 6];
168
+ const color7 = colors[colorOffset + 7];
169
+ if (color0) buffer0[index] = color0;
170
+ if (color1) buffer1[index] = color1;
171
+ if (color2) buffer2[index] = color2;
172
+ if (color3) buffer3[index] = color3;
173
+ if (color4) buffer4[index] = color4;
174
+ if (color5) buffer5[index] = color5;
175
+ if (color6) buffer6[index] = color6;
176
+ if (color7) buffer7[index] = color7;
177
+ } else if (variantCount === 2) {
178
+ const color0 = colors[colorOffset];
179
+ const color1 = colors[colorOffset + 1];
180
+ if (color0) buffer0[index] = color0;
181
+ if (color1) buffer1[index] = color1;
182
+ } else for (let i = 0; i < variantCount; i++) {
183
+ const color = colors[colorOffset + i];
184
+ if (color) variantBuffers[i][index] = color;
185
+ }
186
+ }
187
+ if (trackMissing && missing.size) throw new Error(`${options?.imageName ? `${options.imageName}` : "Palette Swap"}: Missing ${Array.from(new Set([...missing].map(([variant, color]) => `'${int32ToHex(color)}' in '${variant}'`))).join(", ")}.`);
188
+ for (let i = 0; i < variantCount; i++) {
189
+ const name = variantNames[i];
190
+ const existingImage = images?.get(name);
191
+ const existingImageData = existingImage ? readImage(existingImage, width, height) : null;
192
+ const newImageData = variantImageData[i];
193
+ if (!existingImageData || !equals(newImageData, existingImageData)) results.set(name, writeImage(newImageData));
194
+ }
195
+ return results;
196
+ };
197
+ }
198
+ export { createPaletteSwap as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/palette-swap",
3
- "version": "3.2.0",
3
+ "version": "4.0.1",
4
4
  "description": "Image palette swapping and color replacement tool based on canvas for Node.js and browsers.",
5
5
  "keywords": [
6
6
  "canvas",
@@ -14,16 +14,38 @@
14
14
  "url": "https://github.com/cpojer/palette-swap.git"
15
15
  },
16
16
  "type": "module",
17
- "main": "lib/palette-swap.js",
18
- "types": "lib/palette-swap.d.ts",
17
+ "main": "lib/node.js",
18
+ "browser": "./lib/browser.js",
19
+ "types": "lib/node.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "browser": {
23
+ "types": "./lib/browser.d.ts",
24
+ "default": "./lib/browser.js"
25
+ },
26
+ "node": {
27
+ "types": "./lib/node.d.ts",
28
+ "default": "./lib/node.js"
29
+ },
30
+ "default": "./lib/browser.js"
31
+ }
32
+ },
19
33
  "dependencies": {
20
- "canvas": "^3.2.3"
34
+ "@napi-rs/canvas": "^1.1.0"
21
35
  },
22
36
  "devDependencies": {
23
- "@types/node": "^25.9.1",
24
- "tsdown": "^0.22.0",
25
- "typescript": "^6.0.3",
26
- "vitest": "^4.1.7"
37
+ "@types/node": "^26.6.6",
38
+ "playwright": "^1.65.0",
39
+ "tsdown": "^0.23.7",
40
+ "typescript": "^7.0.2",
41
+ "vitest": "^5.1.0"
42
+ },
43
+ "devEngines": {
44
+ "packageManager": {
45
+ "name": "pnpm",
46
+ "version": "12.18.0",
47
+ "onFail": "download"
48
+ }
27
49
  },
28
50
  "engines": {
29
51
  "node": ">=22.0.0"
@@ -31,6 +53,6 @@
31
53
  "scripts": {
32
54
  "benchmark": "vitest bench --run",
33
55
  "build": "tsdown",
34
- "test": "tsc && vitest run"
56
+ "test": "pnpm build && tsc && vitest run"
35
57
  }
36
58
  }
@@ -1,14 +0,0 @@
1
- import { Canvas, Image as Image$1 } from "canvas";
2
-
3
- //#region src/palette-swap.d.ts
4
- type Hue = number;
5
- type HEX = `#${string}`;
6
- type HSL = [hue: number, saturation: number, lightness: number];
7
- type VariantName = number | string;
8
- type Image = Image$1 | HTMLImageElement;
9
- declare function paletteSwap(image: Image, inputVariants: ReadonlyMap<VariantName, ReadonlyMap<HEX, HEX> | Hue>, staticColors?: Set<HEX> | null, images?: ReadonlyMap<VariantName, Image> | null, options?: {
10
- imageName?: string;
11
- ignoreMissing?: boolean;
12
- }): ReadonlyMap<VariantName, Canvas>;
13
- //#endregion
14
- export { HEX, HSL, Image, VariantName, paletteSwap as default };
@@ -1,203 +0,0 @@
1
- import { createCanvas, createImageData } from "canvas";
2
- //#region src/palette-swap.ts
3
- const hexToUint32LE = (hex) => {
4
- const [r, g, b] = int32ToRgb(Number.parseInt(hex.slice(1), 16) & 16777215);
5
- return b << 16 | g << 8 | r;
6
- };
7
- const int32ToRgb = (color) => {
8
- return [
9
- color >> 16 & 255,
10
- color >> 8 & 255,
11
- color & 255
12
- ];
13
- };
14
- const hex = (value) => Math.round(value).toString(16).padStart(2, "0");
15
- const rgbToHex = ([r, g, b]) => `#${hex(r)}${hex(g)}${hex(b)}`;
16
- const int32ToHex = (color) => {
17
- const [r, g, b] = int32ToRgb(color);
18
- return rgbToHex([
19
- b,
20
- g,
21
- r
22
- ]);
23
- };
24
- const applyHue = (color, hue) => {
25
- const h = hue / 360;
26
- const r = (color & 255) / 255;
27
- const g = (color >> 8 & 255) / 255;
28
- const b = (color >> 16 & 255) / 255;
29
- const min = Math.min(r, g, b);
30
- const max = Math.max(r, g, b);
31
- const delta = max - min;
32
- let s;
33
- const l = (min + max) / 2;
34
- if (max === min) s = 0;
35
- else if (l <= .5) s = delta / (max + min);
36
- else s = delta / (2 - max - min);
37
- let t2;
38
- let t3;
39
- let val;
40
- if (s === 0) {
41
- val = l * 255;
42
- return val << 16 | val << 8 | val;
43
- }
44
- if (l < .5) t2 = l * (1 + s);
45
- else t2 = l + s - l * s;
46
- const t1 = 2 * l - t2;
47
- let result0 = 0;
48
- let result1 = 0;
49
- let result2 = 0;
50
- for (let i = 0; i < 3; i++) {
51
- t3 = h + 1 / 3 * -(i - 1);
52
- if (t3 < 0) t3++;
53
- if (t3 > 1) t3--;
54
- if (6 * t3 < 1) val = t1 + (t2 - t1) * 6 * t3;
55
- else if (2 * t3 < 1) val = t2;
56
- else if (3 * t3 < 2) val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
57
- else val = t1;
58
- if (i === 0) result0 = val * 255;
59
- else if (i === 1) result1 = val * 255;
60
- else result2 = val * 255;
61
- }
62
- return result2 << 16 | result1 << 8 | result0;
63
- };
64
- const createCanvasFromImage = (image) => {
65
- const canvas = createCanvas(image.width, image.height);
66
- canvas.getContext("2d").drawImage(image, 0, 0);
67
- return canvas;
68
- };
69
- const equals = (a, b) => {
70
- if (a.width !== b.width || a.height !== b.height) return false;
71
- for (let i = 0; i < a.data.length; i++) if (a.data[i] !== b.data[i]) return false;
72
- return true;
73
- };
74
- const colorCacheSlot = (keys, key, mask) => {
75
- let slot = Math.imul(key, 2654435761) & mask;
76
- let cachedKey = keys[slot];
77
- while (cachedKey !== 0 && cachedKey !== key) {
78
- slot = slot + 1 & mask;
79
- cachedKey = keys[slot];
80
- }
81
- return slot;
82
- };
83
- const maxCachedColors = 4096;
84
- function paletteSwap(image, inputVariants, staticColors, images, options) {
85
- const canvas = createCanvasFromImage(image);
86
- const context = canvas.getContext("2d");
87
- const { height, width } = canvas;
88
- const size = width * height;
89
- const imageData = new Uint32Array(context.getImageData(0, 0, width, height).data.buffer);
90
- const results = /* @__PURE__ */ new Map();
91
- const variantNames = [];
92
- const variantImageData = [];
93
- const variantBuffers = [];
94
- const variantHues = [];
95
- let i = 0;
96
- const palettes = /* @__PURE__ */ new Map();
97
- for (const [variant, palette] of inputVariants) {
98
- const newImageData = createImageData(new Uint8ClampedArray(size * 4), width);
99
- if (typeof palette !== "number") for (const [key, value] of palette) {
100
- const k = hexToUint32LE(key);
101
- const v = palettes.get(k) || [];
102
- v[i] = hexToUint32LE(value);
103
- palettes.set(k, v);
104
- }
105
- variantNames.push(variant);
106
- variantImageData.push(newImageData);
107
- variantBuffers.push(new Uint32Array(newImageData.data.buffer));
108
- variantHues.push(typeof palette === "number" ? palette : null);
109
- i++;
110
- }
111
- const variantCount = variantNames.length;
112
- const staticColorNumbers = staticColors ? new Set([...staticColors].map(hexToUint32LE)) : null;
113
- const trackMissing = !options?.ignoreMissing;
114
- const missing = /* @__PURE__ */ new Set();
115
- const colorCacheKeys = new Uint32Array(maxCachedColors * 2);
116
- const colorCacheValues = new Uint32Array(colorCacheKeys.length * variantCount);
117
- const colorCacheMask = colorCacheKeys.length - 1;
118
- let colorCacheSize = 0;
119
- const uncachedColors = new Uint32Array(variantCount);
120
- const buffer0 = variantBuffers[0];
121
- const buffer1 = variantBuffers[1];
122
- const buffer2 = variantBuffers[2];
123
- const buffer3 = variantBuffers[3];
124
- const buffer4 = variantBuffers[4];
125
- const buffer5 = variantBuffers[5];
126
- const buffer6 = variantBuffers[6];
127
- const buffer7 = variantBuffers[7];
128
- for (let index = 0; index < size; index++) {
129
- const originalColor = imageData[index];
130
- const a = originalColor >> 24 & 255;
131
- if (a === 0) continue;
132
- const key = originalColor >>> 0;
133
- const slot = colorCacheSlot(colorCacheKeys, key, colorCacheMask);
134
- const cachedKey = colorCacheKeys[slot];
135
- const canCache = cachedKey === key || colorCacheSize < maxCachedColors;
136
- const colors = canCache ? colorCacheValues : uncachedColors;
137
- const colorOffset = canCache ? slot * variantCount : 0;
138
- if (cachedKey !== key) {
139
- if (canCache) {
140
- colorCacheKeys[slot] = key;
141
- colorCacheSize++;
142
- }
143
- const color = originalColor & 16777215;
144
- const isStatic = staticColorNumbers?.has(color);
145
- const paletteColors = !isStatic ? palettes.get(color) : null;
146
- for (let i = 0; i < variantCount; i++) {
147
- if (isStatic) {
148
- colors[colorOffset + i] = originalColor;
149
- continue;
150
- }
151
- const hue = variantHues[i];
152
- if (hue) colors[colorOffset + i] = a << 24 | applyHue(color, hue);
153
- else if (paletteColors?.[i]) colors[colorOffset + i] = a << 24 | paletteColors[i];
154
- else if (!staticColorNumbers) colors[colorOffset + i] = originalColor;
155
- else {
156
- colors[colorOffset + i] = 0;
157
- if (trackMissing) missing.add([variantNames[i], color]);
158
- }
159
- }
160
- }
161
- if (variantCount === 8) {
162
- const color0 = colors[colorOffset];
163
- const color1 = colors[colorOffset + 1];
164
- const color2 = colors[colorOffset + 2];
165
- const color3 = colors[colorOffset + 3];
166
- const color4 = colors[colorOffset + 4];
167
- const color5 = colors[colorOffset + 5];
168
- const color6 = colors[colorOffset + 6];
169
- const color7 = colors[colorOffset + 7];
170
- if (color0) buffer0[index] = color0;
171
- if (color1) buffer1[index] = color1;
172
- if (color2) buffer2[index] = color2;
173
- if (color3) buffer3[index] = color3;
174
- if (color4) buffer4[index] = color4;
175
- if (color5) buffer5[index] = color5;
176
- if (color6) buffer6[index] = color6;
177
- if (color7) buffer7[index] = color7;
178
- } else if (variantCount === 2) {
179
- const color0 = colors[colorOffset];
180
- const color1 = colors[colorOffset + 1];
181
- if (color0) buffer0[index] = color0;
182
- if (color1) buffer1[index] = color1;
183
- } else for (let i = 0; i < variantCount; i++) {
184
- const color = colors[colorOffset + i];
185
- if (color) variantBuffers[i][index] = color;
186
- }
187
- }
188
- if (trackMissing && missing.size) throw new Error(`${options?.imageName ? `${options.imageName}` : "Palette Swap"}: Missing ${Array.from(new Set([...missing].map(([variant, color]) => `'${int32ToHex(color)}' in '${variant}'`))).join(", ")}.`);
189
- for (let i = 0; i < variantCount; i++) {
190
- const name = variantNames[i];
191
- const existingImage = images?.get(name);
192
- const existingImageData = existingImage ? createCanvasFromImage(existingImage).getContext("2d").getImageData(0, 0, width, height) : null;
193
- const newImageData = variantImageData[i];
194
- if (!existingImageData || !equals(newImageData, existingImageData)) {
195
- const newCanvas = createCanvas(width, height);
196
- newCanvas.getContext("2d").putImageData(newImageData, 0, 0);
197
- results.set(name, newCanvas);
198
- }
199
- }
200
- return results;
201
- }
202
- //#endregion
203
- export { paletteSwap as default };
@@ -1,4 +0,0 @@
1
- allowBuilds:
2
- canvas: true
3
- onlyBuiltDependencies:
4
- - canvas