@jjlmoya/utils-games-development 1.56.0 → 1.57.0
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/package.json
CHANGED
package/src/entries.ts
CHANGED
|
@@ -23,7 +23,8 @@ import { spriteSheetPacker } from './tool/spriteSheetPacker/entry';
|
|
|
23
23
|
import { audioLoopPointFinder } from './tool/audioLoopPointFinder/entry';
|
|
24
24
|
import { saveFileEditor } from './tool/saveFileEditor/entry';
|
|
25
25
|
import { localizationSanitizer } from './tool/localizationSanitizer/entry';
|
|
26
|
+
import { steamBbcodeTranslator } from './tool/steamBbcodeTranslator/entry';
|
|
26
27
|
import { retroSfxGenerator } from './tool/retroSfxGenerator/entry';
|
|
27
28
|
import { pixelArtPaletteSwapper } from './tool/pixelArtPaletteSwapper/entry';
|
|
28
29
|
|
|
29
|
-
export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, retroSfxGenerator, pixelArtPaletteSwapper];
|
|
30
|
+
export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, steamBbcodeTranslator, retroSfxGenerator, pixelArtPaletteSwapper];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { ALL_ENTRIES } from '../entries';
|
|
5
|
+
import { ALL_TOOLS } from '../tools';
|
|
6
|
+
|
|
7
|
+
describe('runtime registry', () => {
|
|
8
|
+
it('keeps every runtime tool represented in the data registry', () => {
|
|
9
|
+
const toolIds = ALL_TOOLS.map(({ entry }) => entry.id).sort();
|
|
10
|
+
const entryIds = ALL_ENTRIES.map(({ id }) => id).sort();
|
|
11
|
+
|
|
12
|
+
expect(entryIds).toEqual(toolIds);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('keeps every runtime directory represented in the data registry', () => {
|
|
16
|
+
const runtimeIds = readdirSync(join(process.cwd(), 'src', 'tool'), { withFileTypes: true })
|
|
17
|
+
.filter((directory) => directory.isDirectory())
|
|
18
|
+
.map((directory) => join(process.cwd(), 'src', 'tool', directory.name))
|
|
19
|
+
.filter((directory) => existsSync(join(directory, 'index.ts')) && existsSync(join(directory, 'entry.ts')))
|
|
20
|
+
.map((directory) => readFileSync(join(directory, 'entry.ts'), 'utf8').match(/\bid\s*:\s*['"]([^'"]+)['"]/)?.[1])
|
|
21
|
+
.filter((id): id is string => Boolean(id))
|
|
22
|
+
.sort();
|
|
23
|
+
|
|
24
|
+
const entryIds = ALL_ENTRIES.map(({ id }) => id).sort();
|
|
25
|
+
expect(entryIds).toEqual(runtimeIds);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -61,7 +61,10 @@ function queryAppElements(root: HTMLElement): AppEls | null {
|
|
|
61
61
|
|
|
62
62
|
function renderSwatches(root: HTMLElement): void {
|
|
63
63
|
root.querySelectorAll<HTMLElement>('[data-swatch-palette]').forEach((strip) => {
|
|
64
|
-
const
|
|
64
|
+
const id = strip.dataset.swatchPalette;
|
|
65
|
+
const palette = id && id in PRESET_PALETTES
|
|
66
|
+
? PRESET_PALETTES[id as keyof typeof PRESET_PALETTES]
|
|
67
|
+
: [];
|
|
65
68
|
strip.replaceChildren(...palette.map((c) => {
|
|
66
69
|
const s = document.createElement('span');
|
|
67
70
|
s.style.backgroundColor = c.hex;
|
|
@@ -151,8 +154,10 @@ function setupFileHandler(els: AppEls, onLoaded: (src: SourceImageState) => void
|
|
|
151
154
|
function setupPresetButtons(els: AppEls, onApply: (p: PaletteColor[], id: string) => void): void {
|
|
152
155
|
els.root.querySelectorAll<HTMLButtonElement>('[data-palette-id]').forEach((b) => b.addEventListener('click', () => {
|
|
153
156
|
const id = b.dataset.paletteId ?? 'gameBoy';
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
const palette = PRESET_PALETTES[id as keyof typeof PRESET_PALETTES];
|
|
158
|
+
if (!palette) return;
|
|
159
|
+
els.customPaletteInput.value = palette.map((c) => c.hex).join(', ');
|
|
160
|
+
onApply(palette, id);
|
|
156
161
|
}));
|
|
157
162
|
}
|
|
158
163
|
|
|
@@ -8,13 +8,21 @@ export interface PaletteColor extends RGBColor {
|
|
|
8
8
|
hex: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
interface PresetPalettes {
|
|
12
|
+
gameBoy: PaletteColor[];
|
|
13
|
+
nes: PaletteColor[];
|
|
14
|
+
pico8: PaletteColor[];
|
|
15
|
+
commodore64: PaletteColor[];
|
|
16
|
+
dawnBringer16: PaletteColor[];
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export interface QuantizationResult {
|
|
12
20
|
data: Uint8ClampedArray;
|
|
13
21
|
sourceColors: number;
|
|
14
22
|
mappedColors: number;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
|
-
export const PRESET_PALETTES:
|
|
25
|
+
export const PRESET_PALETTES: PresetPalettes = {
|
|
18
26
|
gameBoy: ['#0f380f', '#306230', '#8bac0f', '#9bbc0f'].map(createPresetPaletteColor),
|
|
19
27
|
nes: [
|
|
20
28
|
'#000000', '#fcfcfc', '#f8f8f8', '#b8b8b8', '#7c7c7c', '#a80000', '#e45c10', '#f8b800',
|
|
@@ -94,11 +102,17 @@ export function findNearestColor(source: RGBColor, palette: PaletteColor[]): Pal
|
|
|
94
102
|
return null;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
const nearestColor = palette[0];
|
|
106
|
+
if (!nearestColor) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let nearest = nearestColor;
|
|
98
111
|
let distance = colorDistanceSquared(source, nearest);
|
|
99
112
|
|
|
100
113
|
for (let index = 1; index < palette.length; index += 1) {
|
|
101
114
|
const candidate = palette[index];
|
|
115
|
+
if (!candidate) continue;
|
|
102
116
|
const candidateDistance = colorDistanceSquared(source, candidate);
|
|
103
117
|
if (candidateDistance < distance) {
|
|
104
118
|
nearest = candidate;
|
|
@@ -125,16 +139,32 @@ interface QuantizePixelContext {
|
|
|
125
139
|
mappedColors: Set<string>;
|
|
126
140
|
}
|
|
127
141
|
|
|
142
|
+
interface PixelData {
|
|
143
|
+
alpha: number;
|
|
144
|
+
color: RGBColor;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function readPixel(data: Uint8ClampedArray, index: number): PixelData | null {
|
|
148
|
+
const alpha = data[index + 3];
|
|
149
|
+
const red = data[index];
|
|
150
|
+
const green = data[index + 1];
|
|
151
|
+
const blue = data[index + 2];
|
|
152
|
+
if (alpha === undefined) return null;
|
|
153
|
+
if (red === undefined) return null;
|
|
154
|
+
if (green === undefined) return null;
|
|
155
|
+
if (blue === undefined) return null;
|
|
156
|
+
return { alpha, color: { r: red, g: green, b: blue } };
|
|
157
|
+
}
|
|
158
|
+
|
|
128
159
|
function processPixel(ctx: QuantizePixelContext): void {
|
|
129
160
|
const { data, index, palette, preserveTransparency, sourceColors, mappedColors } = ctx;
|
|
130
|
-
const
|
|
131
|
-
if (alpha === 0 && preserveTransparency) return;
|
|
161
|
+
const pixel = readPixel(data, index);
|
|
162
|
+
if (!pixel || (pixel.alpha === 0 && preserveTransparency)) return;
|
|
132
163
|
|
|
133
|
-
const
|
|
134
|
-
const mapped = findNearestColor(sourceColor, palette);
|
|
164
|
+
const mapped = findNearestColor(pixel.color, palette);
|
|
135
165
|
if (!mapped) return;
|
|
136
166
|
|
|
137
|
-
sourceColors.add(rgbToHex(
|
|
167
|
+
sourceColors.add(rgbToHex(pixel.color));
|
|
138
168
|
mappedColors.add(mapped.hex);
|
|
139
169
|
data[index] = mapped.r;
|
|
140
170
|
data[index + 1] = mapped.g;
|