Skip to content

v0.0.8

Choose a tag to compare

@MagicalTux MagicalTux released this 30 May 02:51
· 32 commits to master since this release
81d9866

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 carries ANHD.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; a 0 pointer marks the plane unchanged). Per plane
    the bitplane is split into vertical columns of width data_size,
    controlled by ANHD.bits bit 0 (0 = short 2-byte items, 1 =
    long 4-byte items); column count = row_bytes / data_size. Each
    column starts with an op_count byte (0 = column unchanged)
    followed by op_count opcode 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 — copy byte & 0x7F data
    items literally from the data list, one per consecutive row) and
    Same (0x00 byte followed by a count byte — copy one data item
    count times to consecutive rows). Advancing one row adds
    row_bytes (NOT data_size) to the byte offset within the
    bitplane. Tested in tests/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)
    and Drng::cycle_step(palette, steps) rotate the closed range
    ([low..=high] for CRNG, [start..=end] for CCRT,
    [min..=max] for DRNG) in place by steps ticks. Crng and
    Ccrt honour their reverse-direction flag (CRNG's FLAG_REVERSE,
    CCRT's direction < 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 optional DrngTrueCell /
    DrngRegCell lists, so the cell list is left untouched and callers
    layer their own splice on top after the rotation. Each helper takes
    steps modulo the range length so feeding an accumulated tick
    counter into it is O(range) regardless of how large steps grows;
    inactive cycles, malformed ranges (low > high), ranges that extend
    past the palette tail, single-slot ranges, and steps == 0 mod range_len are all no-ops and the helper returns false to signal
    "palette unchanged." Pchg::palette_at_line(base, y) returns the
    cumulative PCHG-overridden palette at the start of scanline y by
    folding every PCHG entry whose line <= y over base; out-of-range
    register indices are skipped silently to match the parser's
    tolerance. A free palette_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 == 24 the BODY carries 8 red bitplanes (LSB-first),
    then 8 green, then 8 blue per scanline with no CMAP chunk, per the
    EGFF / fileformat.info §3.3.4 description of NewTek / LightWave Toaster
    IFF24 files. Both Compression::None and Compression::ByteRun1 are
    supported (per-plane-per-row, identical to the indexed planar path);
    Compression::Auto picks the shorter of the two. Masking::HasMask
    is undefined for literal-RGB and is rejected at decode/encode time;
    the HAM / EHB CAMG flags are also rejected because they describe
    6/8-plane indexed viewports. Alpha is dropped on encode (always
    0xFF on decode) — 24-bit ILBM has no transparent-colour key. New
    MuxerMode::TrueColor24 reaches the encoder through the streaming
    IlbmMuxer API; the muxer emits a CAMG-free, CMAP-free ILBM file
    with n_planes = 24. Tested in tests/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 header min, max, rate, flags, ntrue, nregs followed
    by ntrue × DrngTrueCell (cell, r, g, b) and nregs ×
    DrngRegCell (cell, index)). A super-set of CRNG that lets the
    cycle window step through true-colour RGB samples and/or follow live
    palette registers at arbitrary positions inside [min, max].
    parse_ilbm collects every DRNG chunk into IlbmImage::drngs
    (order preserved); encode_ilbm re-emits them right after the
    CCRT block so a parse → encode is byte-stable. Accessors:
    Drng::cycles_per_second() (same rate / 16384 × 60 Hz as Crng),
    Drng::is_active(), Drng::has_true_cells() /
    Drng::has_reg_cells() (honour both the cell list and the DP_RGB
    / DP_REGS flag bits — robust against generators that set the flag
    without writing any cells), Drng::range_len(). Cell-list lengths
    are clamped to u8::MAX on encode; the parser rejects truncated
    payloads and short headers (< 8 bytes) rather than tolerating
    malformed input. Tested in tests/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) and ilbm::Ccrt (Commodore
    Graphicraft colour-cycling timing, 14-byte chunk: direction, start, end, seconds, micros, pad) parse/round-trip support.
    parse_ilbm collects every CRNG / CCRT chunk it sees into
    IlbmImage::crngs / IlbmImage::ccrts (order preserved);
    encode_ilbm re-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 × 60 Hz),
    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
    walk image.crngs / image.ccrts to apply their own palette
    rotation.

  • anim::encode_anim_op5(frames) and anim::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, v for 3..=0xFF same bytes), or
    literal ops (0x80 | cnt, then cnt bytes 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_body pointer-table
    has slot 0 = 32 + slots 1..=7 = 0 when only plane 0 dirty,
    encode_op5_body rejects > 8 colour planes with Unsupported.

  • SvxDemuxer::seek_to(stream_index, pts) — sample-exact seek across
    FORM / 8SVX bodies. 8SVX is keyframe-only pcm_s8 (Fibonacci-delta
    is decompressed at open()), so seek is a constant-time cursor
    reset over the in-memory interleaved frame buffer; the returned pts
    equals pts.clamp(0, total_frames) with no keyframe quantisation.
    Works uniformly across raw and Fibonacci-compressed bodies.
    Integration tests in tests/seek.rs cover seek-to-zero, half-second
    exact landing, past-EOF clamping, invalid stream index, and seek
    through a Fibonacci body against the demuxer-decoded reference.