amiga-bootcamp/11_libraries/icon_format.md
Ilia Sharin c6f6253ea0 docs(amiga): add comprehensive .info icon format article with real icon examples
New article covering all five icon format generations:
- Old-Style (OS 1.0, 4-color planar)
- MagicWB (Stefan Stuntz, 8-color standardized palette)
- NewIcons (Nicola Salmoria, 256-color ASCII in ToolTypes)
- ColorIcons (OS 3.5, FORM ICON IFF with palette chunky)
- GlowIcons/PNG (OS 3.5+, NeXTSTEP-inspired, ARMS chunks)

Includes binary layout specs, API reference, 5 practical examples
(C + Python), decision guide, historical timeline, and real icon
images extracted from actual .info files across all formats.
2026-07-15 18:51:18 -04:00

69 KiB
Raw Blame History

← Home · Libraries · icon.library API

Icon File Format (.info) — Binary Layout, MagicWB, NewIcons, ColorIcons, GlowIcons

Overview

Every file visible on the Workbench has a companion .info file — a binary container holding icon imagery, metadata (ToolTypes), default tool path, drawer window geometry, and stack size. The format evolved across five generations, each a response to the limitations of its predecessor:

  1. Old-Style Icons (OS 1.0, 1985) — A raw memory dump of struct DiskObject with embedded planar bitmaps. Four colors, no palette, no transparency. Designed to be trivial to load on a 7 MHz 68000.
  2. MagicWB (1993, Stefan Stuntz / SASG) — Not a new binary format, but a standardized 8-color palette and professional icon style layered on top of old-style planar icons. The first attempt to make Workbench look professional.
  3. NewIcons (1995, Nicola Salmoria) — A third-party system that encoded 256-color images as ASCII in the ToolTypes array, because modifying the binary format would have broken every existing tool. Salmoria later created MAME.
  4. ColorIcons (OS 3.5, 1999, Haage & Partner) — The first official color icon format: an IFF FORM ICON block appended after the legacy data, with palette-mapped chunky pixels and run-length encoding.
  5. GlowIcons / PNG Icons (OS 3.5+, 1999) — True-color PNG bitmaps with 8-bit alpha, embedded inside the IFF block via ARMS chunks. The defining art style of AmigaOS 3.5/3.9, inspired by NeXTSTEP. Still the native format on AmigaOS 3.2 and 4.1.

The .info file is big-endian (Motorola byte order). It is not an IFF file — the old-style portion predates IFF and stores structures as serialized memory layouts with pointers replaced by booleans. Only the OS 3.5+ extension block uses IFF chunk syntax.

Important

GlowIcons vs ColorIcons: The term "GlowIcon" refers to the art style (vibrant gradients, soft shadows, a characteristic yellow glow on selected icons) — not a distinct binary format. The underlying container is the ColorIcon format (FORM ICON IFF block). A GlowIcon file may contain either palette-mapped IMAG chunks or PNG ARMS chunks. In practice, most GlowIcons use PNG data.

Warning

Big-Endian Format: All multi-byte values in .info files are stored in Motorola (big-endian) byte order — most significant byte first. If you are parsing these files on x86/ARM (little-endian), you must byte-swap every UWORD and ULONG.


All five icon format generations compared


File Format Architecture — Five Generations Layered

The .info format is a matryoshka doll: each generation wraps the previous one for backward compatibility. An OS 3.5 GlowIcon still starts with the same $E310 magic and struct DiskObject header that Workbench 1.0 would recognize — the new-format data is simply appended after the old-format payload.

graph TB
    subgraph Legacy ["Old-Style Icon — OS 1.0+ (always present)"]
        MAGIC["$E310 magic<br/>struct DiskObject"]
        GADGET["struct Gadget"]
        IMG1["Image 1: planar bitmap<br/>(unselected)"]
        IMG2["Image 2: planar bitmap<br/>(selected, optional)"]
        TEXT["DefaultTool +<br/>ToolTypes strings"]
        MAGIC --> GADGET --> IMG1 --> IMG2 --> TEXT
    end

    subgraph MagicWB ["MagicWB Style (optional, 1993+)"]
        MWB["Same old-style format<br/>but 3-plane (8-color)<br/>with standardized palette"]
    end

    subgraph NewIcons ["NewIcons Extension (optional, 1995+)"]
        NI["Encoded ASCII in ToolTypes<br/>IM1= / IM2= lines<br/>256-color + palette"]
    end

    subgraph ColorIcon ["OS 3.5 ColorIcon / IFF (optional, 1999+)"]
        FORM["FORM ICON IFF block"]
        FACE["FACE chunk<br/>(dimensions, flags)"]
        IMAG["IMAG chunks<br/>(chunky + palette)"]
        FORM --> FACE --> IMAG
    end

    subgraph PNGIcon ["GlowIcon / PNG (optional, OS 3.5+)"]
        ARMS["ARMS chunk"]
        PNG1["PNG image 1<br/>(24-bit + alpha)"]
        PNG2["PNG image 2<br/>(selected, optional)"]
        ARMS --> PNG1 --> PNG2
    end

    Legacy -.->|"MagicWB uses 3 planes<br/>with fixed palette"| MagicWB
    Legacy -.->|"NewIcons reuses<br/>ToolTypes slots"| NewIcons
    Legacy -.->|"IFF appended<br/>after legacy data"| ColorIcon
    ColorIcon -.->|"PNG replaces<br/>IMAG in practice"| PNGIcon

    style Legacy fill:#e8f4fd,stroke:#2196f3,color:#333
    style MagicWB fill:#e3f2fd,stroke:#1565c0,color:#333
    style NewIcons fill:#fff9c4,stroke:#f9a825,color:#333
    style ColorIcon fill:#c8e6c9,stroke:#4caf50,color:#333
    style PNGIcon fill:#f3e5f5,stroke:#9c27b0,color:#333

Format Identification

Magic Bytes Format How to Detect
$E3 0x10 at offset 0 All formats Check first 2 bytes; then scan for FORM block for ColorIcon/GlowIcon
Image depth = 3, no FORM or IM1= MagicWB 8-color planar icon using standardized MagicWB palette — no binary marker; detected heuristically
FORM .... ICON after legacy data OS 3.5 ColorIcon Search for ASCII FORM after the old-style payload
FORM ... ICON ... ARMS chunk GlowIcon (PNG) The ARMS chunk inside FORM ICON contains raw PNG data
IM1= / IM2= in ToolTypes NewIcons Scan ToolTypes strings for these markers

Quick Detection from a Host Language

import struct

def detect_icon_format(data: bytes) -> str:
    """Identify which icon generation a .info file belongs to."""
    if len(data) < 2:
        return "not_an_icon"
    magic = struct.unpack_from('>H', data, 0)[0]
    if magic != 0xE310:
        return "not_an_icon"

    # Search for FORM ICON block (OS 3.5+)
    form_pos = data.find(b'FORM')
    if form_pos > 0:
        # Check for ARMS chunk (GlowIcon / PNG)
        if data.find(b'ARMS', form_pos) > 0:
            return "glow_icon_png"
        # Check for FACE/IMAG chunks (ColorIcon)
        if data.find(b'FACE', form_pos) > 0:
            return "color_icon"

    # Search for NewIcons markers in raw data
    if data.find(b'IM1=') >= 0 or data.find(b'IM2=') >= 0:
        return "new_icon"

    # Check for MagicWB (3-plane old-style, no FORM or NewIcons)
    # Read Gadget.Flags at offset 0x0C (2 bytes), then Image.Depth
    # Gadget is at do_GadgetRender offset, typically starts at $04
    gadget_depth_off = 0x04 + 32  # Gadget.GadgetRender is a pointer-sized field
    # Image.Depth is at offset +16 within the Image struct
    if len(data) > gadget_depth_off + 16:
        img_depth = struct.unpack_from('>H', data, gadget_depth_off + 16)[0]
        if img_depth == 3:
            return "magicwb_style"  # heuristic — 8-color planar

    return "old_style"

Old-Style Icons — Binary Layout (OS 1.0+)

Real OS 1.3 disk icon (4-color planar)

The old-style format is essentially a frozen memory dump. When Commodore designed the .info file in 1985, they serialized the in-memory struct DiskObject directly to disk — replacing pointer fields with boolean flags (0 = absent, nonzero = present). This means the on-disk layout mirrors the Amiga's struct alignment exactly, including padding.

DiskObject Header (offset $00$4E)

The first 78 bytes of every .info file contain the fixed DiskObject header. This structure never changes — it is present in all five format generations.

