]> git.draconx.ca Git - liblbx.git/blob - src/pnm.c
lbximg: Simplify pnm text 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-2014 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 #include <limits.h>
28
29 #include "image.h"
30 #include "tools.h"
31 #include "imgoutput.h"
32
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
60 #else
61 #  define NATIVE_ASCII 0
62 #endif
63
64 /* Convert c from the basic execution character set to ASCII. */
65 static unsigned char to_ascii(unsigned char c)
66 {
67         switch (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));
168         }
169
170         return c;
171 }
172
173 #if NATIVE_ASCII && defined(NDEBUG)
174 /* fprintf can be used directly. */
175 #define fprintf_ascii fprintf
176 #else
177 /* Variant of fprintf which converts all output to ASCII. */
178 static int fprintf_ascii(FILE *f, char *fmt, ...)
179 {
180         char buf[100];
181         va_list ap;
182         int i, len;
183
184         va_start(ap, fmt);
185         len = vsnprintf(buf, sizeof buf, fmt, ap);
186         va_end(ap);
187
188         if (len < 0 || len >= sizeof buf)
189                 return -1;
190
191         for (i = 0; i < len; i++) {
192                 if (fputc(to_ascii(buf[i]), f) == EOF)
193                         return -1;
194         }
195
196         return len;
197 }
198 #endif
199
200 /*
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.
205  */
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)
210 {
211         unsigned x, y;
212
213         if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
214                 goto err;
215
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);
219
220                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
221                         goto err;
222
223                 if (fputc(to_ascii(vis ? '0' : '1'), f) == EOF)
224                         goto err;
225         }
226
227         if (fputc(to_ascii('\n'), f) == EOF)
228                 goto err;
229         return 0;
230 err:
231         tool_err(0, "error writing %s", filename);
232         return -1;
233 }
234
235 /*
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.
239  */
240 static int write_pgm(FILE *f, unsigned width, unsigned height,
241                      unsigned char *pixels, unsigned char *mask)
242 {
243         unsigned x, y;
244
245         if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
246                 return -1;
247
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);
251
252                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
253                         return -1;
254
255                 if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 0)
256                         return -1;
257         }
258
259         if (fputc(to_ascii('\n'), f) == EOF)
260                 return -1;
261
262         return 0;
263 }
264
265 /*
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).
270  */
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)
275 {
276         unsigned x, y;
277
278         if (!palette) {
279                 /*
280                  * For no-palette mode, write a PGM instead which is basically
281                  * the same format but has only one value per pixel.
282                  */
283                 if (write_pgm(f, width, height, pixels, mask) < 0)
284                         goto err;
285                 return 0;
286         }
287
288         if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
289                 goto err;
290
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 };
295
296                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
297                         goto err;
298
299                 if (vis)
300                         c = palette[pixels[offset]];
301
302                 if (fprintf_ascii(f, "%2d %2d %2d", c.red, c.green, c.blue) < 0)
303                         goto err;
304         }
305
306         if (fputc(to_ascii('\n'), f) == EOF)
307                 goto err;
308         return 0;
309 err:
310         tool_err(0, "error writing %s", filename);
311         return -1;
312 }
313
314 /*
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.
318  */
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)
323 {
324         bool masked = img_is_masked(mask, width, height);
325         unsigned x, y;
326         size_t depth;
327
328         if (fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\n", width, height) < 0)
329                 goto err;
330
331         if (palette) {
332                 depth = masked ? 4 : 3;
333
334                 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 63\n"
335                                      "TUPLTYPE RGB%s\nENDHDR\n",
336                                      depth, masked ? "_ALPHA" : "") < 0)
337                         goto err;
338
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];
343                         bool vis;
344
345                         vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
346                         buf[0] = c->red;
347                         buf[1] = c->green;
348                         buf[2] = c->blue;
349                         buf[3] = vis ? 63 : 0;
350
351                         if (fwrite(buf, 1, depth, f) < depth)
352                                 goto err;
353                 }
354         } else {
355                 depth = masked ? 2 : 1;
356
357                 if (fprintf_ascii(f, "DEPTH %zu\nMAXVAL 255\n"
358                                      "TUPLTYPE GRAYSCALE%s\nENDHDR\n",
359                                      depth, masked ? "_ALPHA" : "") < 0)
360                         goto err;
361
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];
365                         bool vis;
366
367                         vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
368                         buf[0] = pixels[offset];
369                         buf[1] = vis ? 255 : 0;
370
371                         if (fwrite(buf, 1, depth, f) < depth)
372                                 goto err;
373                 }
374         }
375
376         return 0;
377 err:
378         tool_err(0, "error writing %s", filename);
379         return -1;
380 }