Skip to content

Assets Guides

Working with Graphics

Decode Doom-format pictures and flats to PIL Images, render composite textures, and encode standard images back to WAD format.

from wadlib import WadFile
from wadlib.lumps.picture import encode_picture
from wadlib.lumps.flat import encode_flat
from wadlib.compositor import TextureCompositor
from PIL import Image

with WadFile("DOOM2.WAD") as wad:
    palette = wad.playpal.get_palette(0)

    # Sprites / Pictures -> PIL Image
    sprite = wad.sprites["POSSA1"]
    img = sprite.decode(palette)              # RGBA
    img.save("zombie.png")

    # PIL Image -> Doom picture bytes
    source = Image.open("custom_sprite.png")
    picture_bytes = encode_picture(source, palette)

    # Flats (64x64 floor/ceiling textures) -> PIL Image
    flat = wad.flats["FLOOR0_1"]
    img = flat.decode(palette)                # RGB, 64x64
    img.save("floor.png")

    # PIL Image -> flat bytes
    floor_img = Image.open("my_floor.png").resize((64, 64))
    flat_bytes = encode_flat(floor_img, palette)

    # Composite wall textures (patches assembled by TEXTURE1/2 definitions)
    comp = TextureCompositor(wad)
    wall = comp.render("BRICK7")              # palette-mode Image
    wall_rgba = comp.render_rgba("BRICK7")    # RGBA
    wall_rgba.save("brick7.png")

Working with Sounds and Music

Convert between WAD audio formats (DMX, MUS) and standard WAV/MIDI in both directions.

from wadlib import WadFile
from wadlib.lumps.sound import wav_to_dmx
from wadlib.lumps.mid2mus import midi_to_mus

with WadFile("DOOM2.WAD") as wad:
    # DMX -> WAV (format 3: digitized PCM)
    pistol = wad.sounds["DSPISTOL"]
    print(f"format {pistol.format}: {pistol.rate} Hz, {pistol.sample_count} samples")
    wav_bytes = pistol.to_wav()

    # PC speaker sounds (format 0) are synthesized as square waves
    # DP* lumps contain note sequences, not PCM; to_wav() handles both
    for name, snd in wad.sounds.items():
        if snd.format == 0:
            pc_wav = snd.to_wav()   # synthesized from 8253-PIT timer table
    with open("pistol.wav", "wb") as f:
        f.write(wav_bytes)

    # MUS -> MIDI
    midi_bytes = wad.music["D_RUNNIN"].to_midi()
    with open("d_runnin.mid", "wb") as f:
        f.write(midi_bytes)

# WAV -> DMX
with open("pistol.wav", "rb") as f:
    dmx_bytes = wav_to_dmx(f.read())

# MIDI -> MUS
with open("e1m1.mid", "rb") as f:
    mus_bytes = midi_to_mus(f.read())

# wad.music detects format from magic bytes — modern source-port PWADs often
# ship OGG, MP3, or raw MIDI instead of MUS.  The returned type varies:
#   Mus       — MUS format; .to_midi() converts to MIDI bytes
#   MidiLump  — raw MIDI; .data holds the bytes unchanged
#   OggLump   — raw OGG; .data holds the bytes unchanged
#   Mp3Lump   — raw MP3; .data holds the bytes unchanged
with WadFile("mod.wad") as wad:
    for name, track in wad.music.items():
        fmt = type(track).__name__
        print(f"{name}: {fmt} ({track.byte_size} bytes)")

Building a Colormap

Generate COLORMAP light-level tables from a palette, optionally with a custom invulnerability tint colour.

from wadlib import WadFile
from wadlib.lumps.colormap import build_colormap, hex_to_rgb, rgb_to_hex

with WadFile("DOOM2.WAD") as wad:
    palette = wad.playpal.get_palette(0)
    colormap_bytes = build_colormap(palette)                    # standard
    colormap_bytes = build_colormap(palette, invuln_tint="#FFD700")  # gold tint

# Hex colour utilities
r, g, b = hex_to_rgb("#FF8800")       # (255, 136, 0)
r, g, b = hex_to_rgb("F80")           # short form works too
hex_str = rgb_to_hex(255, 136, 0)     # "#FF8800"