Offset Size Type Field Value / Notes
$00 2 UWORD do_Magic Always $E310 (WB_DISKMAGIC)
$02 2 UWORD do_Version Always 1 (WB_DISKVERSION)
$04 44 struct Gadget do_Gadget Embedded gadget — see Gadget Layout below
$30 1 UBYTE do_Type Icon type: 1=disk, 2=drawer, 3=tool, 4=project, 5=garbage, 6=device, 7=kick, 8=appicon
$31 1 UBYTE pad Undefined — usually 0
$32 4 ULONG do_DefaultTool Boolean: nonzero = string follows later
$36 4 ULONG do_ToolTypes Boolean: nonzero = string array follows later
$3A 4 LONG do_CurrentX X position in drawer, or $80000000 = NO_ICON_POSITION
$3E 4 LONG do_CurrentY Y position in drawer, or $80000000 = NO_ICON_POSITION
$42 4 ULONG do_DrawerData Boolean: nonzero = DrawerData follows (drawers/disks only)
$46 4 ULONG do_ToolWindow Boolean: nonzero = string follows (never implemented in practice)
$4A 4 LONG do_StackSize Stack size in bytes; values < 4096 default to 4096

Note

The pointer fields (do_DefaultTool, do_ToolTypes, etc.) are stored as booleans on disk, not real pointers. When icon.library reads the file, it checks each boolean: if nonzero, it reads the corresponding data block from the end of the file. This design was chosen because the original PutDiskObject simply wrote the struct verbatim — in 1985, zeroing pointer values and using their nonzero presence as a flag was the fastest serialization strategy.

Gadget Structure (offset $04$2F)

The struct Gadget embedded at offset $04 carries the icon imagery metadata. Only a few fields are meaningful on disk — the rest are undefined.

/* intuition/intuition.h — NDK39 */
struct Gadget {
    struct Gadget *NextGadget;   /* unused on disk — always 0 */
    WORD LeftEdge, TopEdge;     /* unused on disk */
    WORD Width, Height;         /* icon dimensions in pixels */
    UWORD Flags;                /* GFLG_GADGIMAGE always set; highlight mode */
    UWORD Activation;           /* unused on disk */
    UWORD GadgetType;           /* unused on disk */
    APTR GadgetRender;          /* boolean: nonzero = image 1 present */
    APTR SelectRender;          /* boolean: nonzero = image 2 (selected) present */
    struct IntuiText *GadgetText; /* unused — always 0 */
    LONG MutualExclude;         /* unused */
    APTR SpecialInfo;           /* unused */
    UWORD GadgetID;             /* unused */
    APTR UserData;              /* lower 8 bits: revision (0=old, 1=OS2.x+) */
};
Offset Size Field Meaning
$04 4 NextGadget Undefined — always 0
$08 2 LeftEdge Unused
$0A 2 TopEdge Unused
$0C 2 Width Icon width in pixels
$0E 2 Height Icon height in pixels
$10 2 Flags Bit 2 (GADGIMAGE) always set; bits 0-1 select highlight mode
$12 2 Activation Undefined
$14 2 GadgetType Undefined
$16 4 GadgetRender Boolean: nonzero = first image present
$1A 4 SelectRender Boolean: nonzero = second (selected) image present
$1E 4 GadgetText Undefined — always 0
$22 4 MutualExclude Undefined
$26 4 SpecialInfo Undefined
$2A 2 GadgetID Undefined
$2C 4 UserData Lower 8 bits: revision flag (0=OS1.x, 1=OS2.x+)

Gadget Flags (bits 0-1 of Flags at offset $10)

Bit 1 Bit 0 Mode Behavior When Selected
0 0 GADGHCOMP Invert colors within icon bounds
0 1 GADGBACKFILL Invert, then flood-fill exterior to color 0
1 0 GADGHIMAGE Show alternate (selected) image
1 1 Invalid combination

Old-Style Payload — Images, DrawerData, Strings

After the 78-byte DiskObject header, the file contains a sequence of optional data blocks in a fixed order. Each block is present only if its corresponding boolean in the header is nonzero.

graph LR
    HDR["DiskObject Header<br/>78 bytes"]
    DD["DrawerData<br/>(if drawer/disk)"]
    IMG1["Image 1 struct<br/>+ planar bitmap data"]
    IMG2["Image 2 struct<br/>+ planar bitmap data<br/>(if selected image)"]
    DEF["DefaultTool<br/>string"]
    TT["ToolTypes<br/>string array"]
    TW["ToolWindow<br/>string (rare)"]
    DD2["DrawerData2<br/>(OS2.x extension)"]

    HDR --> DD --> IMG1 --> IMG2 --> DEF --> TT --> TW --> DD2

    style HDR fill:#e8f4fd,stroke:#2196f3,color:#333
    style DD fill:#fff9c4,stroke:#f9a825,color:#333
    style IMG1 fill:#c8e6c9,stroke:#4caf50,color:#333
    style IMG2 fill:#c8e6c9,stroke:#4caf50,color:#333
    style DEF fill:#fce4ec,stroke:#e91e63,color:#333
    style TT fill:#fce4ec,stroke:#e91e63,color:#333

Image Structure

Each image is stored as a 20-byte struct Image header followed by the planar bitmap data.

/* intuition/intuition.h — NDK39 */
struct Image {
    WORD  LeftEdge;      /* always 0 on disk */
    WORD  TopEdge;       /* always 0 on disk */
    WORD  Width;         /* pixel width */
    WORD  Height;        /* pixel height */
    WORD  Depth;         /* number of bitplanes (typically 2) */
    UWORD *ImageData;    /* boolean on disk: nonzero = data follows */
    UBYTE PlanePick;     /* foreground color register mask (typically 3) */
    UBYTE PlaneOnOff;    /* background color register (typically 0) */
    struct Image *NextImage; /* always NULL on disk */
};
Offset (relative) Size Field Notes
+0x00 2 LeftEdge Always 0
+0x02 2 TopEdge Always 0
+0x04 2 Width Pixel width of image
+0x06 2 Height Pixel height
+0x08 2 Depth Number of bitplanes (usually 2 = 4 colors)
+0x0A 4 ImageData Boolean: always nonzero = data follows
+0x0E 1 PlanePick Which planes have data (bitmask, typically $03)
+0x0F 1 PlaneOnOff Background color fill (typically $00)
+0x10 4 NextImage Always 0

Immediately after this 20-byte header comes the planar bitmap data: Depth bitplanes, each ceil(Width / 16) × Height words in size. The width is always rounded up to the next 16-pixel boundary.

Image data size formula:

bytes = Depth × ((Width + 15) / 16) × 2 × Height

For a typical 4-color (2-plane) icon at 52×22 pixels: 2 × ((52+15)/16) × 2 × 22 = 2 × 4 × 2 × 22 = 352 bytes

Planar Bitmap Encoding

The image data uses Amiga's native interleaved planar format (same as ILBM). Each bitplane is stored as a sequence of UWORD values, MSB first. Within each UWORD, the leftmost pixel is bit 15.

Plane 0:  bits select color register bit 0 (colors 0, 1)
Plane 1:  bits select color register bit 1 (colors 0, 2)

Pixel value = (plane1_bit << 1) | plane0_bit

0 = background    1 = color 1 (detail)
2 = color 2       3 = color 3 (fill)

The actual colors displayed depend on the Workbench palette — the icon file does not store a palette in old-style format.

DrawerData Structure

Present only when do_DrawerData boolean is nonzero (disk and drawer icons). Stores window geometry for the drawer view.

Offset (relative) Size Field Notes
+0x00 48 struct NewWindow Window position, size, min/max dimensions
+0x30 4 dd_CurrentX Scroll X offset of drawer contents
+0x34 4 dd_CurrentY Scroll Y offset of drawer contents

If the icon's Gadget.UserData lower byte is 1 (OS 2.x+), an extended DrawerData2 follows:

Offset (relative) Size Field Notes
+0x00 4 dd_Flags Bit 0 = show icons, Bit 1 = show all files
+0x04 2 dd_ViewModes 0=default, 1=by icon, 2=by name, 3=by date, 4=by size, 5=by type

Text Storage Format (DefaultTool, ToolTypes, ToolWindow)

All strings in old-style icons use a length-prefixed format:

Offset  Size   Field
+0x00   4      ULONG tx_Size    (length including NUL terminator)
+0x04   N      char[] tx_Text   (the string, NOT NUL-padded)
+0x04+N 1      UBYTE  tx_Zero   (NUL terminator)

For example, the text "Hallo" is encoded as: 00 00 00 06 48 61 6C 6C 6F 00

ToolTypes use the same string format, but are preceded by a count value:

Offset  Size   Field
+0x00   4      ULONG tt_Count   (encoded as (numEntries + 1) × 4)
+0x04   ...    String[0]        (length-prefixed, as above)
...     ...    String[1]
...     ...    ... (last entry is always an empty string: 00 00 00 01 00)

Note

The ToolTypes count is encoded as (number of entries + 1) × 4. This unusual encoding is an artifact of how the original BCPL-era code stored arrays — it represents the BCPL pointer arithmetic for array traversal. For 3 entries: (3+1)×4 = 16 = $00000010.


MagicWB Icons (Stefan Stuntz / SASG, 1993)

Real MagicWB disk icon (8-color planar)

