v4.0b Windows & Linux
v4.0b (2026-04-27) — format simplification + diagonal DC neighbor context
v4.0b is the new starting point of the post-v4.0 lineage. It collapses
the three-format dispatch (v4.0 + v4.0a + v3.1d-via-legacy) into a single
accepted version byte (0x28/ 40) plus an optional sub-marker (0x02)
that flags the new diagonal DC neighbor context. The-legacyflag is
removed; v3.1d archives are no longer decoded by this build.Versioning policy (new):
N.0xreleases (4.0, 4.0a, 4.0b, …) are
LTS-style with binary filenamepackJPG, bug-fix only after their
initial drop.N.Mxreleases (4.1, 4.1a, 4.2, …) are feature-bearing
with binary filenamepackJPG-N.Mx, format breaks land here. v4.0b is
a one-time exception — it carries the diagonal DC change that was
originally tagged v4.1 (never released publicly), rebranded so the v4.1
slot stays available for a real feature drop.The diagonal DC change adds a 4-bucket variance context computed from
|L − T| + |T − TR|(absolute values of the already-encoded left, top
and top-right DC neighbors). It captures directional gradient patterns
that the existing weighted-average context blends away. Result: a small
but consistent ratio win at neutral wall time.Inspired by the SITX (StuffIt JPEG) reverse-engineered codebase shared
by Melirius on encode.su. SITX dequant achieves ~5 % better than
packJPG via ensemble-blended context models (4 parallel histograms with
weights 8/6/4/2); packJPG's single-context arith coder can't replicate
that without a major refactor. Of three SITX-inspired ideas tested
(diagonal context, multi-resolution variance, zigzag-position AC priors)
only the diagonal context paid off — multi-resolution variance
correlated too tightly with the existingctx_len, and zigzag AC priors
spread statistics too thin across the well-tuned AC bit-length model.
Format
- new: 0x02 sub-marker before the version byte (
0x28) signals
v4.0b features. Decoder semantics:JS 28 …→ v4.0 / v4.0a file,pjg_use_diag_dc_now = false
(old DC context, full backward-compat decode of legacy archives)JS 02 28 …→ v4.0b file,pjg_use_diag_dc_now = true
(new diagonal DC context active)- v4.0 / v4.0a binaries reading a v4.0b file see
0x02, fall through
to "unknown header code" → clean error, no silent corruption.
- removed:
-legacyCLI flag andformat_version_legacy = 31/
format_version_v40_compat = 40constants. Single accepted format byte. - removed:
legacy_modeTHREAD_LOCAL global and its MT-worker propagation.
Encoder / decoder
- new: diagonal/anti-diagonal DC neighbor context in
pjg_encode_dc
andpjg_decode_dc. 4 buckets from|L − T| + |T − TR|of the
absolute-value neighbors, multiplyingmod_len_maxcby 4 (or 32 when
stacked with cross-component). Gated bypjg_use_diag_dc_now. - the v4.0a cross-component DC prediction stays on permanently in the
sequential encode/decode path. Sfth workers keep it off (no Y available
during parallel Cb/Cr encoding) — unchanged from v4.0.
Repository
- renamed
source4xp/→sourcelegacy/andbuild4xp.sh→build_legacy.sh
(Win XP/Win7/Win8 community-maintained port now lives here; documents
the boundary explicitly:source/targets Win10+/Linux/macOS via
std::filesystem,sourcelegacy/targets legacy Windows via Win32 API
throughxp_compat.h). source/Win-version-specific comments updated to reflect Win10+ scope.
Bench
43 mixed JPGs / 10.86 MB on Linux x64:
| Output | Ratio | Time | |
|---|---|---|---|
| v4.0a | 6,730,670 B | 65.574 % | 12.09 s |
| v4.0b | 6,727,753 B | 65.566 % | 12.02 s |
Δ: −0.043 % bytes, 0.994× wall time, all round-trips byte-exact. All
v4.0/v4.0a-encoded files in the test corpus decoded byte-exactly with
v4.0b (backward-compat verified).
Migration notes
- Existing v4.0 / v4.0a
.pjgarchives keep working — v4.0b reads them
transparently. - v4.0b-encoded
.pjgfiles are NOT readable by v4.0/v4.0a binaries
(clean rejection, no crash). - v3.1d archives need the original v3.1d binary (or v4.0 with
-legacy,
for which a v4.0a build is still archived indist/of the v4.0a tag).