/* * 2ooM: The Master of Orion II Reverse Engineering Project * Netpbm output routines for lbximg extration. * Copyright © 2013 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 * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "image.h" #include "tools.h" #include "imgoutput.h" /* Convert c from the basic execution character set to ASCII. */ static unsigned char to_ascii(unsigned char c) { switch (c) { case'\a': return 0x07; case'\b': return 0x08; case'\t': return 0x09; case'\n': return 0x0a; case'\v': return 0x0b; case'\f': return 0x0c; case'\r': return 0x0d; case ' ': return 0x20; case '!': return 0x21; case '"': return 0x22; case '#': return 0x23; case '%': return 0x25; case '&': return 0x26; case'\'': return 0x27; case '(': return 0x28; case ')': return 0x29; case '*': return 0x2a; case '+': return 0x2b; case ',': return 0x2c; case '-': return 0x2d; case '.': return 0x2e; case '/': return 0x2f; case '0': return 0x30; case '1': return 0x31; case '2': return 0x32; case '3': return 0x33; case '4': return 0x34; case '5': return 0x35; case '6': return 0x36; case '7': return 0x37; case '8': return 0x38; case '9': return 0x39; case ':': return 0x3a; case ';': return 0x3b; case '<': return 0x3c; case '=': return 0x3d; case '>': return 0x3e; case '?': return 0x3f; case 'A': return 0x41; case 'B': return 0x42; case 'C': return 0x43; case 'D': return 0x44; case 'E': return 0x45; case 'F': return 0x46; case 'G': return 0x47; case 'H': return 0x48; case 'I': return 0x49; case 'J': return 0x4a; case 'K': return 0x4b; case 'L': return 0x4c; case 'M': return 0x4d; case 'N': return 0x4e; case 'O': return 0x4f; case 'P': return 0x50; case 'Q': return 0x51; case 'R': return 0x52; case 'S': return 0x53; case 'T': return 0x54; case 'U': return 0x55; case 'V': return 0x56; case 'W': return 0x57; case 'X': return 0x58; case 'Y': return 0x59; case 'Z': return 0x5a; case '[': return 0x5b; case'\\': return 0x5c; case ']': return 0x5d; case '^': return 0x5e; case '_': return 0x5f; case 'a': return 0x61; case 'b': return 0x62; case 'c': return 0x63; case 'd': return 0x64; case 'e': return 0x65; case 'f': return 0x66; case 'g': return 0x67; case 'h': return 0x68; case 'i': return 0x69; case 'j': return 0x6a; case 'k': return 0x6b; case 'l': return 0x6c; case 'm': return 0x6d; case 'n': return 0x6e; case 'o': return 0x6f; case 'p': return 0x70; case 'q': return 0x71; case 'r': return 0x72; case 's': return 0x73; case 't': return 0x74; case 'u': return 0x75; case 'v': return 0x76; case 'w': return 0x77; case 'x': return 0x78; case 'y': return 0x79; case 'z': return 0x7a; case '{': return 0x7b; case '|': return 0x7c; case '}': return 0x7d; case '~': return 0x7e; default: assert((tool_err(-1, "invalid codepoint: %hhx", c), 0)); } return c; } /* Printf variant which converts all output to ASCII. */ static int fprintf_ascii(FILE *f, char *fmt, ...) { unsigned char *buf; va_list ap; int rc, len; size_t ret; va_start(ap, fmt); rc = vsnprintf(NULL, 0, fmt, ap); va_end(ap); if (rc < 0) return -1; assert(rc < SIZE_MAX); buf = malloc(rc+1u); if (!buf) return -1; va_start(ap, fmt); len = vsprintf((char *)buf, fmt, ap); va_end(ap); assert(rc == len); for (int i = 0; i < len; i++) { buf[i] = to_ascii(buf[i]); } ret = fwrite(buf, 1, len, f); free(buf); if (ret < len) return -(int)ret; return ret; } /* * Output filter for Netpbm's "plain" PBM format. This is a bitmap format * which is not really suitable for image data, but it can output the image * transparency mask, and thus complements the PPM output. The image colour * data is lost in the conversion. */ int img_output_pbm(FILE *f, const char *filename, unsigned width, unsigned height, unsigned char **framedata, unsigned char **mask, struct lbx_colour *palette) { unsigned x, y; if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0) goto err; for (x = y = 0; y < height; ++x < width || (x = 0, y++)) { if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) goto err; if (mask[y][x]) { if (fputc(to_ascii('0'), f) == EOF) goto err; } else { if (fputc(to_ascii('1'), f) == EOF) goto err; } } if (fputc(to_ascii('\n'), f) == EOF) goto err; return 0; err: tool_err(0, "error writing %s", filename); return -1; } /* * Helper for Netpbm's "plain" PGM output. This is only meant for no-palette * mode as normally LBX images are not grayscale, so it is not available as a * normal output format. */ static int write_pgm(FILE *f, unsigned width, unsigned height, unsigned char **framedata, unsigned char **mask) { unsigned x, y; if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0) return -1; for (x = y = 0; y < height; ++x < width || (x = 0, y++)) { if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) return -1; if (!mask[y][x]) { if (fprintf_ascii(f, " 0") < 0) return -1; } else { if (fprintf_ascii(f, "%3hhu", framedata[y][x]) < 0) return -1; } } if (fputc(to_ascii('\n'), f) == EOF) return -1; return 0; } /* * Output filter for Netpbm's "plain" PPM format. This supports RGB but not * transparency. As a pure text format, it is useful for testing but is not * particularly efficient in terms of storage. The image mask is lost in * the conversion (output pixels will be black). */ int img_output_ppm(FILE *f, const char *filename, unsigned width, unsigned height, unsigned char **framedata, unsigned char **mask, struct lbx_colour *palette) { unsigned x, y; if (!palette) { /* * For no-palette mode, write a PGM instead which is basically * the same format but has only one value per pixel. */ if (write_pgm(f, width, height, framedata, mask) < 0) goto err; return 0; } if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0) goto err; for (x = y = 0; y < height; ++x < width || (x = 0, y++)) { if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) goto err; if (!mask[y][x]) { if (fprintf_ascii(f, " 0 0 0") < 0) goto err; } else { struct lbx_colour *c = &palette[framedata[y][x]]; if (fprintf_ascii(f, "%2d %2d %2d", c->red, c->green, c->blue) < 0) goto err; } } if (fputc(to_ascii('\n'), f) == EOF) goto err; return 0; err: tool_err(0, "error writing %s", filename); return -1; } /* * Output filter for Netpbm's PAM format. This format combines the features * of all the other Netpbm formats, supporting RGB and grayscale images with * or without alpha channels. This format supports all lbximg output. */ int img_output_pam(FILE *f, const char *filename, unsigned width, unsigned height, unsigned char **framedata, unsigned char **mask, struct lbx_colour *palette) { bool masked = img_is_masked(mask, width, height); unsigned x, y; size_t depth; if (fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\n", width, height) < 0) goto err; if (palette) { depth = masked ? 4 : 3; if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 63\n" "TUPLTYPE RGB%s\nENDHDR\n", depth, masked ? "_ALPHA" : "") < 0) goto err; for (x = y = 0; y < height; ++x < width || (x = 0, y++)) { struct lbx_colour *c = &palette[framedata[y][x]]; unsigned char buf[4]; buf[0] = c->red; buf[1] = c->green; buf[2] = c->blue; buf[3] = mask[y][x] ? 63 : 0; if (fwrite(buf, 1, depth, f) < depth) goto err; } } else { depth = masked ? 2 : 1; if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 255\n" "TUPLTYPE GRAYSCALE%s\nENDHDR\n", depth, masked ? "_ALPHA" : "") < 0) goto err; for (x = y = 0; y < height; ++x < width || (x = 0, y++)) { unsigned char buf[2]; buf[0] = framedata[y][x]; buf[1] = mask[y][x] ? 0xff : 0; if (fwrite(buf, 1, depth, f) < depth) goto err; } } return 0; err: tool_err(0, "error writing %s", filename); return -1; }