MagicWB is not a separate binary format — it uses the same old-style planar icon structure. What makes MagicWB distinctive is its standardized 8-color palette and professional icon art style. Created by Stefan Stuntz (who also created MUI, the Magic User Interface), MagicWB was the first serious attempt to make the Workbench look professional.

The MagicWB Palette

MagicWB standard 8-color palette

MagicWB uses 3 bitplanes (8 colors) instead of the standard 2 bitplanes (4 colors). The palette was carefully designed to work on both OCS (4-bit per gun) and AGA (8-bit per gun) hardware:

Color Index Name R G B Typical Use
0 Fill 149 149 149 Window/icon background (grey)
1 Detail 000 000 000 Text and outlines (black)
2 Shine 255 255 255 Highlights and specular (white)
3 Halfshine 203 203 203 Top gradient / raised surfaces
4 Shadow 080 080 080 Bottom gradient / recessed surfaces
5 Halfshadow 123 123 123 Mid-gradient between halfshine and shadow
6 Accent (Pink) 175 170 175 Special highlights / error states
7 Accent (Blue) 123 123 200 Links / selected text / special items

Why MagicWB Was Revolutionary

Before MagicWB, Workbench icons used an arbitrary 4-color palette that varied between systems. A disk icon created on one Amiga might look completely different on another user's machine. MagicWB solved this by standardizing the palette so icons would look identical everywhere. The "MagicWB-Demon" background process managed color reallocation when switching screen modes or depths.

Detecting MagicWB Icons

There is no binary marker for MagicWB icons — they are structurally identical to old-style planar icons. To detect one programmatically:

  1. Check if the image depth is 3 (8 colors)
  2. Verify there is no FORM block (not ColorIcon/GlowIcon)
  3. Verify there are no IM1=/IM2= ToolTypes (not NewIcons)
  4. Check if the palette matches (approximately) the MagicWB standard colors

Relationship to Other Formats

MagicWB icons can coexist with NewIcons: a single .info file can contain a MagicWB-style planar image as the old-style fallback, plus NewIcons-encoded 256-color data in the ToolTypes. This was a common pattern for software that wanted to look good on all systems.


NewIcons Extension (Nicola Salmoria, 1995)

Real NewIcons drawer icon (256-color)

The old-style format was stuck at 4 colors with no palette — icons looked drab and identical. Italian programmer Nicola Salmoria (who later created MAME, the Multiple Arcade Machine Emulator) created NewIcons in 1995 to solve this without breaking the binary format. Subsequent development was done by Eric Sauvageau and Phil Vedovatti. The solution was brilliant in its hackery: encode 256-color images as ASCII text in the ToolTypes array.

NewIcons' key innovation was backward compatibility through graceful degradation. Instead of replacing the old planar image, it added the new color image as a hidden payload in the ToolTypes. If the user had NewIcons installed, they saw the 256-color version. If not, they fell back to the old-style 4-color image. This meant .info files remained portable across all Amiga systems.

Another critical innovation was intelligent per-icon palette management. Unlike Workbench, which used a fixed system palette (making icons look wrong at different screen depths), NewIcons stored a custom palette with each icon and dynamically remapped colors to the best available match on the current screen — a form of real-time color dithering.

Note

NewIcons was released as freeware and quickly became the de facto color icon standard for AmigaOS 2.x3.1 systems. It required only a 68000 CPU and 1 MB of RAM, making it accessible to the lowest-spec Amigas.

How NewIcons Works

NewIcons appends special ToolTypes entries after the normal ones. The marker lines are:

" "
"*** DON'T EDIT THE FOLLOWING LINES!! ***"

After these markers, image data is encoded as ASCII using two prefixes:

  • IM1= — image 1 (normal/unselected)
  • IM2= — image 2 (selected, optional)

Each line is at most 128 bytes including the prefix and NUL terminator.

NewIcons Image Header (first IMx= line)

Offset Type Field Encoding
+0 UBYTE Transparency 'B' = transparent, 'C' = opaque
+1 UBYTE Width Actual width = value - $21 (so '}' = 92 px)
+2 UBYTE Height Actual height = value - $21
+3 UWORD Colors ((buf[3] - 0x21) << 6) + (buf[4] - 0x21)

Maximum dimensions: 93×93 pixels. Maximum colors: 256.

NewIcons Encoding Algorithm

The image data and palette are encoded as a 7-bit-per-byte bitstream:

Byte Range Meaning
$20$6F Represents 7-bit value 0x00$4F (80 values)
$A1$D0 Represents 7-bit value $50$7F (48 values)
$D1$FF RLE: repeat zero bits. $D1 = 1×7 zeros, $FF = 47×7 zeros

Unlike old-style icons, NewIcons stores pixel data in chunky format (one byte per pixel), not planar. Each pixel requires ceil(log2(numColors)) bits.

Warning

NewIcons is not an official format. It is not understood by OS 3.1 icon.library or by Workbench itself without the NewIcons patch. On modern systems, icon.library v44+ can decode NewIcons transparently via ICONCTRLA_SetGlobalNewIconsSupport.


OS 3.5 ColorIcons — FORM ICON IFF Block

Real OS 3.5 ColorIcon disk icon

AmigaOS 3.5 (1999, Haage & Partner) introduced the first official color icon format. Instead of hacking ToolTypes like NewIcons, the new data is stored as a standard IFF FORM ICON block appended after the old-style payload. This block is invisible to old software — icon.library v33-40 will simply ignore the trailing bytes.

Important

ColorIcon is the format; GlowIcon is the style. The FORM ICON IFF block can hold two types of image data:

  • IMAG chunks — palette-mapped chunky pixels (the original ColorIcon format, up to 256 colors)
  • ARMS chunks — raw PNG bitmaps (the GlowIcon format, true-color + alpha)

Most OS 3.5/3.9 icons use PNG (ARMS) data and are commonly referred to as "GlowIcons."

FORM ICON Structure

Offset  Size     Contents
+0x00   4 bytes  "FORM"           (IFF group identifier)
+0x04   4 bytes  ULONG size        (total size - 8, big-endian)
+0x08   4 bytes  "ICON"           (FORM type)
+0x0C   ...      IFF chunks (FACE, IMAG, etc.)

Each chunk follows standard IFF format: 4-byte tag, 4-byte big-endian size, data (padded to even length).

FACE Chunk — Icon Frame Information

Offset Size Field Description
+0x00 1 fc_Width Icon width - 1 (so $32 = 51 px)
+0x01 1 fc_Height Icon height - 1
+0x02 1 fc_Flags Bit 0 = frameless icon
+0x03 1 fc_Aspect Upper 4 bits: X aspect, lower 4: Y aspect
+0x04 2 fc_MaxPalBytes Max palette entries across images - 1

IMAG Chunk — Image Data (one per image)

Offset Size Field Description
+0x00 1 im_Transparent Transparent color index (if flag bit 0 set)
+0x01 1 im_NumColors Number of colors - 1
+0x02 1 im_Flags Bit 0 = transparent color exists, Bit 1 = palette attached
+0x03 1 im_ImageFormat 0 = uncompressed, 1 = RLE compressed
+0x04 1 im_PalFormat Palette compression (same encoding as image)
+0x05 1 im_Depth Bits per pixel (log2 of numColors)
+0x06 2 im_ImageSize Image data size - 1 (big-endian)
+0x08 2 im_PalSize Palette data size - 1
+0x0A ... Image data Chunky bytes (1 byte per pixel), optionally RLE
... ... Palette data RGB triplets (3 bytes per color)

RLE Compression (ByteRun1)

When im_ImageFormat or im_PalFormat is 1, the data uses the same PackBits RLE as IFF ILBM:

Byte Value Action
$00$7F Copy next n+1 bytes literally
$80 NOP — skip this byte
$81$FF Repeat next byte (257 - n) times (signed: n is negative)

GlowIcons / PNG Icons (OS 3.5+, 1999)

When Haage & Partner took over AmigaOS development in the late 1990s, they officially retired the old 4-color icon system. AmigaOS 3.5 (1999) debuted with the first official GlowIcon set — a revolutionary icon collection designed by professional graphic designers and inspired by the NeXTSTEP graphical user interface.

The GlowIcon Design Language

GlowIcons were a departure from everything that came before:

  • Vibrant gradients — smooth color transitions instead of flat fills
  • Soft drop-shadows — 8-bit alpha channel for pixel-perfect transparency
  • Photorealistic styling — icons looked like miniature photographs
  • The signature "glow" effect — a clear, illuminating outline on the selected-state image that indicated when an icon was highlighted

The standard GlowIcon size is 46×46 pixels — larger than the old-style icons (which were typically 2040 pixels). This gave artists room for detail without overwhelming the Workbench desktop.

Historical Timeline

