]> git.draconx.ca Git - liblbx.git/blob - src/png.c
Trivial manual fixes.
[liblbx.git] / src / png.c
1 /*
2  * 2ooM: The Master of Orion II Reverse Engineering Project
3  * PNG 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 <string.h>
23 #include <stdarg.h>
24 #include <stdbool.h>
25 #include <setjmp.h>
26 #include <assert.h>
27
28 #include <png.h>
29
30 #include "image.h"
31 #include "tools.h"
32
33 #include "imgoutput.h"
34
35 struct file_info {
36         const char *filename;
37         FILE *f;
38 };
39
40 static void fatal(png_structp png, int err, const char *fmt, ...)
41 {
42         va_list ap;
43
44         va_start(ap, fmt);
45         tool_verr(err, fmt, ap);
46         va_end(ap);
47
48         png_longjmp(png, 1);
49 }
50
51 static void warning_fn(png_structp png, const char *msg)
52 {
53         tool_err(-1, "libpng warning: %s", msg);
54 }
55
56 static void error_fn(png_structp png, const char *msg)
57 {
58         fatal(png, -1, "libpng error: %s", msg);
59 }
60
61 /*
62  * We implement our own I/O routines because libpng's internal stdio calls do
63  * not handle errors particularly well.
64  */
65 static void write_data(png_structp png, png_bytep data, png_size_t len)
66 {
67         struct file_info *file = png_get_io_ptr(png);
68         size_t ret;
69
70         ret = fwrite(data, 1, len, file->f);
71         if (ret < len) {
72                 fatal(png, 0, "error writing %s", file->filename);
73         }
74 }
75
76 static void flush_data(png_structp png)
77 {
78         struct file_info *file = png_get_io_ptr(png);
79
80         if (fflush(file->f) == EOF) {
81                 fatal(png, 0, "error writing %s", file->filename);
82         }
83 }
84
85 static inline unsigned scale6to8(unsigned x)
86 {
87         assert(x <= 0x3f);
88
89         return x*0xff / 0x3f;
90 }
91
92 /* Scale the 18-bit LBX palette into a 24-bit PNG palette. */
93 static void fill_png_palette(png_color *out, const struct lbx_colour *in)
94 {
95         for (unsigned i = 0; i < 256; i++) {
96                 out[i] = (struct png_color_struct) {
97                         .red   = scale6to8(in[i].red),
98                         .green = scale6to8(in[i].green),
99                         .blue  = scale6to8(in[i].blue),
100                 };
101         }
102 }
103
104 static void
105 write_rgba_frame(png_structp png, png_infop info,
106                  unsigned char *pixels, unsigned char *mask,
107                  struct lbx_colour *lbx_palette)
108 {
109         png_uint_32 width = png_get_image_width(png, info);
110         png_uint_32 height = png_get_image_height(png, info);
111         png_color png_palette[256];
112         unsigned char (*row)[4];
113         jmp_buf parent;
114
115         if (width >= PNG_UINT_32_MAX / sizeof *row)
116                 fatal(png, -1, "image too wide to allocate row buffer");
117
118         row = png_malloc(png, width * sizeof *row);
119
120         /*
121          * We need to establish our own error handler to free the row buffer.
122          * Some care must be taken to save/restore the caller's handler.
123          */
124         memcpy(&parent, &png_jmpbuf(png), sizeof parent);
125         if (setjmp(png_jmpbuf(png))) {
126                 png_free(png, row);
127
128                 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
129                 png_longjmp(png, 1);
130         }
131
132         png_write_info(png, info);
133         fill_png_palette(png_palette, lbx_palette);
134         for (png_uint_32 y = 0; y < height; y++) {
135                 for (png_uint_32 x = 0; x < width; x++) {
136                         unsigned long offset = (unsigned long) y * width + x;
137                         png_color *c = &png_palette[pixels[offset]];
138                         bool vis;
139
140                         vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
141                         row[x][0] = c->red;
142                         row[x][1] = c->green;
143                         row[x][2] = c->blue;
144                         row[x][3] = -vis;
145                 }
146                 png_write_row(png, (void *)row);
147         }
148         png_write_end(png, NULL);
149
150         memcpy(&png_jmpbuf(png), &parent, sizeof parent);
151 }
152
153 static void
154 write_masked_index_frame(png_structp png, png_infop info,
155                          unsigned char *pixels, unsigned char *mask)
156 {
157         png_uint_32 width = png_get_image_width(png, info);
158         png_uint_32 height = png_get_image_height(png, info);
159         unsigned char (*row)[2];
160         jmp_buf parent;
161
162         if (width >= PNG_UINT_32_MAX / sizeof *row)
163                 fatal(png, -1, "image too wide to allocate row buffer");
164
165         row = png_malloc(png, width * sizeof *row);
166
167         /*
168          * We need to establish our own error handler to free the row buffer.
169          * Some care must be taken to save/restore the caller's handler.
170          */
171         memcpy(&parent, &png_jmpbuf(png), sizeof parent);
172         if (setjmp(png_jmpbuf(png))) {
173                 png_free(png, row);
174
175                 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
176                 png_longjmp(png, 1);
177         }
178
179         png_write_info(png, info);
180         for (png_uint_32 y = 0; y < height; y++) {
181                 for (png_uint_32 x = 0; x < width; x++) {
182                         unsigned long offset = (unsigned long) y * width + x;
183                         bool vis;
184
185                         vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
186                         row[x][0] = pixels[offset];
187                         row[x][1] = -vis;
188                 }
189                 png_write_row(png, (void *)row);
190         }
191         png_write_end(png, NULL);
192
193         memcpy(&png_jmpbuf(png), &parent, sizeof parent);
194 }
195
196 static void
197 write_index_frame(png_structp png, png_infop info, unsigned char *pixels)
198 {
199         png_uint_32 width = png_get_image_width(png, info);
200         png_uint_32 height = png_get_image_height(png, info);
201
202         png_write_info(png, info);
203         for (png_uint_32 y = 0; y < height; y++) {
204                 unsigned long offset = (unsigned long) y * width;
205
206                 png_write_row(png, (void *)(pixels+offset));
207         }
208         png_write_end(png, NULL);
209 }
210
211 static void
212 write_palette_frame(png_structp png, png_infop info,
213                     unsigned char *pixels, struct lbx_colour *lbx_palette)
214 {
215         png_color png_palette[256];
216         fill_png_palette(png_palette, lbx_palette);
217         png_set_PLTE(png, info, png_palette, 256);
218
219         write_index_frame(png, info, pixels);
220 }
221
222 int img_output_png(FILE *f, const char *filename,
223                    unsigned width, unsigned height,
224                    unsigned char *pixels, unsigned char *mask,
225                    struct lbx_colour *palette)
226 {
227         bool masked = img_is_masked(mask, width, height);
228         struct file_info file = { filename, f };
229         png_structp png;
230         png_infop info;
231
232         png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
233         if (!png) {
234                 tool_err(-1, "failed to init libpng.");
235                 return -1;
236         }
237
238         info = png_create_info_struct(png);
239         if (!info) {
240                 tool_err(-1, "failed to init libpng.");
241                 png_destroy_write_struct(&png, NULL);
242                 return -1;
243         }
244
245         if (setjmp(png_jmpbuf(png))) {
246                 png_destroy_write_struct(&png, &info);
247                 return -1;
248         }
249
250         png_set_error_fn(png, NULL, error_fn, warning_fn);
251         png_set_write_fn(png, &file, write_data, flush_data);
252
253         if (palette && masked) {
254                 /*
255                  * Indexed colour mode can only be used for images without
256                  * transparency, since indexed PNG requires sacrificing one or
257                  * more palette colours to indicate transparency, while LBX
258                  * does not.  So we use full-up RGBA in this case.
259                  */
260                 png_set_IHDR(png, info, width, height, 8,
261                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
262                              PNG_COMPRESSION_TYPE_DEFAULT,
263                              PNG_FILTER_TYPE_DEFAULT);
264
265                 write_rgba_frame(png, info, pixels, mask, palette);
266         } else if (palette) {
267                 png_set_IHDR(png, info, width, height, 8,
268                              PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
269                              PNG_COMPRESSION_TYPE_DEFAULT,
270                              PNG_FILTER_TYPE_DEFAULT);
271
272                 write_palette_frame(png, info, pixels, palette);
273         } else if (masked) {
274                 png_set_IHDR(png, info, width, height, 8,
275                              PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE,
276                              PNG_COMPRESSION_TYPE_DEFAULT,
277                              PNG_FILTER_TYPE_DEFAULT);
278
279                 write_masked_index_frame(png, info, pixels, mask);
280         } else {
281                 png_set_IHDR(png, info, width, height, 8,
282                              PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
283                              PNG_COMPRESSION_TYPE_DEFAULT,
284                              PNG_FILTER_TYPE_DEFAULT);
285
286                 write_index_frame(png, info, pixels);
287         }
288
289         png_destroy_write_struct(&png, &info);
290         return 0;
291 }