Releases: YadeWira/packPNG
packPNG 2.0 LTS
First long-term-support release. Lossless, byte-exact recompressor for the
PNG family — PNG / APNG / JNG / MNG. The default backend undoes the deflate exactly
(preflate) and re-stores the image with WebP-lossless, reconstructing the
byte-identical original file (same SHA-256).
Frozen format
From 2.0 onward the wire format is frozen — every 2.0x point release decodes
any other 2.0x's output. Magics: TCIP (default, preflate + WebP-lossless),
TVCP (-fast, kanzi+zstd), TMCP (-preflate-max, kanzi-TPAQX),
TPCL (-tpcl, preflate + LZMA2), TCIJ (JNG), TCIM (MNG).
Highlights since v1.9
- TPCL (
-tpcl): preflate + multi-threaded LZMA2 — precomp's recipe, but beats
precomp on ratio and batch speed (newer preflate). - MNG Level B: parallel per-segment recompression (large MNGs ~4× faster encode).
- libpackPNG: static library + C API (
packpng.h) —make lib/make lib-win. - Suite-wide never-expands guarantee; clean help screen +
-h/--help.
vs precomp (17 real PNGs, byte-exact both sides)
| ratio | encode | decode | |
|---|---|---|---|
| packPNG TCIP (default) | 45.7 % | 2.4 s | 0.7 s |
packPNG TPCL (-tpcl) |
64.3 % | 1.7 s | 0.6 s |
| precomp 0.4.8 | 75.3 % | 2.2 s | 1.0 s |
packPNG's default is ~39 % smaller than precomp — it models the image
(WebP-lossless) instead of LZMA2-ing the deflate stream. Full tables, backends and
the library API are in the wiki.
Downloads
- Linux x86-64 (static, dependency-free):
packPNG-linux-x86_64-static-v2.0 - Windows x86-64 (mingw, OS DLLs only):
packPNG-windows-x86_64-full-v2.0.exe
4d87c73f6fbef93b14a97cae511cc222d229d72f4b43d2b9403120f666b5be90 packPNG-linux-x86_64-static-v2.0
a486239e76c1d01581739ff1289fe87f93289afca29c740bf60a9cc21cee19b4 packPNG-windows-x86_64-full-v2.0.exe
The Windows
.exeis unsigned; some AVs may flag a generic heuristic (false
positive). Verify the SHA-256 or build from source.### Library (libpackPNG) — SDK archives
Static library + C API for embedding the codec, shipped as per-platform SDK
bundles (library + packpng.h + README with the link line). make lib /
make lib-win reproduce them. Static only for now (a shared .so/.dll needs PIC
rebuilds of the vendored deps). API + example: wiki → Library.
packPNG-2.0-linux-x64-lib.tar.gz— Linux x86-64 (AVX2),libpackpng.a+ headerpackPNG-2.0-win64-lib.zip— Windows x86-64 (mingw-w64),libpackpng-win.a+ header
72d2cf54b810c71238d00d9fbd805a4bbbc8efe0f32ec6f11efdbe4737682a36 packPNG-2.0-linux-x64-lib.tar.gz
55d02cb1f0040d24e09b136ec8d609b2e05a578e26b82e2002fc61e8b1c8c71e packPNG-2.0-win64-lib.zip
packPNG 2.0a — libpackPNG shared libs + in-memory API
packPNG 2.0a
LTS point release. Wire format unchanged — 2.0a decodes any 2.0x and vice-versa. This release is about the library (libpackPNG).
Motivated by an archiver-embedding request: a DLL with buffer functions (like packJPG's pjglib) and multithreading on by default.
What's new
- Shared libraries:
libpackpng.so(Linux) andpackpng.dll+ import lib (libpackpng.dll.a) /packpng.def(Windows), alongside the static.a. - In-memory API for archivers — buffer in → buffer out, no temp files needed at the call site:
packpng_compress_mem/packpng_decompress_mem/packpng_freepackpng_set_threads(n)(0 = auto)
- Multithreading ON by default in the library (auto = hardware threads).
- Verified byte-exact on Linux and Windows (file + memory API, static + DLL).
Assets
packPNG-linux-x86_64-static-v2.0a— Linux CLI (static)packPNG-windows-x86_64-full-v2.0a.exe— Windows CLIpackPNG-2.0a-linux-x64-lib.tar.gz— Linux SDK (static + shared + header)packPNG-2.0a-win64-lib.zip— Windows SDK (static + DLL + import lib + .def + header)
Library docs: https://github.com/YadeWira/packPNG/wiki/Library
SHA-256
02354ab776343ac8ff048f7053d3cadc08ebf312a37b0183332be6b23fe878b9 packPNG-linux-x86_64-static-v2.0a
d9414456f344f23bce34688f395100c62ab45a084948f353db29a6504f92b20c packPNG-windows-x86_64-full-v2.0a.exe
e7fe44bde9e758d8cc7c25320167079c06c64a331a992d3714908645bce1d997 packPNG-2.0a-linux-x64-lib.tar.gz
1fe718f06358978d866f5299820f3bbffd6ab109b36cbbceaa5831a06443b28e packPNG-2.0a-win64-lib.zip
packPNG v1.9
Lossless PNG / APNG / JNG / MNG recompressor. Byte-exact, reversible to the original file (verified via SHA-256).
Highlights
- New default backend (tovyCIP): preflate undoes the deflate byte-exact, then WebP-lossless stores the image → ≈ −54% on real PNGs with fast decode (~40–90 ms/file).
-fastkeeps the old kanzi+zstd backend for max speed;-preflate-maxadds kanzi-TPAQX for extreme ratio.- MNG support (Level A): whole-file preflate container with a store-raw fallback so output never bloats. The Network Graphics family is now complete: PNG / APNG / JNG / MNG.
- Self-contained build: all deps vendored;
makebuilds the 100%-autonomous Linux static + Windows full binaries.
v2 magic naming
Every output magic spells out its backend (this drops pre-2.0 backward compat for the solid backends; 2.0 will freeze the format):
| Magic | Expansion | Mode |
|---|---|---|
TCIP |
Tovy Compresor de Imágenes PNG | default (preflate + WebP-lossless) |
TVCP |
Tovy Veloz Compresor PNG | -fast (kanzi + zstd) |
TMCP |
Tovy Máximo Compresor PNG | -preflate-max (kanzi-TPAQX) |
TCIJ |
Tovy Compresor de Imágenes JNG | JNG inputs |
TCIM |
Tovy Compresor de Imágenes MNG | MNG inputs |
Note the swap:
TCIPnow means the preflate default (it was the kanzi backend in v1.x, nowTVCP).
Downloads
- Linux x86-64 (static, dependency-free):
packPNG-linux-x86_64-static-v1.9 - Windows x86-64 (mingw, OS DLLs only):
packPNG-windows-x86_64-full-v1.9.exe
SHA-256
6418f485943297a1397040a8d11563e4f2a69a14654b9e353c11db11bd789567 packPNG-linux-x86_64-static-v1.9
d1f2f8b94b082391cf915f1ba398b419bf4c564a9b207cefb8533939d962daf7 packPNG-windows-x86_64-full-v1.9.exe
The Windows
.exeis unsigned; some AVs may flag it as a generic heuristic (false positive). Verify the SHA-256 or build from source.
packPNG v1.8 — JNG container support
v1.8 — JNG (JPEG Network Graphics) container support
First release with JNG input support, plus a small build-system fix for
the kanzi Makefile target.
What's new
-
JNG inputs are recognised and processed. A
.jngfile is parsed into
three sections (head / image / tail) and emitted as a.ppgwith a new
internal magic,TCIJ. Round-trip is byte-exact on the full sembiance JNG
corpus (8/8 samples covering grayscale, color, deflate-alphaIDAT,
JPEG-alphaJDAA, and progressive JPEG bitstreams). -
Output stays
.ppg. No new file extension was introduced. Inside the
file, the 4-byte magic distinguishes content type:TCIP— PNG/APNG (unchanged from v1.7e)TCIJ— JNG (new in v1.8)PPG1/PPGS— legacy formats, still decode
-
Wire format (TCIJ v1): 4 B magic + 1 B version + 1 B flags + 2 B
filename length + UTF-8 filename + three (raw_size, comp_size, bytes)
sections. The image section useszstd-19 --long=27; head and tail
sections use plain zstd. Original chunk length / type / data / CRC bytes
survive untouched, so byte-exact roundtrip is guaranteed regardless of how
the source JNG was encoded.
Phase 1 vs Phase 2
This release ships the container parser + wrapper format only. The JDAT
chunks (the JPEG bitstream — typically the bulk of a JNG file) are stored
through zstd-19, which can't beat JPEG entropy. Compression on a mixed
corpus is therefore modest (≈ 90 % of original on a 70 KB sembiance batch);
small JNGs (< 2 KB) can grow slightly because the wrapper overhead exceeds
zstd savings on the head / tail.
Phase 2 (planned) will route JDAT through packJPG (already vendored in
the syc archiver), unlocking a ~25 % additional win on the JPEG portion. The
TCIJ format reserves a flags byte for that future content-type bit.
CLI
packPNG a image.jng # → image.ppg (TCIJ wrapper)
packPNG a -ver -dry image.jng # round-trip verify in memory, no write
packPNG a -r -od out/ src/ # PNG/APNG/JNG inputs all dispatch correctly
packPNG x image.ppg # decoder picks TCIP or TCIJ by magicBuild-system fix
The kanzi Makefile target now passes -DUSE_ZSTD and links -lzstd. This
is required for both the existing tovyCIP IDAT-passthrough path and the
new TCIJ wrapper. If you build manually, the recommended flag set is now:
-DUSE_KANZI -DUSE_ZSTD [-DUSE_LIBDEFLATE]
-lz -llzma -lzstd [-ldeflate] libkanzi.a -lpthread
Linux/Debian dependency: apt install libzstd-dev (already needed since
v1.6 in practice; the Makefile was just missing the flag).
Compatibility
- PNG/APNG path unchanged from v1.7e. v1.7e
.ppgfiles decode under
v1.8 and vice versa. - v1.7e binaries fail cleanly with
bad TCIP magicon a.ppgproduced
from a JNG input (by design — old binaries can't reconstruct a JNG, so
refusing the file is safer than silently misparsing). - Legacy
.tcip/.ppgsarchives from v1.4–v1.6 still decode unchanged.
Bug fixes / housekeeping
- Help text and banner subtitle updated to mention JNG (
PNG/APNG/JNG …). collect()recursion picks up.jngfiles in-rmode.- Legacy backends (
-perfile/-m/-zstd/-fl2/-kanzi/-kpng)
reject JNG inputs cleanly with a one-line message instead of silently
misrouting them — Phase 1 JNG support is wired exclusively through the
default tovyCIP backend. - Manifest
assemblyIdentity@versionbumped to1.8.0.0.
Binaries
packPNG-linux-x86_64-v1.8— Linux x86-64, dynamically linked
(zlib, liblzma, libzstd, libdeflate, libkanzi, pthread).packPNG-windows-x86_64-full-v1.8.exe— Windows x86-64, statically
linked, mingw-w64 cross-compiled. Includes the UTF-8 active-code-page
manifest so non-ASCII filenames work without achcp 65001dance.
🤖 Generated with Claude Code
packPNG v1.8c - smart console-detection prompt
v1.8c - smart console detection: only prompt on a fresh console
Third bugfix on the v1.8 line. v1.8a fixed the double-click console-close
problem by always printing "Press to quit" on every help / error
exit. Real-world feedback: that was correct for double-click but noisy
in every other context -- running from cmd.exe, PowerShell, Windows
Terminal, SSH, or a .bat would also show the prompt, even though the
console is shared with a parent shell that survives our exit.
The fix
Detect whether the console is fresh (only packPNG attached, will
close on our exit) before showing the prompt. On Windows, this is
GetConsoleProcessList(buf, 2) returning 1. On Linux, terminal
emulators outlive us in every realistic case, so it always returns
false -- the prompt was always noise on Linux and v1.8c retires it
there too.
| Invocation | console_is_fresh | prompt? |
|---|---|---|
Windows: double-click .exe directly |
yes (1 proc) | yes |
| Windows: from cmd.exe / PowerShell / Windows Terminal | no (2+ procs) | no |
Windows: from a .bat file |
no (cmd is parent) | no |
| Windows: from SSH | no (sshd shell is ancestor) | no |
| Linux: any context | no (always) | no |
-np and -module continue to bypass the prompt unconditionally for
scripted / machine invocations, exactly as before.
Verified
On Windows 11 (DESKTOP-6R6L470, build 26200, PowerShell 5.1.26100) via
SSH:
Test 1 no-args from cmd no prompt PASS
Test 2 -mZ error from cmd no prompt PASS
Test 3 -np regression no prompt PASS
The fresh-console branch is verified manually by double-clicking the
.exe in Explorer and confirming "Press <enter> to quit" still
appears -- this is the path the bat selftest cannot reach (invoking a
.bat already establishes cmd.exe as parent, defeating the fresh
detection).
What didn't change
- Wire format unchanged:
.ppgfiles written by v1.8 / v1.8a /
v1.8b decode under v1.8c and vice versa. BothTCIP(PNG/APNG) and
TCIJ(JNG) magics are intact. - Compression algorithm unchanged: the tovyCIP backend
(kanzi RLT+BWT+SRT+ZRLT/FPAQ+zstd-19 --long=27) is byte-exact
identical to every prior release on the v1.8 line. - All v1.8a/b behaviours preserved apart from the prompt narrowing:
the wait_and_return helper, exit-code handling for invalid magic
(v1.8b), JNG support (v1.8), are all untouched. - Manifest
assemblyIdentity@versionbumped1.8.0.2->1.8.0.3.
Binaries
packPNG-linux-x86_64-v1.8c- Linux x86-64.packPNG-windows-x86_64-full-v1.8c.exe- Windows x86-64, statically
linked, mingw-w64 cross-compiled, UTF-8 active-code-page manifest.test-v1.8c.bat- drop-next-to-the-exe selftest harness, 14 tests
(regression set + flipped prompt expectations from v1.8a/b).
🤖 Generated with Claude Code
packPNG v1.8b - audit-driven bugfix: exit code on invalid magic
v1.8b - bugfix: exit non-zero when input has invalid magic (audit-driven)
Second bugfix on the v1.8 line, found by an 8-phase Windows 11 audit
harness run against the v1.8a binary on a real Windows 11 box
(DESKTOP-6R6L470, build 26200, PowerShell 5.1.26100). v1.8a passed 7
of 8 phases cleanly - the holdout was Phase 4 (malformed inputs).
The bug
Files with invalid magic - a 0-byte file named empty.png, or random
bytes named garbage.png - were silently skipped with a warning
but the program exited 0, lying to any script checking exit status.
packPNG a empty.png -> "skipped 1 file(s) with invalid magic" exit 0 (wrong)
packPNG a garbage.png -> "skipped 1 file(s) with invalid magic" exit 0 (wrong)
packPNG a missing.png -> "ERROR missing file: missing.png" exit 1 (correct)
Cause
The legacy filter near the end of main() accumulates unprocessable
inputs into three buckets:
skip_missing(file does not exist)skip_bad_magic(file exists but is not a PNG / .ppg / .jng)skip_compress/skip_decompress(wrong direction for the chosen
subcommand)
skip_missing has always bumped g_errors per file - the program's
final return errs ? 1 : 0 then properly exits non-zero. skip_bad_magic
was missing the same line, so when every input had bad magic the
program exited 0 even though nothing was processed.
The fix
One line. After the verbose-skip loop for skip_bad_magic, mirror the
existing skip_missing bookkeeping:
g_errors += (int)skip_bad_magic.size();Mixed batches (some valid + some bad-magic) now also exit non-zero,
matching how Unix cp / tar / gzip behave when any source fails.
This is stricter than v1.8a behaviour. If you have a script that
intentionally feeds packPNG a directory containing non-PNG files
and was relying on exit 0, switch to per-file invocation or pre-filter
the input list.
Audit results (Windows 11, v1.8b)
| Phase | What it covers | v1.8a | v1.8b |
|---|---|---|---|
| 1. PngSuite full corpus roundtrip | 162 valid files (color_type x bit_depth x interlace) | 162/162 | 162/162 |
| 2. Path edge cases | space, paren, &, [], ;, %, #, +, ' | 9/10 (audit-script bug) | 10/10 |
| 3. Mode coverage | mode 0 default + mode 2 (-ldf) |
2/2 | 2/2 |
| 4. Malformed inputs | 0-byte / garbage / truncated / sig-only / corrupt CRC | 3/5 | 5/5 |
5. -r recurse + -fs |
3-deep dir tree, structure preserve | 2/2 | 2/2 |
6. Output collisions + -od |
dup basename rename, auto-mkdir | 2/2 | 2/2 |
| 7. Big-file stress | 3.42 MB random RGB pack+verify | PASS (0.25s) | PASS (0.25s) |
| 8. Multi-thread determinism | -th1 == -th4 == -th0 SHA256 |
PASS | PASS |
The Phase 2 long-path test (>260 char total) is consistently SKIPPED
because the host filesystem rejects creating such a path before
packPNG ever runs - that is a system / LongPathsEnabled group-policy
setting, not a packPNG bug. The manifest already declares
longPathAware=true.
What didn't change
- Wire format unchanged:
.ppgfiles written by v1.8 / v1.8a decode
under v1.8b and vice versa. BothTCIP(PNG/APNG) andTCIJ(JNG)
magics are intact. - All v1.8a behaviours preserved - the wait_and_return helper for
the help/error console-close fix (encode.su feedback) still works. - Manifest
assemblyIdentity@versionbumped1.8.0.1->1.8.0.2.
Acknowledgements
Thanks to the encode.su community for the v1.8a console paper-cut
report and the YCoCg-R / STRATA pointer that prompted the v1.9
prototype work earlier in this cycle (the latter didn't pan out in
this pipeline, but useful negative evidence).
Binaries
packPNG-linux-x86_64-v1.8b- Linux x86-64, dynamically linked.packPNG-windows-x86_64-full-v1.8b.exe- Windows x86-64, statically
linked, mingw-w64 cross-compiled, UTF-8 active-code-page manifest.
🤖 Generated with Claude Code
packPNG v1.8a — keep console open on help/error exits
v1.8a — bugfix: keep console window open on help and error exits
A small but annoying paper-cut reported on encode.su: running
packPNG.exe via double-click (or any context that creates a fresh
console — Windows shortcut, drag-and-drop, etc.) would close that console
window before the user could read the help text or error message.
Cause: only the two success-path branches of main() went through the
existing "Press to quit" wait. Every other return — help on
no-args, help on empty filelist, all argument-parsing errors, and the
"no PNG/JNG files to compress" exit — return-ed straight from main(),
so the console terminated immediately after.
Fix: extract a tiny wait_and_return(int code) helper that respects
wait_exit && !module_mode (the same predicate the existing two
success-path waits already used), and route every user-visible return
through it. -np and -module keep skipping the prompt for scripted /
machine invocations exactly as before.
What changed
- 9 user-visible
returnpaths inmain()now flow through
wait_and_return(...)so the console stays open for the user to read
output, regardless of howpackPNG.exewas launched. - Manifest
assemblyIdentity@versionbumped1.8.0.0→1.8.0.1
(revision tick for the bugfix-letter, per Win32 PE convention).
What didn't change
- Wire format unchanged:
.ppgfiles written by v1.8 decode under
v1.8a and vice versa, bothTCIP(PNG/APNG) andTCIJ(JNG) magics. - PNG / APNG / JNG paths: identical compression behaviour to v1.8.
- CLI flags:
-npand-modulecontinue to bypass the wait
exactly as before.
Verified on Linux
no-args path → "Press <enter> to quit" shown OK
-mZ error → error + "Press <enter> to quit" OK
-od no path → error + "Press <enter> to quit" OK
-np → no prompt OK
-module → no prompt OK
PNG / JNG round-trip → byte-exact OK
Verified on Windows 11
A test-v1.8a.bat selftest harness ships alongside the Windows binary in
the release. It runs 14 tests covering every v1.7e regression check, the
4 new wait-path tests, and a JNG round-trip regression for the v1.8
feature. ASCII-only with CRLF so cmd.exe parses it correctly before
chcp 65001 applies. Just drop it next to the .exe and double-click.
Binaries
packPNG-linux-x86_64-v1.8a— Linux x86-64, dynamically linked
(zlib, liblzma, libzstd, libdeflate, libkanzi, pthread).packPNG-windows-x86_64-full-v1.8a.exe— Windows x86-64, statically
linked, mingw-w64 cross-compiled, UTF-8 active-code-page manifest.
Side note: YCoCg-R prototype
Following a suggestion on encode.su, I prototyped YCoCg-R (lifting form,
mod-256, fully reversible) as an opt-in pre-BWT transform in this same
cycle. Result: +2.06% regression on a combined 42-file corpus
(PngSuite, synthetic gradients, real photos, wild PNGs). The kanzi
RLT+BWT+SRT+ZRLT/FPAQ pipeline already does enough byte-decorrelation
that adding YCoCg on top tends to disrupt rather than help. Reverted
before this release — the negative result is documented for future
re-experimentation if the pipeline architecture changes.
🤖 Generated with Claude Code
v1.7e — audit fixes (-ver/-dry in tovyCIP, strict numeric flags)
Audit fixes — -ver / -dry now work in tovyCIP, strict numeric flags
P0 — silent bugs
-
-verwas silently ignored in the default tovyCIP path. Before
v1.7e, passing-verwith a default tovyCIP encode took exactly
the same wallclock as without it — verify ran zero work even though
the flag was accepted. Fixed by routing the encoder output through
an in-memory buffer (new optionalout_bytesparameter on
compress_tovycip_archive) so-vercan decompress to a temp dir
and byte-compare against the original PNG (or pixel-compare under
-ldf). -
-drywas silently ignored in the default tovyCIP path. Files
were written to disk regardless. Same plumbing: the encoder no
longer writes whendry_runis set, but still tallies totals from
the in-memory buffer so the summary line stays accurate.-ver -dry
now does a full round-trip verify without touching disk.
P1 — strict flag validation
-m,-th,-nofsep=,-zl=now reject non-numeric and
out-of-range values with a clear stderr message and exit 2.
Previously e.g.-mZsilently became a no-op (atoi returned 0,
branch matched but did nothing) — inconsistent with the existing
unknown flag: X → exit 2policy. Removed a duplicate-nofsep=
handler.
P2 / docs / UX
-
collect()recursion now picks up.tcipand.ppgsfiles
(legacy archive extensions). Previously-rover a tree containing
those silently skipped them, even thoughdetect_typewould have
dispatched them correctly. -
Banner subtitle now reads
tovyCIP backend (kanzi BWT + zstd-19-long)
instead ofbrute-force zlib match + solid LZMA(the old default). -
Help text:
-perfileno longer documented as(no-op); describes
the legacy LZMA opt-out it actually performs. -
Help text:
-kpng-maxno longer claimsarchive use case
(archives were removed in v1.7) — explains the per-file ratio /
decode trade-off instead. -
Help text:
-sfthmentions parallel kanzi encode in the tovyCIP
path, not just MT-LZMA. -
Help text:
-deepclaim of~2x slower / ~6% smallersoftened to
reflect that on libpng-default encoders it's effectively a no-op
(match always found on candidate 0); helps a few percent on exotic
encoders. -
-odwithout a glued path now prints
missing value: -od<path> (glue the path: -od/some/dir)instead
ofunknown flag: -od. -
Single missing file now shows the actual path:
ERROR missing file: /path/to/file.pnginstead of
ERROR 1 missing file(s).
Notes
-
Windows binary not shipped this release (no Windows host available
during the cycle). Linux x86-64 only. Windows users on v1.7d
experience the-ver/-drybugs too — workaround until v1.7e
Windows lands: use-m6(legacy LZMA path) where both flags work
correctly. -
Compression algorithm and wire format unchanged from v1.7d. v1.7d
output decompresses byte-exact under v1.7e and vice versa.
v1.7 — per-file tovyCIP with rename-on-collision
Major behaviour change
packPNG returns to its roots: a per-file PNG recompressor. tovyCIP
remains the default compression algorithm (kanzi RLT+BWT+SRT+ZRLT/FPAQ
for raw pixels + zstd-19-long for IDAT-passthrough), now applied
per-input-file instead of bundling N PNGs into a single archive.
```
packPNG a image.png → image.ppg
packPNG a *.png → N .ppg files (one per input)
packPNG a -r TILIN -odout → 9,406 .ppg in out/ (with collision rename)
```
Each .ppg holds exactly one entry, in TCIP wire format.
Why the revert
packPNG was always intended as a per-file recompressor ("Compresor de
Imágenes PNG"). The v1.5 multi-PNG archive mode mixed two concerns —
codec and container — and made selective extraction awkward. v1.7
separates them cleanly: tovyCIP is the codec, each .ppg is one PNG.
Collision handling
When a flat `-od` layout would produce two .ppg files with the same
basename (e.g. `-r` over a tree with 177× `Imagen1(1).png` in
different subdirs), the second file is renamed:
```
image.ppg → image(1).ppg → image(2).ppg → ...
```
Subdir structure is preserved inside the .ppg entry metadata, so the
decoder rebuilds the original tree even when output files were
renamed.
Legacy flags
`-tcip`, `-solid`, `-tovycip`, `-perfile` parse as no-ops.
Scripts using them keep working; the multi-PNG archive behaviour
those flags used to enable in v1.5–v1.6b is gone.
TILIN benchmark (Win11 i7-12700T, 9,406 PNGs, 572 MB)
| Mode | Time | Output size | Ratio | Files lost |
|---|---|---|---|---|
| v1.7 per-file tovyCIP | 55 s | 410.2 MB (9,406 .ppg) | 71.7% | 0 |
| v1.6b archive tovyCIP (removed) | 159 s | 303.2 MB (1 .ppg) | 53.0% | 0 |
| v1.1 per-file LZMA | 97 s | ~418 MB (9,383 .ppg) | 73.1% | 58 |
| xz preset 6 -mt | 39 s | 358.6 MB (1 .tar.xz) | 62.7% | 0 |
v1.7 beats v1.1 on every axis: 1.8× faster, better ratio, zero
collisions (v1.1 silently lost 58 files to basename overwrite).
For grouped/solid storage use a real archiver (tar+xz, zpaqfranz,
syc) — packPNG isn't one.
Binaries
- `packPNG-linux-x86_64-v1.7` — 786 KB (full feature, stripped)
- `packPNG-windows-x86_64-full-v1.7.exe` — 4.7 MB (full feature,
mingw cross, statically linked, UTF-8 manifest)
`Unblock-File` after download on Windows to clear MOTW.
v1.6 — unified .ppg extension
What changed
Single extension for all packPNG output. The .tcip extension introduced
in v1.5 for tovyCIP archives is gone — every output is now .ppg, the same
extension used by per-file packPNG mode. The decoder selects the right path
by reading the file's magic byte (PPG1 = single-file packPNG, TCIP /
PPGS = tovyCIP archive), so the unified extension is unambiguous.
packPNG a image.png # → image.ppg (1-entry tovyCIP archive)
packPNG a *.png # → archive.ppg (multi-PNG tovyCIP archive)
packPNG x archive.ppg # extract back to byte-exact PNGs
packPNG a -perfile *.png # opt-out: per-file packPNG (also .ppg)Compatibility
- Wire format unchanged. v1.6 archives carry the same
TCIPmagic
internally as v1.5; existing v1.5 readers can decode v1.6 archives if you
feed them through (they only inspect the bytes, not the filename). - v1.6 still decodes everything. All historical
.ppgfiles (v1..v15),
every.ppgsarchive (v1.4–v1.5pre), and every.tciparchive (v1.5)
continue to round-trip byte-exact. -tcipflag retained as alias of-tovycip.
Validation
| Test | Result |
|---|---|
3-PNG round-trip via archive.ppg |
3/3 byte-exact |
1-PNG round-trip via image.ppg |
byte-exact, magic = TCIP |
Decode legacy v1.5 .tcip archive |
OK |
| Full feature build (kanzi+zstd+libdeflate) | clean |
Binaries
packPNG-linux-x86_64-v1.6— 770 KB, full feature build (LZMA + zstd +
libdeflate + kanzi-cpp). Stripped.packPNG-windows-x86_64-full-v1.6.exe— 2.9 MB, full feature mingw
cross-compiled, statically linked.
The Windows .exe is unsigned (mingw cross-build) — some AVs may flag it as
a generic heuristic (Trojan:Win32/Wacatac etc.). False positive; verify on
VirusTotal or build from source.
Full changelog since v1.5c
- `353010b` release: v1.6 — unify archive extension to .ppg
- `1b2367e` docs: README v1.6 — unified .ppg extension