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 /* PNM formats are ASCII, check if C exec charset is too */
34 #if '\a' == 0x07 && '\b' == 0x08 && '\t' == 0x09 && '\n' == 0x0a && \
35 '\v' == 0x0b && '\f' == 0x0c && '\r' == 0x0d && ' ' == 0x20 && \
36 '!' == 0x21 && '"' == 0x22 && '#' == 0x23 && '%' == 0x25 && \
37 '&' == 0x26 && '\'' == 0x27 && '(' == 0x28 && ')' == 0x29 && \
38 '*' == 0x2a && '+' == 0x2b && ',' == 0x2c && '-' == 0x2d && \
39 '.' == 0x2e && '/' == 0x2f && '0' == 0x30 && '1' == 0x31 && \
40 '2' == 0x32 && '3' == 0x33 && '4' == 0x34 && '5' == 0x35 && \
41 '6' == 0x36 && '7' == 0x37 && '8' == 0x38 && '9' == 0x39 && \
42 ':' == 0x3a && ';' == 0x3b && '<' == 0x3c && '=' == 0x3d && \
43 '>' == 0x3e && '?' == 0x3f && 'A' == 0x41 && 'B' == 0x42 && \
44 'C' == 0x43 && 'D' == 0x44 && 'E' == 0x45 && 'F' == 0x46 && \
45 'G' == 0x47 && 'H' == 0x48 && 'I' == 0x49 && 'J' == 0x4a && \
46 'K' == 0x4b && 'L' == 0x4c && 'M' == 0x4d && 'N' == 0x4e && \
47 'O' == 0x4f && 'P' == 0x50 && 'Q' == 0x51 && 'R' == 0x52 && \
48 'S' == 0x53 && 'T' == 0x54 && 'U' == 0x55 && 'V' == 0x56 && \
49 'W' == 0x57 && 'X' == 0x58 && 'Y' == 0x59 && 'Z' == 0x5a && \
50 '[' == 0x5b && '\\' == 0x5c && ']' == 0x5d && '^' == 0x5e && \
51 '_' == 0x5f && 'a' == 0x61 && 'b' == 0x62 && 'c' == 0x63 && \
52 'd' == 0x64 && 'e' == 0x65 && 'f' == 0x66 && 'g' == 0x67 && \
53 'h' == 0x68 && 'i' == 0x69 && 'j' == 0x6a && 'k' == 0x6b && \
54 'l' == 0x6c && 'm' == 0x6d && 'n' == 0x6e && 'o' == 0x6f && \
55 'p' == 0x70 && 'q' == 0x71 && 'r' == 0x72 && 's' == 0x73 && \
56 't' == 0x74 && 'u' == 0x75 && 'v' == 0x76 && 'w' == 0x77 && \
57 'x' == 0x78 && 'y' == 0x79 && 'z' == 0x7a && '{' == 0x7b && \
58 '|' == 0x7c && '}' == 0x7d && '~' == 0x7e
59 # define NATIVE_ASCII 1
61 # define NATIVE_ASCII 0
64 /* Convert c from the basic execution character set to ASCII. */
65 static unsigned char to_ascii(unsigned char c)
68 case'\a': return 0x07;
69 case'\b': return 0x08;
70 case'\t': return 0x09;
71 case'\n': return 0x0a;
72 case'\v': return 0x0b;
73 case'\f': return 0x0c;
74 case'\r': return 0x0d;
75 case ' ': return 0x20;
76 case '!': return 0x21;
77 case '"': return 0x22;
78 case '#': return 0x23;
79 case '%': return 0x25;
80 case '&': return 0x26;
81 case'\'': return 0x27;
82 case '(': return 0x28;
83 case ')': return 0x29;
84 case '*': return 0x2a;
85 case '+': return 0x2b;
86 case ',': return 0x2c;
87 case '-': return 0x2d;
88 case '.': return 0x2e;
89 case '/': return 0x2f;
90 case '0': return 0x30;
91 case '1': return 0x31;
92 case '2': return 0x32;
93 case '3': return 0x33;
94 case '4': return 0x34;
95 case '5': return 0x35;
96 case '6': return 0x36;
97 case '7': return 0x37;
98 case '8': return 0x38;
99 case '9': return 0x39;
100 case ':': return 0x3a;
101 case ';': return 0x3b;
102 case '<': return 0x3c;
103 case '=': return 0x3d;
104 case '>': return 0x3e;
105 case '?': return 0x3f;
106 case 'A': return 0x41;
107 case 'B': return 0x42;
108 case 'C': return 0x43;
109 case 'D': return 0x44;
110 case 'E': return 0x45;
111 case 'F': return 0x46;
112 case 'G': return 0x47;
113 case 'H': return 0x48;
114 case 'I': return 0x49;
115 case 'J': return 0x4a;
116 case 'K': return 0x4b;
117 case 'L': return 0x4c;
118 case 'M': return 0x4d;
119 case 'N': return 0x4e;
120 case 'O': return 0x4f;
121 case 'P': return 0x50;
122 case 'Q': return 0x51;
123 case 'R': return 0x52;
124 case 'S': return 0x53;
125 case 'T': return 0x54;
126 case 'U': return 0x55;
127 case 'V': return 0x56;
128 case 'W': return 0x57;
129 case 'X': return 0x58;
130 case 'Y': return 0x59;
131 case 'Z': return 0x5a;
132 case '[': return 0x5b;
133 case'\\': return 0x5c;
134 case ']': return 0x5d;
135 case '^': return 0x5e;
136 case '_': return 0x5f;
137 case 'a': return 0x61;
138 case 'b': return 0x62;
139 case 'c': return 0x63;
140 case 'd': return 0x64;
141 case 'e': return 0x65;
142 case 'f': return 0x66;
143 case 'g': return 0x67;
144 case 'h': return 0x68;
145 case 'i': return 0x69;
146 case 'j': return 0x6a;
147 case 'k': return 0x6b;
148 case 'l': return 0x6c;
149 case 'm': return 0x6d;
150 case 'n': return 0x6e;
151 case 'o': return 0x6f;
152 case 'p': return 0x70;
153 case 'q': return 0x71;
154 case 'r': return 0x72;
155 case 's': return 0x73;
156 case 't': return 0x74;
157 case 'u': return 0x75;
158 case 'v': return 0x76;
159 case 'w': return 0x77;
160 case 'x': return 0x78;
161 case 'y': return 0x79;
162 case 'z': return 0x7a;
163 case '{': return 0x7b;
164 case '|': return 0x7c;
165 case '}': return 0x7d;
166 case '~': return 0x7e;
167 default: assert((tool_err(-1, "invalid codepoint: %hhx", c), 0));
173 #if NATIVE_ASCII && defined(NDEBUG)
174 /* fprintf can be used directly. */
175 #define fprintf_ascii fprintf
177 /* Variant of fprintf which converts all output to ASCII. */
178 static int fprintf_ascii(FILE *f, char *fmt, ...)
185 len = vsnprintf(buf, sizeof buf, fmt, ap);
188 if (len < 0 || len >= sizeof buf)
191 for (i = 0; i < len; i++) {
192 if (fputc(to_ascii(buf[i]), f) == EOF)
201 * Output filter for Netpbm's "plain" PBM format. This is a bitmap format
202 * which is not really suitable for image data, but it can output the image
203 * transparency mask, and thus complements the PPM output. The image colour
204 * data is lost in the conversion.
206 int img_output_pbm(FILE *f, const char *filename,
207 unsigned width, unsigned height,
208 unsigned char *pixels, unsigned char *mask,
209 struct lbx_colour *palette)
213 if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
216 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
217 unsigned long offset = (unsigned long) y * width + x;
218 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
220 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
223 if (fputc(to_ascii(vis ? '0' : '1'), f) == EOF)
227 if (fputc(to_ascii('\n'), f) == EOF)
231 tool_err(0, "error writing %s", filename);
236 * Helper for Netpbm's "plain" PGM output. This is only meant for no-palette
237 * mode as normally LBX images are not grayscale, so it is not available as a
238 * normal output format.
240 static int write_pgm(FILE *f, unsigned width, unsigned height,
241 unsigned char *pixels, unsigned char *mask)
245 if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
248 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
249 unsigned long offset = (unsigned long) y * width + x;
250 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
252 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
255 if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 0)
259 if (fputc(to_ascii('\n'), f) == EOF)
266 * Output filter for Netpbm's "plain" PPM format. This supports RGB but not
267 * transparency. As a pure text format, it is useful for testing but is not
268 * particularly efficient in terms of storage. The image mask is lost in
269 * the conversion (output pixels will be black).
271 int img_output_ppm(FILE *f, const char *filename,
272 unsigned width, unsigned height,
273 unsigned char *pixels, unsigned char *mask,
274 struct lbx_colour *palette)
280 * For no-palette mode, write a PGM instead which is basically
281 * the same format but has only one value per pixel.
283 if (write_pgm(f, width, height, pixels, mask) < 0)
288 if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
291 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
292 unsigned long offset = (unsigned long) y * width + x;
293 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
294 struct lbx_colour c = { 0 };
296 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
300 c = palette[pixels[offset]];
302 if (fprintf_ascii(f, "%2d %2d %2d", c.red, c.green, c.blue) < 0)
306 if (fputc(to_ascii('\n'), f) == EOF)
310 tool_err(0, "error writing %s", filename);
315 * Output filter for Netpbm's PAM format. This format combines the features
316 * of all the other Netpbm formats, supporting RGB and grayscale images with
317 * or without alpha channels. This format supports all lbximg output.
319 int img_output_pam(FILE *f, const char *filename,
320 unsigned width, unsigned height,
321 unsigned char *pixels, unsigned char *mask,
322 struct lbx_colour *palette)
324 bool masked = img_is_masked(mask, width, height);
328 if (fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\n", width, height) < 0)
332 depth = masked ? 4 : 3;
334 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 63\n"
335 "TUPLTYPE RGB%s\nENDHDR\n",
336 depth, masked ? "_ALPHA" : "") < 0)
339 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
340 unsigned long offset = (unsigned long) y * width + x;
341 struct lbx_colour *c = &palette[pixels[offset]];
342 unsigned char buf[4];
345 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
349 buf[3] = vis ? 63 : 0;
351 if (fwrite(buf, 1, depth, f) < depth)
355 depth = masked ? 2 : 1;
357 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 255\n"
358 "TUPLTYPE GRAYSCALE%s\nENDHDR\n",
359 depth, masked ? "_ALPHA" : "") < 0)
362 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
363 unsigned long offset = (unsigned long) y * width + x;
364 unsigned char buf[2];
367 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
368 buf[0] = pixels[offset];
369 buf[1] = vis ? 255 : 0;
371 if (fwrite(buf, 1, depth, f) < depth)
378 tool_err(0, "error writing %s", filename);