Skip to main content

Generate dwarf portraits from Dwarf Fortress sprite sheets

Project description

df-portrait-compositor

Generate portrait images for dwarves, elves, humans, goblins, and kobolds from Dwarf Fortress (Steam/Premium) sprite sheets by parsing the game's graphics definition files, evaluating layer conditions against appearance data, and compositing the matching tiles with palette recoloring.

Features

  • Multi-race support: generates portraits for DWARF, ELF, HUMAN, GOBLIN, and KOBOLD races
  • Parses DF's graphics_creatures_portrait_{race}.txt into structured layer rules
  • All three age groups: BABY (age < 1), CHILD (age 1-11), and PORTRAIT (adult 12+) layer sets
  • Evaluates 10+ condition types: caste, tissue color/length/shaping/curliness, body part modifiers (head broadness, nose shape/broadness, eye shape), equipment, syndromes (vampire, zombie, necromancer, ghost), material flags/types, item quality, and randomized part selection
  • BP_MISSING condition support for injury-based layer filtering
  • Implements DF's "first match wins" layer group logic for mutually exclusive alternatives
  • Composites selected tiles onto an RGBA canvas with proper alpha blending
  • Race-specific palette recoloring for skin and hair colors using DF's per-race palette PNGs
  • Auto-detection of hair palette source row (varies per race -- e.g. human tiles use row 12, dwarves use row 0)
  • HSV clothing tinting: clothing layers use HSV color space to apply the item's material/dye color with naturally muted saturation, matching DF's subdued portrait aesthetic
  • Creature portraits: load portrait sprites for 500+ creatures across 6 sprite sheets (domestic, surface, aquatic, animal people, and more)
  • Appearance-hash-based caching so portraits only regenerate when the dwarf's look changes
  • Deterministic random seed (from unit ID) for consistent clothing/feature variation

Installation

pip install df-portrait-compositor

Or install from source:

git clone https://github.com/Been012/df-portrait-compositor.git
cd df-portrait-compositor
pip install -e .

Requirements

  • Python 3.11+
  • Pillow
  • A Dwarf Fortress Premium (Steam) installation (for the sprite sheet PNGs and graphics definition file)

Quick Start

CLI

The fastest way to see it in action:

# Generate a random demo portrait (dwarf by default)
df-portrait demo

# Generate 10 random portraits in a directory
df-portrait demo -n 10 -o my_portraits/

# Generate with a specific seed for reproducibility
df-portrait demo --seed 42

# Generate an elf portrait
df-portrait --race elf demo

# Generate human portraits
df-portrait --race human demo -n 5 -o human_portraits/

# Generate from JSON appearance data
df-portrait generate '{"sex":"female","skin_color":"PEACH","hair_color":"BROWN","hair_length":200,"hair_shaping":"BRAIDED"}'

# Generate from a df-storyteller snapshot file
df-portrait from-snapshot path/to/snapshot.json -o portraits/

# Generate for a specific dwarf from a snapshot
df-portrait from-snapshot path/to/snapshot.json --unit-id 7116

# Specify DF install path (auto-detected on common Steam paths)
df-portrait --df-path "C:\path\to\Dwarf Fortress" demo

Python API

from df_portrait_compositor import compose_portrait, DwarfAppearanceData, PORTRAIT_RACES

# Point to your DF install directory
df_install = r"C:\Program Files (x86)\Steam\steamapps\common\Dwarf Fortress"

# Build appearance data (all fields have sensible defaults)
appearance = DwarfAppearanceData(
    sex="female",
    skin_color="PEACH",
    hair_color="BROWN",
    hair_length=150,
    hair_shaping="BRAIDED",
    beard_length=0,
    head_broadness=120,
    eye_round_vs_narrow=80,
    nose_upturned=130,
    nose_broadness=90,
    random_seed=42,
)

# Compose a dwarf portrait (default)
img = compose_portrait(df_install, appearance, scale=2)
img.save("portrait.png")

# Compose an elf portrait
img = compose_portrait(df_install, appearance, scale=2, race="ELF")
img.save("elf_portrait.png")

# See all supported races
print(PORTRAIT_RACES)  # {"DWARF", "ELF", "HUMAN", "GOBLIN", "KOBOLD"}

Child and Baby Portraits

The compositor automatically selects the correct sprite set based on age:

# Baby portrait (age < 1)
baby = DwarfAppearanceData(age=0.5, skin_color="PEACH", random_seed=1)
img = compose_portrait(df_install, baby)

