-
-
Save mmalex/908299 to your computer and use it in GitHub Desktop.
bug fix png encoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
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:
compile with gcc -lz pngencode.c
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
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:
compile with gcc -lz pngencode.c
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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;