refactor(creative): consolidate pixel-art skills into single preset-based skill

Merges pixel-art-arcade and pixel-art-snes into one pixel-art skill with
named presets (arcade, snes) + parametric overrides. The underlying
pipeline was already identical across both variants — only palette size,
block size, and enhancement strength differed. A single preset-based
function is easier to discover, maintain, and extend (adding a new era
like gameboy or nes is just another preset dict).

Contributor authorship preserved on original additive commit.
This commit is contained in:
Teknium 2026-04-19 11:47:46 -07:00 committed by Teknium
parent 06845b6a03
commit bbc8499e8c
3 changed files with 170 additions and 236 deletions

View file

@ -1,118 +0,0 @@
---
name: pixel-art-arcade
description: Convert images into bold arcade-era pixel art with a 16-color palette, Floyd-Steinberg dithering, and 8px block scaling.
version: 1.1.0
author: dodo-reach
license: MIT
metadata:
hermes:
tags: [creative, pixel-art, arcade, retro, image]
category: creative
---
# Pixel Art Arcade
Convert any image into authentic 80s/90s arcade cabinet pixel art. This skill uses a 16-color palette with Floyd-Steinberg dithering and 8px block scaling for a bold, high-impact retro look.
When needed, you may adjust block size, palette size, and enhancement strength slightly to fit the source image or the user's request, but keep the result unmistakably arcade-style: bold, chunky, and high-impact.
## When to Use
- The user wants pixel art with maximum visual impact
- A retro arcade aesthetic fits posters, covers, social posts, sprites, or backgrounds
- The subject can tolerate aggressive simplification and chunky 8px blocks
## Procedure
1. Boost contrast to `1.8x`, color to `1.5x`, and sharpness to `1.2x`.
2. Lightly posterize the image to simplify tonal regions before quantization.
3. Downscale the image to `w // 8` by `h // 8` with `Image.NEAREST`.
4. Quantize the reduced image to 16 colors with Floyd-Steinberg dithering.
5. Upscale back to the original size with `Image.NEAREST`.
6. Save the output as PNG.
## Code
```python
from PIL import Image, ImageEnhance, ImageOps
def pixel_art_arcade(input_path, output_path):
"""
Convert an image to arcade cabinet style.
Args:
input_path: path to source image
output_path: path to save the resulting PNG
"""
img = Image.open(input_path).convert("RGB")
# Initial boost for heavily limited palette
img = ImageEnhance.Contrast(img).enhance(1.8)
img = ImageEnhance.Color(img).enhance(1.5)
img = ImageEnhance.Sharpness(img).enhance(1.2)
# Light posterization helps separate tonal regions before quantization
img = ImageOps.posterize(img, 5)
w, h = img.size
small = img.resize((max(1, w // 8), max(1, h // 8)), Image.NEAREST)
# Quantize after downscaling so dithering is applied at block level
quantized = small.quantize(colors=16, dither=Image.FLOYDSTEINBERG)
result = quantized.resize((w, h), Image.NEAREST)
result.save(output_path, "PNG")
return result
```
## Example Usage
```python
pixel_art_arcade("/path/to/image.jpg", "/path/to/output.png")
```
## Technical Specs
| Parameter | Value |
|-----------|-------|
| Palette | 16 colors |
| Block size | 8px |
| Dithering | Floyd-Steinberg (after downscale) |
| Pre-processing | Light posterization before quantization |
| Resize method | Nearest Neighbor (downscale and upscale) |
| Output format | PNG |
## Result Style
Best for posters, album covers, bold hero images, and other cases where you want the feeling of an arcade cabinet screen glowing in a dark room.
## Why This Order Works
Floyd-Steinberg dithering distributes quantization error to adjacent pixels. Applying it after downscaling keeps that error diffusion aligned with the reduced pixel grid, so each dithered pixel maps cleanly to a final enlarged block. Quantizing before downscaling can waste the dithering pattern on full-resolution detail that disappears during resize.
A light posterization step before downscaling can improve separation between tonal regions, which helps photographic inputs read more like stylized pixel art instead of simple pixelated photos.
## Pitfalls
- `8px` blocks are aggressive and can destroy fine detail
- Highly detailed photographs may simplify too much
- For softer, more detailed retro output, prefer `pixel-art-snes`
## Verification
The output is correct if:
- A PNG file is created at the output path
- The image shows clear 8px pixel blocks
- Dithering is visible in gradients
- The palette is limited to about 16 colors
- The overall look feels consistent with arcade-era pixel art
## Dependencies
- Python 3
- Pillow
```bash
pip install Pillow
```

View file