Year Event
1993 Stefan Stuntz releases MagicWB — standardized 8-color icon palette
1995 Nicola Salmoria releases NewIcons — 256-color icons encoded in ToolTypes
1999 AmigaOS 3.5 ships with native GlowIcon support and a professional icon set
2000 AmigaOS 3.9 expands the built-in GlowIcon library considerably
2018 AmigaOS 3.1.4 (Hyperion Entertainment) revives classic development with GlowIcon support
2021 AmigaOS 3.2 — over 2,100 GlowIcon-style icons included on the CD-ROM as an installable option (not present on .adf floppy images)
Present AmigaOS 4.x (PowerPC) continues to use the GlowIcon standard natively

PNG Icons: The Binary Format

Real PNG icon (Amiga logo, PowerIcons format)

PNG icons embed standard PNG files inside the FORM ICON IFF block. This gives full 24-bit RGB color with an 8-bit alpha channel — the same image quality as modern desktop icons.

PNG Icon Structure

PNG icons live inside the FORM ICON block using the ARMS chunks (the chunk ID is literally the ASCII string ARMS):

FORM ICON {
    FACE chunk    (dimensions + flags — same as ColorIcon)
    ARMS chunk    (PNG image 1: unselected state)
    ARMS chunk    (PNG image 2: selected state, optional)
}

The ARMS chunk contains a complete, standard PNG file verbatim — no re-encoding. Any PNG reader can extract and decode it directly.

Extracting a PNG from a .info File

import struct

def extract_png_icons(data: bytes) -> list[bytes]:
    """Extract all PNG images embedded in a .info file."""
    pngs = []
    pos = 0
    while True:
        pos = data.find(b'ARMS', pos)
        if pos < 0:
            break
        # Read chunk size (4 bytes before the tag in IFF format,
        # but ARMS uses IFF convention: size is BEFORE the tag)
        chunk_size = struct.unpack_from('>I', data, pos - 4)[0]
        png_data = data[pos + 4 : pos + 4 + chunk_size]
        # Verify it's a valid PNG
        if png_data[:4] == b'\x89PNG':
            pngs.append(png_data)
        pos += 4
    return pngs

PNG Icon Constraints

Property Constraint
Max size Typically 46×46 to 64×64 pixels (no hard limit, but Workbench clips)
Color depth 24-bit RGB + 8-bit alpha (RGBA)
Transparency Full 8-bit alpha channel
Compression Standard PNG deflate
Interlacing Adam7 interlacing supported but discouraged (slower to load)
Gamma sRGB recommended; no gamma correction applied by icon.library

Note

The FACE chunk is still required for PNG icons — it carries the icon dimensions that Workbench uses for layout. The actual PNG dimensions must match.

What GlowIcons Look Like in Practice

Real GlowIcon examples extracted from OS 3.5 icon set

These are real GlowIcons extracted from actual .info files. Left: a Games drawer icon. The icons use smooth gradients, anti-aliased edges, and the characteristic 42×42 pixel canvas. Compare these to the flat 4-color old-style icons above to see why GlowIcons were revolutionary for the Amiga desktop.

GlowIcons were notable for their anti-aliased edges and smooth gradients. On the standard 4-color Workbench palette, these icons look like photographs compared to the flat, blocky old-style icons. The 8-bit alpha channel allows icons to have soft shadows and transparent backgrounds that blend seamlessly with any Workbench wallpaper.


API Reference — Reading and Writing Icons

The icon.library provides two API layers: the classic API (OS 1.0+, used via GetDiskObject/PutDiskObject) and the tag-based API (OS 3.5+, GetIconTagList/PutIconTagList/IconControlA). The tag-based API is required for ColorIcons and PNG icons.

Classic API (OS 1.0+)

Function LVO Description
GetDiskObject(name) -30 Load .info file into DiskObject struct
PutDiskObject(name, dobj) -36 Write DiskObject to .info file
FreeDiskObject(dobj) -42 Free a DiskObject from GetDiskObject
FindToolType(ttArray, name) -48 Look up a ToolType by name, return its value
MatchToolValue(typeStr, value) -54 Check if a value is in a pipe-separated ToolType
BumpRevision(newname, oldname) -60 Generate a unique copy name ("copy_1_of_file")
AddFreeList(fl, mem, size) -66 Add allocated memory to a FreeList
FreeFreeList(fl) -72 Free all memory in a FreeList
GetDefDiskObject(type) -78 Get the system default icon for a type
PutDefDiskObject(dobj) -84 Replace a default icon
GetDiskObjectNew(name) -90 Like GetDiskObject, but returns a default if no .info exists
DeleteDiskObject(name) -96 Delete a .info file from disk

Warning

Classic API only reads old-style and NewIcons data. It does not return ColorIcon or PNG image data. To access palette-mapped or PNG icons, use the tag-based API with GetIconTagList().

Tag-Based API (OS 3.5+, icon.library v44+)

The modern API uses TagItems for extensible parameters:

/* workbench/icon.h — NDK 3.9/3.2 */

/* GetIconTagList tags */
#define ICONGETA_GetDefaultType        (ICONA_Dummy+45)
#define ICONGETA_GetDefaultName        (ICONA_Dummy+46)
#define ICONGETA_FailIfUnavailable     (ICONA_Dummy+47)
#define ICONGETA_GetPaletteMappedIcon  (ICONA_Dummy+48)  /* request chunky data */
#define ICONGETA_IsDefaultIcon         (ICONA_Dummy+49)
#define ICONGETA_RemapIcon             (ICONA_Dummy+50)
#define ICONGETA_GenerateImageMasks    (ICONA_Dummy+51)
#define ICONGETA_Label                 (ICONA_Dummy+52)
#define ICONGETA_Screen                (ICONA_Dummy+69)

/* PutIconTagList tags */
#define ICONPUTA_NotifyWorkbench       (ICONA_Dummy+53)
#define ICONPUTA_PutDefaultType        (ICONA_Dummy+54)
#define ICONPUTA_DropPlanarIconImage   (ICONA_Dummy+56)  /* save space */
#define ICONPUTA_DropChunkyIconImage   (ICONA_Dummy+57)
#define ICONPUTA_DropNewIconToolTypes  (ICONA_Dummy+58)
#define ICONPUTA_OnlyUpdatePosition    (ICONA_Dummy+72)

/* IconControlA tags — per-icon settings */
#define ICONCTRLA_GetImageData1       (ICONA_Dummy+29)   /* chunky pixel data */
#define ICONCTRLA_GetImageData2       (ICONA_Dummy+31)
#define ICONCTRLA_SetPalette1         (ICONA_Dummy+20)
#define ICONCTRLA_GetPalette1         (ICONA_Dummy+21)
#define ICONCTRLA_GetImageMask1       (ICONA_Dummy+14)   /* transparency mask */
#define ICONCTRLA_SetFrameless        (ICONA_Dummy+32)
#define ICONCTRLA_GetWidth            (ICONA_Dummy+39)
#define ICONCTRLA_GetHeight           (ICONA_Dummy+41)
#define ICONCTRLA_IsPaletteMapped     (ICONA_Dummy+42)   /* is this a ColorIcon? */
#define ICONCTRLA_HasRealImage2       (ICONA_Dummy+44)   /* has selected image? */
#define ICONCTRLA_IsNewIcon           (ICONA_Dummy+79)   /* is this a NewIcon? */
#define ICONCTRLA_IsNativeIcon        (ICONA_Dummy+80)   /* allocated by icon.library? */

Key Function Prototypes

/* clib/icon_protos.h — NDK 3.2 (V47) */

/* Read an icon (OS 3.5+) */
struct DiskObject *GetIconTagList(CONST_STRPTR name,
    CONST struct TagItem *tags);

/* Write an icon (OS 3.5+) */
BOOL PutIconTagList(CONST_STRPTR name,
    CONST struct DiskObject *icon,
    CONST struct TagItem *tags);

/* Query/set icon properties (OS 3.5+) */
ULONG IconControlA(struct DiskObject *icon,
    CONST struct TagItem *tags);

/* Duplicate an icon with selective deep-copy (OS 3.5+) */
struct DiskObject *DupDiskObjectA(
    CONST struct DiskObject *diskObject,
    CONST struct TagItem *tags);

/* Create a new icon from scratch (OS 3.5+) */
struct DiskObject *NewDiskObject(LONG type);

/* Draw an icon at a given position/state (OS 3.5+) */
VOID DrawIconStateA(struct RastPort *rp,
    CONST struct DiskObject *icon,
    CONST_STRPTR label,
    LONG leftOffset, LONG topOffset,
    ULONG state, CONST struct TagItem *tags);

/* Get the bounding rectangle of an icon (OS 3.5+) */
BOOL GetIconRectangleA(struct RastPort *rp,
    CONST struct DiskObject *icon,
    CONST_STRPTR label,
    struct Rectangle *rect,
    CONST struct TagItem *tags);

Icon States (for DrawIconStateA)

State Constant Value Description
IDS_NORMAL 0 Unselected, normal display
IDS_SELECTED 1 Selected (highlighted) display
IDS_NORMALSELECTED 2 Partially selected (in drag)
IDS_INACTIVENORMAL 3 Unselected, window inactive
IDS_INACTIVESELECTED 4 Selected, window inactive

