High-color background quantization for SEGA Genesis / Mega Drive workflows. Fork of the original tiledpalettequant tool, rewritten with React + TypeScript + MUI 7, bundled via Webpack 5, and wired to the original quantizer in a Web Worker.
Designed for SGDK 2.11+ on real hardware. Exports 8-bpp indexed BMP with correct palette order and 4-byte padded, bottom-up pixel rows (BI_RGB / Windows V3).
-
Genesis-friendly quantization:
- Tile size (default 8×8)
- Up to 4 palettes × 16 colors (Genesis limits), Original UI supports up to 8 palettes
- Bits per channel (typ. 3 bpc)
- Dithering: Off / Fast / Slow, weight and pattern
- Color index 0 policy: unique, shared, transparent from transparent, transparent from color (+ picker)
- Fraction of pixels sampling (0–1) to accelerate large images
-
Live preview and a palette legend showing P0..Pn with 16 entries each (index 0 outlined).
-
Export: Indexed 8-bpp BMP (exact palette order; no RGB re-mapping).
-
Original-style filenames:
basename-8x8-4p16c-s.bmp(u= unique,s= shared,t= transparent). -
OS theme auto (prefers-color-scheme) with light/dark Material UI theme.
- Node.js 22+ (recommended) and npm
- (Optional) Docker 24+ if you want to run in containers
cd ui
npm ci
npm run devOpen http://localhost:8080 (or whatever your dev server prints).
cd ui
npm run buildThis produces a static bundle in ui/dist/. Serve it with any static server (e.g., Node/Express, nginx, or npm run serve if you’ve added one).
If you’re using the compose setup:
docker compose up --build
# open http://localhost:8080Tear down:
docker compose down-
Choose Image… (PNG/JPG/BMP). The raw image renders immediately.
-
Adjust Quantization (tile size, palettes, colors/palette, bits/channel).
-
Optionally tune Dithering (mode/weight/pattern) and Fraction of pixels.
-
Set Color index zero behaviour:
- unique – index 0 per palette
- shared – a single color across palettes (pick it)
- transparent from transparent pixels
- transparent from color – pick the key color
-
Click Run Quantizer. Preview updates; palette legend reflects the export palette (what GIMP/SGDK will see).
-
Save BMP (Indexed 8-bpp) to export SGDK-ready art.
- Filenames:
basename-8x8-4p16c-{u|s|t}.bmp
- Filenames:
- Indexed BMP (8-bpp), BI_RGB (no compression), Windows V3 header.
- Palette has 256 entries (B,G,R,0). Only the first
palettes × 16are relevant. - Pixels are 1 byte per pixel, bottom-up rows, width padded to a multiple of 4 bytes (BMP standard).
- Global color index =
palette * 16 + color. - The “transparent” policies choose which index to use (BMP has no alpha channel in the palette). Transparency is handled on the engine side (e.g., SGDK using index 0).
| Setting | Notes / Typical |
|---|---|
| Tile size | Genesis backgrounds: 8×8 |
| Palettes | Genesis max 4 (Original UI supports up to 8 for experimentation) |
| Colors / palette | Genesis max 16 |
| Bits / channel | Approx Genesis 3 |
| Dither mode | Off / Fast / Slow |
| Dither weight | 0..1 |
| Dither pattern | diag4 / horiz4 / vert4 / diag2 / horiz2 / vert2 |
| Fraction of pixels | 0..1 sampling ratio (1 = use all pixels) |
| Color index zero behaviour | unique / shared (with color) / transparentFromTransparent / transparentFromColor (with color) |
repo/
├─ ui/
│ ├─ src/
│ │ ├─ components/
│ │ │ ├─ app.tsx ← top-level layout
│ │ │ ├─ previewPane.tsx ← canvas + palette legend + save
│ │ │ └─ PaletteLegend.tsx ← “P0..Pn” 16× swatches per palette
│ │ ├─ pages/
│ │ │ └─ form.tsx ← settings UI (MUI 7, Boxes not tables)
│ │ ├─ hooks/
│ │ │ └─ useQuantizer.ts ← state, image IO, worker bridge, save BMP
│ │ ├─ workers/
│ │ │ └─ legacy/worker-legacy.js ← original quantizer (unchanged core)
│ │ └─ @types/
│ │ └─ tpq.d.ts ← global types (no imports)
│ ├─ webpack.config.cjs ← Webpack 5 + ts-loader
│ └─ tsconfig.json ← ESNext modules, DOM + WebWorker libs
└─ handlers/
└─ quantize.ts (optional stub while porting)
-
UI (React + MUI 7) keeps all settings in
TpqSettings. -
Worker runs the quantization (original
worker.jsmigrated intoui/src/workers/legacy/worker-legacy.js). -
Bridge (
useQuantizer) translates settings → original numeric options, receives:- preview RGBA (for canvas),
- final paletteData (B,G,R,0 × 256),
- final colorIndexes (8-bpp, padded, bottom-up),
- optional
palettessnapshot during iteration.
-
Palette legend derives from paletteData so it always matches the exported BMP (what GIMP sees).
GIMP palette order looks different from preview
The legend now derives from paletteData, which is exactly what the BMP writes. In GIMP’s Colormap dialog, set Columns = 16 to align with the “P0..Pn” blocks.
package.json scripts:
{
"scripts": {
"start": "NODE_ENV=production cd service && npm start",
"dev": "cd ui && npm start",
"install": "./install.sh",
"reinstall": "./reinstall.sh",
"build": "./build.sh",
"test": "mocha test/**/*.test.mjs",
"deploy": "gcloud config set project tiledpalettequant && gcloud app deploy",
"docker-build": "docker compose -p augmentedjs/tiledpalettequant build",
"docker-run": "DOCKER_BUILDKIT=1 docker compose up -d",
"docker-down": "docker compose down",
"docker-stop": "docker stop augmentedjs/tiledpalettequant",
"docker-restart": "docker restart augmentedjs/tiledpalettequant",
"docker-teardown": "./teardown.sh"
},
}- Original quantizer idea/tool by rilden (tiledpalettequant).
- This React rewrite, SGDK export path, and workflow glue by @Augmentedjs and contributors.
MIT © 2025 Augmentedjs
See LICENSE for full text.
Portions of the Original quantizer are derived from the original tiledpalettequant tool by rilden. Attribution retained.
Open an issue, or ping with:
- A sample input image,
- The settings you used,
- A screenshot of the legend vs what you see in GIMP/SGDK.
Happy quantizing! 🎮🟣🟢🟡🟠