]> git.draconx.ca Git - liblbx.git/commitdiff
lbximg: Add a new PNG writing routine.
authorNick Bowler <nbowler@draconx.ca>
Tue, 11 Jun 2013 22:29:07 +0000 (18:29 -0400)
committerNick Bowler <nbowler@draconx.ca>
Fri, 14 Jun 2013 20:32:56 +0000 (16:32 -0400)
This splits out the PNG output from the rest of the lbximg processing,
so that it will be easier to add additional output formats (in par-
ticular, it would be nice to have some sort of text-based format to
facilitate automated testing).

Take this opportunity to vastly improve the error handling.

Makefile.am
configure.ac
src/imgoutput.h [new file with mode: 0644]
src/lbximg.c
src/png.c [new file with mode: 0644]

index 55fa7e390f9d55e26ebc05870cd9b17d6a6fbdd5..8cfec29a86307182b9258e4fd4c7a22753307844 100644 (file)
@@ -37,7 +37,7 @@ lbxtool_SOURCES = src/lbxtool.c src/tools.c
 lbxtool_LDADD = liblbx.la libgnu.la
 $(lbxtool_OBJECTS): $(gnulib_headers)
 
 lbxtool_LDADD = liblbx.la libgnu.la
 $(lbxtool_OBJECTS): $(gnulib_headers)
 
-lbximg_SOURCES = src/lbximg.c src/tools.c
+lbximg_SOURCES = src/lbximg.c src/tools.c src/png.c
 lbximg_LDADD = liblbx.la libgnu.la $(LIBPNG_LIBS)
 $(lbximg_OBJECTS): $(gnulib_headers)
 
 lbximg_LDADD = liblbx.la libgnu.la $(LIBPNG_LIBS)
 $(lbximg_OBJECTS): $(gnulib_headers)
 
index 2a3ddca5c01dd372a774a1f9fe68f5df9ff8f0f9..9ba3d42a008c495b04bbb9ea7667b609a0fb3aeb 100644 (file)
@@ -33,10 +33,10 @@ AC_ARG_ENABLE([lbximg],
 
 have_libpng=no
 if test x"$enable_lbximg" = x"auto"; then
 
 have_libpng=no
 if test x"$enable_lbximg" = x"auto"; then
-DX_CHECK_LIBPNG([1.2], [have_libpng=yes], [have_libpng=no])
+DX_CHECK_LIBPNG([1.5], [have_libpng=yes], [have_libpng=no])
 fi
 if test x"$enable_lbximg" = x"yes"; then
 fi
 if test x"$enable_lbximg" = x"yes"; then
-DX_CHECK_LIBPNG([1.2], [have_libpng=yes])
+DX_CHECK_LIBPNG([1.5], [have_libpng=yes])
 fi
 AM_CONDITIONAL([BUILD_LBXIMG], [test x"$have_libpng" = x"yes"])
 
 fi
 AM_CONDITIONAL([BUILD_LBXIMG], [test x"$have_libpng" = x"yes"])
 
diff --git a/src/imgoutput.h b/src/imgoutput.h
new file mode 100644 (file)
index 0000000..46d9c9a
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef IMGOUTPUT_H_
+#define IMGOUTPUT_H_
+
+int img_output_png(FILE *f, const char *filename,
+                   unsigned width, unsigned height,
+                   unsigned char **framedata, unsigned char **mask,
+                  struct lbx_colour *palette);
+
+#endif
index ff38c89b71f998f0dff387f5f2404e4ffb0783cf..c761c176f6ee308e421cbdf67dfe73bdaaee0b2d 100644 (file)
 #include <getopt.h>
 #include <errno.h>
 
 #include <getopt.h>
 #include <errno.h>
 
-#include <png.h>
-
 #include "tools.h"
 #include "image.h"
 #include "error.h"
 #include "lbx.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";
 /* Global flags */
 static int verbose = 0;
 static char *outname = "out";
@@ -104,127 +104,39 @@ int parserange(unsigned frames, char *str, unsigned char *bits)
        return 0;
 }
 
        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"];
 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;
        FILE *of;
-
-       png_structp png;
-       png_infop   info;
+       int rc;
 
        assert(frameno < 65536);
        snprintf(name, sizeof name, "%s.%03d.png", outname, frameno);
 
 
        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;
        }
 
        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);
        if (verbose)
                printf("wrote %s\n", name);
+
        return 0;
        return 0;
-err:
-       fclose(of);
-       remove(name);
-       free(row);
-       return -1;
 }
 
 static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
 }
 
 static int loadoverride(FILE *f, struct lbx_colour palette[static 256])
diff --git a/src/png.c b/src/png.c
new file mode 100644 (file)
index 0000000..ee5d0a5
--- /dev/null
+++ b/src/png.c
@@ -0,0 +1,210 @@
+/*
+ * 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;
+}