diff --git a/optional-skills/creative/pixel-art-arcade/SKILL.md b/optional-skills/creative/pixel-art-arcade/SKILL.md deleted file mode 100644 index ad9cf28a4..000000000 --- a/optional-skills/creative/pixel-art-arcade/SKILL.md +++ /dev/null @@ -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 -``` diff --git a/optional-skills/creative/pixel-art-snes/SKILL.md b/optional-skills/creative/pixel-art-snes/SKILL.md deleted file mode 100644 index 0c3a2b2e0..000000000 --- a/optional-skills/creative/pixel-art-snes/SKILL.md +++ /dev/null @@ -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 -``` diff --git a/optional-skills/creative/pixel-art/SKILL.md b/optional-skills/creative/pixel-art/SKILL.md new file mode 100644 index 000000000..96e1e4f10 --- /dev/null +++ b/optional-skills/creative/pixel-art/SKILL.md @@ -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 +```