= 9e3e7c3e94 Logo from a domain, programmatic generate()
- logo_fetch.py: find a company's logo from its domain (homepage logo candidates, staging
  URLs, touch icons, favicon services), cached by domain
- generate_sprite.py: --domain / --logo-cache, domain column in --batch, logos as PIL
  images, generate(..., write_files=False) for use as a library
2026-10-02 15:20:35 -05:00
2026-10-01 15:15:20 -05:00
2026-10-01 15:15:20 -05:00
2026-10-02 07:01:10 -05:00
2026-10-02 07:01:10 -05:00
2026-10-01 15:15:20 -05:00
2026-10-02 07:01:10 -05:00
2026-10-02 07:01:10 -05:00
2026-10-01 15:15:20 -05:00
2026-10-01 22:46:04 -05:00
2026-10-02 07:01:10 -05:00
2026-10-02 07:01:10 -05:00
2026-10-01 22:46:04 -05:00
2026-10-01 15:15:20 -05:00
2026-10-01 15:15:20 -05:00
2026-10-01 15:15:20 -05:00
2026-10-02 07:01:10 -05:00
2026-10-01 22:46:04 -05:00
2026-10-01 15:15:20 -05:00
2026-10-01 22:46:04 -05:00

sprite_gen

Procedural creature sprites for businesses. You give it a business name and logo. It picks a type, builds a creature from parts and recolors it with the logo's brand colors.

There are two part libraries:

