]> git.draconx.ca Git - liblbx.git/blob - src/png.c
lbximg: Add support for Netpbm output.
[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 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 **framedata, 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                         row[x][0] = png_palette[framedata[y][x]].red;
137                         row[x][1] = png_palette[framedata[y][x]].green;
138                         row[x][2] = png_palette[framedata[y][x]].blue;
139                         row[x][3] = mask[y][x] ? -1 : 0;
140                 }
141                 png_write_row(png, (void *)row);
142         }
143         png_write_end(png, NULL);
144
145         memcpy(&png_jmpbuf(png), &parent, sizeof parent);
146 }
147
148 static void
149 write_masked_index_frame(png_structp png, png_infop info,
150                          unsigned char **framedata, unsigned char **mask)
151 {
152         png_uint_32 width = png_get_image_width(png, info);
153         png_uint_32 height = png_get_image_height(png, info);
154         unsigned char (*row)[2];
155         jmp_buf parent;
156
157         if (width >= PNG_UINT_32_MAX / sizeof *row)
158                 fatal(png, -1, "image too wide to allocate row buffer");
159
160         row = png_malloc(png, width * sizeof *row);
161
162         /*
163          * We need to establish our own error handler to free the row buffer.
164          * Some care must be taken to save/restore the caller's handler.
165          */
166         memcpy(&parent, &png_jmpbuf(png), sizeof parent);
167         if (setjmp(png_jmpbuf(png))) {
168                 png_free(png, row);
169
170                 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
171                 png_longjmp(png, 1);
172         }
173
174         png_write_info(png, info);
175         for (png_uint_32 y = 0; y < height; y++) {
176                 for (png_uint_32 x = 0; x < width; x++) {
177                         row[x][0] = framedata[y][x];
178                         row[x][1] = mask[y][x] ? -1 : 0;
179                 }
180                 png_write_row(png, (void *)row);
181         }
182         png_write_end(png, NULL);
183
184         memcpy(&png_jmpbuf(png), &parent, sizeof parent);
185 }
186
187 static void
188 write_palette_frame(png_structp png, png_infop info,
189                     unsigned char **framedata, struct lbx_colour *lbx_palette)
190 {
191         png_color png_palette[256];
192
193         fill_png_palette(png_palette, lbx_palette);
194         png_set_PLTE(png, info, png_palette, 256);
195         png_set_rows(png, info, framedata);
196         png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
197 }
198
199 static void
200 write_index_frame(png_structp png, png_infop info, unsigned char **framedata)
201 {
202         png_set_rows(png, info, framedata);
203         png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
204 }
205
206 int img_output_png(FILE *f, const char *filename,
207                    unsigned width, unsigned height,
208                    unsigned char **framedata, unsigned char **mask,
209                    struct lbx_colour *palette)
210 {
211         bool masked = img_is_masked(mask, width, height);
212         struct file_info file = { filename, f };
213         png_structp png;
214         png_infop info;
215
216         png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
217         if (!png) {
218                 tool_err(-1, "failed to init libpng.");
219                 return -1;
220         }
221
222         info = png_create_info_struct(png);
223         if (!info) {
224                 tool_err(-1, "failed to init libpng.");
225                 png_destroy_write_struct(&png, NULL);
226                 return -1;
227         }
228
229         if (setjmp(png_jmpbuf(png))) {
230                 png_destroy_write_struct(&png, &info);
231                 return -1;
232         }
233
234         png_set_error_fn(png, NULL, error_fn, warning_fn);
235         png_set_write_fn(png, &file, write_data, flush_data);
236
237         if (palette && masked) {
238                 /*
239                  * Indexed colour mode can only be used for images without
240                  * transparency, since indexed PNG requires sacrificing one or
241                  * more palette colours to indicate transparency, while LBX
242                  * does not.  So we use full-up RGBA in this case.
243                  */
244                 png_set_IHDR(png, info, width, height, 8,
245                              PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
246                              PNG_COMPRESSION_TYPE_DEFAULT,
247                              PNG_FILTER_TYPE_DEFAULT);
248
249                 write_rgba_frame(png, info, framedata, mask, palette);
250         } else if (palette) {
251                 png_set_IHDR(png, info, width, height, 8,
252                              PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
253                              PNG_COMPRESSION_TYPE_DEFAULT,
254                              PNG_FILTER_TYPE_DEFAULT);
255
256                 write_palette_frame(png, info, framedata, palette);
257         } else if (masked) {
258                 png_set_IHDR(png, info, width, height, 8,
259                              PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE,
260                              PNG_COMPRESSION_TYPE_DEFAULT,
261                              PNG_FILTER_TYPE_DEFAULT);
262
263                 write_masked_index_frame(png, info, framedata, mask);
264         } else {
265                 png_set_IHDR(png, info, width, height, 8,
266                              PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
267                              PNG_COMPRESSION_TYPE_DEFAULT,
268                              PNG_FILTER_TYPE_DEFAULT);
269
270                 write_index_frame(png, info, framedata);
271         }
272
273         png_destroy_write_struct(&png, &info);
274         return 0;
275 }