Last active
April 24, 2024 03:41
-
-
Save skeeto/9cd74b5e7e4d61b54b50f79f0686b15b to your computer and use it in GitHub Desktop.
Small, no-dependency 8-bit indexed PNG writer
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
// 8-bit indexed PNG writer, no dependencies | |
// $ cc -o png png.c | |
// $ ./png >image.png | |
// This is free and unencumbered software released into the public domain. | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
static uint32_t adler32(uint32_t sum, uint8_t *buf, ptrdiff_t len) | |
{ | |
uint32_t a = sum & 0xffff; | |
uint32_t b = sum >> 16; | |
for (ptrdiff_t i = 0; i < len; i++) { | |
a = (a + buf[i]) % 0xfff1UL; | |
b = (b + a ) % 0xfff1UL; | |
} | |
return b<<16 | a; | |
} | |
static uint32_t crc32(uint32_t crc, uint8_t *buf, ptrdiff_t len) | |
{ | |
static uint32_t t[256] = { | |
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, | |
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, | |
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, | |
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, | |
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, | |
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, | |
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, | |
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, | |
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, | |
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, | |
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, | |
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, | |
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, | |
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, | |
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, | |
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, | |
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, | |
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, | |
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, | |
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, | |
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, | |
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, | |
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, | |
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, | |
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, | |
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, | |
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, | |
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, | |
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, | |
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, | |
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, | |
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, | |
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, | |
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, | |
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, | |
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, | |
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, | |
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, | |
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, | |
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, | |
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, | |
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, | |
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, | |
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, | |
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, | |
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, | |
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, | |
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, | |
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, | |
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, | |
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, | |
0x2d02ef8d | |
}; | |
crc ^= -1; | |
for (ptrdiff_t n = 0; n < len; n++) { | |
crc = t[(crc ^ buf[n]) & 255] ^ (crc >> 8); | |
} | |
return crc ^ -1; | |
} | |
static uint32_t writebuf(uint32_t crc, uint8_t *buf, ptrdiff_t len) | |
{ | |
fwrite(buf, len, 1, stdout); | |
return crc32(crc, buf, len); | |
} | |
static uint32_t writestr(uint32_t crc, char *buf, ptrdiff_t len) | |
{ | |
return writebuf(crc, (uint8_t *)buf, len); | |
} | |
static uint32_t writeu8(uint32_t crc, uint8_t b) | |
{ | |
return writebuf(crc, &b, 1); | |
} | |
static uint32_t writeu16(uint32_t crc, uint16_t x) | |
{ | |
uint8_t buf[2] = {(uint8_t)x, (uint8_t)(x>>8)}; // little endian | |
return writebuf(crc, buf, 2); | |
} | |
static uint32_t writeu32(uint32_t crc, uint32_t x) | |
{ | |
uint8_t buf[4] = { | |
(uint8_t)(x>>24), (uint8_t)(x>>16), (uint8_t)(x>>8), (uint8_t)x | |
}; | |
return writebuf(crc, buf, 4); | |
} | |
// Write an 8-bit indexed PNG to standard output. RGB palette is 3*256 | |
// bytes, and the image can be no wider than 65,534 pixels. | |
static _Bool writepng(uint8_t *pal, uint8_t *data, int32_t w, int32_t h) | |
{ | |
if (w>0xfffe || w<=0 || h<=0 || h>(0x7fffffff-6)/(6+w)) return 0; | |
writestr(0, "\x89PNG\r\n\x1a\n", 8); | |
uint32_t crc = 0; | |
writeu32(crc, 4+4+1+1+1+1+1); | |
crc = writestr(crc, "IHDR", 4); | |
crc = writeu32(crc, w); | |
crc = writeu32(crc, h); | |
crc = writeu8 (crc, 8); // depth | |
crc = writeu8 (crc, 3); // indexed | |
crc = writeu8 (crc, 0); // compression | |
crc = writeu8 (crc, 0); // filter | |
crc = writeu8 (crc, 0); // interlace | |
writeu32(crc, crc); | |
crc = 0; | |
writeu32(crc, 3*256); | |
crc = writestr(crc, "PLTE", 4); | |
crc = writebuf(crc, pal, 3*256); | |
writeu32(crc, crc); | |
crc = 0; | |
int32_t len = (1+2+2+1+w) * h; | |
writeu32(crc, 2+len+4); | |
crc = writestr(crc, "IDAT", 4); | |
crc = writestr(crc, "\x78\x01", 2); // zlib header | |
uint32_t sum = 1; | |
uint8_t zero = 0; | |
for (int32_t y = 0; y < h; y++) { | |
uint8_t bhdr = y == h-1; | |
crc = writebuf(crc, &bhdr, 1); // block header | |
crc = writeu16(crc, (uint16_t) (w+1)); | |
crc = writeu16(crc, (uint16_t)~(w+1)); | |
sum = adler32 (sum, &zero, 1); | |
crc = writeu8 (crc, 0); // filter (none) | |
sum = adler32 (sum, data + y*w, w); | |
crc = writebuf(crc, data + y*w, w); | |
} | |
crc = writeu32(crc, sum); | |
writeu32(crc, crc); | |
crc = 0; | |
writeu32(crc, 0); | |
crc = writestr(crc, "IEND", 4); | |
writeu32(crc, crc); | |
return 1; | |
} | |
int main(void) | |
{ | |
#if _WIN32 | |
int _setmode(int, int); | |
_setmode(0, 0x8000); | |
_setmode(1, 0x8000); | |
#endif | |
uint8_t pal[3*256]; | |
for (int i = 0; i < 256; i++) { | |
pal[3*i+0] = pal[3*i+1] = pal[3*i+2] = (uint8_t)i; | |
} | |
enum { W=640, H=480 }; | |
uint8_t data[W*H]; | |
uint64_t rng = 1; | |
for (int i = 0; i < W*H; i++) { | |
data[i] = (uint8_t)((rng = rng*0x3243f6a8885a308d + 1) >> 56); | |
} | |
_Bool err = !writepng(pal, data, W, H); | |
fflush(stdout); | |
return ferror(stdout) || err; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment