Skip to content

Fix PNG encoding on Linux: bundled libpng headers must win over GTK3's system libpng16 - #114

Merged
slajerek merged 1 commit into
slajerek:masterfrom
arekbr:fix/libpng-header-version-mismatch
Aug 4, 2026
Merged

Fix PNG encoding on Linux: bundled libpng headers must win over GTK3's system libpng16#114
slajerek merged 1 commit into
slajerek:masterfrom
arekbr:fix/libpng-header-version-mismatch

Conversation

@arekbr

@arekbr arekbr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

On Linux, c64/screen/snapshot and c64/screen/save always answer HTTP 500 with
{"error":"Screen image not available or PNG encoding failed"}, in every machine state.
The same applies to the atari800/* and nes/* variants.

Cause

It is not the screen image -- that part works. It is a libpng version handshake failure.

src/Remote/CDebuggerServerApi.cpp includes png.h, and on Linux that resolves to the
system header, because pkg_check_modules(GTK3 REQUIRED gtk+-3.0) puts
-I/usr/include/libpng16 into the include path before the bundled libpng directory
(CMakeLists.txt:32-33 vs :92; in the generated flags.make they land at position 8 and 72).

The binary, however, links the bundled libpng 1.5.2 from MTEngineSDL statically:

$ ldd build/retrodebugger | grep -i png        # nothing
$ nm --defined-only build/retrodebugger | grep -w png_create_write_struct
0000000000d8e120 T png_create_write_struct

So the call site passes PNG_LIBPNG_VER_STRING = "1.6.48" into an implementation that
identifies itself as "1.5.2". png_create_write_struct() compares the version
(pngwrite.c:523-525), refuses and returns NULL, and the lambda exits at
if (!png_ptr) return {}; -- which the endpoints translate into HTTP 500.

Measured on the running process:

(gdb) p (void*)png_create_write_struct("1.5.2", 0, 0, 0)
$1 = (void *) 0x55fb22b586a0
(gdb) p (void*)png_create_write_struct("1.6.48", 0, 0, 0)
$2 = (void *) 0x0

The only difference is the version string. Breakpoints confirmed the exit point: line 1102
is hit, none of the later PNG calls are.

This is Linux-only -- macOS and Windows have no GTK3 in the include path, so the bundled
header wins there and everything works. That is likely why it went unnoticed.

Fix

One line in CMakeLists.txt: add the bundled libpng directory with BEFORE, so the header
matches the implementation that is actually linked. That also revives
src/Emulators/vice/gfxoutputdrv/pngdrv.c, which has the same dead call today.

I checked the PNG API used across the repo (png_create_write_struct, png_set_IHDR,
png_set_PLTE, png_write_*, png_jmpbuf) -- all of it exists in 1.5.2, so compiling against
the older header is safe.

The second commit part splits the error message. One if chain merged five distinct causes
(no image, no pixel data, zero width, zero height, encoder init failure, libpng longjmp) into a
single string, and no branch logged anything -- with GLOBAL_DEBUG_OFF the log stays empty, so
diagnosing this needed a debugger. Each cause now has its own message and a LOGError.

Verification

Debian 13, GCC 14, same binary before/after:

before after
c64/screen/snapshot HTTP 500, 0 bytes HTTP 200, 2016-byte PNG, 384x272
c64/screen/save HTTP 500, no file file starting with \x89PNG\r\n\x1a\n

Note on MTEngineSDL

CImageData::Save() has the same defect for the same reason (its own build has the same
include order), so the GUI's "Save screenshot as PNG" is dead on Linux too -- silently, since
it only calls LOGError. I have not touched MTEngineSDL, as AGENTS.md asks. It needs the same
one-line change; happy to send it if you want.

The binary statically links the bundled libpng 1.5.2 from MTEngineSDL, but
pkg_check_modules(GTK3) adds -I/usr/include/libpng16 to the include path before
the bundled directory, so png.h resolves to the system 1.6.x header. That makes
png_create_write_struct() receive PNG_LIBPNG_VER_STRING "1.6.48", fail its
version handshake against the 1.5.2 implementation and return NULL.

Effect on Linux: c64/screen/snapshot and c64/screen/save always answered
HTTP 500, and the same dead call sits in vice/gfxoutputdrv/pngdrv.c. macOS and
Windows are unaffected -- they have no GTK3 in the include path.

Also split the single 'Screen image not available or PNG encoding failed'
string, which merged five distinct failures into one message with no log entry,
into per-cause messages with LOGError.

Verified on Debian 13: screen/snapshot now returns a 384x272 PNG.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants