diff options
-rw-r--r-- | bin/glitch.c | 687 |
1 files changed, 377 insertions, 310 deletions
diff --git a/bin/glitch.c b/bin/glitch.c index acb2615c..d0c926f9 100644 --- a/bin/glitch.c +++ b/bin/glitch.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 June McEnroe <june@causal.agency> +/* Copyright (C) 2018, 2021 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -14,8 +14,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <arpa/inet.h> #include <err.h> +#include <inttypes.h> +#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> @@ -25,286 +26,328 @@ #include <unistd.h> #include <zlib.h> -#define PACKED __attribute__((packed)) - -#define CRC_INIT (crc32(0, Z_NULL, 0)) +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) 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 pngRead(void *ptr, size_t len, const char *desc) { + size_t n = fread(ptr, len, 1, file); + if (!n && ferror(file)) err(EX_IOERR, "%s", path); + if (!n) errx(EX_DATAERR, "%s: missing %s", path, desc); + crc = crc32(crc, ptr, len); } -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 void pngWrite(const void *ptr, size_t len) { + size_t n = fwrite(ptr, len, 1, file); + if (!n) err(EX_IOERR, "%s", path); + crc = crc32(crc, ptr, len); } -static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n"; +static const uint8_t Sig[8] = "\x89PNG\r\n\x1A\n"; -static void readSignature(void) { - uint8_t signature[8]; - readExpect(signature, 8, "signature"); - if (0 != memcmp(signature, Signature, 8)) { +static void sigRead(void) { + uint8_t sig[sizeof(Sig)]; + pngRead(sig, sizeof(sig), "signature"); + if (memcmp(sig, Sig, sizeof(sig))) { errx(EX_DATAERR, "%s: invalid signature", path); } } -static void writeSignature(void) { - writeExpect(Signature, sizeof(Signature)); +static void sigWrite(void) { + pngWrite(Sig, sizeof(Sig)); } -struct PACKED Chunk { - uint32_t size; - char type[4]; -}; +static uint32_t u32Read(const char *desc) { + uint8_t b[4]; + pngRead(b, sizeof(b), desc); + return (uint32_t)b[0] << 24 | (uint32_t)b[1] << 16 + | (uint32_t)b[2] << 8 | (uint32_t)b[3]; +} -static const char *typeStr(struct Chunk chunk) { - static char buf[5]; - memcpy(buf, chunk.type, 4); - return buf; +static void u32Write(uint32_t x) { + uint8_t b[4] = { x >> 24 & 0xFF, x >> 16 & 0xFF, x >> 8 & 0xFF, x & 0xFF }; + pngWrite(b, sizeof(b)); } -static struct Chunk readChunk(void) { +struct Chunk { + uint32_t len; + char type[5]; +}; + +static struct Chunk chunkRead(void) { struct Chunk chunk; - readExpect(&chunk, sizeof(chunk), "chunk"); - chunk.size = ntohl(chunk.size); - crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); + chunk.len = u32Read("chunk length"); + crc = crc32(0, Z_NULL, 0); + pngRead(chunk.type, 4, "chunk type"); + chunk.type[4] = 0; return chunk; } -static void writeChunk(struct Chunk chunk) { - chunk.size = htonl(chunk.size); - writeExpect(&chunk, sizeof(chunk)); - crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type)); +static void chunkWrite(struct Chunk chunk) { + u32Write(chunk.len); + crc = crc32(0, Z_NULL, 0); + pngWrite(chunk.type, 4); } -static void readCrc(void) { - uint32_t expected = crc; - uint32_t found; - readExpect(&found, sizeof(found), "CRC32"); - found = ntohl(found); - if (found != expected) { - errx( - EX_DATAERR, "%s: expected CRC32 %08X, found %08X", - path, expected, found - ); - } +static void crcRead(void) { + uint32_t expect = crc; + uint32_t actual = u32Read("CRC32"); + if (actual == expect) return; + errx( + EX_DATAERR, "%s: expected CRC32 %08X, found %08X", + path, expect, actual + ); } -static void writeCrc(void) { - uint32_t net = htonl(crc); - writeExpect(&net, sizeof(net)); +static void crcWrite(void) { + u32Write(crc); } -static void skipChunk(struct Chunk chunk) { - uint8_t discard[chunk.size]; - readExpect(discard, sizeof(discard), "chunk data"); - readCrc(); +static void chunkSkip(struct Chunk chunk) { + if (!(chunk.type[0] & 0x20)) { + errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, chunk.type); + } + uint8_t buf[4096]; + while (chunk.len > sizeof(buf)) { + pngRead(buf, sizeof(buf), "chunk data"); + chunk.len -= sizeof(buf); + } + if (chunk.len) pngRead(buf, chunk.len, "chunk data"); + crcRead(); } -static struct PACKED { +enum Color { + Grayscale = 0, + Truecolor = 2, + Indexed = 3, + GrayscaleAlpha = 4, + TruecolorAlpha = 6, +}; +enum Compression { + Deflate, +}; +enum FilterMethod { + Adaptive, +}; +enum Interlace { + Progressive, + Adam7, +}; + +enum { HeaderLen = 13 }; +static struct { uint32_t width; uint32_t height; uint8_t depth; - enum PACKED { - Grayscale = 0, - Truecolor = 2, - Indexed = 3, - GrayscaleAlpha = 4, - TruecolorAlpha = 6, - } color; + uint8_t color; uint8_t compression; uint8_t filter; uint8_t interlace; } header; -_Static_assert(13 == sizeof(header), "header size"); -static size_t pixelBits(void) { +static size_t pixelLen; +static size_t lineLen; +static size_t dataLen; + +static void recalc(void) { + size_t pixelBits = header.depth; switch (header.color) { - case Grayscale: return 1 * header.depth; - case Truecolor: return 3 * header.depth; - case Indexed: return 1 * header.depth; - case GrayscaleAlpha: return 2 * header.depth; - case TruecolorAlpha: return 4 * header.depth; - default: abort(); + break; case GrayscaleAlpha: pixelBits *= 2; + break; case Truecolor: pixelBits *= 3; + break; case TruecolorAlpha: pixelBits *= 4; } + pixelLen = (pixelBits + 7) / 8; + lineLen = (header.width * pixelBits + 7) / 8; + dataLen = (1 + lineLen) * header.height; } -static size_t pixelSize(void) { - return (pixelBits() + 7) / 8; +static void headerRead(struct Chunk chunk) { + if (chunk.len != HeaderLen) { + errx( + EX_DATAERR, "%s: expected %s length %" PRIu32 ", found %" PRIu32, + path, chunk.type, (uint32_t)HeaderLen, chunk.len + ); + } + header.width = u32Read("header width"); + header.height = u32Read("header height"); + pngRead(&header.depth, 1, "header depth"); + pngRead(&header.color, 1, "header color"); + pngRead(&header.compression, 1, "header compression"); + pngRead(&header.filter, 1, "header filter"); + pngRead(&header.interlace, 1, "header interlace"); + crcRead(); + recalc(); +} + +static void headerWrite(void) { + struct Chunk ihdr = { HeaderLen, "IHDR" }; + chunkWrite(ihdr); + u32Write(header.width); + u32Write(header.height); + pngWrite(&header.depth, 1); + pngWrite(&header.color, 1); + pngWrite(&header.compression, 1); + pngWrite(&header.filter, 1); + pngWrite(&header.interlace, 1); + crcWrite(); } -static size_t lineSize(void) { - return (header.width * pixelBits() + 7) / 8; -} +static struct { + uint32_t len; + uint8_t rgb[256][3]; +} pal; -static size_t dataSize(void) { - return (1 + lineSize()) * header.height; +static struct { + uint32_t len; + uint8_t a[256]; +} trans; + +static void palClear(void) { + pal.len = 0; + trans.len = 0; } -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)); +static void palRead(struct Chunk chunk) { + if (chunk.len % 3) { + errx( + EX_DATAERR, "%s: %s length %" PRIu32 " not divisible by 3", + path, chunk.type, chunk.len + ); } - if (ihdr.size != sizeof(header)) { + pal.len = chunk.len / 3; + if (pal.len > 256) { errx( - EX_DATAERR, "%s: expected IHDR size %zu, found %u", - path, sizeof(header), ihdr.size + EX_DATAERR, "%s: %s length %" PRIu32 " > 256", + path, chunk.type, pal.len ); } - readExpect(&header, sizeof(header), "header"); - readCrc(); - header.width = ntohl(header.width); - header.height = ntohl(header.height); - if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path); - if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path); + pngRead(pal.rgb, chunk.len, "palette data"); + crcRead(); } -static void writeHeader(void) { - struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" }; - 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 void palWrite(void) { + struct Chunk plte = { 3 * pal.len, "PLTE" }; + chunkWrite(plte); + pngWrite(pal.rgb, plte.len); + crcWrite(); } -static struct { - uint32_t len; - uint8_t entries[256][3]; -} palette; - -static void readPalette(void) { - struct Chunk chunk; - for (;;) { - chunk = readChunk(); - if (0 == memcmp(chunk.type, "PLTE", 4)) break; - skipChunk(chunk); +static void transRead(struct Chunk chunk) { + trans.len = chunk.len; + if (trans.len > 256) { + errx( + EX_DATAERR, "%s: %s length %" PRIu32 " > 256", + path, chunk.type, trans.len + ); } - palette.len = chunk.size / 3; - readExpect(palette.entries, chunk.size, "palette data"); - readCrc(); + pngRead(trans.a, chunk.len, "transparency data"); + crcRead(); } -static void writePalette(void) { - struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" }; - writeChunk(plte); - writeExpect(palette.entries, plte.size); - writeCrc(); +static void transWrite(void) { + struct Chunk trns = { trans.len, "tRNS" }; + chunkWrite(trns); + pngWrite(trans.a, trns.len); + crcWrite(); } static uint8_t *data; -static void readData(void) { - data = malloc(dataSize()); - if (!data) err(EX_OSERR, "malloc(%zu)", dataSize()); +static void dataAlloc(void) { + data = malloc(dataLen); + if (!data) err(EX_OSERR, "malloc"); +} - struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() }; +static void dataRead(struct Chunk chunk) { + z_stream stream = { .next_out = data, .avail_out = dataLen }; int error = inflateInit(&stream); - if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg); + if (error != Z_OK) errx(EX_SOFTWARE, "inflateInit: %s", stream.msg); for (;;) { - struct Chunk chunk = readChunk(); - if (0 == memcmp(chunk.type, "IDAT", 4)) { - uint8_t *idat = malloc(chunk.size); - if (!idat) err(EX_OSERR, "malloc"); - - readExpect(idat, chunk.size, "image data"); - readCrc(); + if (strcmp(chunk.type, "IDAT")) { + errx(EX_DATAERR, "%s: missing IDAT chunk", path); + } - stream.next_in = idat; - stream.avail_in = chunk.size; - int error = inflate(&stream, Z_SYNC_FLUSH); - free(idat); + uint8_t *idat = malloc(chunk.len); + if (!idat) err(EX_OSERR, "malloc"); - if (error == Z_STREAM_END) break; - if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); + pngRead(idat, chunk.len, "image data"); + crcRead(); + + stream.next_in = idat; + stream.avail_in = chunk.len; + error = inflate(&stream, Z_SYNC_FLUSH); + free(idat); - } else if (0 == memcmp(chunk.type, "IEND", 4)) { - errx(EX_DATAERR, "%s: missing IDAT chunk", path); - } else { - skipChunk(chunk); + if (error == Z_STREAM_END) break; + if (error != Z_OK) { + errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg); } - } + chunk = chunkRead(); + } inflateEnd(&stream); - if ((size_t)stream.total_out != dataSize()) { + if ((size_t)stream.total_out != dataLen) { errx( - EX_DATAERR, "%s: expected data size %zu, found %zu", - path, dataSize(), (size_t)stream.total_out + EX_DATAERR, "%s: expected data length %zu, found %zu", + path, dataLen, (size_t)stream.total_out ); } } -static void writeData(void) { - uLong size = compressBound(dataSize()); - uint8_t *deflate = malloc(size); - if (!deflate) err(EX_OSERR, "malloc"); +static void dataWrite(void) { + z_stream stream = { + .next_in = data, + .avail_in = dataLen, + }; + int error = deflateInit2( + &stream, Z_BEST_COMPRESSION, Z_DEFLATED, 15, 8, Z_FILTERED + ); + if (error != Z_OK) errx(EX_SOFTWARE, "deflateInit2: %s", stream.msg); - int error = compress2(deflate, &size, data, dataSize(), Z_BEST_SPEED); - if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error); + uLong bound = deflateBound(&stream, dataLen); + uint8_t *buf = malloc(bound); + if (!buf) err(EX_OSERR, "malloc"); - struct Chunk idat = { .size = size, .type = "IDAT" }; - writeChunk(idat); - writeExpect(deflate, size); - writeCrc(); + stream.next_out = buf; + stream.avail_out = bound; + deflate(&stream, Z_FINISH); + deflateEnd(&stream); - free(deflate); -} + struct Chunk idat = { stream.total_out, "IDAT" }; + chunkWrite(idat); + pngWrite(buf, stream.total_out); + crcWrite(); + free(buf); -static void writeEnd(void) { - struct Chunk iend = { .size = 0, .type = "IEND" }; - writeChunk(iend); - writeCrc(); + struct Chunk iend = { 0, "IEND" }; + chunkWrite(iend); + crcWrite(); } -enum PACKED Filter { +enum Filter { None, Sub, Up, Average, Paeth, - FilterCount, + FilterCap, }; -static struct { - bool brokenPaeth; - bool filt; - bool recon; - uint8_t declareFilter; - uint8_t applyFilter; - enum Filter declareFilters[255]; - enum Filter applyFilters[255]; - bool invert; - bool mirror; - bool zeroX; - bool zeroY; -} options; - struct Bytes { - uint8_t x; - uint8_t a; - uint8_t b; - uint8_t c; + uint8_t x, a, b, c; }; +static bool brokenPaeth; static uint8_t paethPredictor(struct Bytes f) { int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c; - int32_t pa = abs(p - (int32_t)f.a); - int32_t pb = abs(p - (int32_t)f.b); - int32_t pc = abs(p - (int32_t)f.c); + int32_t pa = labs(p - (int32_t)f.a); + int32_t pb = labs(p - (int32_t)f.b); + int32_t pc = labs(p - (int32_t)f.c); if (pa <= pb && pa <= pc) return f.a; - if (options.brokenPaeth) { + if (brokenPaeth) { if (pb < pc) return f.b; } else { if (pb <= pc) return f.b; @@ -319,7 +362,7 @@ static uint8_t recon(enum Filter type, struct Bytes f) { case Up: return f.x + f.b; case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2; case Paeth: return f.x + paethPredictor(f); - default: abort(); + default: abort(); } } @@ -330,59 +373,59 @@ static uint8_t filt(enum Filter type, struct Bytes f) { case Up: return f.x - f.b; case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2; case Paeth: return f.x - paethPredictor(f); - default: abort(); + default: abort(); } } -static struct Line { - enum Filter type; - uint8_t data[]; -} **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(); - for (uint32_t y = 0; y < header.height; ++y) { - lines[y] = (struct Line *)&data[y * stride]; - if (lines[y]->type >= FilterCount) { - errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type); - } - } +static uint8_t *lineType(uint32_t y) { + return &data[y * (1 + lineLen)]; +} +static uint8_t *lineData(uint32_t y) { + return 1 + lineType(y); } static struct Bytes origBytes(uint32_t y, size_t i) { - bool a = (i >= pixelSize()), b = (y > 0), c = (a && b); + bool a = (i >= pixelLen), b = (y > 0), c = (a && b); return (struct Bytes) { - .x = lines[y]->data[i], - .a = a ? lines[y]->data[i - pixelSize()] : 0, - .b = b ? lines[y - 1]->data[i] : 0, - .c = c ? lines[y - 1]->data[i - pixelSize()] : 0, + .x = lineData(y)[i], + .a = (a ? lineData(y)[i-pixelLen] : 0), + .b = (b ? lineData(y-1)[i] : 0), + .c = (c ? lineData(y-1)[i-pixelLen] : 0), }; } -static void reconData(void) { +static bool reconFilter; +static void dataRecon(void) { for (uint32_t y = 0; y < header.height; ++y) { - for (size_t i = 0; i < lineSize(); ++i) { - if (options.filt) { - lines[y]->data[i] = filt(lines[y]->type, origBytes(y, i)); + for (size_t i = 0; i < lineLen; ++i) { + if (reconFilter) { + lineData(y)[i] = filt(*lineType(y), origBytes(y, i)); } else { - lines[y]->data[i] = recon(lines[y]->type, origBytes(y, i)); + lineData(y)[i] = recon(*lineType(y), origBytes(y, i)); } } - lines[y]->type = None; + *lineType(y) = None; } } -static void filterData(void) { - for (uint32_t y = header.height - 1; y < header.height; --y) { - uint8_t filter[FilterCount][lineSize()]; - uint32_t heuristic[FilterCount] = {0}; +static bool filterRecon; +static size_t applyFilter; +static enum Filter applyFilters[256]; +static size_t declFilter; +static enum Filter declFilters[256]; + +static void dataFilter(void) { + uint8_t *filter[FilterCap]; + for (enum Filter i = None; i < FilterCap; ++i) { + filter[i] = malloc(lineLen); + if (!filter[i]) err(EX_OSERR, "malloc"); + } + for (uint32_t y = header.height-1; y < header.height; --y) { + uint32_t heuristic[FilterCap] = {0}; enum Filter minType = None; - for (enum Filter type = None; type < FilterCount; ++type) { - for (size_t i = 0; i < lineSize(); ++i) { - if (options.recon) { + for (enum Filter type = None; type < FilterCap; ++type) { + for (size_t i = 0; i < lineLen; ++i) { + if (filterRecon) { filter[type][i] = recon(type, origBytes(y, i)); } else { filter[type][i] = filt(type, origBytes(y, i)); @@ -391,49 +434,26 @@ static void filterData(void) { } if (heuristic[type] < heuristic[minType]) minType = type; } - - if (options.declareFilter) { - lines[y]->type = options.declareFilters[y % options.declareFilter]; + if (declFilter) { + *lineType(y) = declFilters[y % declFilter]; } else { - lines[y]->type = minType; + *lineType(y) = minType; } - - if (options.applyFilter) { - enum Filter type = options.applyFilters[y % options.applyFilter]; - memcpy(lines[y]->data, filter[type], lineSize()); + if (applyFilter) { + memcpy(lineData(y), filter[applyFilters[y % applyFilter]], lineLen); } else { - memcpy(lines[y]->data, filter[minType], lineSize()); + memcpy(lineData(y), filter[minType], lineLen); } } -} - -static void invert(void) { - for (uint32_t y = 0; y < header.height; ++y) { - for (size_t i = 0; i < lineSize(); ++i) { - lines[y]->data[i] ^= 0xFF; - } - } -} - -static void mirror(void) { - for (uint32_t y = 0; y < header.height; ++y) { - for (size_t i = 0, j = lineSize() - 1; i < j; ++i, --j) { - uint8_t t = lines[y]->data[i]; - lines[y]->data[i] = lines[y]->data[j]; - lines[y]->data[j] = t; - } - } -} - -static void zeroX(void) { - for (uint32_t y = 0; y < header.height; ++y) { - memset(lines[y]->data, 0, pixelSize()); + for (enum Filter i = None; i < FilterCap; ++i) { + free(filter[i]); } } -static void zeroY(void) { - memset(lines[0]->data, 0, lineSize()); -} +static bool invertData; +static bool mirrorData; +static bool zeroX; +static bool zeroY; static void glitch(const char *inPath, const char *outPath) { if (inPath) { @@ -441,98 +461,145 @@ static void glitch(const char *inPath, const char *outPath) { file = fopen(path, "r"); if (!file) err(EX_NOINPUT, "%s", path); } else { - path = "(stdin)"; + path = "stdin"; file = stdin; } - readSignature(); - readHeader(); - if (header.color == Indexed) readPalette(); - readData(); + sigRead(); + struct Chunk ihdr = chunkRead(); + if (strcmp(ihdr.type, "IHDR")) { + errx(EX_DATAERR, "%s: expected IHDR, found %s", path, ihdr.type); + } + headerRead(ihdr); + if (header.interlace != Progressive) { + errx(EX_CONFIG, "%s: unsupported interlacing", path); + } + + palClear(); + dataAlloc(); + for (;;) { + struct Chunk chunk = chunkRead(); + if (!strcmp(chunk.type, "PLTE")) { + palRead(chunk); + } else if (!strcmp(chunk.type, "tRNS")) { + transRead(chunk); + } else if (!strcmp(chunk.type, "IDAT")) { + dataRead(chunk); + } else if (!strcmp(chunk.type, "IEND")) { + break; + } else { + chunkSkip(chunk); + } + } fclose(file); - scanlines(); - reconData(); - filterData(); - if (options.invert) invert(); - if (options.mirror) mirror(); - if (options.zeroX) zeroX(); - if (options.zeroY) zeroY(); - free(lines); + dataRecon(); + dataFilter(); + if (invertData) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0; i < lineLen; ++i) { + lineData(y)[i] ^= 0xFF; + } + } + } + if (mirrorData) { + for (uint32_t y = 0; y < header.height; ++y) { + for (size_t i = 0, j = lineLen-1; i < j; ++i, --j) { + uint8_t x = lineData(y)[i]; + lineData(y)[i] = lineData(y)[j]; + lineData(y)[j] = x; + } + } + } + if (zeroX) { + for (uint32_t y = 0; y < header.height; ++y) { + memset(lineData(y), 0, pixelLen); + } + } + if (zeroY) { + memset(lineData(0), 0, lineLen); + } + + char buf[PATH_MAX]; if (outPath) { path = outPath; - file = fopen(path, "w"); - if (!file) err(EX_CANTCREAT, "%s", path); + if (outPath == inPath) { + snprintf(buf, sizeof(buf), "%sg", outPath); + file = fopen(buf, "wx"); + if (!file) err(EX_CANTCREAT, "%s", buf); + } else { + file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", outPath); + } } else { - path = "(stdout)"; + path = "stdout"; file = stdout; } - writeSignature(); - writeHeader(); - if (header.color == Indexed) writePalette(); - writeData(); - writeEnd(); + sigWrite(); + headerWrite(); + if (header.color == Indexed) { + palWrite(); + if (trans.len) transWrite(); + } + dataWrite(); free(data); - int error = fclose(file); if (error) err(EX_IOERR, "%s", path); + + if (outPath && outPath == inPath) { + error = rename(buf, outPath); + if (error) err(EX_CANTCREAT, "%s", outPath); + } } -static enum Filter parseFilter(const char *s) { - switch (s[0]) { +static enum Filter parseFilter(const char *str) { + switch (str[0]) { case 'N': case 'n': return None; case 'S': case 's': return Sub; case 'U': case 'u': return Up; case 'A': case 'a': return Average; case 'P': case 'p': return Paeth; - default: errx(EX_USAGE, "invalid filter type %s", s); + default: errx(EX_USAGE, "invalid filter type %s", str); } } -static uint8_t parseFilters(enum Filter *filters, const char *s) { - uint8_t len = 0; - do { - filters[len++] = parseFilter(s); - s = strchr(s, ','); - } while (s++); +static size_t parseFilters(enum Filter *filters, char *str) { + size_t len = 0; + while (str) { + char *filt = strsep(&str, ","); + filters[len++] = parseFilter(filt); + } return len; } int main(int argc, char *argv[]) { bool stdio = false; - char *output = NULL; + char *outPath = NULL; - int opt; - while (0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"))) { + for (int opt; 0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"));) { switch (opt) { - break; case 'a': - options.applyFilter = parseFilters(options.applyFilters, optarg); + break; case 'a': applyFilter = parseFilters(applyFilters, optarg); break; case 'c': stdio = true; - break; case 'd': - options.declareFilter = parseFilters(options.declareFilters, optarg); - break; case 'f': options.filt = true; - break; case 'i': options.invert = true; - break; case 'm': options.mirror = true; - break; case 'o': output = optarg; - break; case 'p': options.brokenPaeth = true; - break; case 'r': options.recon = true; - break; case 'x': options.zeroX = true; - break; case 'y': options.zeroY = true; - break; default: return EX_USAGE; + break; case 'd': declFilter = parseFilters(declFilters, optarg); + break; case 'f': reconFilter = true; + break; case 'i': invertData = true; + break; case 'm': mirrorData = true; + break; case 'o': outPath = optarg; + break; case 'p': brokenPaeth = true; + break; case 'r': filterRecon = true; + break; case 'x': zeroX = true; + break; case 'y': zeroY = true; + break; default: return EX_USAGE; } } - if (argc - optind == 1 && (output || stdio)) { - glitch(argv[optind], output); - } else if (optind < argc) { + if (optind < argc) { for (int i = optind; i < argc; ++i) { - glitch(argv[i], argv[i]); + glitch(argv[i], (stdio ? NULL : outPath ? outPath : argv[i])); } } else { - glitch(NULL, output); + glitch(NULL, outPath); } - - return EX_OK; } |