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/>.
32 #include "imgoutput.h"
34 /* PNM formats are ASCII, check if C exec charset is too */
35 #if '\a' == 0x07 && '\b' == 0x08 && '\t' == 0x09 && '\n' == 0x0a && \
36 '\v' == 0x0b && '\f' == 0x0c && '\r' == 0x0d && ' ' == 0x20 && \
37 '!' == 0x21 && '"' == 0x22 && '#' == 0x23 && '%' == 0x25 && \
38 '&' == 0x26 && '\'' == 0x27 && '(' == 0x28 && ')' == 0x29 && \
39 '*' == 0x2a && '+' == 0x2b && ',' == 0x2c && '-' == 0x2d && \
40 '.' == 0x2e && '/' == 0x2f && '0' == 0x30 && '1' == 0x31 && \
41 '2' == 0x32 && '3' == 0x33 && '4' == 0x34 && '5' == 0x35 && \
42 '6' == 0x36 && '7' == 0x37 && '8' == 0x38 && '9' == 0x39 && \
43 ':' == 0x3a && ';' == 0x3b && '<' == 0x3c && '=' == 0x3d && \
44 '>' == 0x3e && '?' == 0x3f && 'A' == 0x41 && 'B' == 0x42 && \
45 'C' == 0x43 && 'D' == 0x44 && 'E' == 0x45 && 'F' == 0x46 && \
46 'G' == 0x47 && 'H' == 0x48 && 'I' == 0x49 && 'J' == 0x4a && \
47 'K' == 0x4b && 'L' == 0x4c && 'M' == 0x4d && 'N' == 0x4e && \
48 'O' == 0x4f && 'P' == 0x50 && 'Q' == 0x51 && 'R' == 0x52 && \
49 'S' == 0x53 && 'T' == 0x54 && 'U' == 0x55 && 'V' == 0x56 && \
50 'W' == 0x57 && 'X' == 0x58 && 'Y' == 0x59 && 'Z' == 0x5a && \
51 '[' == 0x5b && '\\' == 0x5c && ']' == 0x5d && '^' == 0x5e && \
52 '_' == 0x5f && 'a' == 0x61 && 'b' == 0x62 && 'c' == 0x63 && \
53 'd' == 0x64 && 'e' == 0x65 && 'f' == 0x66 && 'g' == 0x67 && \
54 'h' == 0x68 && 'i' == 0x69 && 'j' == 0x6a && 'k' == 0x6b && \
55 'l' == 0x6c && 'm' == 0x6d && 'n' == 0x6e && 'o' == 0x6f && \
56 'p' == 0x70 && 'q' == 0x71 && 'r' == 0x72 && 's' == 0x73 && \
57 't' == 0x74 && 'u' == 0x75 && 'v' == 0x76 && 'w' == 0x77 && \
58 'x' == 0x78 && 'y' == 0x79 && 'z' == 0x7a && '{' == 0x7b && \
59 '|' == 0x7c && '}' == 0x7d && '~' == 0x7e
60 # define NATIVE_ASCII 1
62 # define NATIVE_ASCII 0
65 /* Convert c from the basic execution character set to ASCII. */
66 static unsigned char to_ascii(unsigned char c)
69 case'\a': return 0x07;
70 case'\b': return 0x08;
71 case'\t': return 0x09;
72 case'\n': return 0x0a;
73 case'\v': return 0x0b;
74 case'\f': return 0x0c;
75 case'\r': return 0x0d;
76 case ' ': return 0x20;
77 case '!': return 0x21;
78 case '"': return 0x22;
79 case '#': return 0x23;
80 case '%': return 0x25;
81 case '&': return 0x26;
82 case'\'': return 0x27;
83 case '(': return 0x28;
84 case ')': return 0x29;
85 case '*': return 0x2a;
86 case '+': return 0x2b;
87 case ',': return 0x2c;
88 case '-': return 0x2d;
89 case '.': return 0x2e;
90 case '/': return 0x2f;
91 case '0': return 0x30;
92 case '1': return 0x31;
93 case '2': return 0x32;
94 case '3': return 0x33;
95 case '4': return 0x34;
96 case '5': return 0x35;
97 case '6': return 0x36;
98 case '7': return 0x37;
99 case '8': return 0x38;
100 case '9': return 0x39;
101 case ':': return 0x3a;
102 case ';': return 0x3b;
103 case '<': return 0x3c;
104 case '=': return 0x3d;
105 case '>': return 0x3e;
106 case '?': return 0x3f;
107 case 'A': return 0x41;
108 case 'B': return 0x42;
109 case 'C': return 0x43;
110 case 'D': return 0x44;
111 case 'E': return 0x45;
112 case 'F': return 0x46;
113 case 'G': return 0x47;
114 case 'H': return 0x48;
115 case 'I': return 0x49;
116 case 'J': return 0x4a;
117 case 'K': return 0x4b;
118 case 'L': return 0x4c;
119 case 'M': return 0x4d;
120 case 'N': return 0x4e;
121 case 'O': return 0x4f;
122 case 'P': return 0x50;
123 case 'Q': return 0x51;
124 case 'R': return 0x52;
125 case 'S': return 0x53;
126 case 'T': return 0x54;
127 case 'U': return 0x55;
128 case 'V': return 0x56;
129 case 'W': return 0x57;
130 case 'X': return 0x58;
131 case 'Y': return 0x59;
132 case 'Z': return 0x5a;
133 case '[': return 0x5b;
134 case'\\': return 0x5c;
135 case ']': return 0x5d;
136 case '^': return 0x5e;
137 case '_': return 0x5f;
138 case 'a': return 0x61;
139 case 'b': return 0x62;
140 case 'c': return 0x63;
141 case 'd': return 0x64;
142 case 'e': return 0x65;
143 case 'f': return 0x66;
144 case 'g': return 0x67;
145 case 'h': return 0x68;
146 case 'i': return 0x69;
147 case 'j': return 0x6a;
148 case 'k': return 0x6b;
149 case 'l': return 0x6c;
150 case 'm': return 0x6d;
151 case 'n': return 0x6e;
152 case 'o': return 0x6f;
153 case 'p': return 0x70;
154 case 'q': return 0x71;
155 case 'r': return 0x72;
156 case 's': return 0x73;
157 case 't': return 0x74;
158 case 'u': return 0x75;
159 case 'v': return 0x76;
160 case 'w': return 0x77;
161 case 'x': return 0x78;
162 case 'y': return 0x79;
163 case 'z': return 0x7a;
164 case '{': return 0x7b;
165 case '|': return 0x7c;
166 case '}': return 0x7d;
167 case '~': return 0x7e;
168 default: assert((tool_err(-1, "invalid codepoint: %hhx", c), 0));
174 #if NATIVE_ASCII && defined(NDEBUG)
175 /* fprintf can be used directly. */
176 #define fprintf_ascii fprintf
178 /* Variant of fprintf which converts all output to ASCII. */
179 static int fprintf_ascii(FILE *f, char *fmt, ...)
186 len = vsnprintf(buf, sizeof buf, fmt, ap);
189 if (len < 0 || len >= sizeof buf)
192 for (i = 0; i < len; i++) {
193 if (fputc(to_ascii(buf[i]), f) == EOF)
202 * Output filter for Netpbm's "plain" PBM format. This is a bitmap format
203 * which is not really suitable for image data, but it can output the image
204 * transparency mask, and thus complements the PPM output. The image colour
205 * data is lost in the conversion.
207 int img_output_pbm(FILE *f, const char *filename,
208 unsigned width, unsigned height,
209 unsigned char *pixels, unsigned char *mask,
210 struct lbx_colour *palette)
214 if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
217 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
218 unsigned long offset = (unsigned long) y * width + x;
219 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
221 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
224 if (fputc(to_ascii(vis ? '0' : '1'), f) == EOF)
228 if (fputc(to_ascii('\n'), f) == EOF)
232 tool_err(0, "error writing %s", filename);
237 * Helper for Netpbm's "plain" PGM output. This is only meant for no-palette
238 * mode as normally LBX images are not grayscale, so it is not available as a
239 * normal output format.
241 static int write_pgm(FILE *f, unsigned width, unsigned height,
242 unsigned char *pixels, unsigned char *mask)
246 if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
249 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
250 unsigned long offset = (unsigned long) y * width + x;
251 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
253 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
256 if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 0)
260 if (fputc(to_ascii('\n'), f) == EOF)
267 * Output filter for Netpbm's "plain" PPM format. This supports RGB but not
268 * transparency. As a pure text format, it is useful for testing but is not
269 * particularly efficient in terms of storage. The image mask is lost in
270 * the conversion (output pixels will be black).
272 int img_output_ppm(FILE *f, const char *filename,
273 unsigned width, unsigned height,
274 unsigned char *pixels, unsigned char *mask,
275 struct lbx_colour *palette)
281 * For no-palette mode, write a PGM instead which is basically
282 * the same format but has only one value per pixel.
284 if (write_pgm(f, width, height, pixels, mask) < 0)
289 if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
292 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
293 unsigned long offset = (unsigned long) y * width + x;
294 bool vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
295 struct lbx_colour c = { 0 };
297 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
301 c = palette[pixels[offset]];
303 if (fprintf_ascii(f, "%2d %2d %2d", c.red, c.green, c.blue) < 0)
307 if (fputc(to_ascii('\n'), f) == EOF)
311 tool_err(0, "error writing %s", filename);
315 static void pam_format_rgba(unsigned char *rowbuf, const unsigned char *pixels,
316 unsigned char *mask, unsigned width,
317 unsigned long offset, struct lbx_colour *palette)
321 for (x = 0; x < width; x++) {
322 struct lbx_colour val = palette[pixels[offset+x]];
323 unsigned vis = 1 & ( mask[(offset+x)/CHAR_BIT]
324 >> ((offset+x)%CHAR_BIT) );
326 if (sizeof val == 4) {
327 memcpy(rowbuf+4ul*x, &val, 4);
329 rowbuf[4ul*x+0] = val.red;
330 rowbuf[4ul*x+1] = val.green;
331 rowbuf[4ul*x+2] = val.blue;
333 rowbuf[4ul*x+3] = -vis & 0x3f;
337 static void pam_format_rgb(unsigned char *rowbuf, const unsigned char *pixels,
338 unsigned width, unsigned long offset,
339 struct lbx_colour *palette)
343 for (x = 0; x < width; x++) {
344 struct lbx_colour val = palette[pixels[offset+x]];
346 if (sizeof val == 4) {
347 memcpy(rowbuf+3ul*x, &val, 4);
349 rowbuf[3ul*x+0] = val.red;
350 rowbuf[3ul*x+1] = val.green;
351 rowbuf[3ul*x+2] = val.blue;
356 static void pam_format_ga(unsigned char *rowbuf,
357 const unsigned char *pixels, const unsigned char *mask,
358 unsigned width, unsigned long offset)
362 for (x = 0; x < width; x++) {
363 unsigned vis = 1 & ( mask[(offset+x)/CHAR_BIT]
364 >> ((offset+x)%CHAR_BIT) );
366 rowbuf[2ul*x+0] = pixels[offset+x];
367 rowbuf[2ul*x+1] = -vis & 0xff;
371 static void pam_format_g(unsigned char *rowbuf, const unsigned char *pixels,
372 unsigned width, unsigned long offset)
374 memcpy(rowbuf, pixels+offset, width);
378 * Output filter for Netpbm's PAM format. This format combines the features
379 * of all the other Netpbm formats, supporting RGB and grayscale images with
380 * or without alpha channels. This format supports all lbximg output.
382 int img_output_pam(FILE *f, const char *filename,
383 unsigned width, unsigned height,
384 unsigned char *pixels, unsigned char *mask,
385 struct lbx_colour *palette)
387 bool masked = img_is_masked(mask, width, height);
388 unsigned char *rowbuf;
389 unsigned long offset;
393 depth = (palette ? 3 : 1) + masked;
394 if (width >= (size_t)-1 / depth)
397 rowbuf = malloc(width * depth + 1);
400 tool_err(-1, "failed to allocate memory");
404 rc = fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\nDEPTH %u\n",
405 width, height, depth);
409 rc = fprintf_ascii(f, "MAXVAL %d\nTUPLTYPE %s",
411 palette ? "RGB" : "GRAYSCALE");
415 rc = fprintf_ascii(f, "_ALPHA\nENDHDR\n" + 6*!masked);
419 for (offset = y = 0; y < height; y++, offset += width) {
422 pam_format_rgba(rowbuf, pixels, mask, width, offset, palette);
425 pam_format_rgb(rowbuf, pixels, width, offset, palette);
428 pam_format_ga(rowbuf, pixels, mask, width, offset);
431 pam_format_g(rowbuf, pixels, width, offset);
435 if (fwrite(rowbuf, depth, width, f) < width)
442 tool_err(0, "error writing %s", filename);