Library Size Art Registry
pixellab (default) 64x64 Generated with PixelLab, see PixelLab library sprite_parts_pl.py + pixellab_library/*.json
classic 96x96 Hand-written text grids sprite_parts.py

Setup

pip install -r requirements.txt

You only need an OpenRouter API key if you want the type picked automatically. Set OPENROUTER_API_KEY in the environment or in .env, or skip the key by passing --type.

Generate a sprite

# Type picked from the business name
OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py "Rosas Landscaping" logos/rosas.png

# Add a description to help pick the type
OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py "Rosas Landscaping" logos/rosas.png --desc "Lawn care and garden design"

# Set the type yourself (no API key needed)
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass

# Set the colors yourself instead of using a logo (main color first)
python generate_sprite.py "Rosas Landscaping" --type grass --colors "#2E7D32,#FBC02D"

# A different creature for the same business
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass --reroll 1

# Both libraries side by side
python generate_sprite.py "Rosas Landscaping" logos/rosas.png --type grass --compare

# No logo file: find the logo on the business's website
python generate_sprite.py "Rosas Landscaping" --domain rosaslandscaping.com --type grass

Each run writes sprites/<slug>.png, sprites/<slug>@4x.png and sprites/<slug>.json (library, type, parts, colors). With --reroll N, the file names end in -rN. With --compare, each library writes <slug>-<library>.* and <slug>-compare.png shows them next to each other at the same height. Both use the same type and colors.

Option Meaning
--type T Skip the classifier. One of: normal fire water grass electric ice fighting poison ground flying psychic bug rock ghost dragon
--desc TEXT Description sent to the classifier (default: the name)
--colors "#main,#accent" Manual colors; overrides the logo
--reroll N Alternate creature; the name alone always gives the same sprite
--library NAME pixellab (default) or classic
--compare Render with both libraries and write a comparison sheet
--out DIR Output folder (default sprites)
--scale N Size of the upscaled copy (default 4, 1 disables it)
--domain D Find the logo on this website when no logo file or --colors is given (see Logo from a domain)
--logo-cache DIR Where logos found with --domain are cached (default logos)

Logo colors: transparent pixels, soft edge pixels and a solid white/black/gray background are ignored, and colored brand colors are used before white, black or gray. A logo with only white, black or gray in it still uses those colors.

Batch

Use a CSV with the columns name,logo,domain,type,description. Only name is required. Rows with no type use the classifier, and rows with a domain but no logo look the logo up on that website.

OPENROUTER_API_KEY=<API_KEY> python generate_sprite.py --batch businesses.csv --out sprites

Add --compare to get both libraries for every row.

Logo from a domain

logo_fetch.py finds a business's logo from its website and caches it by domain.

python logo_fetch.py rosaslandscaping.com --out logos       # -> logos/rosaslandscaping.com.png (method)
python logo_fetch.py rosaslandscaping.com --extra https://staging.example.net/   # also search this page
from logo_fetch import fetch_logo, fetch_logo_info, normalize_domain
fetch_logo("rosaslandscaping.com", "logos")                 # Path or None
fetch_logo_info("rosaslandscaping.com", "logos", extra_urls=["https://staging.example.net/"])  # (Path or None, method)
normalize_domain("HTTPS://www.Rosas.com/about")              # "rosas.com"

Search order, best first. The first image with a brand hue wins; an image with only white/black/gray is kept as a fallback in case nothing colorful turns up (its method ends in -mono).

Method Source
site-logo The homepage (https, then http, without and with www): images with "logo" in class/id/file name/alt, images in a logo/brand container, the first image in <header>/<nav>, inline <svg> logos, JSON-LD "logo"
extra-logo The same on each extra_urls page (e.g. a staging copy of the site)
site-icon, extra-icon apple-touch-icon, large <link rel=icon>, msapplication-TileImage
google-favicon, ddg-favicon Google s2 favicons (sz=128), then DuckDuckGo ip3; their default globe icons (HTTP 404) are rejected

A homepage og:image is used (as a site-logo candidate) only when its URL says "logo"; otherwise it is nearly always a photo.

Images named after something other than the domain (partner badges, "brands we carry") and images in partner/client/carousel sections rank lower, and only candidates close to the best one's score are tried. Theme stand-ins (mock-logo, your-logo, my-company, ...) are skipped. Parked/for-sale/expired domains and redirects to social or review sites are skipped. SVG is rasterized with cairosvg if it is installed, else the rsvg-convert binary if it is on PATH, else skipped. Blank, fully transparent or single-color images are rejected. Each download has a wall-clock cap and one search stops trying new candidates after 75 s, so a slow or stalled site can't hold up a batch. Logos are stored as PNG (at most 256 px) with a <domain>.json next to them holding the method; misses are cached too, so reruns make no requests (--refresh / refresh=True retries). fetch_logo is thread-safe, so callers can run many lookups in a thread pool.

Python API

import generate_sprite as gs
lib = gs.load_library("pixellab")
info, sprite = gs.generate(lib, "Rosas Landscaping", None, "grass", logo="logos/rosas.png", write_files=False)

With write_files=False, generate returns the info dict (plan, parts, attributes, colors) and the PIL image without writing or printing anything. logo may be a path or a PIL image. The part registries find their art folders (parts_pl/, parts/) relative to the working directory, so run from this folder (or os.chdir to it) before importing.

PixelLab library

Each base creature has a spec in pixellab_library/<body>.json: the prompt and seed for its base image, plus one edit prompt per part ("add a pair of gold horns ..."). pixellab_library.py generates the base with PixelLab's pixen model and makes each part as an edit of that exact image. The part is the pixel difference between the edit and the base, so it lines up with the base and gets its attach point automatically. The full spec schema and conventions are in the docstring at the top of pixellab_library.py.

Art is drawn in a stand-in palette (teal body, crimson details, gold elemental bits) that maps to the recolorable palette keys. Elemental features can use a fixed-color ramp instead (fire, bluefire, ice, electric, ghostfire): they keep their own colors whatever the brand, and they only appear on their types. When the generator picks parts, it skips any part that would push the sprite past 64x64, so every part can be offered to every body.

Needs PIXELLAB_API_KEY in the environment or in .env.

python pixellab_library.py generate pl_fox --base-only   # base image only: review it first
python pixellab_library.py generate pl_fox               # every missing edit (raw images in pixellab_src/pl_fox/)
python pixellab_library.py extract pl_fox                # grids in parts_pl_src/, PNGs in parts_pl/, diffs in preview/pixellab/diff/
python pixellab_library.py preview pl_fox                # the body with each of its parts
python pixellab_library.py preview --cross               # every part on every body, one sheet per slot
python pixellab_library.py preview --random 60           # real random picks
python pixellab_library.py cost                          # spend so far (pixellab_src/ledger.jsonl) and balance
python validate_parts.py --library pixellab --sample 3000

To change a part, edit its spec (new seed or wording), then run generate <body> --parts <part> --force and extract <body>. Regenerating a base deletes that body's old edits and variants.

A new body needs no parts of its own: for each slot without a part, generate makes a probe edit (a standard horn pair, wing pair or tail) that is used only to find where that slot attaches. A body's variants are edits of its base ("make it chubbier with crimson stripes"): the same creature idea with a different look. They share the parent's attach points and tags. generate runs up to 10 PixelLab jobs at once, shared between all processes. preview <body> writes one review sheet per family (base, attach points, a test fit with library parts, every variant), and preview --sheet out.png <bodies...> shows many raw bases on one image.

generate stops before starting new work once the month's included PixelLab generations drop to 800 (PIXELLAB_RESERVE changes the floor), so a big run can't use up the whole monthly allowance.

Classic library

The parts are text grids in parts_src/<slot>/<name>.txt, compiled to parts/<slot>/<name>.png. Weights, compatibility and deny lists live in sprite_parts.py. See AI_PIXEL_ART_GUIDE.md for the palette and anchor rules. The PixelLab library's grids in parts_pl_src/ use the same format, plus the fixed-ramp characters listed in build_parts.py.

python build_parts.py                                       # compile all grids (or pass part names)
python validate_parts.py --library classic --sample 3000    # check colors, anchors, canvas fit on random composites
python preview.py --library classic bases                   # contact sheets in preview/
python preview.py --library classic part back plant_bulb    # one part on every base it fits
python preview.py --library classic zoom body=base_fox tail=tail_leaf
python generate_sprite.py --library classic --sprite-list SPRITES.md   # regenerate the part list and weight report

Do not run --make-placeholders --overwrite, because it replaces the real art with test placeholders.

Description
No description provided
Readme 8.3 MiB
Languages
Python 100%