2 * 2ooM: The Master of Orion II Reverse Engineering Project
3 * PNG output routines for lbximg extration.
4 * Copyright © 2013 Nick Bowler
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.
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.
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/>.
33 #include "imgoutput.h"
40 static int is_masked(unsigned char **mask, unsigned width, unsigned height)
42 for (unsigned y = 0; y < height; y++) {
43 for (unsigned x = 0; x < width; x++) {
52 static void fatal(png_structp png, int err, const char *fmt, ...)
57 tool_verr(err, fmt, ap);
63 static void warning_fn(png_structp png, const char *msg)
65 tool_err(-1, "libpng warning: %s", msg);
68 static void error_fn(png_structp png, const char *msg)
70 fatal(png, -1, "libpng error: %s", msg);
74 * We implement our own I/O routines because libpng's internal stdio calls do
75 * not handle errors particularly well.
77 static void write_data(png_structp png, png_bytep data, png_size_t len)
79 struct file_info *file = png_get_io_ptr(png);
82 ret = fwrite(data, 1, len, file->f);
84 fatal(png, 0, "error writing %s", file->filename);
88 static void flush_data(png_structp png)
90 struct file_info *file = png_get_io_ptr(png);
92 if (fflush(file->f) == EOF) {
93 fatal(png, 0, "error writing %s", file->filename);
97 static inline unsigned scale6to8(unsigned x)
101 return x*0xff / 0x3f;
104 /* Scale the 18-bit LBX palette into a 24-bit PNG palette. */
105 static void fill_png_palette(png_color *out, const struct lbx_colour *in)
107 for (unsigned i = 0; i < 256; i++) {
108 out[i] = (struct png_color_struct) {
109 .red = scale6to8(in[i].red),
110 .green = scale6to8(in[i].green),
111 .blue = scale6to8(in[i].blue),
117 write_rgba_frame(png_structp png, png_infop info,
118 unsigned char **framedata, unsigned char **mask,
119 struct lbx_colour *lbx_palette)
121 png_uint_32 width = png_get_image_width(png, info);
122 png_uint_32 height = png_get_image_height(png, info);
123 png_color png_palette[256];
124 unsigned char (*row)[4];
127 if (width >= PNG_UINT_32_MAX / sizeof *row)
128 fatal(png, -1, "image too wide to allocate row buffer");
130 row = png_malloc(png, width * sizeof *row);
133 * We need to establish our own error handler to free the row buffer.
134 * Some care must be taken to save/restore the caller's handler.
136 memcpy(&parent, &png_jmpbuf(png), sizeof parent);
137 if (setjmp(png_jmpbuf(png))) {
140 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
144 png_write_info(png, info);
145 fill_png_palette(png_palette, lbx_palette);
146 for (png_uint_32 y = 0; y < height; y++) {
147 for (png_uint_32 x = 0; x < width; x++) {
148 row[x][0] = png_palette[framedata[y][x]].red;
149 row[x][1] = png_palette[framedata[y][x]].green;
150 row[x][2] = png_palette[framedata[y][x]].blue;
151 row[x][3] = mask[y][x] ? -1 : 0;
153 png_write_row(png, (void *)row);
155 png_write_end(png, NULL);
157 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
161 write_masked_index_frame(png_structp png, png_infop info,
162 unsigned char **framedata, unsigned char **mask)
164 png_uint_32 width = png_get_image_width(png, info);
165 png_uint_32 height = png_get_image_height(png, info);
166 unsigned char (*row)[2];
169 if (width >= PNG_UINT_32_MAX / sizeof *row)
170 fatal(png, -1, "image too wide to allocate row buffer");
172 row = png_malloc(png, width * sizeof *row);
175 * We need to establish our own error handler to free the row buffer.
176 * Some care must be taken to save/restore the caller's handler.
178 memcpy(&parent, &png_jmpbuf(png), sizeof parent);
179 if (setjmp(png_jmpbuf(png))) {
182 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
186 png_write_info(png, info);
187 for (png_uint_32 y = 0; y < height; y++) {
188 for (png_uint_32 x = 0; x < width; x++) {
189 row[x][0] = framedata[y][x];
190 row[x][1] = mask[y][x] ? -1 : 0;
192 png_write_row(png, (void *)row);
194 png_write_end(png, NULL);
196 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
200 write_palette_frame(png_structp png, png_infop info,
201 unsigned char **framedata, struct lbx_colour *lbx_palette)
203 png_color png_palette[256];
205 fill_png_palette(png_palette, lbx_palette);
206 png_set_PLTE(png, info, png_palette, 256);
207 png_set_rows(png, info, framedata);
208 png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
212 write_index_frame(png_structp png, png_infop info, unsigned char **framedata)
214 png_set_rows(png, info, framedata);
215 png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
218 int img_output_png(FILE *f, const char *filename,
219 unsigned width, unsigned height,
220 unsigned char **framedata, unsigned char **mask,
221 struct lbx_colour *palette)
223 bool masked = is_masked(mask, width, height);
224 struct file_info file = { filename, f };
228 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
230 tool_err(-1, "failed to init libpng.");
234 info = png_create_info_struct(png);
236 tool_err(-1, "failed to init libpng.");
237 png_destroy_write_struct(&png, NULL);
241 if (setjmp(png_jmpbuf(png))) {
242 png_destroy_write_struct(&png, &info);
246 png_set_error_fn(png, NULL, error_fn, warning_fn);
247 png_set_write_fn(png, &file, write_data, flush_data);
249 if (palette && masked) {
251 * Indexed colour mode can only be used for images without
252 * transparency, since indexed PNG requires sacrificing one or
253 * more palette colours to indicate transparency, while LBX
254 * does not. So we use full-up RGBA in this case.
256 png_set_IHDR(png, info, width, height, 8,
257 PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
258 PNG_COMPRESSION_TYPE_DEFAULT,
259 PNG_FILTER_TYPE_DEFAULT);
261 write_rgba_frame(png, info, framedata, mask, palette);
262 } else if (palette) {
263 png_set_IHDR(png, info, width, height, 8,
264 PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
265 PNG_COMPRESSION_TYPE_DEFAULT,
266 PNG_FILTER_TYPE_DEFAULT);
268 write_palette_frame(png, info, framedata, palette);
270 png_set_IHDR(png, info, width, height, 8,
271 PNG_COLOR_TYPE_GRAY_ALPHA, PNG_INTERLACE_NONE,
272 PNG_COMPRESSION_TYPE_DEFAULT,
273 PNG_FILTER_TYPE_DEFAULT);
275 write_masked_index_frame(png, info, framedata, mask);
277 png_set_IHDR(png, info, width, height, 8,
278 PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
279 PNG_COMPRESSION_TYPE_DEFAULT,
280 PNG_FILTER_TYPE_DEFAULT);
282 write_index_frame(png, info, framedata);
285 png_destroy_write_struct(&png, &info);