Ink Modes
All Director sprite ink modes with numeric IDs, pixel semantics, hit-testing implications, and imaging Lingo ink usage.
What ink is
Ink is per-sprite operation state: it changes how the sprite's source pixels combine with the pixels behind it at composite time. Ink is not baked into the cast member; the same member can render with different inks in different sprites simultaneously. (The native renderer keeps source pixels, coverage, key state, ink id, and blend as separate inputs until the final compositing callback; see Native rendering.)
sprite(5).ink = 8 -- Matte
sprite(5).ink = 36 -- Background Transparent
image.copyPixels(src, dstRect, srcRect, [#ink: 8, #blendLevel: 128])The ink table
| ID | Name | Pixel behavior |
|---|---|---|
| 0 | Copy | Source drawn opaquely (32-bit alpha respected when useAlpha). Fastest. Default. |
| 1 | Transparent | Light/white pixels become transparent (palette-era rule: background-color pixels of 1-bit art). |
| 2 | Reverse | Source inverts overlapping destination pixels; white source pixels are transparent. XOR-family. |
| 3 | Ghost | Like Reverse but only where source overlaps dark destination; elsewhere transparent. |
| 4 | Not Copy | Invert source first, then Copy. |
| 5 | Not Transparent | Invert source, then Transparent. |
| 6 | Not Reverse | Invert source, then Reverse. |
| 7 | Not Ghost | Invert source, then Ghost. |
| 8 | Matte | Removes the white region connected to the bitmap's bounding edge (the "matte"); interior artwork opaque. Changes the mouse hit area to visible pixels. |
| 9 | Mask | Uses the NEXT cast member slot as a mask: black opaque, white transparent, grays partial. |
| 32 | Blend | Uniform opacity from the sprite blend percentage. |
| 33 | Add Pin | dst + src per channel, clamped at 255. |
| 34 | Add | dst + src per channel, wrapping (mod 256). |
| 35 | Subtract Pin | dst - src per channel, clamped at 0. |
| 36 | Background Transparent | Pixels matching the sprite's backColor key become transparent; rest opaque. |
| 37 | Lightest | max(dst, src) per channel. |
| 38 | Subtract | dst - src per channel, wrapping. |
| 39 | Darkest | min(dst, src) per channel. |
| 40 | Lighten | Background acts as brightness filter; foreColor tints ("color filter" family). |
| 41 | Darken | Background darkens through the source; foreColor added like colored light ("color filter" family). |
IDs 10-31 are unused gaps from the QuickDraw transfer-mode heritage. The imaging Lingo copyPixels #ink parameter accepts the same IDs/symbols.
Semantics that content depends on
Matte (8)
- The matte is computed from the source bitmap: white pixels connected to the border are removed; enclosed white areas stay opaque. It is not "all white is transparent".
- Matte state is coverage data derived from the member, cached per member, and applied at composite time. Native evidence: 4-bit and 8-bit sources have dedicated coverage-converter paths where stored bytes act as coverage, not visible paint (details).
- Matte changes hit testing: mouse events trigger on the visible area only.
createMatte()(imaging Lingo) builds the same coverage for an image object.
Background Transparent (36)
- The transparent key is the sprite's
backColor(default white). It is a keying rule, not a recoloring rule: surviving pixels keep their source colors. - For indexed (paletted) sources, the key comparison happens against the resolved key color; palette index 0 acts as the default key while the source retains index provenance (native finding).
- Anti-aliased edges against white produce halos; that is authentic behavior, not an emulator bug.
Blend (32) and the blend property
blend(0-100) maps to an internal 0-256 alpha scale (native formula:src*alpha + dst*(256-alpha)fixed-point).- Blend combines with other inks: a Matte sprite at
blend = 50composites its matte-covered pixels at half opacity. The native arithmetic-ink callbacks ignore blend (Add/Subtract/Darkest/Lightest write RGB results directly); sprite blend does not attenuate arithmetic inks.
Arithmetic family (33/34/35/37/38/39)
- Pin variants saturate; non-pin variants wrap modulo 256, producing visible wraparound artifacts that some content exploits deliberately.
- Operate on RGB channels independently. For 8-bit grayscale sources with index provenance, the native path feeds storage bytes as luminance operands, not palette-visible RGB.
Mask (9)
- The mask is the cast member in the next slot after the sprite's member. Black = opaque, white = transparent, gray = partial.
- For low-depth masks Director interprets grayscale-style coverage regardless of the display palette.
- Partial mask coverage uses its own blend formula distinct from uniform sprite blend (native: shifted products with
coverage xor 0xFFas the destination complement). - Imaging Lingo
#maskImage/createMask()provide the same per-blit.
Reverse/Ghost/Not family (2-7)
QuickDraw XOR heritage; on direct-color displays Director approximates the classic 1-bit behavior channel-wise. Rarely load-bearing in games except for selection rectangles and "invert" highlights.
Lighten/Darken (40/41)
Color-filter inks: source RGB is scaled/offset by backColor/foreColor acting as filter parameters. Native path applies a fixed-point scale and offset and writes the result with the source alpha byte (no alpha-over compositing of the filtered result).
Ink and hit testing
| Ink | Mouse hit area (documented) |
|---|---|
| Matte | Visible (matte-covered) pixels only |
| All others | Bounding box |
The documented rule is coarse; per-title behavior around 32-bit alpha and alphaThreshold adds nuance (alphaThreshold gates hits on 32-bit members with useAlpha). Emulators should not make Background Transparent or Blend sprites click-through just because pixels ended up transparent; that diverges from native behavior. Open questions and native probe status: Input and hit testing.
Performance notes
Copy is the fast path (opaque blit). Matte and Mask cost matte/mask coverage generation and per-pixel tests; classic advice was to avoid them on large or animated sprites, so shipped content uses Background Transparent heavily (cheaper: single key compare). Emulators can implement everything as shaders, but must preserve the coverage/key semantics above rather than converting sources to premultiplied RGBA at load time; that early flattening is the root cause of a whole family of emulator rendering bugs (wrong halos, vanished white UI, broken arithmetic inks).