Tags

Tags give the ability to mark specific points in history as being important
  • v4.0.0

    v4.0.0 - first published release (@kevcom71/pgfjs); full container 0x06 support
  • v3.10.0

    v3.10.0 - full container 0x06 support, both-direction format verification, typed API guard
  • v3.9.3

    v3.9.3 - correct the --map-palette record
  • v3.9.2

    v3.9.2 - both-direction verification for all supported formats
  • v3.9.1

    v3.9.1 - 0x06 mode-0 K schedule measured at every quality
  • v3.9.0

    v3.9.0 - full container 0x06 support
  • v3.8.2

    v3.8.2 - fix plane-record length truncation
  • v3.8.1

    v3.8.1 - fix record 0 stream-length truncation
  • v3.8.0

    v3.8.0 - close the 0x06 unequal-height tail
  • v3.7.0

    v3.7.0 - decode container version 0x06 (HL/LH interleave)
  • v3.6.0

    Stop refusing measured facts as spec gaps: five entry points corrected
    
    The bitstream module's surveying entry points spent a release refusing, as gaps in
    the published specification, facts this project had measured, implemented and
    shipped. The shipped decoder was never affected -- a full decode is byte-identical
    to 3.5.1 on all 11 decodable corpus files, with all 11 refusals unchanged -- but the
    error these functions raised asserted something false about the format.
    
    WHAT WAS WRONG. Four throws in src/bitstream.js carried `{ gap: 8 }`, and gap 8 is
    the k-adaptation rule, closed by 7.5.42. None of the four topics was that. The
    mis-keying was the smaller half.
    
    The class was the real defect. All four raised PgfUndeterminedError, which
    src/errors.js defines as "the published specification does not determine the
    behaviour required here" and explicitly NOT "we chose not to implement it". But
    section 9.1 records every paper-era gap row 0..11 as closed, including row 7 (`d`
    field width -- the position field is split around the sign bit, LSB-first) and row
    10 (macroblock serialization, all of it). The scan order is measured for every band,
    channel and pyramid level -- 8x8 blocks with clipped edges, LL/HL/LH/HH within an
    entry, channel-major, entries topmost-first (7.5.51, 7.5.53, 7.5.58) -- and shipped
    as resolveSlot/buildPlan in src/geometry.js, exercised by every conformance harness.
    Two of the messages stated the falsehood outright: "that order is established only
    for the levels = 1 LL sub-band".
    
    A fifth site, mapStreamIndexToPosition, and decodeSignificanceStream's doc comment
    carried the same framing.
    
    WHY THEY STILL REFUSE, WHICH IS THE POINT. Each entry point lacks the GEOMETRY, not
    the rule. mapStreamIndexToPosition(streamIndex) is handed one integer;
    decodeSignificanceRecord(record, context) is handed 8 bytes; bitsFor holds a fixed
    12-byte slice. A slot index means nothing without a plan -- width, height, nLevels,
    channels, quality -- so the mapping is a property of the geometry rather than of the
    record, and no further measurement can make those signatures sufficient. That is
    why the fix is neither to implement them nor to delete them.
    
    They now raise PgfUnsupportedError / PGF_UNSUPPORTED, state that the rule IS
    determined with the sections that closed it, explain why this entry point cannot
    apply it, and name the function that can -- decodeMacroblock for payloads,
    resolveSlot/buildPlan for slot-to-position, decodeToPixels for whole files. The
    `gap` key is gone from all of them; no paper-derived row applies. The "do not
    improve this function by decoding the record" warning is kept in substance, with its
    reason corrected from "the spec does not say" to "the geometry lives elsewhere and
    the shipped path already does this correctly".
    
    TWO REFUSALS STAY SPEC GAPS, and the distinction is now load-bearing in the module
    header: a bit-plane count of 0, and a byte 0 breaking the bit5 invariant. Those are
    genuinely unspecified values.
    
    ALSO CORRECTED. 7.5.57 had already measured multi-coefficient refinement packing
    (increasing slot order, LSB-first, 5/5), so bitsFor's "never been observed" was
    false on its own terms and not merely mis-classed. src/rlr.js carried the same stale
    framing in two places -- its header called the reordering "only partly recovered",
    and its kMax note omitted 7.5.106's result that the ceiling is UNREACHABLE rather
    than unknown. src/geometry.js back-referenced the bitstream notice as if current.
    Several remaining descriptions of the bit-plane count as a "low nibble" are now
    "bits 0..4", the field being five bits (7.5.86) -- except in tools/analysis/search.js,
    where `planeSource: 'nibble'` NAMES A CANDIDATE READING the tool enumerates on
    purpose, and the narrower hypothesis is the point rather than a stale claim.
    
    VERSION. A minor rather than a patch, because the error class and code change on
    five public exports: code catching PgfUndeterminedError from decodeSignificanceRecord
    would stop catching it. Those are bitstream-surveying entry points that have always
    thrown, and no decoding path is affected.
    
    619 tests, up from 618, the addition being a regression test that enumerates all
    five refusals and asserts none is a PgfUndeterminedError and none carries a `gap`,
    so the old framing cannot come back quietly. Full decode byte-identical to 3.5.1
    across the corpus; roi-check, progressive-check, formats-check (210/210 each
    direction), encode-check, and the bitmap, indexed, gray, lab and untransformed
    harnesses all pass. Recorded as SPEC-DIGEST 7.5.122, explicitly an API-hygiene
    defect rather than a decoding one.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
  • v3.5.1

    2852aaa4 · Release 3.5.1 ·
    Release 3.5.1 -- the documentation was wrong in ways that mattered
    
    A patch release with no intended behaviour change to the codec: a full decode is
    byte-identical to 3.5.0 on all 11 decodable corpus files, with all 11 refusals
    unchanged. What changed is what the project SAYS, which in several places was
    false, and one public function that refused files it should have accepted.
    
    Three false statements in the clean-room record. CLEANROOM.md claimed no PGF
    decoder binary was ever obtained or run, and that verification therefore rested on
    round-trips rather than differential comparison -- while eight harnesses invoke the
    reference decoder and the digest's strongest evidence is 48/48 files exact against
    it. docs/SOURCE-LOG.md had no entry at all for the reference binary, despite its
    own entry 1 promising one would be added when a binary was obtained; the entropy
    coder was derived through that binary. SPEC-DIGEST.md's front matter said no source
    other than the two Stamm papers was consulted, which was untrue of some 5,800 of
    its 6,325 lines. All three are corrected with explicit withdrawals rather than
    silent replacements, and the backfilled log entries are labelled as backfilled.
    Attestation clause 5 now says "compiled encoder and decoder output"; the signature
    block remains unsigned.
    
    Four defects in the published format specification, each sufficient to produce a
    broken implementation (docs/PGF-FORMAT.md 1.13): the bit-plane count documented as
    a 4-bit nibble when it is 5 bits, misdecoding every 16-bit-per-channel file; the
    retracted 45/32 deadzone constant still standing in the quantizer pseudocode, with
    the prose that corrects it directly beneath -- stale for nine document versions and
    wrong on the |v| = 359 case the v1.3 changelog names; the deliberate int32 deadzone
    overflow unspecified, without which qualities 29-31 disagree with the reference;
    and a quality-18 ceiling in the shift pseudocode, refusing 13 valid values.
    
    One real bug. parseMacroblock enforced "bit 4 clear" as an invariant after section
    7.5.86 had shown bit 4 to be the bit-plane count's high bit, so it rejected
    legitimate RGB48 and CMYK64 payloads with PGF_SPEC_GAP -- claiming the format was
    undetermined about a field this project had measured. decodeToPixels never used
    that path and was unaffected. Fixed, with a regression test; the existing test
    named for the invariant turned out to exercise only bit 5.
    
    tools/roundtrip.js ran its whole sweep on import, the only harness lacking
    import.meta.url gating.
    
    The supported quality range, stale in roughly forty places -- README, types, the
    npm description, the specification and a dozen source comments still said 0..18 or
    0..19 where the code has done 0..31 since 3.4.0, and two comments claimed quality 3
    was refused when it is among the best-verified values. Every corrected figure was
    re-derived from harness output rather than copied; two numbers no shipped harness
    produces were dropped instead of reattributed.
    
    Recorded rather than guessed: four mis-keyed gap tags in bitstream.js, types
    declarations that no type-checker has compiled, and one reference tool that cannot
    be hashed or dated, whose contribution is now stated as unknown.
    
    618 tests. roi-check, progressive-check, formats-check (420/420 each direction),
    encode-check, pgfa-quality-check (960/960 over qualities 0..31), and the bitmap,
    indexed, gray, lab, untransformed and gray8-step harnesses all pass.
  • v3.5.0

    Release 3.5.0 -- progressive partial decoding, and a region result that says no
    
    decode(buf, { maxLevel: k }) returns the image reconstructed from level-directory
    entries 0..k, at levelDimensions(width, height, nLevels-1-k). The directory is
    coarsest-first, so entry 0 alone is the coarsest thumbnail, and a buffer TRUNCATED
    after entry k's bytes is accepted -- a client that fetched only a byte prefix can
    decode what it has. For a 64x64 thumbnail of a 256x256 ROI image that is 7.9% of
    the file.
    
    Verified 576/576 prefixes coefficient-exact and 216/216 truncated buffers, across
    ROI and plain files, qualities 0/4/7, 1/3/4 channels and four geometries including
    non-power-of-two. 130x70 at three levels gives 33x18, which refutes a floor-based
    formula on its own.
    
    READ THE LIMIT HONESTLY. This is verified by REDUCTION to the already
    reference-verified full decode, not against a reference reduced decode. libpgf has
    no level-selective decode mode and refuses byte-truncated files outright, so no
    such comparison exists or can be made with this tooling. Whether libpgf's own
    hypothetical reduced decode would agree is undetermined, and recorded as such.
    
    Three findings that corrected earlier guesses:
    
      - Partial decode is NOT ROI-only. A plain file's macroblocks straddle level
        boundaries, but the straddler's payload lies wholly inside the byte range of
        the entry it starts in, so plain prefixes decode at every k (241/288 straddle,
        all exact). ROI's real advantage is that it is TIGHT: it never straddles and is
        never vacuous, where plain has 21/288 vacuous prefixes and sometimes delivers a
        whole extra level by accident. The shipped API promises only the floor.
    
      - The slot plan must be built from the header's nLevels and only then stopped
        early. A prefix-length plan collapses the per-band shift, and at quality 0
        every shift is 0 -- so that mistake is INVISIBLE there and would have shipped.
        Pinned in both directions by test.
    
      - Spatial region decode is characterised and deliberately NOT shipped. An ROI
        block is independently entropy-decodable but not independently reconstructable:
        the inverse lifting crosses block seams, and the error penetrates
        2^(nLevels-1)-1 pixels. A halo of one block at every directory entry makes the
        region bit-exact -- the minimal halo was 0 or 1 on all 1197 measured cases,
        never more, which refuted a prediction that it must grow past four levels. It
        is not shipped because the payoff is absent at the depths real files use: at
        three levels or fewer an exact region decode reads 100% of the file, an ROI file
        is larger than its plain twin, and no corpus file is ROI-coded at all.
    
    Reduced-scale chroma is the one place this returns pixels nothing can check. At
    quality >= 4 with a subsampling format the chroma is upsampled at reduced scale,
    and upsample() is measured at full size only, so those results carry
    reducedScaleChroma: 'unverified'. That is not a specification gap -- no
    implementation produces this output at all, so nothing is being guessed at -- and
    CLEANROOM.md section 6 permits an explicitly marked value as the alternative to
    refusing. Refusing would have denied reduced decoding to the most useful case, a
    lossy colour thumbnail, while improving nothing checkable.
    
    Also fixes stale capability claims that outlived the 3.4.0 quality work: README
    and PGF-FORMAT.md still said quality 19 and above was refused and undetermined,
    and errors.js still cited it as an example of an unsupported feature. The
    supported range is the whole expressible one, 0 through 31.
    
    617 tests, up from 595. Conformance unchanged where already established:
    960/960 coefficient-exact against pgfa across qualities 0..31, the ROI matrix
    green, and a full decode byte-identical to 3.4.0 on all 11 decodable corpus files
    with all 11 refusals unchanged.
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
  • v3.4.0

    Release 3.4.0 -- the full quality range, and two more colour modes
    
    Qualities 0 through 31, the whole range the format can express. The field is a UINT8
    but 31 is the highest value that means anything: the reference clamps any higher
    request to 31.
    
    Reaching it needed a change of method rather than more brute force. A deep
    coefficient's magnitude is bounded by the SAMPLE range, so 31-bit samples survive a
    shift near 30 where 8-bit samples survive 8: quality 28 is measured on a 64 KiB probe,
    where the old approach of doubling the image per quality would have needed 330 TiB for
    31.
    
    Qualities 29 to 31 also required emulating a defect in the reference, deliberately. It
    computes its deadzone threshold in 32-bit signed arithmetic, which overflows at a
    divisor of 2^29 and wraps negative, switching the deadzone off. This encoder
    reproduces that, because the alternative is writing files the reference disagrees with;
    it is commented as a defect rather than a rule, and pinned by a test.
    
    Two more colour modes: modes 5 and 6 as 'untransformed5' and 'untransformed6' --
    reconstruction measured, identity deliberately not asserted, since the two are
    byte-identical to each other and to already-named paths. Sixteen colour models over
    twenty measured (bpp, channels, mode) triples.
    
    Two overflow fixes, both latent behind bounds that hid them: the pyramid-depth clamp's
    shift was masked to five bits, so 32 levels wrote 32 on an image supporting 9; and the
    quantizer's divisor went negative at shift 31. The encoder's level bound now matches
    the decoder's, so it can write the depths the reference chooses.
    
    kMax leaves the undetermined list by being shown impossible rather than measured. The
    reserved header bytes are characterised as an encoder constant, ignored on read.
    
    595 tests. Conformance unchanged where already established.
  • v3.3.0

    Release 3.3.0 -- quality 19
    
    Quality 19 is now decoded and encoded, extending the measured range to 0..19.
    Purely additive; it was previously refused.
    
    K = q - 2 confirmed at 19 over four independent probe patterns, with both
    neighbouring values excluded on every one. Decoding verified end to end: our decode
    of the reference's own 9-level quality-19 file matches the reference decoder on every
    pixel of a 2560x2560 image.
    
    The probe needed to be sixteen times smaller in area than estimated: a constant
    image holds the coarsest LL band at 127, which is not the ceiling -- a 512-pixel
    checkerboard reaches 229, surviving one further shift step.
    
    Quality 20 now has a precise requirement rather than an estimate: 10 pyramid levels,
    so a probe at least 5120 pixels on its short side.
    
    573 tests. Conformance unchanged.
  • v3.2.0

    Release 3.2.0 -- ROI coding, decode and encode
    
    Region-of-interest coding, the last wholly unanalysed part of the format, is now
    supported in both directions. ROI changes only how a level's coefficients are
    partitioned into segments; everything else is identical to ordinary coding, which
    1728 matched pairs establish directly.
    
      decode  1728/1728 against the reference decoder, and ROI == non-ROI
      encode  1728/1728 coefficient-exact against the reference's own ROI file
    
    Zero-level ROI is refused: the reference segfaults on such files, so writing one
    would produce output no other implementation can read.
    
    Three bitstream corrections came out of it, none ROI-specific -- a one-word payload
    is legal, the RAW record's slot field floors, and the RLR terminator's sign bit is 1
    rather than 0. The last changes this encoder's output bytes for every format; files
    from earlier versions remain valid and readable, and decoding is unaffected.
    
    Not byte-identical to the reference: it writes two reserved header bytes whose
    meaning is unmeasured and which this encoder declines to imitate, and its
    significance streams are one bit longer than minimal on textured images. On a flat
    image, output is byte-identical apart from those two bytes.
    
    573 tests. Specification 1.10.
  • v3.1.0

    Release 3.1.0 -- 16- and 31-bit grayscale, and a lossy-grayscale fix
    
    IF YOU USE LOSSY GRAYSCALE, UPGRADE. 3.0.0 and earlier wrote 8-bit grayscale
    files at quality >= 1 that the reference implementation misread by roughly 34
    grey levels, roughly uniformly. One wrong per-format flag: the quantization
    constant's step at quality 4 belongs to formats that subsample chroma, and
    grayscale has a single channel. Mean error at quality 4 falls from 34.05 to 1.24.
    Lossless grayscale and all other formats were unaffected; lossy grayscale files
    already written should be re-encoded.
    
    Adds 16-bit grayscale (mode 10) and 31-bit grayscale (mode 18, which is bpp 32
    with usedBitsPerChannel 31), taking the codec to fourteen formats. Both return
    native samples rather than being reduced to 8 bits. 31-bit content needing 32
    bit-planes is refused, the plane-count field holding only five bits and the
    reference breaking rather than erroring there.
    
    Verified against the reference as a black box: 60/60 and 60/60 at quality 0,
    120/120 each way lossy, 400/400 coefficient-exact, and the twelve existing
    formats unaffected. 544 tests. Specification 1.8.
  • v3.0.0

    Release 3.0.0 -- twelve pixel formats
    
    Every colour model PGF defines is now implemented, decode and encode: bitmap,
    grayscale, indexed, RGB, CMYK, L*a*b, RGB48, L*a*b48, CMYK64, RGBA, RGB12 and
    RGB16. Lossy for all but bitmap and indexed, which are quality-0 only because
    their samples cannot survive quantization meaningfully.
    
    MAJOR for two behaviour changes, both defect fixes: the colour-mode byte is now
    a whitelist gate, so a file with an unrecognised mode on a known geometry refuses
    instead of being rendered as RGB; and the encoder writes nLevels = 0 below
    min(w,h) = 10, changing output bytes for small images and fixing files libpgf
    misread.
    
    Verified against libpgf as a black box: 210/210 and 210/210 at quality 0 both
    directions, 420/420 each way lossy, 120/120 and 450/450 coefficient-exact
    against the reference encoder, 527 tests.
    
    Still refused: container version 0x06 (frozen), quality 19+, grayscale at 16 and
    31 bpp, ROI. The indexed palette's entry order is documented, not measured.
  • v2.2.0

    Release 2.2.0 — qualities 10..18
    
    K = q - 2 measured contiguously from quality 4 to 18 by solving for the shift
    from the reference encoder's own quantized coefficients. Decode and encode both
    cover 0..18; 54/54 agreement with the reference encoder and decoder at 10..18.
    
    Quality 19+ stays refused: the shift stops being observable below an 8192x8192
    probe, not because the format stops there.
  • v2.1.1

    Release 2.1.1 — divisor-256 deadzone threshold measured
    
    The last undetermined quantizer input is now measured, and corrects 2.1.0:
    the threshold at divisor 256 is 359, not 360, so the deadzone ratio is 7/5
    rather than 45/32.
    
    All eight reachable divisors are now measured directly; the quantizer contains
    no extrapolated value.