2 * 2ooM: The Master of Orion II Reverse Engineering Project
3 * PNG output routines for lbximg extration.
4 * Copyright © 2013-2014 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 void fatal(png_structp png, int err, const char *fmt, ...)
45 tool_verr(err, fmt, ap);
51 static void warning_fn(png_structp png, const char *msg)
53 tool_err(-1, "libpng warning: %s", msg);
56 static void error_fn(png_structp png, const char *msg)
58 fatal(png, -1, "libpng error: %s", msg);
62 * We implement our own I/O routines because libpng's internal stdio calls do
63 * not handle errors particularly well.
65 static void write_data(png_structp png, png_bytep data, png_size_t len)
67 struct file_info *file = png_get_io_ptr(png);
70 ret = fwrite(data, 1, len, file->f);
72 fatal(png, 0, "error writing %s", file->filename);
76 static void flush_data(png_structp png)
78 struct file_info *file = png_get_io_ptr(png);
80 if (fflush(file->f) == EOF) {
81 fatal(png, 0, "error writing %s", file->filename);
85 static inline unsigned scale6to8(unsigned x)
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)
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),
105 write_rgba_frame(png_structp png, png_infop info,
106 unsigned char *pixels, unsigned char *mask,
107 struct lbx_colour *lbx_palette)
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];
115 if (width >= PNG_UINT_32_MAX / sizeof *row)
116 fatal(png, -1, "image too wide to allocate row buffer");
118 row = png_malloc(png, width * sizeof *row);
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.
124 memcpy(&parent, &png_jmpbuf(png), sizeof parent);
125 if (setjmp(png_jmpbuf(png))) {
128 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
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]];
140 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
142 row[x][1] = c->green;
146 png_write_row(png, (void *)row);
148 png_write_end(png, NULL);
150 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
154 write_masked_index_frame(png_structp png, png_infop info,
155 unsigned char *pixels, unsigned char *mask)
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];
162 if (width >= PNG_UINT_32_MAX / sizeof *row)
163 fatal(png, -1, "image too wide to allocate row buffer");
165 row = png_malloc(png, width * sizeof *row);
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.
171 memcpy(&parent, &png_jmpbuf(png), sizeof parent);
172 if (setjmp(png_jmpbuf(png))) {
175 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
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;
185 vis = mask[offset/CHAR_BIT] & (1u << offset%CHAR_BIT);
186 row[x][0] = pixels[offset];
189 png_write_row(png, (void *)row);
191 png_write_end(png, NULL);
193 memcpy(&png_jmpbuf(png), &parent, sizeof parent);
197 write_index_frame(png_structp png, png_infop info, unsigned char *pixels)
199 png_uint_32 width = png_get_image_width(png, info);
200 png_uint_32 height = png_get_image_height(png, info);
202 png_write_info(png, info);
203 for (png_uint_32 y = 0; y < height; y++) {
204 unsigned long offset = (unsigned long) y * width;
206 png_write_row(png, (void *)(pixels+offset));
208 png_write_end(png, NULL);
212 write_palette_frame(png_structp png, png_infop info,
213 unsigned char *pixels, struct lbx_colour *lbx_palette)
215 png_color png_palette[256];
216 fill_png_palette(png_palette, lbx_palette);
217 png_set_PLTE(png, info, png_palette, 256);
219 write_index_frame(png, info, pixels);
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)
227 bool masked = img_is_masked(mask, width, height);
228 struct file_info file = { filename, f };
232 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
234 tool_err(-1, "failed to init libpng.");
238 info = png_create_info_struct(png);
240 tool_err(-1, "failed to init libpng.");
241 png_destroy_write_struct(&png, NULL);
245 if (setjmp(png_jmpbuf(png))) {
246 png_destroy_write_struct(&png, &info);
250 png_set_error_fn(png, NULL, error_fn, warning_fn);
251 png_set_write_fn(png, &file, write_data, flush_data);
253 if (palette && masked) {
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.
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);
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);
272 write_palette_frame(png, info, pixels, palette);
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);
279 write_masked_index_frame(png, info, pixels, mask);
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);
286 write_index_frame(png, info, pixels);
289 png_destroy_write_struct(&png, &info);