Practical Examples

Example 1: Creating an Old-Style Icon from Scratch

This creates a simple 4-color planar icon using the classic API. The image data is hardcoded as planar bitplane arrays — the same format stored in the .info file.

#include <exec/types.h>
#include <workbench/workbench.h>
#include <clib/icon_protos.h>
#include <proto/exec.h>
#include <stdio.h>

/* 16x16 icon, 2 planes (4 colors) — a simple folder shape */
/* Plane 0: outline */
static UWORD img_plane0[16] = {
    0x07FF, 0x0801, 0x0801, 0x0801, 0x0801, 0x0801, 0x0801, 0x0801,
    0x0801, 0x0801, 0x0801, 0x0801, 0x0801, 0x0801, 0x0801, 0x0FFE,
};
/* Plane 1: fill */
static UWORD img_plane1[16] = {
    0x07FF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF,
    0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFF, 0x0FFE,
};

static UWORD *img_data[] = { img_plane0, img_plane1 };

static struct Image icon_img = {
    0, 0,           /* LeftEdge, TopEdge */
    16, 16, 2,      /* Width, Height, Depth */
    img_data,       /* ImageData (will be adjusted below) */
    0x03, 0x00,     /* PlanePick=3, PlaneOnOff=0 */
    NULL            /* NextImage */
};

int main(void)
{
    struct Library *IconBase = OpenLibrary("icon.library", 0);
    if (!IconBase) {
        printf("Cannot open icon.library\n");
        return 20;
    }

    /* Build a DiskObject */
    struct DiskObject dobj = {0};
    dobj.do_Magic = WB_DISKMAGIC;
    dobj.do_Version = WB_DISKVERSION;
    dobj.do_Gadget.LeftEdge = 0;
    dobj.do_Gadget.TopEdge = 0;
    dobj.do_Gadget.Width = 16;
    dobj.do_Gadget.Height = 16;
    dobj.do_Gadget.Flags = GFLG_GADGIMAGE | GFLG_GADGHCOMP;
    dobj.do_Gadget.GadgetRender = &icon_img;
    dobj.do_Type = WBTOOL;
    dobj.do_DefaultTool = NULL;
    dobj.do_ToolTypes = NULL;
    dobj.do_CurrentX = NO_ICON_POSITION;
    dobj.do_CurrentY = NO_ICON_POSITION;
    dobj.do_StackSize = 4096;

    /* Write the .info file */
    if (PutDiskObject("RAM:MyTool", &dobj))
        printf("Icon created: RAM:MyTool.info\n");
    else
        printf("Failed to create icon\n");

    CloseLibrary(IconBase);
    return 0;
}

Note

The img_data pointer in icon_img.ImageData must point to a contiguous array of bitplane data. When writing to disk, icon.library serializes Depth planes, each ((Width+15)/16)*2*Height bytes in size. The planes are stored consecutively: plane 0 first, then plane 1.

Example 2: Reading an Icon and Extracting Image Data

This example reads a .info file and dumps the planar bitmap data, demonstrating how to access the raw image bytes.

#include <workbench/workbench.h>
#include <clib/icon_protos.h>
#include <proto/exec.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    struct Library *IconBase;
    struct DiskObject *dobj;
    struct Image *img;
    int plane, row, col;
    UWORD *data;

    if (argc < 2) {
        printf("Usage: %s <icon-name>\n", argv[0]);
        return 20;
    }

    IconBase = OpenLibrary("icon.library", 0);
    if (!IconBase) return 20;

    dobj = GetDiskObject(argv[1]);
    if (!dobj) {
        printf("Cannot read icon for %s\n", argv[1]);
        CloseLibrary(IconBase);
        return 20;
    }

    /* Access the first image (unselected state) */
    img = (struct Image *)dobj->do_Gadget.GadgetRender;
    if (img) {
        int words_per_row = (img->Width + 15) / 16;
        int plane_size = words_per_row * img->Height;

        printf("Image: %dx%d, %d planes\n",
               img->Width, img->Height, img->Depth);
        printf("Data per plane: %d words\n", plane_size);

        data = img->ImageData;
        for (plane = 0; plane < img->Depth; plane++) {
            printf("--- Plane %d ---\n", plane);
            for (row = 0; row < img->Height; row++) {
                printf("  ");
                for (col = 0; col < words_per_row; col++) {
                    printf("%04X ", data[row * words_per_row + col]);
                }
                printf("\n");
            }
            data += plane_size; /* advance to next plane */
        }
    }

    /* Print metadata */
    printf("Type: %d\n", dobj->do_Type);
    printf("Stack: %ld\n", dobj->do_StackSize);
    if (dobj->do_DefaultTool)
        printf("Default tool: %s\n", dobj->do_DefaultTool);

    /* Print ToolTypes */
    if (dobj->do_ToolTypes) {
        char **tt = dobj->do_ToolTypes;
        while (*tt) {
            printf("  ToolType: %s\n", *tt);
            tt++;
        }
    }

    FreeDiskObject(dobj);
    CloseLibrary(IconBase);
    return 0;
}

Example 3: Creating a PNG Icon (OS 3.5+)

This example creates a true-color PNG icon using the modern tag-based API.

#include <workbench/workbench.h>
#include <workbench/icon.h>
#include <proto/icon.h>
#include <proto/exec.h>
#include <stdio.h>

/* You would typically load this PNG data from a file */
/* Here we use a placeholder — replace with actual PNG bytes */
extern UBYTE png_normal_data[];   /* unselected PNG */
extern ULONG png_normal_size;
extern UBYTE png_selected_data[]; /* selected PNG */
extern ULONG png_selected_size;

int main(void)
{
    struct Library *IconBase;
    struct DiskObject *icon;
    LONG error = 0;

    IconBase = OpenLibrary("icon.library", 44); /* OS 3.5+ */
    if (!IconBase) {
        printf("Need icon.library V44+\n");
        return 20;
    }

    /* Create a new project icon */
    icon = NewDiskObject(WBPROJECT);
    if (!icon) {
        CloseLibrary(IconBase);
        return 20;
    }

    /* Set up chunky image data and palette via IconControlA */
    /* For PNG icons, icon.library handles PNG storage internally */
    /* when you write with PutIconTagList */
    icon->do_DefaultTool = "SYS:Utilities/MultiView";
    icon->do_StackSize = 8192;
    icon->do_CurrentX = NO_ICON_POSITION;
    icon->do_CurrentY = NO_ICON_POSITION;

    char *tooltypes[] = {
        "PUBSCREEN=Workbench",
        NULL
    };
    icon->do_ToolTypes = tooltypes;

    /* Write the icon with PNG data */
    struct TagItem put_tags[] = {
        { ICONPUTA_NotifyWorkbench, TRUE },
        { TAG_END, 0 }
    };

    /* To embed PNG data, you need to set it via IconControlA first:
     *
     * IconControlA(icon, TagItems:
     *   ICONCTRLA_SetImageData1, png_chunky_data,
     *   ICONCTRLA_SetPalette1,    palette,
     *   ICONCTRLA_SetWidth,       46,
     *   ICONCTRLA_SetHeight,      46,
     *   TAG_END);
     *
     * Then PutIconTagList will serialize it as a FORM ICON block.
     *
     * Alternatively, for direct PNG embedding, use the raw file
     * manipulation approach shown in the Python example below.
     */

    if (PutIconTagList("RAM:MyPNGIcon", icon, put_tags))
        printf("PNG icon created\n");
    else
        printf("Failed\n");

    FreeDiskObject(icon);
    CloseLibrary(IconBase);
    return 0;
}

Example 4: Embedding a PNG into a .info File (Python)

For tooling outside AmigaOS (build scripts, icon editors on macOS/Linux), you can construct a .info file directly:

import struct

def create_png_info_file(output_path: str, png_data: bytes,
                         icon_type: int = 3,  # WBTOOL
                         tool_types: list[str] = None,
                         default_tool: str = None,
                         stack_size: int = 4096):
    """Create a minimal .info file with an embedded PNG icon."""

    # --- 1. DiskObject header (78 bytes) ---
    # We need valid old-style data for backward compatibility,
    # but the real image is in the FORM ICON block.

    # Create a tiny 1x1 placeholder planar image
    placeholder_width = 1
    placeholder_height = 1
    placeholder_depth = 1
    placeholder_data = struct.pack('>H', 0x8000)  # 1 pixel set

    # Gadget structure (44 bytes)
    gadget = struct.pack('>I', 0)           # NextGadget (NULL)
    gadget += struct.pack('>hh', 0, 0)      # LeftEdge, TopEdge
    gadget += struct.pack('>hh', placeholder_width, placeholder_height)
    gadget += struct.pack('>HH', 0x0006, 0)  # Flags=GADGIMAGE|GADGHCOMP, Activation
    gadget += struct.pack('>HH', 0, 0)      # GadgetType, padding
    gadget += struct.pack('>I', 1)           # GadgetRender (boolean=1)
    gadget += struct.pack('>I', 0)           # SelectRender (boolean=0)
    gadget += struct.pack('>I', 0)           # GadgetText (NULL)
    gadget += struct.pack('>I', 0)           # MutualExclude
    gadget += struct.pack('>I', 0)           # SpecialInfo
    gadget += struct.pack('>H', 0)           # GadgetID
    gadget += struct.pack('>I', 1)           # UserData (revision=1, OS2.x+)

    # Image structure (20 bytes)
    img_header = struct.pack('>hh', 0, 0)              # LeftEdge, TopEdge
    img_header += struct.pack('>hhH', placeholder_width, placeholder_height, placeholder_depth)
    img_header += struct.pack('>I', 1)                   # ImageData (boolean=1)
    img_header += struct.pack('>BB', 1, 0)              # PlanePick=1, PlaneOnOff=0
    img_header += struct.pack('>I', 0)                   # NextImage (NULL)

    # DiskObject header
    header = struct.pack('>HH', 0xE310, 1)  # Magic, Version
    header += gadget                          # do_Gadget
    header += struct.pack('>B', icon_type)    # do_Type
    header += struct.pack('>B', 0)            # pad
    header += struct.pack('>I', 1 if default_tool else 0)  # do_DefaultTool boolean
    header += struct.pack('>I', 1 if tool_types else 0)    # do_ToolTypes boolean
    header += struct.pack('>i', 0x80000000)   # do_CurrentX = NO_ICON_POSITION
    header += struct.pack('>i', 0x80000000)   # do_CurrentY = NO_ICON_POSITION
    header += struct.pack('>I', 0)            # do_DrawerData (boolean=0)
    header += struct.pack('>I', 0)            # do_ToolWindow (boolean=0)
    header += struct.pack('>i', stack_size)   # do_StackSize

    # --- 2. Payload: image data ---
    payload = img_header + placeholder_data

    # --- 3. DefaultTool string ---
    if default_tool:
        dt_bytes = default_tool.encode('ascii') + b'\x00'
        payload += struct.pack('>I', len(dt_bytes)) + dt_bytes

    # --- 4. ToolTypes ---
    if tool_types:
        count = len(tool_types)
        encoded_count = (count + 1) * 4  # BCPL encoding
        payload += struct.pack('>I', encoded_count)
        for tt in tool_types:
            tt_bytes = tt.encode('ascii') + b'\x00'
            payload += struct.pack('>I', len(tt_bytes)) + tt_bytes
        # Empty terminator string
        payload += struct.pack('>I', 1) + b'\x00'

    # --- 5. FORM ICON block with PNG ---
    # Get PNG dimensions from IHDR chunk
    if png_data[:8] == b'\x89PNG\r\n\x1a\n':
        ihdr_width = struct.unpack('>I', png_data[16:20])[0]
        ihdr_height = struct.unpack('>I', png_data[20:24])[0]
    else:
        raise ValueError("Invalid PNG data")

    # FACE chunk
    face_data = struct.pack('>BB', ihdr_width - 1, ihdr_height - 1)
    face_data += struct.pack('>BB', 0, 0x11)  # flags=0, aspect=1:1
    face_data += struct.pack('>H', 0)         # max pal bytes
    face_chunk = b'FACE' + struct.pack('>I', len(face_data)) + face_data
    if len(face_data) % 2: face_chunk += b'\x00'  # pad to even

    # ARMS chunk (the PNG data)
    arms_chunk = b'ARMS' + struct.pack('>I', len(png_data)) + png_data
    if len(png_data) % 2: arms_chunk += b'\x00'

    # FORM ICON wrapper
    form_content = b'ICON' + face_chunk + arms_chunk
    form_block = b'FORM' + struct.pack('>I', len(form_content)) + form_content

    # --- Assemble the file ---
    data = header + payload + form_block

    with open(output_path, 'wb') as f:
        f.write(data)

    print(f"Created {output_path}: {len(data)} bytes")
    print(f"  PNG: {ihdr_width}x{ihdr_height}, {len(png_data)} bytes")

Example 5: Extracting and Visualizing Icon Images (Python)

import struct
from PIL import Image

def extract_and_render_icon(info_path: str, output_png: str):
    """Extract icon image from .info file and render as PNG."""
    with open(info_path, 'rb') as f:
        data = f.read()

    # Try PNG first (ARMS chunk)
    pos = 0
    while True:
        pos = data.find(b'ARMS', pos)
        if pos < 0:
            break
        chunk_size = struct.unpack_from('>I', data, pos - 4)[0]
        png_data = data[pos + 4 : pos + 4 + chunk_size]
        if png_data[:8] == b'\x89PNG\r\n\x1a\n':
            # Found a PNG — save it directly
            with open(output_png, 'wb') as f:
                f.write(png_data)
            print(f"Extracted PNG icon to {output_png}")
            return
        pos += 4

    # No PNG — try old-style planar
    magic = struct.unpack_from('>H', data, 0)[0]
    if magic != 0xE310:
        print("Not an icon file")
        return

    width = struct.unpack_from('>h', data, 0x0C)[0]
    height = struct.unpack_from('>h', data, 0x0E)[0]
    depth = struct.unpack_from('>h', data, 0x08 + 0x0A)[0]  # image depth

    # Standard Workbench 4-color palette (blue, white, black, orange)
    wb_palette = [
        (108, 108, 108),   # color 0: grey-blue
        (255, 255, 255),   # color 1: white
        (0, 0, 0),         # color 2: black
        (115, 82, 255),    # color 3: blue-purple
    ]

    # Find the image data (after header + optional drawerdata)
    # For simplicity, search for the image by its known width/height
    # The image struct starts right after the DiskObject header (78 bytes)
    # unless DrawerData is present

    img_offset = 78  # right after header (no drawerdata for tools)
    img_width = struct.unpack_from('>h', data, img_offset + 4)[0]
    img_height = struct.unpack_from('>h', data, img_offset + 6)[0]
    img_depth = struct.unpack_from('>h', data, img_offset + 8)[0]

    words_per_row = (img_width + 15) // 16
    data_offset = img_offset + 20  # after Image struct
    bytes_per_plane = words_per_row * 2 * img_height

    img = Image.new('RGB', (img_width, img_height))
    pixels = img.load()

    for y in range(img_height):
        for x in range(img_width):
            word_idx = x // 16
            bit_idx = 15 - (x % 16)

            color = 0
            for plane in range(img_depth):
                plane_start = data_offset + plane * bytes_per_plane
                word_offset = plane_start + (y * words_per_row + word_idx) * 2
                word = struct.unpack_from('>H', data, word_offset)[0]
                if word & (1 << bit_idx):
                    color |= (1 << plane)

            pixels[x, y] = wb_palette[color % len(wb_palette)]

    img.save(output_png)
    print(f"Rendered planar icon to {output_png} ({img_width}x{img_height}, {1 << img_depth} colors)")

Hex dump of a .info file showing the binary layout


Decision Guide — Which Format to Use

graph TD
    START["Creating an icon"] --> Q1{"Target OS version?"}
    Q1 -->|"OS 1.x"| OLD["Old-Style<br/>4-color planar"]
    Q1 -->|"OS 2.x3.1"| Q1a{"Professional look<br/>needed?"}
    Q1 -->|"OS 3.5+"| Q2{"Need true color / alpha?"}
    Q1 -->|"Cross-platform / modern"| PNG["GlowIcon<br/>PNG via FORM ICON"]

    Q1a -->|"Yes, 8 colors OK"| MWB["MagicWB style<br/>3-plane + standard palette"]
    Q1a -->|"Yes, 256 colors needed"| NI["NewIcons<br/>ASCII in ToolTypes"]
    Q1a -->|"No"| OLD

    Q2 -->|"Yes"| Q3{"Size constrained?"}
    Q2 -->|"No, 256 colors OK"| COLOR["ColorIcon<br/>IMAG chunky"]
    Q3 -->|"Yes, minimize file size"| COLOR
    Q3 -->|"No, best quality"| PNG

    style OLD fill:#e8f4fd,stroke:#2196f3,color:#333
    style MWB fill:#e3f2fd,stroke:#1565c0,color:#333
    style NI fill:#fff9c4,stroke:#f9a825,color:#333
    style COLOR fill:#c8e6c9,stroke:#4caf50,color:#333
    style PNG fill:#f3e5f5,stroke:#9c27b0,color:#333

Format Comparison Matrix

Criterion Old-Style MagicWB NewIcons ColorIcon GlowIcon/PNG
Max colors 4 8 (fixed palette) 256 256 16M (24-bit)
Alpha transparency None (1-bit mask) None (1-bit mask) None None (color key) Full 8-bit alpha
File size (46×46 icon) ~1-2 KB ~2-3 KB ~3-5 KB ~4-8 KB ~5-15 KB
Min OS version 1.0 2.0+ 2.0+ (with patch) 3.5 3.5
Official support Yes Third-party No (third-party) Yes Yes
Creator Commodore Stefan Stuntz Nicola Salmoria Haage & Partner Haage & Partner
Tool support Universal Good Limited Good Good
Quality Low Medium Medium Medium High
Use today Legacy only Legacy / retro Legacy only For compatibility Recommended

Historical Context & Competitive Landscape

The Amiga's icon format reflects a design frozen in 1985 and then patched repeatedly over 15 years. To understand why the format is so convoluted, it helps to look at what contemporary platforms were doing.

Why the Format Is So Strange

In 1985, RAM was measured in kilobytes. The fastest way to save a struct DiskObject to disk was to write its memory image directly. Pointer fields became boolean flags because the actual pointer values were meaningless outside the current process. The BCPL legacy of AmigaDOS influenced the ToolTypes count encoding ((n+1)*4).

This "memory dump" approach worked well until users demanded color icons — and by then, every tool on the platform knew the exact byte layout. Changing the header would have broken every file manager, every icon editor, every installer. So each generation added to the end instead: MagicWB reused the old format with a different palette, NewIcons hid data in ToolTypes strings, and OS 3.5 appended an IFF block.

Competitive Landscape (19851994)

Platform Icon Format Colors Transparency Key Advantage
Amiga (OS 1.0, 1985) .info memory dump 4 1-bit mask Metadata-rich (ToolTypes, stack, position)
Macintosh (System 1, 1984) ICON/ICN# resource 2 (B&W) None Simple — just a 32x32 bitmap
Atari ST (TOS, 1985) .RSC resource 2 (16 colors) None Compiled into resource fork
Windows (1.0, 1985) .ICO format 2 XOR mask Separate mask plane
Commodore 64 (GEOS, 1986) VLIR records 2 None Tiny — 24x21 pixels
Amiga (MagicWB, 1993) .info 3-plane 8 1-bit mask Standardized palette — consistent look
Amiga (NewIcons, 1995) .info + ToolTypes 256 1-bit 256 colors on OCS hardware
Amiga (OS 3.5, 1999) .info + PNG 16M 8-bit alpha True-color with alpha
Windows (98/2000) .ICO + PNG 16M 8-bit alpha Similar evolution, different container
Mac OS X (2001) .icns 16M 8-bit alpha Multiple resolutions in one file

Evolution Timeline

graph LR
    Y85["1985: OS 1.0<br/>4-color .info"] --> Y89["1989: OS 1.3<br/>DrawerData2"]
    Y89 --> Y92["1992: OS 2.0<br/>Stack size field"]
    Y92 --> Y93["1993: MagicWB<br/>8-color palette"]
    Y93 --> Y95["1995: NewIcons<br/>256-color via ToolTypes"]
    Y95 --> Y99["1999: OS 3.5<br/>ColorIcon + PNG"]
    Y99 --> Y00["2000: OS 3.9<br/>Expanded GlowIcon set"]
    Y00 --> Y19["2019: OS 3.1.4/3.2<br/>Hyperion revival"]

    style Y85 fill:#e8f4fd,stroke:#2196f3,color:#333
    style Y93 fill:#e3f2fd,stroke:#1565c0,color:#333
    style Y95 fill:#fff9c4,stroke:#f9a825,color:#333
    style Y99 fill:#f3e5f5,stroke:#9c27b0,color:#333
    style Y19 fill:#c8e6c9,stroke:#4caf50,color:#333

Modern Analogies

The Amiga .info format maps closely to modern icon systems — the concepts are identical, only the containers differ.

Concept Amiga .info Modern Equivalent Notes
Icon file myapp.info myapp.ico (Windows), myapp.icns (macOS) All are binary containers with image data + metadata
Metadata ToolTypes app.json manifest, Info.plist Key-value pairs controlling app behavior
Default tool do_DefaultTool File association registry Which program opens this file type
Stack size do_StackSize ulimit -s (Unix) Amiga stored this per-icon — more flexible
Drawer position do_CurrentX/Y .DS_Store (macOS), desktop.ini (Windows) Window geometry persistence
Old-style planar 2-bitplane bitmap 4-color GIF Palette-limited, no alpha
MagicWB style 3-bitplane + standard palette CSS theme / color scheme Not a format change — a palette standard
NewIcons ASCII in ToolTypes Base64 in JSON Clever encoding hack to avoid format changes
GlowIcon style NeXTSTEP-inspired design macOS Big Sur icon style Photorealistic, soft shadows, anti-aliased
PNG icon Embedded PNG in FORM ICON .ico with PNG payload (Vista+) Same idea: PNG as the image codec
ColorIcon IMAG chunky + palette 8-bit PNG / GIF Palette-indexed pixels
Selected image SelectRender image ICO's multiple sizes/states Amiga stored one alternate state
Frameless flag FACE.fc_Flags bit 0 macOS tintable icons Removes the square frame border

Note

Unlike modern .ico/.icns which bundle multiple resolutions (16x16, 32x32, 256x256) in one file, Amiga icons store one resolution with two states (selected/unselected). This is sufficient for Workbench's fixed-size icon display.


Best Practices & Antipatterns

Best Practices

  1. Always write a valid old-style fallback image — even if your primary icon is a PNG icon, include a tiny 2-plane planar image in the legacy position so that old software and old OS versions display something sensible instead of a garbled icon.
  2. Set do_StackSize to at least 4096 — values below 4096 are silently rounded up to 4096 by Workbench. For programs that use recursion or heavy stack, set 8192+ explicitly.
  3. Preserve do_CurrentX/Y when updating icons — if your app modifies an icon that already exists, load it first with GetDiskObject, modify only the fields you need, then PutDiskObject. Never overwrite a user's icon position.
  4. Use ICONPUTA_DropPlanarIconImage when writing PNG icons — saves disk space by not storing a redundant planar image alongside the PNG data.
  5. Free pointers before FreeDiskObject — if you replaced any pointer fields (like do_ToolTypes) on a DiskObject loaded via GetDiskObject, restore the original pointers before calling FreeDiskObject.
  6. Use PutIconTagList instead of PutDiskObject for OS 3.5+ targets — it correctly handles PNG and ColorIcon data and can notify Workbench to refresh.
  7. Store palette data with ColorIcons — if you create a ColorIcon via IconControlA, always attach palette data to the first image (im_Flags bit 1).

Named Antipatterns

"The Pointer Free"

Bad:

struct DiskObject *dobj = GetDiskObject("myapp");
if (dobj) {
    /* Replace tooltypes with our own */
    dobj->do_ToolTypes = my_new_tooltypes;  /* LEAKS original! */
    PutDiskObject("myapp", dobj);
    FreeDiskObject(dobj);  /* Frees my_new_tooltypes — wrong! */
}

Why it fails: FreeDiskObject will try to free my_new_tooltypes using the allocation tracking that was set up for the original ToolTypes. This corrupts the FreeList.

Correct:

struct DiskObject *dobj = GetDiskObject("myapp");
if (dobj) {
    char **old_tt = dobj->do_ToolTypes;
    dobj->do_ToolTypes = my_new_tooltypes;
    PutDiskObject("myapp", dobj);
    dobj->do_ToolTypes = old_tt;  /* restore before free */
    FreeDiskObject(dobj);
}

"The NewIcon Forget"

Bad:

/* Using classic API to read a NewIcon */
struct DiskObject *dobj = GetDiskObject("cool_app");
/* dobj->do_ToolTypes now contains IM1=/IM2= garbage strings */
/* Treating them as normal ToolTypes produces garbage */

Why it fails: GetDiskObject returns NewIcons data as raw ToolType strings. The IM1=/IM2= lines are binary-encoded image data, not readable text.

Correct:

/* Use the tag-based API to get decoded data */
struct DiskObject *dobj = GetIconTagList("cool_app",
    {ICONGETA_GetPaletteMappedIcon, TRUE},
    {TAG_END, 0}
);
/* icon.library v44+ strips the NewIcons lines and provides
   clean chunky image data via IconControlA */

"The Stale Position"

Bad:

/* Overwriting icon without preserving position */
struct DiskObject *icon = GetDefDiskObject(WBTOOL);
icon->do_DefaultTool = "myapp";
icon->do_CurrentX = 50;  /* clobbering user's layout! */
icon->do_CurrentY = 50;
PutDiskObject("myapp", icon);

Why it fails: Every time the app writes its icon, it resets the position, overriding any placement the user chose via Workbench drag-and-drop.

Correct:

/* Load existing, preserve position */
struct DiskObject *icon = GetDiskObject("myapp");
if (icon) {
    icon->do_DefaultTool = "myapp";
    /* Don't touch do_CurrentX/Y */
    PutDiskObject("myapp", icon);
    FreeDiskObject(icon);
} else {
    /* New icon — use NO_ICON_POSITION */
    icon = GetDefDiskObject(WBTOOL);
    icon->do_DefaultTool = "myapp";
    icon->do_CurrentX = NO_ICON_POSITION;
    icon->do_CurrentY = NO_ICON_POSITION;
    PutDiskObject("myapp", icon);
    FreeDiskObject(icon);
}

