v0.0.8
Other
- ANIM op-7 (Short / Long Vertical Delta) decode
- palette-cycling step helpers + per-line PCHG palette resolver
- 24-bit literal-RGB true-colour decode + encode
- DRNG DPaint IV extended range cycling chunk
- CRNG (DPaint colour-range) + CCRT (Graphicraft) chunks
- ANIM op-5 Byte Vertical Delta encoder
- add Demuxer::seek_to — sample-exact O(1) cursor reset
Added
-
ANIM op-7 (Short / Long Vertical Delta) decode. When a delta
frame carriesANHD.operation = 7, the running planar state is
patched in place by walking the DLTA chunk's 16 big-endian u32
pointer table (8 opcode-list pointers + 8 data-list pointers, one
pair per plane; a0pointer marks the plane unchanged). Per plane
the bitplane is split into vertical columns of widthdata_size,
controlled byANHD.bitsbit 0 (0= short 2-byte items,1=
long 4-byte items); column count =row_bytes / data_size. Each
column starts with anop_countbyte (0 = column unchanged)
followed byop_countopcode bytes; the three opcode classes are
Skip (hi bit clear, non-zero — forward the dest cursor by N rows,
no data consumed), Uniq (hi bit set — copybyte & 0x7Fdata
items literally from the data list, one per consecutive row) and
Same (0x00byte followed by a count byte — copy one data item
counttimes to consecutive rows). Advancing one row adds
row_bytes(NOTdata_size) to the byte offset within the
bitplane. Tested intests/anim_op7_decode.rs(6 tests): short
Skip + Uniq + Same exercise across all 4 columns of a 1-plane
64×4 image, long-data (4-byte item) exercise across a 1-plane
64×3 image, all-zero pointer table leaves state untouched,
truncated pointer table errors, out-of-range opcode pointer
errors, two-plane independent pointer-pair lookup. Op-7 encode +
op-8 decode/encode remain open follow-ups. -
ILBM palette-cycling step helpers + per-scanline PCHG resolver.
Crng::cycle_step(palette, steps),Ccrt::cycle_step(palette, steps)
andDrng::cycle_step(palette, steps)rotate the closed range
([low..=high]forCRNG,[start..=end]forCCRT,
[min..=max]forDRNG) in place bystepsticks.Crngand
Ccrthonour their reverse-direction flag (CRNG'sFLAG_REVERSE,
CCRT'sdirection < 0); DRNG cycles forward only (its wire format
has no direction flag) and the in-tree DRNG spec material does not
define per-tick semantics for the optionalDrngTrueCell/
DrngRegCelllists, so the cell list is left untouched and callers
layer their own splice on top after the rotation. Each helper takes
stepsmodulo the range length so feeding an accumulated tick
counter into it is O(range) regardless of how largestepsgrows;
inactive cycles, malformed ranges (low > high), ranges that extend
past the palette tail, single-slot ranges, andsteps == 0 mod range_lenare all no-ops and the helper returnsfalseto signal
"palette unchanged."Pchg::palette_at_line(base, y)returns the
cumulative PCHG-overridden palette at the start of scanlineyby
folding every PCHG entry whoseline <= yoverbase; out-of-range
register indices are skipped silently to match the parser's
tolerance. A freepalette_for_line(image, y)convenience wraps the
Option<Pchg>plumbing so animation consumers can write a uniform
per-row "give me the active palette" call without branching on
whether the file carried a PCHG chunk. Tested in
tests/ilbm_palette_cycle.rs(28 tests): forward / reverse single
ticks against synthesised palettes, full-revolution identity, large
modulo step counts, inactive / single-slot / inverted-range / past-
palette no-ops, zero-step no-op, fwd-then-reverse round-trip,
CCRT-direction-zero no-op, DRNG cell-list preservation across the
rotation, PCHG before-first-override / mid-override / past-image-
height resolution, PCHG out-of-range-index tolerance, and an
end-to-end "PCHG override + CRNG rotation on top" composition test
showing the two helpers compose without interfering. -
ILBM 24-bit true-colour (literal-RGB) decode + encode. When
BMHD.n_planes == 24the BODY carries 8 red bitplanes (LSB-first),
then 8 green, then 8 blue per scanline with noCMAPchunk, per the
EGFF / fileformat.info §3.3.4 description of NewTek / LightWave Toaster
IFF24 files. BothCompression::NoneandCompression::ByteRun1are
supported (per-plane-per-row, identical to the indexed planar path);
Compression::Autopicks the shorter of the two.Masking::HasMask
is undefined for literal-RGB and is rejected at decode/encode time;
theHAM/EHBCAMG flags are also rejected because they describe
6/8-plane indexed viewports. Alpha is dropped on encode (always
0xFFon decode) — 24-bit ILBM has no transparent-colour key. New
MuxerMode::TrueColor24reaches the encoder through the streaming
IlbmMuxerAPI; the muxer emits a CAMG-free, CMAP-free ILBM file
withn_planes = 24. Tested intests/ilbm_truecolor24.rs
(12 tests): raw + ByteRun1 + Auto round-trips, Auto beats raw on a
solid fill, no-CMAP emit, full 256-value sweep per channel,
HasMask + n_planes=24 decode rejection, HAM/EHB + n_planes=24 encode
rejection, alpha-dropped-to-opaque, redundant-CMAP-tolerated decode,
indexed-encode-without-palette still rejected, end-to-end through
the streaming muxer. -
ilbm::Drng(DeluxePaint IV extended range cycling, variable-length
chunk: 8-byte headermin, max, rate, flags, ntrue, nregsfollowed
byntrue×DrngTrueCell(cell, r, g, b) andnregs×
DrngRegCell(cell, index)). A super-set ofCRNGthat lets the
cycle window step through true-colour RGB samples and/or follow live
palette registers at arbitrary positions inside[min, max].
parse_ilbmcollects everyDRNGchunk intoIlbmImage::drngs
(order preserved);encode_ilbmre-emits them right after the
CCRTblock so a parse → encode is byte-stable. Accessors:
Drng::cycles_per_second()(samerate / 16384 × 60Hz asCrng),
Drng::is_active(),Drng::has_true_cells()/
Drng::has_reg_cells()(honour both the cell list and theDP_RGB
/DP_REGSflag bits — robust against generators that set the flag
without writing any cells),Drng::range_len(). Cell-list lengths
are clamped tou8::MAXon encode; the parser rejects truncated
payloads and short headers (< 8bytes) rather than tolerating
malformed input. Tested intests/ilbm_drng.rs(13 tests): empty
cell lists, true-cell-only, reg-cell-only, both lists together,
multi-chunk order preservation, byte-stable re-encode, accessor
corner cases (inactive, zero rate, inverted range, flag-without-
cells), short / truncated payload rejection, and a mixed
CRNG + CCRT + DRNG single-file round-trip. -
ilbm::Crng(DeluxePaint colour-range cycling, 8-byte chunk:
pad1, rate, flags, low, high) andilbm::Ccrt(Commodore
Graphicraft colour-cycling timing, 14-byte chunk:direction, start, end, seconds, micros, pad) parse/round-trip support.
parse_ilbmcollects everyCRNG/CCRTchunk it sees into
IlbmImage::crngs/IlbmImage::ccrts(order preserved);
encode_ilbmre-emits them between PCHG and BODY so a parse →
encode is byte-stable. Each struct exposes spec-derived accessors
—Crng::cycles_per_second()(rate / 16384 × 60Hz),
Crng::is_active(),Crng::is_reverse(),Crng::range_len();
Ccrt::delay_seconds()(seconds + micros/1e6),Ccrt::is_active(),
Ccrt::is_reverse(),Ccrt::range_len(). Inverted ranges
(low > high/start > end) and out-of-spec negative timing
components clamp to safe defaults rather than wrap. Tested in
tests/ilbm_crng_ccrt.rs(13 tests): single-chunk round-trip,
multi-chunk order preservation, byte-stable re-encode, inactive /
reversed flags, short-payload rejection, mixed CRNG+CCRT,
unknown-chunk skipping. No animation is performed; consumers
walkimage.crngs/image.ccrtsto apply their own palette
rotation. -
anim::encode_anim_op5(frames)andanim::encode_op5_body(prev, cur, bmhd)— ANIM op-5 (Byte Vertical Delta) encoder. Walks each
plane's columns top-to-bottom; emits skip ops (1..=0x7F rows
unchanged), repeat ops (0x80, cnt, vfor 3..=0xFF same bytes), or
literal ops (0x80 | cnt, thencntbytes for 1..=0x7F differing
bytes), splitting at run-length caps. Pointer table populates only
the plane slots that actually carry deltas; identical frames yield
a 32-byte BODY (just the empty table). Tested in
tests/anim_op5_encode.rs(10 tests): identical-frame trivial
case, sparse 4×4-corner delta round-trip, sparse-delta byte-count
beats op-0 by ≥ 20 % on 64×64, long skip-run (300 rows) crosses
the 0x7F cap correctly, long repeat-run (300 rows) crosses the
0xFF cap correctly, 2-bitplane indexed round-trip, 4-frame
bouncing-dot sequence pixel-exact,encode_op5_bodypointer-table
has slot 0 = 32 + slots 1..=7 = 0 when only plane 0 dirty,
encode_op5_bodyrejects > 8 colour planes withUnsupported. -
SvxDemuxer::seek_to(stream_index, pts)— sample-exact seek across
FORM / 8SVXbodies. 8SVX is keyframe-onlypcm_s8(Fibonacci-delta
is decompressed atopen()), so seek is a constant-time cursor
reset over the in-memory interleaved frame buffer; the returned pts
equalspts.clamp(0, total_frames)with no keyframe quantisation.
Works uniformly across raw and Fibonacci-compressed bodies.
Integration tests intests/seek.rscover seek-to-zero, half-second
exact landing, past-EOF clamping, invalid stream index, and seek
through a Fibonacci body against the demuxer-decoded reference.