Skip to content

Instantly share code, notes, and snippets.

@mmalex
Forked from anonymous/pngencode.cpp
Created April 7, 2011 17:48
Show Gist options
  • Save mmalex/908299 to your computer and use it in GitHub Desktop.
Save mmalex/908299 to your computer and use it in GitHub Desktop.
bug fix png encoder
// by alex evans, 2011. released into the public domain.
// based on a first ever reading of the png spec, it occurs to me that a minimal png encoder should be quite simple.
// this is a first stab - may be buggy! the only external dependency is zlib and some basic typedefs (u32, u8)
//
// VERSION 0.02! now using zlib's crc rather than my own, and avoiding a memcpy and memory scribbler in the old one
// by passing the zero byte at the start of the scanline to zlib first, then the original scanline in place. WIN!
//
// more context at http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
//
// follow me on twitter @mmalex http://twitter.com/mmalex
//
u8* CompressPNG(void *img, int w, int h, int numchans, u32 &len_out) {
int p=w*numchans;
z_stream z={0};
deflateInit(&z,-1);
u8 *zbuf=(u8*)malloc(53+(z.avail_out=deflateBound(&z,(1+p)*h))+1);
if (!zbuf) return 0;
z.next_out=zbuf+41;
for (int y=0;y<h;++y) {
Bytef zero=0; z.avail_in=1;z.next_in=&zero; deflate(&z,Z_NO_FLUSH);
z.avail_in=p;z.next_in=((Bytef*)img)+y*p; deflate(&z,(y==h-1) ? Z_FINISH : Z_NO_FLUSH);
}
len_out=z.next_out-zbuf-41;
u8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
0,0,w>>8,w,0,0,h>>8,h,8,"\0\0\04\02\06"[numchans],0,0,0,0,0,0,0,
len_out>>24,len_out>>16,len_out>>8,len_out,0x49,0x44,0x41,0x54};
*(u32*)(pnghdr+29)=htonl(crc32(0,pnghdr+12,17));
memcpy(zbuf,pnghdr,41);
memcpy(z.next_out+4,"\x49\x45\x4e\x44\xae\x42\x60\x82",8); // footer
*(u32*)z.next_out =htonl(crc32(0, zbuf+41-4, len_out+4));
deflateEnd(&z); len_out+=53; return zbuf;
}
void PNGTest()
{
u32 img[200][320];
for (int y=0;y<200;++y) for (int x=0;x<320;++x) img[y][x]=x+(y<<8)+0xff000000;
u32 blen; u8*zbuf = CompressPNG(img,16,16,4,blen);
FILE *f=fopen("test.png","wb");
fwrite(zbuf,1,blen,f);
fclose(f);
free(zbuf);
}
@Lokno
Copy link

Lokno commented Jan 25, 2014

A modified version of this function is used by miniz.c : https://code.google.com/p/miniz/issues/entry

To except pre-filter image data, add a byte array of filter types (length h):

line 12: add parameter "u8 *filters"
line 20: replace z.next_in=&zero; with z.next_in=filters ? filters+y : &zero;

@anakojm
Copy link

anakojm commented Jan 24, 2024

here is the same program in c

// by alex evans, 2011.  released into the public domain.
// based on a first ever reading of the png spec, it occurs to me that a minimal png encoder should be quite simple.
// this is a first stab - may be buggy! the only external dependency is zlib and some basic typedefs (unsigned int, uint8_t)
//
// VERSION 0.02! now using zlib's crc rather than my own, and avoiding a memcpy and memory scribbler in the old one
// by passing the zero byte at the start of the scanline to zlib first, then the original scanline in place. WIN!
//
// more context at http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
//
// follow me on twitter @mmalex http://twitter.com/mmalex
//
#include <zlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>

