#include <getopt.h>
#include <errno.h>
-#include <png.h>
-
#include "tools.h"
#include "image.h"
#include "error.h"
#include "lbx.h"
+#include "imgoutput.h"
+
/* Global flags */
static int verbose = 0;
static char *outname = "out";
return 0;
}
-static int ismasked(unsigned char **mask, unsigned width, unsigned height)
-{
- unsigned y, x;
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x++) {
- if (mask[y][x] == 0) return 1;
- }
- }
-
- return 0;
-}
-
int outpng(unsigned int frameno,
unsigned char **framedata, unsigned char **mask,
unsigned int width, unsigned int height,
struct lbx_colour palette[static 256])
{
char name[strlen(outname) + sizeof ".65535.png"];
- unsigned char *row;
- unsigned int x, y;
FILE *of;
-
- png_structp png;
- png_infop info;
+ int rc;
assert(frameno < 65536);
snprintf(name, sizeof name, "%s.%03d.png", outname, frameno);
- row = malloc(4 * width);
- if (!row) {
- tool_err(0, "failed to allocate row buffer");
- return -1;
- }
-
of = fopen(name, "wb");
if (!of) {
tool_err(0, "failed to open %s", name);
return -1;
}
- png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png) {
- tool_err(-1, "failed to init libpng.");
- goto err;
- }
-
- info = png_create_info_struct(png);
- if (!info) {
- tool_err(-1, "failed to init libpng.");
- png_destroy_write_struct(&png, NULL);
- goto err;
+ rc = img_output_png(of, name, width, height, framedata, mask, palette);
+ if (rc < 0) {
+ fclose(of);
+ return -1;
}
- if (setjmp(png_jmpbuf(png))) {
- png_destroy_write_struct(&png, &info);
- goto err;
+ if (fclose(of) == EOF) {
+ tool_err(0, "error writing %s", name);
+ return -1;
}
-
- png_init_io(png, of);
-
- if (!ismasked(mask, width, height)) {
- /*
- * This case is easy; we can just feed the palette and pixel
- * data to libpng and let it do its magic.
- */
-
- png_color png_palette[256];
- for (unsigned i = 0; i < 256; i++) {
- png_palette[i].red = palette[i].red;
- png_palette[i].green = palette[i].green;
- png_palette[i].blue = palette[i].blue;
- }
-
- png_set_IHDR(png, info, width, height, 8,
- PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
- PNG_COMPRESSION_TYPE_DEFAULT,
- PNG_FILTER_TYPE_DEFAULT);
- png_set_PLTE(png, info, png_palette, 256);
- png_set_rows(png, info, framedata);
- png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
- } else {
- /*
- * Unfortunately, LBX doesn't translate nicely to PNG here.
- * LBX has a 256 colour palette _plus_ transparency.
- * We'll form an RGBA PNG to deal with this.
- */
-
- png_set_IHDR(png, info, width, height, 8,
- PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
- PNG_COMPRESSION_TYPE_DEFAULT,
- PNG_FILTER_TYPE_DEFAULT);
-
- png_write_info(png, info);
-
- for (y = 0; y < height; y++) {
- for (x = 0; x < width; x++) {
- row[4*x+0] = palette[framedata[y][x]].red;
- row[4*x+1] = palette[framedata[y][x]].green;
- row[4*x+2] = palette[framedata[y][x]].blue;
- row[4*x+3] = (mask[y][x]) ? -1 : 0;
- }
-
- png_write_row(png, row);
- }
-
- png_write_end(png, NULL);
- }
-
- png_destroy_write_struct(&png, &info);
- fclose(of);
- free(row);
-
if (verbose)
printf("wrote %s\n", name);
+
return 0;
-err:
- fclose(of);
- remove(name);
- free(row);
- return -1;
}
static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
--- /dev/null
+/*
+ * 2ooM: The Master of Orion II Reverse Engineering Project
+ * PNG output routines for lbximg extration.
+ * Copyright © 2013 Nick Bowler
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <setjmp.h>
+
+#include <png.h>
+
+#include "image.h"
+#include "tools.h"
+
+#include "imgoutput.h"
+
+struct file_info {
+ const char *filename;
+ FILE *f;
+};
+
+static int is_masked(unsigned char **mask, unsigned width, unsigned height)
+{
+ for (unsigned y = 0; y < height; y++) {
+ for (unsigned x = 0; x < width; x++) {
+ if (mask[y][x] == 0)
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void fatal(png_structp png, int err, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ tool_verr(err, fmt, ap);
+ va_end(ap);
+
+ png_longjmp(png, 1);
+}
+
+static void warning_fn(png_structp png, const char *msg)
+{
+ tool_err(-1, "libpng warning: %s", msg);
+}
+
+static void error_fn(png_structp png, const char *msg)
+{
+ fatal(png, -1, "libpng error: %s", msg);
+}
+
+/*
+ * We implement our own I/O routines because libpng's internal stdio calls do
+ * not handle errors particularly well.
+ */
+static void write_data(png_structp png, png_bytep data, png_size_t len)
+{
+ struct file_info *file = png_get_io_ptr(png);
+ size_t ret;
+
+ ret = fwrite(data, 1, len, file->f);
+ if (ret < len) {
+ fatal(png, 0, "error writing %s", file->filename);
+ }
+}
+
+static void flush_data(png_structp png)
+{
+ struct file_info *file = png_get_io_ptr(png);
+
+ if (fflush(file->f) == EOF) {
+ fatal(png, 0, "error writing %s", file->filename);
+ }
+}
+
+static void
+write_rgba_frame(png_structp png, png_infop info,
+ unsigned char **framedata, unsigned char **mask,
+ struct lbx_colour *palette)
+{
+ png_uint_32 width = png_get_image_width(png, info);
+ png_uint_32 height = png_get_image_height(png, info);
+ unsigned char (*row)[4];
+ jmp_buf parent;
+
+ if (width >= PNG_UINT_32_MAX / sizeof *row)
+ fatal(png, -1, "image too wide to allocate row buffer");
+
+ row = png_malloc(png, width * sizeof *row);
+
+ /*
+ * We need to establish our own error handler to free the row buffer.
+ * Some care must be taken to save/restore the caller's handler.
+ */
+ memcpy(&parent, &png_jmpbuf(png), sizeof parent);
+ if (setjmp(png_jmpbuf(png))) {
+ png_free(png, row);
+
+ memcpy(&png_jmpbuf(png), &parent, sizeof parent);
+ png_longjmp(png, 1);
+ }
+
+ png_write_info(png, info);
+ for (png_uint_32 y = 0; y < height; y++) {
+ for (png_uint_32 x = 0; x < width; x++) {
+ row[x][0] = palette[framedata[y][x]].red;
+ row[x][1] = palette[framedata[y][x]].green;
+ row[x][2] = palette[framedata[y][x]].blue;
+ row[x][3] = mask[y][x] ? -1 : 0;
+ }
+ png_write_row(png, (void *)row);
+ }
+ png_write_end(png, NULL);
+
+ memcpy(&png_jmpbuf(png), &parent, sizeof parent);
+}
+
+static void
+write_index_frame(png_structp png, png_infop info, unsigned char **framedata,
+ struct lbx_colour *palette)
+{
+ png_color png_palette[256];
+
+ for (unsigned i = 0; i < 256; i++) {
+ png_palette[i] = (png_color) {
+ .red = palette[i].red,
+ .green = palette[i].green,
+ .blue = palette[i].blue,
+ };
+ }
+
+ png_set_PLTE(png, info, png_palette, 256);
+ png_set_rows(png, info, framedata);
+ png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL);
+}
+
+int img_output_png(FILE *f, const char *filename,
+ unsigned width, unsigned height,
+ unsigned char **framedata, unsigned char **mask,
+ struct lbx_colour *palette)
+{
+ struct file_info file = { filename, f };
+ png_structp png;
+ png_infop info;
+
+ png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png) {
+ tool_err(-1, "failed to init libpng.");
+ return -1;
+ }
+
+ info = png_create_info_struct(png);
+ if (!info) {
+ tool_err(-1, "failed to init libpng.");
+ png_destroy_write_struct(&png, NULL);
+ return -1;
+ }
+
+ if (setjmp(png_jmpbuf(png))) {
+ png_destroy_write_struct(&png, &info);
+ return -1;
+ }
+
+ png_set_error_fn(png, NULL, error_fn, warning_fn);
+ png_set_write_fn(png, &file, write_data, flush_data);
+
+ if (is_masked(mask, width, height)) {
+ /*
+ * Indexed colour mode can only be used for images without
+ * transparency, since indexed PNG requires sacrificing one or
+ * more palette colours to indicate transparency, while LBX
+ * does not. So we use full-up RGBA in this case.
+ */
+ png_set_IHDR(png, info, width, height, 8,
+ PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ write_rgba_frame(png, info, framedata, mask, palette);
+ } else {
+ png_set_IHDR(png, info, width, height, 8,
+ PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ write_index_frame(png, info, framedata, palette);
+ }
+
+ png_destroy_write_struct(&png, &info);
+ return 0;
+}