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