uint8_t* CompressPNG(void *img, int w, int h, int numchans, unsigned int* len_out) {
        int p = w * numchans;
        z_stream z = {0};
        deflateInit(&z, -1);
        uint8_t* zbuf = malloc(53 + (z.avail_out = deflateBound(&z, (1+p)*h)) + 1);
        if (!zbuf) return 0;
        z.next_out = zbuf + 41;

        for (int y = 0; y < h; ++y) {
                Bytef zero = 0;
                z.avail_in = 1;
                z.next_in = &zero;
                deflate(&z, Z_NO_FLUSH);
                z.avail_in = p;
                z.next_in = ((Bytef*)img) + y*p;
                deflate(&z,(y == h - 1) ? Z_FINISH : Z_NO_FLUSH);
        }

        *len_out = z.next_out - zbuf - 41;
        uint8_t pnghdr[41] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
                0, 0, w>>8, w, 0, 0, h>>8, h, 8, "\0\0\04\02\06"[numchans], 0, 0, 0, 0, 0, 0, 0,
                *len_out>>24, *len_out>>16, *len_out>>8, *len_out, 0x49, 0x44, 0x41, 0x54};
        *(unsigned int*)(pnghdr + 29) = htonl(crc32(0, pnghdr + 12, 17));
        memcpy(zbuf, pnghdr, 41);
        memcpy(z.next_out + 4, "\x49\x45\x4e\x44\xae\x42\x60\x82", 8); /* footer */
        *(unsigned int*)z.next_out = htonl(crc32(0, zbuf+41-4, *len_out+4));
        deflateEnd(&z);
        *len_out += 53;
        return zbuf;
}

void PNGTest() {
        unsigned int img[200][320];
        for (int y = 0; y < 200; ++y) for (int x = 0; x < 320; ++x) img[y][x] = x + (y<<8) + 0xff000000;
        unsigned int blen;
        uint8_t* zbuf = CompressPNG(img, 16, 16, 4, &blen);
        FILE* f = fopen("test.png", "wb");
        fwrite(zbuf, 1, blen, f);
        fclose(f);
        free(zbuf);
}

int main(void) {
        PNGTest();
}

here is the image it produces:
image
compile with gcc -lz pngencode.c

@anakojm
Copy link

anakojm commented Jan 24, 2024

the generated png is invalid though:

/tmp $ pngcheck -vv test.png
File: test.png (488 bytes)
  chunk IHDR at offset 0x0000c, length 13
    16 x 16 image, 32-bit RGB+alpha, non-interlaced
  chunk IDAT at offset 0x00025, length 435
    zlib: deflated, 32K window, default compression
    row filters (0 none, 1 sub, 2 up, 3 avg, 4 paeth):
      0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 (16 out of 16)
  invalid chunk name "B`" (ffffffae 42 60 ffffff82)
ERRORS DETECTED in test.png

@anakojm
Copy link

anakojm commented Jan 24, 2024

here is a patch to my program that fixes that:

--- pngencode.c	2024-01-24 14:52:34.490371971 +0100
+++ pngencode-fixed.c	2024-01-24 14:52:54.517190368 +0100
@@ -19,7 +19,7 @@
         int p = w * numchans;
         z_stream z = {0};
         deflateInit(&z, -1);
-        uint8_t* zbuf = malloc(53 + (z.avail_out = deflateBound(&z, (1+p)*h)) + 1);
+        uint8_t* zbuf = malloc(57 + (z.avail_out = deflateBound(&z, (1+p)*h)) + 1);
         if (!zbuf) return 0;
         z.next_out = zbuf + 41;
 
@@ -39,10 +39,10 @@
                 *len_out>>24, *len_out>>16, *len_out>>8, *len_out, 0x49, 0x44, 0x41, 0x54};
         *(unsigned int*)(pnghdr + 29) = htonl(crc32(0, pnghdr + 12, 17));
         memcpy(zbuf, pnghdr, 41);
-        memcpy(z.next_out + 4, "\x49\x45\x4e\x44\xae\x42\x60\x82", 8); /* footer */
+        memcpy(z.next_out + 4, "\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 12); /* footer */
         *(unsigned int*)z.next_out = htonl(crc32(0, zbuf+41-4, *len_out+4));
         deflateEnd(&z);
-        *len_out += 53;
+        *len_out += 57;
         return zbuf;
 }
 

here is the image it produces:
image
compile with gcc -lz pngencode.c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment