]> git.draconx.ca Git - liblbx.git/blob - src/pnm.c
Trivial manual fixes.
[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 <string.h>
24 #include <stdbool.h>
25 #include <stdarg.h>
26 #include <assert.h>
27 #include <inttypes.h>
28 #include <limits.h>
29
30 #include "image.h"
31 #include "tools.h"
32 #include "imgoutput.h"
33
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
61 #else
62 #  define NATIVE_ASCII 0
63 #endif
64
65 /* Convert c from the basic execution character set to ASCII. */
66 static unsigned char to_ascii(unsigned char c)
67 {
68         switch (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));
169         }
170
171         return c;
172 }
173
174 #if NATIVE_ASCII && defined(NDEBUG)
175 /* fprintf can be used directly. */
176 #define fprintf_ascii fprintf
177 #else
178 /* Variant of fprintf which converts all output to ASCII. */
179 static int fprintf_ascii(FILE *f, char *fmt, ...)
180 {
181         char buf[100];
182         va_list ap;
183         int i, len;
184
185         va_start(ap, fmt);
186         len = vsnprintf(buf, sizeof buf, fmt, ap);
187         va_end(ap);
188
189         if (len < 0 || len >= sizeof buf)
190                 return -1;
191
192         for (i = 0; i < len; i++) {
193                 if (fputc(to_ascii(buf[i]), f) == EOF)
194                         return -1;
195         }
196
197         return len;
198 }
199 #endif
200
201 /*
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.
206  */
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)
211 {
212         unsigned x, y;
213
214         if (fprintf_ascii(f, "P1\n%u %u", width, height) < 0)
215                 goto err;
216
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);
220
221                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
222                         goto err;
223
224                 if (fputc(to_ascii(vis ? '0' : '1'), f) == EOF)
225                         goto err;
226         }
227
228         if (fputc(to_ascii('\n'), f) == EOF)
229                 goto err;
230         return 0;
231 err:
232         tool_err(0, "error writing %s", filename);
233         return -1;
234 }
235
236 /*
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.
240  */
241 static int write_pgm(FILE *f, unsigned width, unsigned height,
242                      unsigned char *pixels, unsigned char *mask)
243 {
244         unsigned x, y;
245
246         if (fprintf_ascii(f, "P2\n%u %u\n255", width, height) < 0)
247                 return -1;
248
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);
252
253                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
254                         return -1;
255
256                 if (fprintf_ascii(f, "%3hhu", vis ? pixels[offset] : 0) < 0)
257                         return -1;
258         }
259
260         if (fputc(to_ascii('\n'), f) == EOF)
261                 return -1;
262
263         return 0;
264 }
265
266 /*
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).
271  */
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)
276 {
277         unsigned x, y;
278
279         if (!palette) {
280                 /*
281                  * For no-palette mode, write a PGM instead which is basically
282                  * the same format but has only one value per pixel.
283                  */
284                 if (write_pgm(f, width, height, pixels, mask) < 0)
285                         goto err;
286                 return 0;
287         }
288
289         if (fprintf_ascii(f, "P3\n%u %u\n63", width, height) < 0)
290                 goto err;
291
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 };
296
297                 if (fputc(to_ascii(x > 0 ? ' ' : '\n'), f) == EOF)
298                         goto err;
299
300                 if (vis)
301                         c = palette[pixels[offset]];
302
303                 if (fprintf_ascii(f, "%2d %2d %2d", c.red, c.green, c.blue) < 0)
304                         goto err;
305         }
306
307         if (fputc(to_ascii('\n'), f) == EOF)
308                 goto err;
309         return 0;
310 err:
311         tool_err(0, "error writing %s", filename);
312         return -1;
313 }
314
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)
318 {
319         unsigned x;
320
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) );
325
326                 if (sizeof val == 4) {
327                         memcpy(rowbuf+4ul*x, &val, 4);
328                 } else {
329                         rowbuf[4ul*x+0] = val.red;
330                         rowbuf[4ul*x+1] = val.green;
331                         rowbuf[4ul*x+2] = val.blue;
332                 }
333                 rowbuf[4ul*x+3] = -vis & 0x3f;
334         }
335 }
336
337 static void pam_format_rgb(unsigned char *rowbuf, const unsigned char *pixels,
338                            unsigned width, unsigned long offset,
339                            struct lbx_colour *palette)
340 {
341         unsigned x;
342
343         for (x = 0; x < width; x++) {
344                 struct lbx_colour val = palette[pixels[offset+x]];
345
346                 if (sizeof val == 4) {
347                         memcpy(rowbuf+3ul*x, &val, 4);
348                 } else {
349                         rowbuf[3ul*x+0] = val.red;
350                         rowbuf[3ul*x+1] = val.green;
351                         rowbuf[3ul*x+2] = val.blue;
352                 }
353         }
354 }
355
356 static void pam_format_ga(unsigned char *rowbuf,
357                           const unsigned char *pixels, const unsigned char *mask,
358                           unsigned width, unsigned long offset)
359 {
360         unsigned x;
361
362         for (x = 0; x < width; x++) {
363                 unsigned vis = 1 & ( mask[(offset+x)/CHAR_BIT]
364                                       >> ((offset+x)%CHAR_BIT) );
365
366                 rowbuf[2ul*x+0] = pixels[offset+x];
367                 rowbuf[2ul*x+1] = -vis & 0xff;
368         }
369 }
370
371 static void pam_format_g(unsigned char *rowbuf, const unsigned char *pixels,
372                          unsigned width, unsigned long offset)
373 {
374         memcpy(rowbuf, pixels+offset, width);
375 }
376
377 /*
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.
381  */
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)
386 {
387         bool masked = img_is_masked(mask, width, height);
388         unsigned char *rowbuf;
389         unsigned long offset;
390         unsigned y, depth;
391         int rc;
392
393         depth = (palette ? 3 : 1) + masked;
394         if (width >= (size_t)-1 / depth)
395                 goto alloc_err;
396
397         rowbuf = malloc(width * depth + 1);
398         if (!rowbuf) {
399 alloc_err:
400                 tool_err(-1, "failed to allocate memory");
401                 return -1;
402         }
403
404         rc = fprintf_ascii(f, "P7\nWIDTH %u\nHEIGHT %u\nDEPTH %u\n",
405                               width, height, depth);
406         if (rc < 0)
407                 goto write_err;
408
409         rc = fprintf_ascii(f, "MAXVAL %d\nTUPLTYPE %s",
410                               palette ? 63 : 255,
411                               palette ? "RGB" : "GRAYSCALE");
412         if (rc < 0)
413                 goto write_err;
414
415         rc = fprintf_ascii(f, "_ALPHA\nENDHDR\n" + 6*!masked);
416         if (rc < 0)
417                 goto write_err;
418
419         for (offset = y = 0; y < height; y++, offset += width) {
420                 switch (depth) {
421                 case 4:
422                         pam_format_rgba(rowbuf, pixels, mask, width, offset, palette);
423                         break;
424                 case 3:
425                         pam_format_rgb(rowbuf, pixels, width, offset, palette);
426                         break;
427                 case 2:
428                         pam_format_ga(rowbuf, pixels, mask, width, offset);
429                         break;
430                 case 1:
431                         pam_format_g(rowbuf, pixels, width, offset);
432                         break;
433                 }
434
435                 if (fwrite(rowbuf, depth, width, f) < width)
436                         goto write_err;
437         }
438
439         free(rowbuf);
440         return 0;
441 write_err:
442         tool_err(0, "error writing %s", filename);
443         free(rowbuf);
444         return -1;
445 }