2 * 2ooM: The Master of Orion II Reverse Engineering Project
3 * Netpbm output routines for lbximg extration.
4 * Copyright © 2013-2014 Nick Bowler
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 #include "imgoutput.h"
33 /* Convert c from the basic execution character set to ASCII. */
34 static unsigned char to_ascii(unsigned char c)
37 case'\a': return 0x07;
38 case'\b': return 0x08;
39 case'\t': return 0x09;
40 case'\n': return 0x0a;
41 case'\v': return 0x0b;
42 case'\f': return 0x0c;
43 case'\r': return 0x0d;
44 case ' ': return 0x20;
45 case '!': return 0x21;
46 case '"': return 0x22;
47 case '#': return 0x23;
48 case '%': return 0x25;
49 case '&': return 0x26;
50 case'\'': return 0x27;
51 case '(': return 0x28;
52 case ')': return 0x29;
53 case '*': return 0x2a;
54 case '+': return 0x2b;
55 case ',': return 0x2c;
56 case '-': return 0x2d;
57 case '.': return 0x2e;
58 case '/': return 0x2f;
59 case '0': return 0x30;
60 case '1': return 0x31;
61 case '2': return 0x32;
62 case '3': return 0x33;
63 case '4': return 0x34;
64 case '5': return 0x35;
65 case '6': return 0x36;
66 case '7': return 0x37;
67 case '8': return 0x38;
68 case '9': return 0x39;
69 case ':': return 0x3a;
70 case ';': return 0x3b;
71 case '<': return 0x3c;
72 case '=': return 0x3d;
73 case '>': return 0x3e;
74 case '?': return 0x3f;
75 case 'A': return 0x41;
76 case 'B': return 0x42;
77 case 'C': return 0x43;
78 case 'D': return 0x44;
79 case 'E': return 0x45;
80 case 'F': return 0x46;
81 case 'G': return 0x47;
82 case 'H': return 0x48;
83 case 'I': return 0x49;
84 case 'J': return 0x4a;
85 case 'K': return 0x4b;
86 case 'L': return 0x4c;
87 case 'M': return 0x4d;
88 case 'N': return 0x4e;
89 case 'O': return 0x4f;
90 case 'P': return 0x50;
91 case 'Q': return 0x51;
92 case 'R': return 0x52;
93 case 'S': return 0x53;
94 case 'T': return 0x54;
95 case 'U': return 0x55;
96 case 'V': return 0x56;
97 case 'W': return 0x57;
98 case 'X': return 0x58;
99 case 'Y': return 0x59;
100 case 'Z': return 0x5a;
101 case '[': return 0x5b;
102 case'\\': return 0x5c;
103 case ']': return 0x5d;
104 case '^': return 0x5e;
105 case '_': return 0x5f;
106 case 'a': return 0x61;
107 case 'b': return 0x62;
108 case 'c': return 0x63;
109 case 'd': return 0x64;
110 case 'e': return 0x65;
111 case 'f': return 0x66;
112 case 'g': return 0x67;
113 case 'h': return 0x68;
114 case 'i': return 0x69;
115 case 'j': return 0x6a;
116 case 'k': return 0x6b;
117 case 'l': return 0x6c;
118 case 'm': return 0x6d;
119 case 'n': return 0x6e;
120 case 'o': return 0x6f;
121 case 'p': return 0x70;
122 case 'q': return 0x71;
123 case 'r': return 0x72;
124 case 's': return 0x73;
125 case 't': return 0x74;
126 case 'u': return 0x75;
127 case 'v': return 0x76;
128 case 'w': return 0x77;
129 case 'x': return 0x78;
130 case 'y': return 0x79;
131 case 'z': return 0x7a;
132 case '{': return 0x7b;
133 case '|': return 0x7c;
134 case '}': return 0x7d;
135 case '~': return 0x7e;
136 default: assert((tool_err(-1, "invalid codepoint: %hhx", c), 0));
142 /* Printf variant which converts all output to ASCII. */
143 static int fprintf_ascii(FILE *f, char *fmt, ...)
151 rc = vsnprintf(NULL, 0, fmt, ap);
157 assert(rc < SIZE_MAX);
163 len = vsprintf((char *)buf, fmt, ap);
167 for (int i = 0; i < len; i++) {
168 buf[i] = to_ascii(buf[i]);
171 ret = fwrite(buf, 1, len, f);
180 * Output filter for Netpbm's "plain" PBM format. This is a bitmap format
181 * which is not really suitable for image data, but it can output the image
182 * transparency mask, and thus complements the PPM output. The image colour
183 * data is lost in the conversion.
185 int img_output_pbm(FILE *f, const char *filename,
186 unsigned width, unsigned height,
187 unsigned char *pixels, unsigned char *mask,
188 struct lbx_colour *palette)
192 if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
195 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
196 unsigned long offset = (unsigned long) y * width + x;
197 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
199 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
202 if (fputc(to_ascii(vis ? '0' : '1'), f) == EOF)
206 if (fputc(to_ascii('\n'), f) == EOF)
210 tool_err(0, "error writing %s", filename);
215 * Helper for Netpbm's "plain" PGM output. This is only meant for no-palette
216 * mode as normally LBX images are not grayscale, so it is not available as a
217 * normal output format.
219 static int write_pgm(FILE *f, unsigned width, unsigned height,
220 unsigned char *pixels, unsigned char *mask)
224 if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
227 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
228 unsigned long offset = (unsigned long) y * width + x;
229 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
231 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
234 if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 0)
238 if (fputc(to_ascii('\n'), f) == EOF)
245 * Output filter for Netpbm's "plain" PPM format. This supports RGB but not
246 * transparency. As a pure text format, it is useful for testing but is not
247 * particularly efficient in terms of storage. The image mask is lost in
248 * the conversion (output pixels will be black).
250 int img_output_ppm(FILE *f, const char *filename,
251 unsigned width, unsigned height,
252 unsigned char *pixels, unsigned char *mask,
253 struct lbx_colour *palette)
259 * For no-palette mode, write a PGM instead which is basically
260 * the same format but has only one value per pixel.
262 if (write_pgm(f, width, height, pixels, mask) < 0)
267 if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
270 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
271 unsigned long offset = (unsigned long) y * width + x;
272 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
273 struct lbx_colour c = { 0 };
275 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
279 c = palette[pixels[offset]];
281 if (fprintf_ascii(f, "%2d %2d %2d", c.red, c.green, c.blue) < 0)
285 if (fputc(to_ascii('\n'), f) == EOF)
289 tool_err(0, "error writing %s", filename);
294 * Output filter for Netpbm's PAM format. This format combines the features
295 * of all the other Netpbm formats, supporting RGB and grayscale images with
296 * or without alpha channels. This format supports all lbximg output.
298 int img_output_pam(FILE *f, const char *filename,
299 unsigned width, unsigned height,
300 unsigned char *pixels, unsigned char *mask,
301 struct lbx_colour *palette)
303 bool masked = img_is_masked(mask, width, height);
307 if (fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\n", width, height) < 0)
311 depth = masked ? 4 : 3;
313 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 63\n"
314 "TUPLTYPE RGB%s\nENDHDR\n",
315 depth, masked ? "_ALPHA" : "") < 0)
318 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
319 unsigned long offset = (unsigned long) y * width + x;
320 struct lbx_colour *c = &palette[pixels[offset]];
321 unsigned char buf[4];
324 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
328 buf[3] = vis ? 63 : 0;
330 if (fwrite(buf, 1, depth, f) < depth)
334 depth = masked ? 2 : 1;
336 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 255\n"
337 "TUPLTYPE GRAYSCALE%s\nENDHDR\n",
338 depth, masked ? "_ALPHA" : "") < 0)
341 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
342 unsigned long offset = (unsigned long) y * width + x;
343 unsigned char buf[2];
346 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
347 buf[0] = pixels[offset];
348 buf[1] = vis ? 255 : 0;
350 if (fwrite(buf, 1, depth, f) < depth)
357 tool_err(0, "error writing %s", filename);