Tags give the ability to mark specific points in history as being important
-
-
v3.10.0
2e386f17 · ·v3.10.0 - full container 0x06 support, both-direction format verification, typed API guard
-
-
-
-
-
-
-
-
-
v3.6.0
7fc1e8bd · ·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 -- 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
5498af3c · ·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
082c155c · ·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
33c062dd · ·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
2a5e9a86 · ·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
d19ea320 · ·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
218bc877 · ·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
eaaa27c0 · ·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
38cbb9a3 · ·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.