/* * 2ooM: The Master of Orion II Reverse Engineering Project * Netpbm output routines for lbximg extration. * Copyright © 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 * 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 #include #include "image.h" #include "tools.h" #include "imgoutput.h" /* PNM formats are ASCII, check if C exec charset is too */ #if '\a' == 0x07 && '\b' == 0x08 && '\t' == 0x09 && '\n' == 0x0a && \ '\v' == 0x0b && '\f' == 0x0c && '\r' == 0x0d && ' ' == 0x20 && \ '!' == 0x21 && '"' == 0x22 && '#' == 0x23 && '%' == 0x25 && \ '&' == 0x26 && '\'' == 0x27 && '(' == 0x28 && ')' == 0x29 && \ '*' == 0x2a && '+' == 0x2b && ',' == 0x2c && '-' == 0x2d && \ '.' == 0x2e && '/' == 0x2f && '0' == 0x30 && '1' == 0x31 && \ '2' == 0x32 && '3' == 0x33 && '4' == 0x34 && '5' == 0x35 && \ '6' == 0x36 && '7' == 0x37 && '8' == 0x38 && '9' == 0x39 && \ ':' == 0x3a && ';' == 0x3b && '<' == 0x3c && '=' == 0x3d && \ '>' == 0x3e && '?' == 0x3f && 'A' == 0x41 && 'B' == 0x42 && \ 'C' == 0x43 && 'D' == 0x44 && 'E' == 0x45 && 'F' == 0x46 && \ 'G' == 0x47 && 'H' == 0x48 && 'I' == 0x49 && 'J' == 0x4a && \ 'K' == 0x4b && 'L' == 0x4c && 'M' == 0x4d && 'N' == 0x4e && \ 'O' == 0x4f && 'P' == 0x50 && 'Q' == 0x51 && 'R' == 0x52 && \ 'S' == 0x53 && 'T' == 0x54 && 'U' == 0x55 && 'V' == 0x56 && \ 'W' == 0x57 && 'X' == 0x58 && 'Y' == 0x59 && 'Z' == 0x5a && \ '[' == 0x5b && '\\' == 0x5c && ']' == 0x5d && '^' == 0x5e && \ '_' == 0x5f && 'a' == 0x61 && 'b' == 0x62 && 'c' == 0x63 && \ 'd' == 0x64 && 'e' == 0x65 && 'f' == 0x66 && 'g' == 0x67 && \ 'h' == 0x68 && 'i' == 0x69 && 'j' == 0x6a && 'k' == 0x6b && \ 'l' == 0x6c && 'm' == 0x6d && 'n' == 0x6e && 'o' == 0x6f && \ 'p' == 0x70 && 'q' == 0x71 && 'r' == 0x72 && 's' == 0x73 && \ 't' == 0x74 && 'u' == 0x75 && 'v' == 0x76 && 'w' == 0x77 && \ 'x' == 0x78 && 'y' == 0x79 && 'z' == 0x7a && '{' == 0x7b && \ '|' == 0x7c && '}' == 0x7d && '~' == 0x7e # define NATIVE_ASCII 1 #else # define NATIVE_ASCII 0 #endif /* 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; } #if NATIVE_ASCII && defined(NDEBUG) /* fprintf can be used directly. */ #define fprintf_ascii fprintf #else /* Variant of fprintf which converts all output to ASCII. */ static int fprintf_ascii(FILE *f, char *fmt, ...) { char buf[100]; va_list ap; int i, len; va_start(ap, fmt); len = vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); if (len < 0 || len >= sizeof buf) return -1; for (i = 0; i < len; i++) { if (fputc(to_ascii(buf[i]), f) == EOF) return -1; } return len; } #endif /* * 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 *pixels, 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++)) { unsigned long offset = (unsigned long) y * width + x; bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT); if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) goto err; if (fputc(to_ascii(vis ? '0' : '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 *pixels, 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++)) { unsigned long offset = (unsigned long) y * width + x; bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT); if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) return -1; if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 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 *pixels, 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, pixels, 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++)) { unsigned long offset = (unsigned long) y * width + x; bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT); struct lbx_colour c = { 0 }; if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF) goto err; if (vis) c = palette[pixels[offset]]; 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; } static void pam_format_rgba(unsigned char *rowbuf, const unsigned char *pixels, unsigned char *mask, unsigned width, unsigned long offset, struct lbx_colour *palette) { unsigned x; for (x = 0; x < width; x++) { struct lbx_colour val = palette[pixels[offset+x]]; unsigned vis = 1 & ( mask[(offset+x)/CHAR_BIT] >> ((offset+x)%CHAR_BIT) ); if (sizeof val == 4) { memcpy(rowbuf+4ul*x, &val, 4); } else { rowbuf[4ul*x+0] = val.red; rowbuf[4ul*x+1] = val.green; rowbuf[4ul*x+2] = val.blue; } rowbuf[4ul*x+3] = -vis & 0x3f; } } static void pam_format_rgb(unsigned char *rowbuf, const unsigned char *pixels, unsigned width, unsigned long offset, struct lbx_colour *palette) { unsigned x; for (x = 0; x < width; x++) { struct lbx_colour val = palette[pixels[offset+x]]; if (sizeof val == 4) { memcpy(rowbuf+3ul*x, &val, 4); } else { rowbuf[3ul*x+0] = val.red; rowbuf[3ul*x+1] = val.green; rowbuf[3ul*x+2] = val.blue; } } } static void pam_format_ga(unsigned char *rowbuf, const unsigned char *pixels, const unsigned char *mask, unsigned width, unsigned long offset) { unsigned x; for (x = 0; x < width; x++) { unsigned vis = 1 & ( mask[(offset+x)/CHAR_BIT] >> ((offset+x)%CHAR_BIT) ); rowbuf[2ul*x+0] = pixels[offset+x]; rowbuf[2ul*x+1] = -vis & 0xff; } } static void pam_format_g(unsigned char *rowbuf, const unsigned char *pixels, unsigned width, unsigned long offset) { memcpy(rowbuf, pixels+offset, width); } /* * 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 *pixels, unsigned char *mask, struct lbx_colour *palette) { bool masked = img_is_masked(mask, width, height); unsigned char *rowbuf; unsigned long offset; unsigned y, depth; int rc; depth = (palette ? 3 : 1) + masked; if (width >= (size_t)-1 / depth) goto alloc_err; rowbuf = malloc(width * depth + 1); if (!rowbuf) { alloc_err: tool_err(-1, "failed to allocate memory"); return -1; } rc = fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\nDEPTH %u\n", width, height, depth); if (rc < 0) goto write_err; rc = fprintf_ascii(f, "MAXVAL %d\nTUPLTYPE %s", palette ? 63 : 255, palette ? "RGB" : "GRAYSCALE"); if (rc < 0) goto write_err; rc = fprintf_ascii(f, "_ALPHA\nENDHDR\n" + 6*!masked); if (rc < 0) goto write_err; for (offset = y = 0; y < height; y++, offset += width) { switch (depth) { case 4: pam_format_rgba(rowbuf, pixels, mask, width, offset, palette); break; case 3: pam_format_rgb(rowbuf, pixels, width, offset, palette); break; case 2: pam_format_ga(rowbuf, pixels, mask, width, offset); break; case 1: pam_format_g(rowbuf, pixels, width, offset); break; } if (fwrite(rowbuf, depth, width, f) < width) goto write_err; } free(rowbuf); return 0; write_err: tool_err(0, "error writing %s", filename); free(rowbuf); return -1; }