X-Git-Url: https://git.draconx.ca/gitweb/liblbx.git/blobdiff_plain/4ec9360786d0bb19cffc76818c1a4c33043f68e1..594902a517f5a77535a2d7bb903f8e1de358edf6:/src/lbximg.c diff --git a/src/lbximg.c b/src/lbximg.c index a7c60c6..2608689 100644 --- a/src/lbximg.c +++ b/src/lbximg.c @@ -1,7 +1,7 @@ /* * 2ooM: The Master of Orion II Reverse Engineering Project - * Simple command-line tool to convert an LBX image to a set of PNGs. - * Copyright © 2006-2011, 2013 Nick Bowler + * Simple command-line tool to convert an LBX image to other formats. + * Copyright © 2006-2011, 2013-2014 Nick Bowler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +19,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -32,16 +34,22 @@ #include "imgoutput.h" +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + /* Global flags */ static int verbose = 0; static char *outname = "out"; static int usepalette = 1; +enum { + OPT_PREFIX = UCHAR_MAX+1, +}; + static void printusage(void) { puts("usage: lbximg [-i|-d] [-v] [-p palette_file] [-O override_file]" " [-f path]"); - puts(" [frameno ...]"); + puts(" [-F format] [frameno ...]"); } static void printhelp(void) @@ -56,6 +64,62 @@ enum { MODE_IDENT, }; +static const struct img_format { + img_output_func *output; + char name[4]; + bool enabled; +} formats[] = { +#if HAVE_LIBPNG + { img_output_png, "png", 1 }, +#endif + { img_output_pam, "pam", 1 }, + { img_output_ppm, "ppm", 1 }, + { img_output_pbm, "pbm", 1 }, +}; + +static int lookup_format(const char *fmt) +{ + for (size_t i = 0; i < sizeof formats / sizeof formats[0]; i++) { + assert(!formats[i].name[sizeof formats[i].name - 1]); + + if (!fmt && formats[i].enabled) + return i; + + if (strcmp(formats[i].name, fmt)) + continue; + + if (!formats[i].enabled) { + tool_err(-1, "%s support disabled at build time", fmt); + return -1; + } + + return i; + } + + tool_err(-1, "unknown format %s", fmt); + return -1; +} + +bool img_is_masked(unsigned char *mask, unsigned width, unsigned height) +{ + unsigned long npixels = (unsigned long) width * height; + unsigned long mask_sz = npixels / CHAR_BIT + (npixels % CHAR_BIT != 0); + + for (unsigned long i = 0; i < mask_sz; i++) { + if (i+1 < mask_sz) { + if (mask[i] != (unsigned char)-1) + return true; + } else { + unsigned char test = (1u << npixels % CHAR_BIT) - 1; + + if ((mask[i] & test) != test) + return true; + } + } + + return false; +} + int parserange(unsigned frames, char *str, unsigned char *bits) { unsigned long start, end; @@ -104,8 +168,8 @@ int parserange(unsigned frames, char *str, unsigned char *bits) return 0; } -int outpng(unsigned int frameno, - unsigned char **framedata, unsigned char **mask, +int output(unsigned int frameno, const struct img_format *fmt, + unsigned char *pixels, unsigned char *pixel_mask, unsigned int width, unsigned int height, struct lbx_colour palette[static 256]) { @@ -113,8 +177,9 @@ int outpng(unsigned int frameno, FILE *of; int rc; + assert(fmt->output != NULL); assert(frameno < 65536); - snprintf(name, sizeof name, "%s.%03d.png", outname, frameno); + snprintf(name, sizeof name, "%s.%03d.%s", outname, frameno, fmt->name); of = fopen(name, "wb"); if (!of) { @@ -122,7 +187,7 @@ int outpng(unsigned int frameno, return -1; } - rc = img_output_png(of, name, width, height, framedata, mask, palette); + rc = fmt->output(of, name, width, height, pixels, pixel_mask, palette); if (rc < 0) { fclose(of); return -1; @@ -132,7 +197,7 @@ int outpng(unsigned int frameno, tool_err(0, "error writing %s", name); return -1; } - + if (verbose) printf("wrote %s\n", name); @@ -180,7 +245,7 @@ static int loadpalette(struct lbx_image *img, struct lbx_imginfo *info, /* Default the palette to a wonderful pink. */ for (i = 0; i < 256; i++) { - palette[i] = (struct lbx_colour){0xff, 0x00, 0xff}; + palette[i] = (struct lbx_colour){0x3f, 0x00, 0x3f}; } /* Read the external palette, if any. */ @@ -203,19 +268,117 @@ static int loadpalette(struct lbx_image *img, struct lbx_imginfo *info, return 0; } -int decode(struct lbx_image *img, FILE *palf, FILE *override, char **argv) +/* Return true iff a divides b. */ +static bool divides(unsigned a, unsigned b) { - unsigned char *framebits; + if (b == 0) + return true; + if (a == 0) + return false; + + return b % a == 0; +} + +/* Set n bits starting from offset in the bitmap. */ +static void +set_bits(unsigned char *bitmap, unsigned long offset, unsigned long n) +{ + if (offset % CHAR_BIT) { + bitmap[offset/CHAR_BIT] |= (n >= CHAR_BIT ? -1u : (1u << n) - 1) + << offset%CHAR_BIT; + + n -= MIN(n, CHAR_BIT - offset%CHAR_BIT); + offset += CHAR_BIT - offset%CHAR_BIT; + } + + if (n > CHAR_BIT) { + memset(&bitmap[offset/CHAR_BIT], -1, n/CHAR_BIT); + + offset += n - n%CHAR_BIT; + n %= CHAR_BIT; + } + + if (n) { + bitmap[offset/CHAR_BIT] |= (1u << n) - 1; + } +} + +static int decode_frame(struct lbx_image *img, unsigned n, + unsigned char *pixels, unsigned char *pixel_mask) +{ + unsigned x, y; + long rc; + + rc = lbx_img_seek(img, n); + if (rc < 0) { + tool_err(-1, "frame %u: invalid frame: %s\n", n, lbx_errmsg()); + return -1; + } + + while ((rc = lbx_img_read_row_header(img, &x, &y)) != 0) { + unsigned long offset; + + if (rc < 0) { + tool_err(-1, "frame %u: invalid row: %s", n, lbx_errmsg()); + return -1; + } + + offset = (unsigned long) y * img->width + x; + rc = lbx_img_read_row_data(img, pixels+offset); + if (rc < 0) { + tool_err(-1, "frame %u: error reading row: %s\n", n, lbx_errmsg()); + return -1; + } + + set_bits(pixel_mask, offset, rc); + } + + return 0; +} + +static int +decode(struct lbx_image *img, FILE *palf, FILE *override, int fmt, char **argv) +{ + unsigned char *pixels = NULL, *pixel_mask = NULL, *framebits = NULL; struct lbx_colour palette[256]; struct lbx_imginfo info; + int rc, ret = EXIT_FAILURE; int extracted = 0; unsigned int i; + size_t npixels, mask_sz; + + assert(fmt >= 0 && fmt < sizeof formats / sizeof formats[0]); lbx_img_getinfo(img, &info); + npixels = img->width; + if (img->height && npixels >= SIZE_MAX / img->height) { + tool_err(-1, "image too large"); + goto err; + } + npixels *= img->height; + + /* Ensure there is at least 1 byte to allocate */ + if (npixels == 0) + npixels = 1; + framebits = calloc(1, img->frames / CHAR_BIT + 1); if (!framebits) { - return EXIT_FAILURE; + tool_err(0, "failed to allocate memory"); + goto err; + } + + pixels = calloc(img->width, img->height); + if (!pixels) { + tool_err(0, "failed to allocate memory"); + goto err; + } + + mask_sz = npixels / CHAR_BIT + (npixels % CHAR_BIT != 0); + pixel_mask = malloc(mask_sz); + if (!pixel_mask) { + tool_err(0, "failed to allocate memory"); + goto err; } /* Figure out what images we're extracting. */ @@ -230,66 +393,69 @@ int decode(struct lbx_image *img, FILE *palf, FILE *override, char **argv) if (usepalette) { if (loadpalette(img, &info, palf, override, palette) == -1) { + ret = EXIT_FAILURE; goto err; } } /* Extract the images, in order. */ + ret = EXIT_SUCCESS; for (i = 0; i < img->frames; i++) { - unsigned char **data; - unsigned char **mask; + if (divides(img->chunk, i)) + memset(pixel_mask, 0, mask_sz); - if (!(framebits[i / CHAR_BIT] & (1 << (i % CHAR_BIT)))) - continue; - - data = lbx_img_getframe(img, i); - if (!data) { - tool_err(-1, "error in frame %u: %s", i, lbx_errmsg()); - continue; + rc = decode_frame(img, i, pixels, pixel_mask); + if (rc < 0) { + ret = EXIT_FAILURE; + goto err; } - mask = lbx_img_getmask(img); + if (framebits[i / CHAR_BIT] & (1u << (i % CHAR_BIT))) { + rc = output(i, &formats[fmt], pixels, pixel_mask, + img->width, img->height, + usepalette ? palette : NULL); - if (!outpng(i, data, mask, img->width, img->height, - usepalette ? palette : NULL)) { - extracted = 1; + if (rc == 0) { + extracted = 1; + } } } if (!extracted) { tool_err(-1, "no frames extracted."); - goto err; + ret = EXIT_FAILURE; } - - free(framebits); - return EXIT_SUCCESS; err: + free(pixels); + free(pixel_mask); free(framebits); - return EXIT_FAILURE; + return ret; } int main(int argc, char **argv) { - int mode = MODE_NONE, opt, rc = EXIT_FAILURE; + int mode = MODE_NONE, fmt, opt, rc = EXIT_FAILURE; struct lbx_pipe_state stdin_handle = { .f = stdin }; + const char *file = NULL, *fmtstring = NULL; FILE *palf = NULL, *overf = NULL; - const char *file = NULL; struct lbx_image *img; - static const char *sopts = "idnvf:p:O:V"; + static const char sopts[] = "idnvF:f:p:O:VH"; static const struct option lopts[] = { - { "ident", 0, NULL, 'i' }, - { "decode", 0, NULL, 'd' }, - { "verbose", 0, NULL, 'v' }, - { "file", 1, NULL, 'f' }, - { "palette", 1, NULL, 'p' }, - { "override", 1, NULL, 'p' }, + { "identify", 0, NULL, 'i' }, + { "decode", 0, NULL, 'd' }, + { "verbose", 0, NULL, 'v' }, + { "file", 1, NULL, 'f' }, + { "format", 1, NULL, 'F' }, + { "palette", 1, NULL, 'p' }, + { "override", 1, NULL, 'O' }, + { "no-palette", 0, NULL, 'n' }, - { "version", 0, NULL, 'V' }, - { "usage", 0, NULL, 'U' }, - { "help", 0, NULL, 'H' }, + { "output-prefix", 1, NULL, OPT_PREFIX }, - { "no-palette", 0, NULL, 'n' }, + { "version", 0, NULL, 'V' }, + { "usage", 0, NULL, 'U' }, + { "help", 0, NULL, 'H' }, { 0 } }; @@ -306,6 +472,9 @@ int main(int argc, char **argv) case 'v': verbose = 1; break; + case 'F': + fmtstring = optarg; + break; case 'f': file = optarg; break; @@ -327,6 +496,9 @@ int main(int argc, char **argv) return EXIT_FAILURE; } break; + case OPT_PREFIX: + outname = optarg; + break; case 'V': tool_version(); return EXIT_SUCCESS; @@ -336,8 +508,7 @@ int main(int argc, char **argv) case 'H': printhelp(); return EXIT_SUCCESS; - case '?': - case ':': + default: return EXIT_FAILURE; } } @@ -347,6 +518,10 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + fmt = lookup_format(fmtstring); + if (fmt < 0) + return EXIT_FAILURE; + if (file) img = lbx_img_fopen(file); else @@ -364,7 +539,7 @@ int main(int argc, char **argv) file = "stdin"; lbx_img_getinfo(img, &info); - printf("%s is %ux%u LBX image, %u frame(s)%s%s\n", + printf("%s is %hux%hu LBX image, %hhu frame(s)%s%s%s\n", file, img->width, img->height, img->frames, info.palettesz ? ", embedded palette" : "", img->chunk ? ", chunked" : "", @@ -372,8 +547,11 @@ int main(int argc, char **argv) } switch (mode) { + case MODE_IDENT: + rc = 0; + break; case MODE_DECODE: - rc = decode(img, palf, overf, &argv[optind]); + rc = decode(img, palf, overf, fmt, &argv[optind]); break; }