# Child portrait (age 1-11)
child = DwarfAppearanceData(age=7, sex="female", skin_color="PEACH", hair_color="BROWN", random_seed=2)
img = compose_portrait(df_install, child)

Creature Portraits

Load portrait sprites for any of DF's 500+ creatures:

from df_portrait_compositor import get_creature_portrait, list_available_creatures

# Get a specific creature's portrait
img = get_creature_portrait(df_install, "DOG", scale=2)
if img:
    img.save("dog_portrait.png")

# Caste-specific portraits (e.g. male/female peacock)
img = get_creature_portrait(df_install, "BIRD_PEAFOWL_BLUE", caste="MALE")

# List all available creature IDs
creatures = list_available_creatures(df_install)
print(f"{len(creatures)} creatures with portraits")

Clothing with Material Colors

Equipment items with material colors are tinted using HSV color space:

appearance = DwarfAppearanceData(
    sex="male",
    skin_color="PEACH",
    hair_color="BROWN",
    equipment=[
        {
            "slot": "BODY_UPPER",
            "item_type": "ARMOR",
            "item_subtype": "ITEM_ARMOR_SHIRT",
            "material_flags": ["IS_METAL"],
            "material_color": [180, 180, 200],  # Steel-gray RGB
            "quality": 3,
        },
    ],
    random_seed=42,
)
img = compose_portrait(df_install, appearance, scale=2)

Using generate_portrait for Caching

If you are generating portraits for many dwarves, generate_portrait handles caching automatically. It takes a raw appearance dict and a cache directory, and only regenerates when appearance data changes:

from pathlib import Path
from df_portrait_compositor import generate_portrait

appearance_dict = {
    "sex": "male",
    "skin_color": "PEACH",
    "hair_color": "BLACK",
    "beard_color": "BLACK",
    "hair_length": 200,
    "beard_length": 300,
    "beard_shaping": "DOUBLE_BRAIDS",
    "head_broadness": 140,
    "nose_length": 160,
    "eyebrow_density": 130,
    "random_seed": 1001,
}

portrait_path = generate_portrait(
    df_install=r"C:\Program Files (x86)\Steam\steamapps\common\Dwarf Fortress",
    unit_id=1001,
    appearance=appearance_dict,
    cache_dir=Path("./portrait_cache"),
)
# Returns Path to the PNG, or None on failure

Getting Appearance Data from DFHack

The appearance fields map to data extracted from DF's internal structures via DFHack. You need a Lua script running inside DFHack to read each dwarf's:

  • Sex: unit.sex (0 = female, 1 = male)
  • Skin/hair/beard colors: Resolved from unit.appearance.colors via caste.color_modifiers and descriptor_color lookups
  • Hair/beard length: From unit.appearance.tissue_length indexed through caste.bp_appearance.style_part_idx
  • Hair/beard shaping: From unit.appearance.tissue_style (0=NEATLY_COMBED, 1=BRAIDED, 2=DOUBLE_BRAIDS, 3=PONY_TAIL, 4=CLEAN_SHAVEN)
  • Body part modifiers (head broadness, nose shape, eye shape): From unit.appearance.bp_modifiers indexed through caste.bp_appearance.modifier_idx/part_idx
  • Equipment: From unit.inventory items with material color via dfhack.matinfo.decode(item)

The dfhack_scripts/ directory contains debug scripts that demonstrate how to read these values:

Script Purpose
storyteller-debug-colors.lua Dump color modifier values for all citizens
storyteller-debug-hair.lua Identify tissue types and lengths for each styled tissue
storyteller-debug-bpmod.lua Show body part appearance modifiers (density, broadness, etc.)

Tissue Length Index Layout

The indices into unit.appearance.tissue_length and unit.appearance.tissue_style vary by sex. The mapping is determined by caste.bp_appearance.style_part_idx and style_layer_idx, but for the default dwarf creature definition:

Male (12 tissue entries):

Index Tissue
0, 1 SIDEBURNS
2 CHIN_WHISKERS (beard)
3 MOUSTACHE
4, 5 EYEBROW
6, 7 HAIR (scalp)
8, 9 CHEEK_WHISKERS (on CHEEK body parts)
10, 11 N/A

Female (6 tissue entries):

Index Tissue
0, 1 EYEBROW
2, 3 HAIR (scalp)
4, 5 N/A

HEAD body part layer indices (for bp_appearance.layer_idx):

Layer Tissue
0, 1 SIDEBURNS
2 CHIN_WHISKERS
3 MOUSTACHE
4, 5 EYEBROW
6, 7 HAIR
8 SKIN

API Reference

Core Functions

compose_portrait(df_install, appearance, scale=2, race="DWARF")

Compose a portrait from sprite sheet layers. Automatically selects BABY, CHILD, or PORTRAIT layer set based on appearance.age.

  • df_install (str): Path to the Dwarf Fortress installation directory.
  • appearance (DwarfAppearanceData): Dwarf appearance data for condition matching.
  • scale (int): Upscale factor. 1 = 96px native, 2 = 192px (default).
  • race (str): Creature race -- one of DWARF, ELF, HUMAN, GOBLIN, KOBOLD. Default: DWARF.
  • Returns: PIL.Image.Image (RGBA).

generate_portrait(df_install, unit_id, appearance, cache_dir=None)

Generate and cache a portrait PNG for a dwarf.

  • df_install (str): Path to the DF installation directory.
  • unit_id (int): Dwarf unit ID (used for filename and random seed).
  • appearance (dict): Raw appearance dict (keys match DwarfAppearanceData fields).
  • cache_dir (Path | None): Directory to store cached PNGs. Required for output.
  • Returns: Path to the generated PNG, or None on failure.

get_creature_portrait(df_install, creature_id, caste="", scale=2)

Get a creature's portrait sprite from DF's creature portrait sprite sheets.

  • df_install (str): Path to the DF installation directory.
  • creature_id (str): DF creature ID (e.g. "DOG", "CAT", "DRAGON").
  • caste (str): Optional caste for dimorphic species (e.g. "MALE", "FEMALE").
  • scale (int): Upscale factor. 1 = 96px, 2 = 192px (default).
  • Returns: PIL.Image.Image (RGBA), or None if no portrait found.

list_available_creatures(df_install)

List all creature IDs that have portrait sprites available.

  • df_install (str): Path to the DF installation directory.
  • Returns: list[str] of sorted creature ID strings.

Data Classes

DwarfAppearanceData

Dataclass holding all appearance attributes used for condition evaluation. All fields have defaults, so partial data still produces a portrait (with fewer layers).

Key fields: sex, skin_color, hair_color, beard_color, hair_length, hair_shaping, hair_curly, beard_length, beard_shaping, head_broadness, eye_round_vs_narrow, eye_deep_set, eyebrow_density, nose_upturned, nose_length, nose_broadness, is_vampire, is_zombie, is_necromancer, is_ghost, equipment, random_seed, age.

SelectedLayer

Dataclass representing a layer selected for rendering: tile_page, tile_x, tile_y, palette_name, palette_index, use_item_palette, item_color.

LayerRule

Dataclass representing a parsed layer rule from the graphics definition file, including all condition types.

Parser and Evaluator

parse_portrait_graphics(filepath)

Parse a DF portrait graphics definition file into a list of LayerRule objects. Parses all three age layer sets (BABY, CHILD, PORTRAIT).

evaluate_layers(rules, appearance)

Evaluate layer rules against appearance data and return matching SelectedLayer objects in render order.

Constants

TILE_SIZE

The native tile size in pixels (96).

PORTRAIT_RACES

Set of supported race strings: {"DWARF", "ELF", "HUMAN", "GOBLIN", "KOBOLD"}.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

df_portrait_compositor-0.4.0.tar.gz (26.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

df_portrait_compositor-0.4.0-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file df_portrait_compositor-0.4.0.tar.gz.

File metadata

  • Download URL: df_portrait_compositor-0.4.0.tar.gz
  • Upload date:
  • Size: 26.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for df_portrait_compositor-0.4.0.tar.gz
Algorithm Hash digest
SHA256 2329acff4537c99bdd630215b50053c7cefbc220c7aa0c19ed15f64c1a452f17
MD5 8321b33fd3581156603f46463eeddf8c
BLAKE2b-256 330a6aaa1f6f254033a3bd33239d57e80930a7703bc278f466e6ec739282f75d

See more details on using hashes here.

File details

Details for the file df_portrait_compositor-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for df_portrait_compositor-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c58c475c432a2c6254807f196d558a618212daa540d35f2aeb5229406291ca37
MD5 4bcc043e0f6c28d657de220fe32ea2de
BLAKE2b-256 a75bfb535a327a3f3371a58b636dee12c1ff9cf1f835d07ec927838073963d2d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page