@ -1,118 +0,0 @@
---
name: pixel-art-snes
description: Convert images into SNES-style pixel art with a 32-color palette, Floyd-Steinberg dithering, and 4px block scaling.
version: 1.1.0
author: dodo-reach
license: MIT
metadata:
hermes:
tags: [creative, pixel-art, snes, retro, image]
category: creative
---
# Pixel Art SNES
Convert any image into authentic SNES-style pixel art. This skill uses a 32-color palette with Floyd-Steinberg dithering and 4px block scaling for a cleaner 16-bit console look with more detail retention than arcade-style output.
When needed, you may adjust block size, palette size, and enhancement strength slightly to fit the source image or the user's request, but keep the result unmistakably SNES-style: cleaner, more detailed, and still clearly retro.
## When to Use
- The user wants a classic 16-bit console aesthetic
- The output needs more retained detail than the arcade variant
- The target use case is sprites, characters, or detailed retro illustrations
## Procedure
1. Boost contrast to `1.6x`, color to `1.4x`, and sharpness to `1.2x`.
2. Lightly posterize the image to reduce photographic noise while preserving more detail.
3. Downscale the image to `w // 4` by `h // 4` with `Image.NEAREST`.
4. Quantize the reduced image to 32 colors with Floyd-Steinberg dithering.
5. Upscale back to the original size with `Image.NEAREST`.
6. Save the output as PNG.
## Code
```python
from PIL import Image, ImageEnhance, ImageOps
def pixel_art_snes(input_path, output_path):
"""
Convert an image to SNES style.
Args:
input_path: path to source image
output_path: path to save the resulting PNG
"""
img = Image.open(input_path).convert("RGB")
# Initial boost
img = ImageEnhance.Contrast(img).enhance(1.6)
img = ImageEnhance.Color(img).enhance(1.4)
img = ImageEnhance.Sharpness(img).enhance(1.2)
# Lighter posterization preserves more detail while reducing photographic noise
img = ImageOps.posterize(img, 6)
w, h = img.size
small = img.resize((max(1, w // 4), max(1, h // 4)), Image.NEAREST)
# Quantize after downscaling so dithering is applied at block level
quantized = small.quantize(colors=32, dither=Image.FLOYDSTEINBERG)
result = quantized.resize((w, h), Image.NEAREST)
result.save(output_path, "PNG")
return result
```
## Example Usage
```python
pixel_art_snes("/path/to/image.jpg", "/path/to/output.png")
```
## Technical Specs
| Parameter | Value |
|-----------|-------|
| Palette | 32 colors |
| Block size | 4px |
| Dithering | Floyd-Steinberg (after downscale) |
| Pre-processing | Light posterization before quantization |
| Resize method | Nearest Neighbor (downscale and upscale) |
| Output format | PNG |
## Result Style
Best for characters, sprites, and detailed illustrations where you want a polished 16-bit console feel and stronger feature retention than the arcade variant.
## Why This Order Works
Floyd-Steinberg dithering distributes quantization error to adjacent pixels. Applying it after downscaling keeps that error diffusion aligned with the reduced pixel grid, so each dithered pixel maps cleanly to a final enlarged block. Quantizing before downscaling can waste the dithering pattern on full-resolution detail that disappears during resize.
A light posterization step before downscaling can improve separation between tonal regions, which helps photographic inputs read more like stylized pixel art instead of simple pixelated photos while still retaining more detail than the arcade variant.
## Pitfalls
- `4px` blocks are still aggressive on small or busy images
- Realistic subjects can become noisy because of the higher color count
- For simpler subjects that need maximum punch, prefer `pixel-art-arcade`
## Verification
The output is correct if:
- A PNG file is created at the output path
- The image shows clear 4px pixel blocks
- Dithering is visible in gradients
- The palette is limited to about 32 colors
- The overall look feels consistent with SNES-era pixel art
## Dependencies
- Python 3
- Pillow
```bash
pip install Pillow
```

View file

