I'm working on a school project and I'm trying to load PNG files without using additional libraries (so no libpng, just zlib and my own parsing). Everything works fine with most PNGs, except for one image that consistently fails with a Zlib inflate error -5, which is Z_BUF_ERROR.
Here’s the relevant part of the code where the decompression happens:
int mipng_data(void *img, unsigned char *dat, png_info_t *pi)
{
unsigned int len;
int b_pos;
unsigned char *buffer;
int ret;
int z_ret;
z_stream z_strm;
unsigned char z_out[Z_CHUNK];
b_pos = 0;
if (!(buffer = malloc((long long)pi->width * (long long)pi->height * (long long)pi->bpp + pi->height)))
return (ERR_MALLOC);
z_strm.zalloc = Z_NULL;
z_strm.zfree = Z_NULL;
z_strm.opaque = Z_NULL;
z_strm.avail_in = 0;
z_strm.next_in = Z_NULL;
z_ret = inflateInit(&z_strm);
if (z_ret != Z_OK) {
free(buffer);
return (ERR_ZLIB);
}
while (mipng_is_type(dat, "IDAT")) {
len = ntohl(*((unsigned int *)dat));
z_strm.avail_in = len;
z_strm.next_in = dat + 8; // skip length + type
z_strm.avail_out = 0;
while (z_strm.avail_out == 0) {
z_strm.avail_out = Z_CHUNK;
z_strm.next_out = z_out;
printf("len=%u avail_in=%u avail_out=%u\n", len, z_strm.avail_in, Z_CHUNK);
if (z_strm.avail_in == 0)
printf("Chunk @ %p | len=%u | type=%.4s\n", dat, len, dat + 4);
z_ret = inflate(&z_strm, Z_NO_FLUSH);
if (z_ret != Z_OK && z_ret != Z_STREAM_END) {
inflateEnd(&z_strm);
free(buffer);
return (ERR_ZLIB);
}
if (b_pos + Z_CHUNK - z_strm.avail_out > pi->width * pi->height * pi->bpp + pi->height) {
inflateEnd(&z_strm);
free(buffer);
return (ERR_DATA_MISMATCH);
}
memcpy(buffer + b_pos, z_out, Z_CHUNK - z_strm.avail_out);
b_pos += Z_CHUNK - z_strm.avail_out;
}
dat += len + 4 + 4 + 4; // len + type + crc + data
}
inflateEnd(&z_strm);
if (b_pos != pi->width * pi->height * pi->bpp + pi->height) {
free(buffer);
return (ERR_DATA_MISMATCH);
}
ret = mipng_fill_img(img, buffer, pi);
free(buffer);
return ret;
}
the full file :https://pastebin.com/NX1V55gj by the way it's not my code it the minilibx code that am trying to fix. ❗ Problem:
Everything works for all PNGs except one. That one fails with:
len=4096 avail_in=4096 avail_out=65536
...
len=4096 avail_in=5 avail_out=65536
len=4096 avail_in=0 avail_out=65536
Chunk @ 0x7f44f095abaa | len=4096 | type=IDAT
cub3d: mlx PNG error : Zlib inflate error
It fails when avail_in becomes 0 while there's still data expected, causing inflate() to return Z_BUF_ERROR (-5).
What I’ve tried:
Increasing Z_CHUNK (output buffer) — no difference.
Double-checked header parsing and chunk CRC — all looks fine.
Checked that I’m not skipping or misreading chunks.
Checked that file with pngcheck, no error with the file, the logs are fine.
Questions:
Why would avail_in drop to 0 if the chunk still contains more IDAT data?
Am I misunderstanding how multiple IDAT chunks are supposed to be joined?
Is this possibly a corner case PNG format (like an unusual compression flag or zlib header)?
Could this happen if the file has multiple small IDAT chunks?
for testing purposes here is the minilibx a small lib for students with a testing main. https://www.transfernow.net/dl/20250623QmMGn9Ln
I'm not experienced with low-level PNG internals, so any help would be appreciated! I'm open to debugging help, fixes, or code cleanup if you see mistakes.
And cuz this only happen with on file, I put it here in case u want to test it with :https://www.transfernow.net/dl/20250623ngBVi21L
Thanks in advance!
Z_BUF_ERROR
isn't really an error. It's just saying that there was nothing for inflate()
to do. You will get that if all of the available input has been consumed, and the resulting uncompressed output just so happened to exactly fill the provided output space, and there's no more output to harvest. Then your while (z_strm.avail_out == 0)
will go and call inflate()
again, but there is no input and it has no more output. So it returns Z_BUF_ERROR
.
No problem. Just carry on.
The frame_0011.png you provided does in fact have one IDAT chunk that happens to decompress to 65,536 bytes. If your Z_CHUNK
is a power of two less than or equal to 65,536, then you will run into exactly the situation I described.
This C code exposes the culprit:
// Show how many bytes each IDAT chunk of a PNG file decompresses to. The PNG
// file is read from stdin.
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "zlib.h"
#ifdef _WIN32
# include <fcntl.h>
# include <io.h>
# define BINARY(file) _setmode(_fileno(file), _O_BINARY)
#else
# define BINARY(file)
#endif
int main(void) {
BINARY(stdin);
unsigned char nub[8];
size_t got = fread(nub, 1, 8, stdin);
assert(got == 8 && memcmp(nub, "\x89PNG\r\n\x1a\n", 8) == 0 && "not png");
z_stream strm = {0};
int ret = inflateInit(&strm);
assert(ret == Z_OK);
while ((got = fread(nub, 1, 8, stdin)) == 8) {
unsigned n = (nub[0] << 24) | (nub[1] << 16) | (nub[2] << 8) | nub[3];
unsigned char dat[n + 4];
got = fread(dat, 1, n + 4, stdin);
assert(got == n + 4);
if (memcmp(nub + 4, "IDAT", 4) == 0) {
assert(ret != Z_STREAM_END);
strm.next_in = dat;
strm.avail_in = n;
size_t got = 0;
do {
unsigned char out[8192];
strm.next_out = out;
strm.avail_out = sizeof(out);
ret = inflate(&strm, Z_NO_FLUSH);
got += sizeof(out) - strm.avail_out;
} while (strm.avail_out == 0);
assert(strm.avail_in == 0);
assert(ret == Z_OK || ret == Z_BUF_ERROR || ret == Z_STREAM_END);
printf("-- IDAT %zu uncompressed%s\n", got,
ret == Z_BUF_ERROR ? " (Z_BUF_ERROR)" : "");
}
}
inflateEnd(&strm);
assert(ret == Z_STREAM_END);
assert(got == 0);
return 0;
}