diff options
Diffstat (limited to '')
-rw-r--r-- | bin/pngo.c | 283 |
1 files changed, 136 insertions, 147 deletions
diff --git a/bin/pngo.c b/bin/pngo.c index 96695ad5..70692c20 100644 --- a/bin/pngo.c +++ b/bin/pngo.c @@ -30,28 +30,39 @@ #define CRC_INIT (crc32(0, Z_NULL, 0)) -static void readExpect( - const char *path, FILE *file, - void *ptr, size_t size, - const char *expect -) { +static const char *path; +static FILE *file; +static uint32_t crc; + +static void readExpect(void *ptr, size_t size, const char *expect) { fread(ptr, size, 1, file); if (ferror(file)) err(EX_IOERR, "%s", path); if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect); + crc = crc32(crc, ptr, size); +} + +static void writeExpect(const void *ptr, size_t size) { + fwrite(ptr, size, 1, file); + if (ferror(file)) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, size); } static const uint8_t SIGNATURE[8] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' }; -static void readSignature(const char *path, FILE *file) { +static void readSignature(void) { uint8_t signature[8]; - readExpect(path, file, signature, 8, "signature"); + readExpect(signature, 8, "signature"); if (0 != memcmp(signature, SIGNATURE, 8)) { errx(EX_DATAERR, "%s: invalid signature", path); } } +static void writeSignature(void) { + writeExpect(SIGNATURE, sizeof(SIGNATURE)); +} + struct PACKED Chunk { uint32_t size; char type[4]; @@ -63,16 +74,24 @@ static const char *typeStr(struct Chunk chunk) { return buf; } -static struct Chunk readChunk(const char *path, FILE *file) { +static struct Chunk readChunk(void) { struct Chunk chunk; - readExpect(path, file, &chunk, sizeof(chunk), "chunk"); + readExpect(&chunk, sizeof(chunk), "chunk"); chunk.size = ntohl(chunk.size); + crc = crc32(CRC_INIT, (Bytef *)chunk.type, sizeof(chunk.type)); return chunk; } -static void readCrc(const char *path, FILE *file, uint32_t expected) { +static void writeChunk(struct Chunk chunk) { + chunk.size = htonl(chunk.size); + writeExpect(&chunk, sizeof(chunk)); + crc = crc32(CRC_INIT, (Bytef *)chunk.type, sizeof(chunk.type)); +} + +static void readCrc(void) { + uint32_t expected = crc; uint32_t found; - readExpect(path, file, &found, sizeof(found), "CRC32"); + readExpect(&found, sizeof(found), "CRC32"); found = ntohl(found); if (found != expected) { errx( @@ -82,6 +101,11 @@ static void readCrc(const char *path, FILE *file, uint32_t expected) { } } +static void writeCrc(void) { + uint32_t net = htonl(crc); + writeExpect(&net, sizeof(net)); +} + enum PACKED Color { GRAYSCALE = 0, TRUECOLOR = 2, @@ -91,7 +115,7 @@ enum PACKED Color { }; #define ALPHA (0x04) -struct PACKED Header { +static struct PACKED { uint32_t width; uint32_t height; uint8_t depth; @@ -99,9 +123,9 @@ struct PACKED Header { enum PACKED { DEFLATE } compression; enum PACKED { ADAPTIVE } filter; enum PACKED { PROGRESSIVE, ADAM7 } interlace; -}; +} header; -static size_t lineSize(struct Header header) { +static size_t lineSize(void) { switch (header.color) { case GRAYSCALE: return (header.width * 1 * header.depth + 7) / 8; case TRUECOLOR: return (header.width * 3 * header.depth + 7) / 8; @@ -111,26 +135,23 @@ static size_t lineSize(struct Header header) { } } -static size_t dataSize(struct Header header) { - return (1 + lineSize(header)) * header.height; +static size_t dataSize(void) { + return (1 + lineSize()) * header.height; } -static struct Header readHeader(const char *path, FILE *file) { - struct Chunk ihdr = readChunk(path, file); +static void readHeader(void) { + struct Chunk ihdr = readChunk(); if (0 != memcmp(ihdr.type, "IHDR", 4)) { errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr)); } - if (ihdr.size != sizeof(struct Header)) { + if (ihdr.size != sizeof(header)) { errx( EX_DATAERR, "%s: expected IHDR size %zu, found %u", - path, sizeof(struct Header), ihdr.size + path, sizeof(header), ihdr.size ); } - uint32_t crc = crc32(CRC_INIT, (Bytef *)ihdr.type, sizeof(ihdr.type)); - - struct Header header; - readExpect(path, file, &header, sizeof(header), "header"); - readCrc(path, file, crc32(crc, (Bytef *)&header, sizeof(header))); + readExpect(&header, sizeof(header), "header"); + readCrc(); header.width = ntohl(header.width); header.height = ntohl(header.height); @@ -161,20 +182,31 @@ static struct Header readHeader(const char *path, FILE *file) { if (header.interlace > ADAM7) { errx(EX_DATAERR, "%s: invalid interlace method %hhu", path, header.interlace); } +} - return header; +static void writeHeader(void) { + struct Chunk ihdr = { .size = sizeof(header), .type = { 'I', 'H', 'D', 'R' } }; + writeChunk(ihdr); + header.width = htonl(header.width); + header.height = htonl(header.height); + writeExpect(&header, sizeof(header)); + writeCrc(); + header.width = ntohl(header.width); + header.height = ntohl(header.height); } -static uint8_t *readData(const char *path, FILE *file, struct Header header) { - uint8_t *data = malloc(dataSize(header)); - if (!data) err(EX_OSERR, "malloc(%zu)", dataSize(header)); +static uint8_t *data; + +static void readData(void) { + data = malloc(dataSize()); + if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); - struct z_stream_s stream = { .next_out = data, .avail_out = dataSize(header) }; + struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; int error = inflateInit(&stream); if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); for (;;) { - struct Chunk chunk = readChunk(path, file); + struct Chunk chunk = readChunk(); if (0 == memcmp(chunk.type, "IEND", 4)) { errx(EX_DATAERR, "%s: missing IDAT chunk", path); } @@ -190,11 +222,9 @@ static uint8_t *readData(const char *path, FILE *file, struct Header header) { ); } - uint32_t crc = crc32(CRC_INIT, (Bytef *)chunk.type, sizeof(chunk.type)); - uint8_t idat[chunk.size]; - readExpect(path, file, idat, sizeof(idat), "image data"); - readCrc(path, file, crc32(crc, idat, sizeof(idat))); + readExpect(idat, sizeof(idat), "image data"); + readCrc(); stream.next_in = idat; stream.avail_in = chunk.size; @@ -204,14 +234,30 @@ static uint8_t *readData(const char *path, FILE *file, struct Header header) { } inflateEnd(&stream); - if (stream.total_out != dataSize(header)) { + if (stream.total_out != dataSize()) { errx( EX_DATAERR, "%s: expected data size %zu, found %zu", - path, dataSize(header), stream.total_out + path, dataSize(), stream.total_out ); } +} - return data; +static void writeData(void) { + size_t size = compressBound(dataSize()); + uint8_t deflate[size]; + int error = compress2(deflate, &size, data, dataSize(), Z_BEST_COMPRESSION); + if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + + struct Chunk idat = { .size = size, .type = { 'I', 'D', 'A', 'T' } }; + writeChunk(idat); + writeExpect(deflate, size); + writeCrc(); +} + +static void writeEnd(void) { + struct Chunk iend = { .size = 0, .type = { 'I', 'E', 'N', 'D' } }; + writeChunk(iend); + writeCrc(); } enum PACKED Filter { @@ -260,18 +306,16 @@ static uint8_t filt(enum Filter type, struct Bytes f) { } } -struct Scanline { +static struct { enum Filter *type; uint8_t *data; -}; +} *lines; -static struct Scanline *scanlines( - const char *path, struct Header header, uint8_t *data -) { - struct Scanline *lines = calloc(header.height, sizeof(*lines)); +static void scanlines(void) { + lines = calloc(header.height, sizeof(*lines)); if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines)); - size_t stride = 1 + lineSize(header); + size_t stride = 1 + lineSize(); for (uint32_t y = 0; y < header.height; ++y) { lines[y].type = &data[y * stride]; lines[y].data = &data[y * stride + 1]; @@ -279,15 +323,10 @@ static struct Scanline *scanlines( errx(EX_DATAERR, "%s: invalid filter type %hhu", path, *lines[y].type); } } - - return lines; } -static struct Bytes origBytes( - struct Header header, const struct Scanline *lines, - uint32_t y, size_t i -) { - size_t pixelSize = lineSize(header) / header.width; +static struct Bytes origBytes(uint32_t y, size_t i) { + size_t pixelSize = lineSize() / header.width; if (!pixelSize) pixelSize = 1; bool a = (i >= pixelSize), b = (y > 0), c = (a && b); return (struct Bytes) { @@ -298,92 +337,42 @@ static struct Bytes origBytes( }; } -static void reconData(struct Header header, const struct Scanline *lines) { +static void reconData(void) { for (uint32_t y = 0; y < header.height; ++y) { - for (size_t i = 0; i < lineSize(header); ++i) { + for (size_t i = 0; i < lineSize(); ++i) { lines[y].data[i] = - recon(*lines[y].type, origBytes(header, lines, y, i)); + recon(*lines[y].type, origBytes(y, i)); } *lines[y].type = NONE; } } -static void filterData(struct Header header, const struct Scanline *lines) { +static void filterData(void) { for (uint32_t y = header.height - 1; y < header.height; --y) { - uint8_t filter[FILTER_COUNT][lineSize(header)]; + uint8_t filter[FILTER_COUNT][lineSize()]; uint32_t heuristic[FILTER_COUNT] = { 0 }; enum Filter minType = NONE; for (enum Filter type = NONE; type < FILTER_COUNT; ++type) { - for (uint32_t i = 0; i < lineSize(header); ++i) { - filter[type][i] = filt(type, origBytes(header, lines, y, i)); + for (uint32_t i = 0; i < lineSize(); ++i) { + filter[type][i] = filt(type, origBytes(y, i)); heuristic[type] += abs((int8_t)filter[type][i]); } if (heuristic[type] < heuristic[minType]) minType = type; } *lines[y].type = minType; - memcpy(lines[y].data, filter[minType], lineSize(header)); + memcpy(lines[y].data, filter[minType], lineSize()); } } -static void writeExpect(const char *path, FILE *file, const void *ptr, size_t size) { - fwrite(ptr, size, 1, file); - if (ferror(file)) err(EX_IOERR, "%s", path); -} - -static void writeSignature(const char *path, FILE *file) { - writeExpect(path, file, SIGNATURE, sizeof(SIGNATURE)); -} - -static void writeChunk(const char *path, FILE *file, struct Chunk chunk) { - chunk.size = htonl(chunk.size); - writeExpect(path, file, &chunk, sizeof(chunk)); -} - -static void writeCrc(const char *path, FILE *file, uint32_t crc) { - uint32_t net = htonl(crc); - writeExpect(path, file, &net, sizeof(net)); -} - -static void writeHeader(const char *path, FILE *file, struct Header header) { - struct Chunk ihdr = { .size = sizeof(header), .type = { 'I', 'H', 'D', 'R' } }; - writeChunk(path, file, ihdr); - uint32_t crc = crc32(CRC_INIT, (Bytef *)ihdr.type, sizeof(ihdr.type)); - header.width = htonl(header.width); - header.height = htonl(header.height); - writeExpect(path, file, &header, sizeof(header)); - writeCrc(path, file, crc32(crc, (Bytef *)&header, sizeof(header))); -} +static void eliminateAlpha(void) { + if (!(header.color & ALPHA)) return; -static void writeData(const char *path, FILE *file, uint8_t *data, size_t size) { - size_t bound = compressBound(size); - uint8_t deflate[bound]; - int error = compress2(deflate, &bound, data, size, Z_BEST_COMPRESSION); - if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); - - struct Chunk idat = { .size = bound, .type = { 'I', 'D', 'A', 'T' } }; - writeChunk(path, file, idat); - uint32_t crc = crc32(CRC_INIT, (Bytef *)idat.type, sizeof(idat.type)); - writeExpect(path, file, deflate, bound); - writeCrc(path, file, crc32(crc, deflate, bound)); -} - -static void writeEnd(const char *path, FILE *file) { - struct Chunk iend = { .size = 0, .type = { 'I', 'E', 'N', 'D' } }; - writeChunk(path, file, iend); - writeCrc(path, file, crc32(CRC_INIT, (Bytef *)iend.type, sizeof(iend.type))); -} - -static void eliminateAlpha( - struct Header *header, uint8_t *data, struct Scanline *lines -) { - if (!(header->color & ALPHA)) return; - - size_t pixelSize = lineSize(*header) / header->width; - size_t alphaSize = header->depth / 8; + size_t pixelSize = lineSize() / header.width; + size_t alphaSize = header.depth / 8; size_t colorSize = pixelSize - alphaSize; - for (uint32_t y = 0; y < header->height; ++y) { - for (uint32_t x = 0; x < header->width; ++x) { + for (uint32_t y = 0; y < header.height; ++y) { + for (uint32_t x = 0; x < header.width; ++x) { for (size_t i = 0; i < alphaSize; ++i) { if (lines[y].data[x * pixelSize + colorSize + i] != 0xFF) return; } @@ -391,11 +380,11 @@ static void eliminateAlpha( } uint8_t *ptr = data; - for (uint32_t y = 0; y < header->height; ++y) { + for (uint32_t y = 0; y < header.height; ++y) { uint8_t *type = ptr++; uint8_t *data = ptr; *type = *lines[y].type; - for (uint32_t x = 0; x < header->width; ++x) { + for (uint32_t x = 0; x < header.width; ++x) { memcpy(ptr, &lines[y].data[x * pixelSize], colorSize); ptr += colorSize; } @@ -403,55 +392,55 @@ static void eliminateAlpha( lines[y].data = data; } - header->color &= ~ALPHA; + header.color &= ~ALPHA; } static void optimize(const char *inPath, const char *outPath) { - FILE *input = stdin; if (inPath) { - input = fopen(inPath, "r"); - if (!input) err(EX_NOINPUT, "%s", inPath); + path = inPath; + file = fopen(path, "r"); + if (!file) err(EX_NOINPUT, "%s", path); } else { - inPath = "stdin"; + path = "(stdin)"; + file = stdin; } - readSignature(inPath, input); - struct Header header = readHeader(inPath, input); + readSignature(); + readHeader(); if (header.interlace != PROGRESSIVE) { errx( EX_CONFIG, "%s: unsupported interlace method %hhu", - inPath, header.interlace + path, header.interlace ); } - uint8_t *data = readData(inPath, input, header); - - int error = fclose(input); - if (error) err(EX_IOERR, "%s", inPath); + readData(); - struct Scanline *lines = scanlines(inPath, header, data); - reconData(header, lines); + int error = fclose(file); + if (error) err(EX_IOERR, "%s", path); - eliminateAlpha(&header, data, lines); - filterData(header, lines); + scanlines(); + reconData(); + eliminateAlpha(); + filterData(); + free(lines); - FILE *output = stdout; if (outPath) { - output = fopen(outPath, "wx"); - if (!output) err(EX_CANTCREAT, "%s", outPath); + path = outPath; + file = fopen(path, "wx"); + if (!file) err(EX_CANTCREAT, "%s", path); } else { - outPath = "stdout"; + path = "(stdout)"; + file = stdout; } - writeSignature(outPath, output); - writeHeader(outPath, output, header); - writeData(outPath, output, data, dataSize(header)); - writeEnd(outPath, output); - - error = fclose(output); - if (error) err(EX_IOERR, "%s", outPath); - - free(lines); + writeSignature(); + writeHeader(); + writeData(); + writeEnd(); free(data); + + error = fclose(file); + if (error) err(EX_IOERR, "%s", path); } int main(int argc, char *argv[]) { |