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.
69 KiB
← 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:
- Old-Style Icons (OS 1.0, 1985) — A raw memory dump of
struct DiskObjectwith embedded planar bitmaps. Four colors, no palette, no transparency. Designed to be trivial to load on a 7 MHz 68000. - 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.
- 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.
- ColorIcons (OS 3.5, 1999, Haage & Partner) — The first official color icon format: an IFF
FORM ICONblock appended after the legacy data, with palette-mapped chunky pixels and run-length encoding. - GlowIcons / PNG Icons (OS 3.5+, 1999) — True-color PNG bitmaps with 8-bit alpha, embedded inside the IFF block via
ARMSchunks. 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 ICONIFF block). A GlowIcon file may contain either palette-mappedIMAGchunks or PNGARMSchunks. In practice, most GlowIcons use PNG data.
Warning
Big-Endian Format: All multi-byte values in
.infofiles 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 everyUWORDandULONG.
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+)
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. Whenicon.libraryreads 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 originalPutDiskObjectsimply 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)
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 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:
- Check if the image depth is 3 (8 colors)
- Verify there is no
FORMblock (not ColorIcon/GlowIcon) - Verify there are no
IM1=/IM2=ToolTypes (not NewIcons) - 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)
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.x–3.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.libraryv44+ can decode NewIcons transparently viaICONCTRLA_SetGlobalNewIconsSupport.
OS 3.5 ColorIcons — FORM ICON IFF Block
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 ICONIFF block can hold two types of image data:
IMAGchunks — palette-mapped chunky pixels (the original ColorIcon format, up to 256 colors)ARMSchunks — 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 20–40 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
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
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_datapointer inicon_img.ImageDatamust point to a contiguous array of bitplane data. When writing to disk,icon.libraryserializesDepthplanes, each((Width+15)/16)*2*Heightbytes 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)")
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.x–3.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 (1985–1994)
| 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/.icnswhich 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
- 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.
- Set
do_StackSizeto 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. - Preserve
do_CurrentX/Ywhen updating icons — if your app modifies an icon that already exists, load it first withGetDiskObject, modify only the fields you need, thenPutDiskObject. Never overwrite a user's icon position. - Use
ICONPUTA_DropPlanarIconImagewhen writing PNG icons — saves disk space by not storing a redundant planar image alongside the PNG data. - Free pointers before
FreeDiskObject— if you replaced any pointer fields (likedo_ToolTypes) on aDiskObjectloaded viaGetDiskObject, restore the original pointers before callingFreeDiskObject. - Use
PutIconTagListinstead ofPutDiskObjectfor OS 3.5+ targets — it correctly handles PNG and ColorIcon data and can notify Workbench to refresh. - Store palette data with ColorIcons — if you create a ColorIcon via
IconControlA, always attach palette data to the first image (im_Flagsbit 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.x–3.1.
- ColorIcons (OS 3.5, 1999) — First official color format. IFF
FORM ICONblock with palette-mapped chunky pixels (IMAGchunks). - GlowIcons (OS 3.5, 1999) — The art style of OS 3.5/3.9 (NeXTSTEP-inspired, professional design). Technically uses the same
FORM ICONcontainer but with PNG images inARMSchunks instead of palette-mappedIMAGchunks.
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.h—struct DiskObject,DrawerData, icon type constants (NDK 3.9:workbench.h 40.1; NDK 3.2:workbench.h 47.5)workbench/icon.h—ICONA_#?tag constants,IconIdentifyMsg(NDK 3.2:icon.h 47.4)clib/icon_protos.h— Function prototypes (NDK 3.1: V33–V37 functions; NDK 3.2: V44–V47 functions)intuition/intuition.h—struct 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
- Amiga Icon Formats — Dirk Stöcker (2002) — Authoritative byte-level specification for all format generations
- Amiga Workbench icon — Archive Team Wiki — Format identification and sample file links
- GlowIcons — Archive Team Wiki — GlowIcon format identification
- Icon Library — AmigaOS Documentation Wiki — Official Hyperion API documentation
- PNG Icons on Amiga OS 3.X — Jon L. Aasenden — Practical PNG icon programming
icontoolon GitHub — Command-line tool for reading/modifying.infofiles- NewIcons — Wikipedia — History of the NewIcons system
- NewIcons Review — Obligement (French, 1995) — Contemporary review of NewIcons by Laurent Camarasa
- MagicWB Features — SASG — Official MagicWB feature list
- Magic User Interface — Wikipedia — Stefan Stuntz and the MUI/MagicWB ecosystem
- AmigaOS 4.2 for Classic Amigas — Hyperion Entertainment — OS 3.2 release announcement with GlowIcon details
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 ICONblocks - bitmap.md — Planar bitmap structure and bitplane layout