]> git.draconx.ca Git - liblbx.git/blob - src/pnm.c
lbximg: Add support for Netpbm output.
[liblbx.git] / src / pnm.c
1 /*
2  * 2ooM: The Master of Orion II Reverse Engineering Project
3  * Netpbm output routines for lbximg extration.
4  * Copyright © 2013 Nick Bowler
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 #include <config.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdbool.h>
24 #include <stdarg.h>
25 #include <assert.h>
26 #include <inttypes.h>
27
28 #include "image.h"
29 #include "tools.h"
30 #include "imgoutput.h"
31
32 /* Convert c from the basic execution character set to ASCII. */
33 static unsigned char to_ascii(unsigned char c)
34 {
35         switch (c) {
36         case'\a': return 0x07;
37         case'\b': return 0x08;
38         case'\t': return 0x09;
39         case'\n': return 0x0a;
40         case'\v': return 0x0b;
41         case'\f': return 0x0c;
42         case'\r': return 0x0d;
43         case ' ': return 0x20;
44         case '!': return 0x21;
45         case '"': return 0x22;
46         case '#': return 0x23;
47         case '%': return 0x25;
48         case '&': return 0x26;
49         case'\'': return 0x27;
50         case '(': return 0x28;
51         case ')': return 0x29;
52         case '*': return 0x2a;
53         case '+': return 0x2b;
54         case ',': return 0x2c;
55         case '-': return 0x2d;
56         case '.': return 0x2e;
57         case '/': return 0x2f;
58         case '0': return 0x30;
59         case '1': return 0x31;
60         case '2': return 0x32;
61         case '3': return 0x33;
62         case '4': return 0x34;
63         case '5': return 0x35;
64         case '6': return 0x36;
65         case '7': return 0x37;
66         case '8': return 0x38;
67         case '9': return 0x39;
68         case ':': return 0x3a;
69         case ';': return 0x3b;
70         case '<': return 0x3c;
71         case '=': return 0x3d;
72         case '>': return 0x3e;
73         case '?': return 0x3f;
74         case 'A': return 0x41;
75         case 'B': return 0x42;
76         case 'C': return 0x43;
77         case 'D': return 0x44;
78         case 'E': return 0x45;
79         case 'F': return 0x46;
80         case 'G': return 0x47;
81         case 'H': return 0x48;
82         case 'I': return 0x49;
83         case 'J': return 0x4a;
84         case 'K': return 0x4b;
85         case 'L': return 0x4c;
86         case 'M': return 0x4d;
87         case 'N': return 0x4e;
88         case 'O': return 0x4f;
89         case 'P': return 0x50;
90         case 'Q': return 0x51;
91         case 'R': return 0x52;
92         case 'S': return 0x53;
93         case 'T': return 0x54;
94         case 'U': return 0x55;
95         case 'V': return 0x56;
96         case 'W': return 0x57;
97         case 'X': return 0x58;
98         case 'Y': return 0x59;
99         case 'Z': return 0x5a;
100         case '[': return 0x5b;
101         case'\\': return 0x5c;
102         case ']': return 0x5d;
103         case '^': return 0x5e;
104         case '_': return 0x5f;
105         case 'a': return 0x61;
106         case 'b': return 0x62;
107         case 'c': return 0x63;
108         case 'd': return 0x64;
109         case 'e': return 0x65;
110         case 'f': return 0x66;
111         case 'g': return 0x67;
112         case 'h': return 0x68;
113         case 'i': return 0x69;
114         case 'j': return 0x6a;
115         case 'k': return 0x6b;
116         case 'l': return 0x6c;
117         case 'm': return 0x6d;
118         case 'n': return 0x6e;
119         case 'o': return 0x6f;
120         case 'p': return 0x70;
121         case 'q': return 0x71;
122         case 'r': return 0x72;
123         case 's': return 0x73;
124         case 't': return 0x74;
125         case 'u': return 0x75;
126         case 'v': return 0x76;
127         case 'w': return 0x77;
128         case 'x': return 0x78;
129         case 'y': return 0x79;
130         case 'z': return 0x7a;
131         case '{': return 0x7b;
132         case '|': return 0x7c;
133         case '}': return 0x7d;
134         case '~': return 0x7e;
135         default: assert((tool_err(-1, "invalid codepoint: %hhx", c), 0));
136         }
137
138         return c;
139 }
140
141 /* Printf variant which converts all output to ASCII. */
142 static int fprintf_ascii(FILE *f, char *fmt, ...)
143 {
144         unsigned char *buf;
145         va_list ap;
146         int rc, len;
147         size_t ret;
148
149         va_start(ap, fmt);
150         rc = vsnprintf(NULL, 0, fmt, ap);
151         va_end(ap);
152
153         if (rc < 0)
154                 return -1;
155
156         assert(rc < SIZE_MAX);
157         buf = malloc(rc+1u);
158         if (!buf)
159                 return -1;
160
161         va_start(ap, fmt);
162         len = vsprintf((char *)buf, fmt, ap);
163         va_end(ap);
164
165         assert(rc == len);
166         for (int i = 0; i < len; i++) {
167                 buf[i] = to_ascii(buf[i]);
168         }
169
170         ret = fwrite(buf, 1, len, f);
171         free(buf);
172
173         if (ret < len)
174                 return -(int)ret;
175         return ret;
176 }
177
178 /*
179  * Output filter for Netpbm's "plain" PBM format.  This is a bitmap format
180  * which is not really suitable for image data, but it can output the image
181  * transparency mask, and thus complements the PPM output.  The image colour
182  * data is lost in the conversion.
183  */
184 int img_output_pbm(FILE *f, const char *filename,
185                    unsigned width, unsigned height,
186                    unsigned char **framedata, unsigned char **mask,
187                    struct lbx_colour *palette)
188 {
189         unsigned x, y;
190
191         if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
192                 goto err;
193
194         for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
195                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
196                         goto err;
197
198                 if (mask[y][x]) {
199                         if (fputc(to_ascii('0'), f) == EOF)
200                                 goto err;
201                 } else {
202                         if (fputc(to_ascii('1'), f) == EOF)
203                                 goto err;
204                 }
205         }
206
207         if (fputc(to_ascii('\n'), f) == EOF)
208                 goto err;
209         return 0;
210 err:
211         tool_err(0, "error writing %s", filename);
212         return -1;
213 }
214
215 /*
216  * Helper for Netpbm's "plain" PGM output.  This is only meant for no-palette
217  * mode as normally LBX images are not grayscale, so it is not available as a
218  * normal output format.
219  */
220 static int write_pgm(FILE *f, unsigned width, unsigned height,
221                      unsigned char **framedata, unsigned char **mask)
222 {
223         unsigned x, y;
224
225         if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
226                 return -1;
227
228         for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
229                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
230                         return -1;
231
232                 if (!mask[y][x]) {
233                         if (fprintf_ascii(f, "  0") < 0)
234                                 return -1;
235                 } else {
236                         if (fprintf_ascii(f, "%3hhu", framedata[y][x]) < 0)
237                                 return -1;
238                 }
239         }
240
241         if (fputc(to_ascii('\n'), f) == EOF)
242                 return -1;
243
244         return 0;
245 }
246
247 /*
248  * Output filter for Netpbm's "plain" PPM format.  This supports RGB but not
249  * transparency.  As a pure text format, it is useful for testing but is not
250  * particularly efficient in terms of storage.  The image mask is lost in
251  * the conversion (output pixels will be black).
252  */
253 int img_output_ppm(FILE *f, const char *filename,
254                    unsigned width, unsigned height,
255                    unsigned char **framedata, unsigned char **mask,
256                    struct lbx_colour *palette)
257 {
258         unsigned x, y;
259
260         if (!palette) {
261                 /*
262                  * For no-palette mode, write a PGM instead which is basically
263                  * the same format but has only one value per pixel.
264                  */
265                 if (write_pgm(f, width, height, framedata, mask) < 0)
266                         goto err;
267                 return 0;
268         }
269
270         if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
271                 goto err;
272
273         for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
274                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
275                         goto err;
276
277                 if (!mask[y][x]) {
278                         if (fprintf_ascii(f, " 0  0  0") < 0)
279                                 goto err;
280                 } else {
281                         struct lbx_colour *c = &palette[framedata[y][x]];
282
283                         if (fprintf_ascii(f, "%2d %2d %2d",
284                                           c->red, c->green, c->blue) < 0)
285                                 goto err;
286                 }
287         }
288
289         if (fputc(to_ascii('\n'), f) == EOF)
290                 goto err;
291         return 0;
292 err:
293         tool_err(0, "error writing %s", filename);
294         return -1;
295 }
296
297 /*
298  * Output filter for Netpbm's PAM format.  This format combines the features
299  * of all the other Netpbm formats, supporting RGB and grayscale images with
300  * or without alpha channels.  This format supports all lbximg output.
301  */
302 int img_output_pam(FILE *f, const char *filename,
303                    unsigned width, unsigned height,
304                    unsigned char **framedata, unsigned char **mask,
305                    struct lbx_colour *palette)
306 {
307         bool masked = img_is_masked(mask, width, height);
308         unsigned x, y;
309         size_t depth;
310
311         if (fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\n", width, height) < 0)
312                 goto err;
313
314         if (palette) {
315                 depth = masked ? 4 : 3;
316
317                 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 63\n"
318                                      "TUPLTYPE RGB%s\nENDHDR\n",
319                                      depth, masked ? "_ALPHA" : "") < 0)
320                         goto err;
321
322                 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
323                         struct lbx_colour *c = &palette[framedata[y][x]];
324                         unsigned char buf[4];
325
326                         buf[0] = c->red;
327                         buf[1] = c->green;
328                         buf[2] = c->blue;
329                         buf[3] = mask[y][x] ? 63 : 0;
330
331                         if (fwrite(buf, 1, depth, f) < depth)
332                                 goto err;
333                 }
334         } else {
335                 depth = masked ? 2 : 1;
336
337                 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 255\n"
338                                      "TUPLTYPE GRAYSCALE%s\nENDHDR\n",
339                                      depth, masked ? "_ALPHA" : "") < 0)
340                         goto err;
341
342                 for (x = y = 0; y < height; ++x < width || (x = 0, y++)) {
343                         unsigned char buf[2];
344
345                         buf[0] = framedata[y][x];
346                         buf[1] = mask[y][x] ? 0xff : 0;
347
348                         if (fwrite(buf, 1, depth, f) < depth)
349                                 goto err;
350                 }
351         }
352
353         return 0;
354 err:
355         tool_err(0, "error writing %s", filename);
356         return -1;
357 }