Pitfalls & Common Mistakes

1. Wrong Byte Order on Cross-Platform Parsing

All .info values are big-endian. Reading them on x86/ARM without byte-swapping is the most common mistake.

Bad:

width = data[0x0C] | (data[0x0D] << 8)  # little-endian — WRONG

Correct:

import struct
width = struct.unpack_from('>h', data, 0x0C)[0]  # big-endian

2. Forgetting the DrawerData Block for Drawer/Disk Icons

If do_Type is WBDISK (1) or WBDRAWER (2) and do_DrawerData is nonzero, the file must contain a DrawerData structure (56 bytes for OS 1.x, 62 bytes for OS 2.x+) between the header and the first image. Forgetting this shifts all subsequent offsets and corrupts parsing.

3. Miscalculating Image Data Size

The planar bitmap data is padded to 16-pixel boundaries per row. A 17-pixel-wide image stores 2 words (32 bits) per row, not 1.

Bad: data_size = width * height * depth / 8 — only works if width is a multiple of 16.

Correct: data_size = depth * ((width + 15) // 16) * 2 * height

4. Missing Image Data for SelectRender

If Gadget.SelectRender is nonzero (indicating a second image) but no second struct Image follows the first one's data, the parser will read garbage. Always verify that the selected image data is present.

5. Stack Size of Zero

A do_StackSize of 0 does not mean "no stack" — Workbench treats it as 4096 bytes (the minimum). If your program needs more stack (e.g., recursive algorithms, large local arrays), set it explicitly.

6. ToolTypes Count Encoding

The ToolTypes count is not the number of entries — it is (entries + 1) × 4. Three ToolTypes are stored as count 16, not 3.


Use Cases

Installers and Software Distribution

Every Amiga application ships with a .info file for its executable. Installers (like the standard Amiga installer) create .info files programmatically:

/* Installer creates a tool icon for the installed program */
struct DiskObject *icon = GetDefDiskObject(WBTOOL);
icon->do_DefaultTool = "SYS:MyApp";
icon->do_StackSize = 16384;  /* generous stack for safety */
char *tt[] = { "PUBSCREEN=Workbench", "FORCEFONT=topaz.font/8", NULL };
icon->do_ToolTypes = tt;
icon->do_CurrentX = NO_ICON_POSITION;
icon->do_CurrentY = NO_ICON_POSITION;
PutDiskObject("SYS:MyApp", icon);
FreeDiskObject(icon);

File Association

To associate a data file type with a program, create a WBPROJECT icon with do_DefaultTool pointing to the handler:

struct DiskObject *icon = GetDefDiskObject(WBPROJECT);
icon->do_DefaultTool = "SYS:Utilities/MultiView";
/* The file "image.iff" gets "image.iff.info" */
PutDiskObject("DF0:images/picture", icon);

Now double-clicking picture in Workbench launches MultiView.

Drawer Window Persistence

Drawers store their window geometry (position, size, scroll offset, view mode) in the .info file. When the user closes and reopens a drawer, Workbench restores the view from this data.

Well-Known Amiga Software Using Custom Icons

Software Icon Type Notable Feature
MagicWB (1993) 8-color planar Standardized palette, "MagicWB-Demon" background process
NewIcons (1995) NewIcons (256-color) Per-icon palette management, graceful degradation
Directory Opus Custom old-style + NewIcons Icon-based file manager with its own icon library
DOpus 5/Magellan ColorIcon + PNG Full desktop replacement with glowing icons
Workbench 3.5/3.9 GlowIcons (PNG) Licensed professional icon set, 46×46 pixels, NeXTSTEP-inspired
Workbench 3.1.4/3.2 Enhanced GlowIcons Hyperion revival — OS 3.2 CD includes 2,100+ icons (installable option)
AmigaOS 4.x GlowIcons (PNG) PowerPC native, same .info format
Scalos All formats Workbench replacement supporting every icon type

FAQ

Q: Can I use a JPEG instead of a PNG for an Amiga icon?

No. The ARMS chunk specifically expects PNG data. The format is hardcoded — icon.library checks for the PNG signature (\x89PNG). Use PNG only.

Q: What is the recommended icon size for modern AmigaOS?

The standard Workbench icon size is 46×46 pixels (OS 3.5+) or 52×23 pixels (old-style). OS 3.2 supports arbitrary sizes, but icons larger than 64×64 are impractical because Workbench's icon spacing is optimized for these dimensions.

Q: How do I convert a PNG to an Amiga icon?

On AmigaOS, use the IconEdit tool (OS 3.5+) or third-party tools like Personal Paint, FxPaint, or Iconian. From a host system, use the Python script in Example 4 or the icontool command-line utility.

Q: Why does my icon show a hammer symbol instead of my image?

The hammer icon is Workbench's default tool icon — it appears when a file has no .info file. This means your .info file is either missing, corrupted, or in the wrong directory. The .info file must be in the same directory as the file it describes and must have the exact name with .info appended.

Q: What happens if I delete a .info file?

The file becomes invisible on Workbench (it shows as a default tool icon with hammer symbol). The data file itself is not affected. You can recreate the .info file at any time to restore the custom icon.

Q: Can one .info file apply to multiple files?

No. The relationship is strictly one-to-one: each file needs its own .info file. However, you can use GetDefDiskObject to share default icons by type.

Q: What is the difference between MagicWB, NewIcons, ColorIcons, and GlowIcons?

These are four different approaches to better-looking icons, each building on the last:

  • MagicWB (1993, Stefan Stuntz) — Not a new format. Old-style planar icons with a standardized 8-color palette so icons look consistent across all systems.
  • NewIcons (1995, Nicola Salmoria) — 256-color images encoded as ASCII in ToolTypes. Third-party patch required on OS 2.x3.1.
  • ColorIcons (OS 3.5, 1999) — First official color format. IFF FORM ICON block with palette-mapped chunky pixels (IMAG chunks).
  • GlowIcons (OS 3.5, 1999) — The art style of OS 3.5/3.9 (NeXTSTEP-inspired, professional design). Technically uses the same FORM ICON container but with PNG images in ARMS chunks instead of palette-mapped IMAG chunks.

A single .info file can contain multiple generations simultaneously: an old-style planar fallback, NewIcons data in ToolTypes, and a FORM ICON block with PNG data.

Q: Who is Nicola Salmoria and why does that name sound familiar?

Nicola Salmoria created NewIcons in 1995. He is better known as the creator of MAME (Multiple Arcade Machine Emulator), the largest and most influential arcade preservation project in existence. NewIcons was his earlier contribution to the Amiga community.

Q: Why are GlowIcons associated with NeXTSTEP?

Haage & Partner, who developed AmigaOS 3.5/3.9, drew design inspiration from NeXTSTEP's visual language — which later became the foundation for Mac OS X/macOS. The photorealistic icon style, smooth gradients, and anti-aliased rendering of GlowIcons echo NeXTSTEP's 48×48 full-color icons, which were revolutionary for their time.

Q: How does icon.library know if a file is a tool vs. a project?

It reads do_Type from the .info file header. WBTOOL (3) means executable; WBPROJECT (4) means data file. When Workbench starts a tool via a project's default tool mechanism, it uses the project's stack size, not the tool's.

Q: Are .info files Amiga-specific?

Yes. The format is unique to AmigaOS. However, the .info extension is also used by other systems (Windows, macOS) for completely different purposes. Do not confuse Amiga .info files with Windows file information files.


References

NDK Headers

  • workbench/workbench.hstruct DiskObject, DrawerData, icon type constants (NDK 3.9: workbench.h 40.1; NDK 3.2: workbench.h 47.5)
  • workbench/icon.hICONA_#? tag constants, IconIdentifyMsg (NDK 3.2: icon.h 47.4)
  • clib/icon_protos.h — Function prototypes (NDK 3.1: V33V37 functions; NDK 3.2: V44V47 functions)
  • intuition/intuition.hstruct Gadget, struct Image, struct NewWindow

ADCD / ROM Kernel Manual

  • ADCD 2.1 → Libraries_Manual_guide/ — Icon Library chapter
  • ADCD 2.1 → Includes_and_Autodocs_3._guide/ — icon.library autodocs
  • iconexample.c — RKM Companion Workbench example (ADCD 11: Extras/Development/RKM_Companion_v2.04/Workbench/)

External Format Documentation

Cross-References

  • icon.md — icon.library API reference (DiskObject, ToolTypes, classic functions)
  • workbench.md — Workbench integration: WBStartup, AppWindow, AppIcon
  • hunk_format.md — Amiga binary format conventions (big-endian, BPTR)
  • iffparse.md — IFF chunk format used by ColorIcon FORM ICON blocks
  • bitmap.md — Planar bitmap structure and bitplane layout