@ -0,0 +1,170 @@
---
name: pixel-art
description: Convert images into retro pixel art using named presets (arcade, snes) with Floyd-Steinberg dithering. Arcade is bold and chunky; SNES is cleaner with more detail retention.
version: 1.2.0
author: dodo-reach
license: MIT
metadata:
hermes:
tags: [creative, pixel-art, arcade, snes, retro, image]
category: creative
---
# Pixel Art
Convert any image into retro-style pixel art. One function with named presets that select different aesthetics:
- `arcade` — 16-color palette, 8px blocks. Bold, chunky, high-impact. 80s/90s arcade cabinet feel.
- `snes` — 32-color palette, 4px blocks. Cleaner 16-bit console look with more detail retention.
The core pipeline is identical across presets — what changes is palette size, block size, and the strength of contrast/color/posterize pre-processing. All presets use Floyd-Steinberg dithering applied AFTER downscale so error diffusion aligns with the final pixel grid.
## When to Use
- User wants retro pixel art from a source image
- Posters, album covers, social posts, sprites, characters, backgrounds
- Subject can tolerate aggressive simplification (arcade) or benefits from retained detail (snes)
## Preset Picker
| Preset | Palette | Block | Best for |
|--------|---------|-------|----------|
| `arcade` | 16 colors | 8px | Posters, hero images, bold covers, simple subjects |
| `snes` | 32 colors | 4px | Characters, sprites, detailed illustrations, photos |
Default is `arcade` for maximum stylistic punch. Switch to `snes` when the subject has detail worth preserving.
## Procedure
1. Pick a preset (`arcade` or `snes`) based on the aesthetic you want.
2. Boost contrast, color, and sharpness using the preset's enhancement values.
3. Lightly posterize the image to simplify tonal regions before quantization.
4. Downscale to `w // block` by `h // block` with `Image.NEAREST`.
5. Quantize the reduced image to the preset's palette size with Floyd-Steinberg dithering.
6. Upscale back to the original size with `Image.NEAREST`.
7. Save the output as PNG.
## Code
```python
from PIL import Image, ImageEnhance, ImageOps
PRESETS = {
"arcade": {
"contrast": 1.8,
"color": 1.5,
"sharpness": 1.2,
"posterize_bits": 5,
"block": 8,
"palette": 16,
},
"snes": {
"contrast": 1.6,
"color": 1.4,
"sharpness": 1.2,
"posterize_bits": 6,
"block": 4,
"palette": 32,
},
}
def pixel_art(input_path, output_path, preset="arcade", **overrides):
"""
Convert an image to retro pixel art.
Args:
input_path: path to source image
output_path: path to save the resulting PNG
preset: "arcade" or "snes"
**overrides: optionally override any preset field
(contrast, color, sharpness, posterize_bits, block, palette)
Returns:
The resulting PIL.Image.
"""
if preset not in PRESETS:
raise ValueError(
f"Unknown preset {preset!r}. Choose from: {sorted(PRESETS)}"
)
cfg = {**PRESETS[preset], **overrides}
img = Image.open(input_path).convert("RGB")
# Stylistic boost — stronger for smaller palettes
img = ImageEnhance.Contrast(img).enhance(cfg["contrast"])
img = ImageEnhance.Color(img).enhance(cfg["color"])
img = ImageEnhance.Sharpness(img).enhance(cfg["sharpness"])
# Light posterization separates tonal regions before quantization
img = ImageOps.posterize(img, cfg["posterize_bits"])
w, h = img.size
block = cfg["block"]
small = img.resize(
(max(1, w // block), max(1, h // block)),
Image.NEAREST,
)
# Quantize AFTER downscaling so dithering aligns with the final pixel grid
quantized = small.quantize(
colors=cfg["palette"], dither=Image.FLOYDSTEINBERG
)
result = quantized.resize((w, h), Image.NEAREST)
result.save(output_path, "PNG")
return result
```
## Example Usage
```python
# Bold arcade look (default)
pixel_art("/path/to/image.jpg", "/path/to/arcade.png")
# Cleaner SNES look with more detail
pixel_art("/path/to/image.jpg", "/path/to/snes.png", preset="snes")
# Override individual parameters — e.g. tighter palette with SNES block size
pixel_art(
"/path/to/image.jpg",
"/path/to/custom.png",
preset="snes",
palette=16,
)
```
## Why This Order Works
Floyd-Steinberg dithering distributes quantization error to adjacent pixels. Applying it AFTER downscaling keeps that error diffusion aligned with the reduced pixel grid, so each dithered pixel maps cleanly to a final enlarged block. Quantizing before downscaling wastes the dithering pattern on full-resolution detail that disappears during resize.
A light posterization step before downscaling improves separation between tonal regions, which helps photographic inputs read as stylized pixel art instead of simple pixelated photos.
Stronger pre-processing (higher contrast/color) pairs with smaller palettes because fewer colors have to carry the whole image. SNES runs softer enhancements because 32 colors can represent gradients and mid-tones directly.
## Pitfalls
- `arcade` 8px blocks are aggressive and can destroy fine detail — use `snes` for subjects that need retention
- Busy photographs can become noisy under `snes` because the larger palette preserves small variations — use `arcade` to flatten them
- Very small source images (<~100px wide) may collapse under 8px blocks. `max(1, w // block)` guards against zero dimensions, but output will be visually degenerate.
- Fractional overrides for `block` or `palette` will break quantization — keep them as positive integers.
## Verification
Output is correct if:
- A PNG file is created at the output path
- The image shows clear square pixel blocks at the preset's block size
- Dithering is visible in gradients
- The palette is limited to approximately the preset's color count
- The overall look matches the targeted era (arcade or SNES)
## Dependencies
- Python 3
- Pillow
```bash
pip install Pillow
```