summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/libpng/pngvalid.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/libpng/pngvalid.c')
-rw-r--r--src/3rdparty/libpng/pngvalid.c6658
1 files changed, 6658 insertions, 0 deletions
diff --git a/src/3rdparty/libpng/pngvalid.c b/src/3rdparty/libpng/pngvalid.c
new file mode 100644
index 0000000000..faebe4b8b7
--- /dev/null
+++ b/src/3rdparty/libpng/pngvalid.c
@@ -0,0 +1,6658 @@
+
+/* pngvalid.c - validate libpng by constructing then reading png files.
+ *
+ * Last changed in libpng 1.5.1 [February 3, 2011]
+ * Copyright (c) 2011 Glenn Randers-Pehrson
+ * Written by John Cunningham Bowler
+ *
+ * This code is released under the libpng license.
+ * For conditions of distribution and use, see the disclaimer
+ * and license in png.h
+ *
+ * NOTES:
+ * This is a C program that is intended to be linked against libpng. It
+ * generates bitmaps internally, stores them as PNG files (using the
+ * sequential write code) then reads them back (using the sequential
+ * read code) and validates that the result has the correct data.
+ *
+ * The program can be modified and extended to test the correctness of
+ * transformations performed by libpng.
+ */
+
+#include "png.h"
+#if PNG_LIBPNG_VER < 10500
+/* This delibarately lacks the PNG_CONST. */
+typedef png_byte *png_const_bytep;
+
+/* This is copied from 1.5.1 png.h: */
+#define PNG_INTERLACE_ADAM7_PASSES 7
+#define PNG_PASS_START_ROW(pass) (((1U&~(pass))<<(3-((pass)>>1)))&7)
+#define PNG_PASS_START_COL(pass) (((1U& (pass))<<(3-(((pass)+1)>>1)))&7)
+#define PNG_PASS_ROW_SHIFT(pass) ((pass)>2?(8-(pass))>>1:3)
+#define PNG_PASS_COL_SHIFT(pass) ((pass)>1?(7-(pass))>>1:3)
+#define PNG_PASS_ROWS(height, pass) (((height)+(((1<<PNG_PASS_ROW_SHIFT(pass))\
+ -1)-PNG_PASS_START_ROW(pass)))>>PNG_PASS_ROW_SHIFT(pass))
+#define PNG_PASS_COLS(width, pass) (((width)+(((1<<PNG_PASS_COL_SHIFT(pass))\
+ -1)-PNG_PASS_START_COL(pass)))>>PNG_PASS_COL_SHIFT(pass))
+#define PNG_ROW_FROM_PASS_ROW(yIn, pass) \
+ (((yIn)<<PNG_PASS_ROW_SHIFT(pass))+PNG_PASS_START_ROW(pass))
+#define PNG_COL_FROM_PASS_COL(xIn, pass) \
+ (((xIn)<<PNG_PASS_COL_SHIFT(pass))+PNG_PASS_START_COL(pass))
+#define PNG_PASS_MASK(pass,off) ( \
+ ((0x110145AFU>>(((7-(off))-(pass))<<2)) & 0xFU) | \
+ ((0x01145AF0U>>(((7-(off))-(pass))<<2)) & 0xF0U))
+#define PNG_ROW_IN_INTERLACE_PASS(y, pass) \
+ ((PNG_PASS_MASK(pass,0) >> ((y)&7)) & 1)
+#define PNG_COL_IN_INTERLACE_PASS(x, pass) \
+ ((PNG_PASS_MASK(pass,1) >> ((x)&7)) & 1)
+
+/* These are needed too for the defualt build: */
+#define PNG_WRITE_16BIT_SUPPORTED
+#define PNG_READ_16BIT_SUPPORTED
+#endif
+
+#include "zlib.h" /* For crc32 */
+
+#include <float.h> /* For floating point constants */
+#include <stdlib.h> /* For malloc */
+#include <string.h> /* For memcpy, memset */
+#include <math.h> /* For floor */
+
+/* Unused formal parameter errors are removed using the following macro which is
+ * expected to have no bad effects on performance.
+ */
+#ifndef UNUSED
+# if defined(__GNUC__) || defined(_MSC_VER)
+# define UNUSED(param) (void)param;
+# else
+# define UNUSED(param)
+# endif
+#endif
+
+/***************************** EXCEPTION HANDLING *****************************/
+#include "contrib/visupng/cexcept.h"
+struct png_store;
+define_exception_type(struct png_store*);
+
+/* The following are macros to reduce typing everywhere where the well known
+ * name 'the_exception_context' must be defined.
+ */
+#define anon_context(ps) struct exception_context *the_exception_context = \
+ &(ps)->exception_context
+#define context(ps,fault) anon_context(ps); png_store *fault
+
+/******************************* UTILITIES ************************************/
+/* Error handling is particularly problematic in production code - error
+ * handlers often themselves have bugs which lead to programs that detect
+ * minor errors crashing. The following functions deal with one very
+ * common class of errors in error handlers - attempting to format error or
+ * warning messages into buffers that are too small.
+ */
+static size_t safecat(char *buffer, size_t bufsize, size_t pos,
+ PNG_CONST char *cat)
+{
+ while (pos < bufsize && cat != NULL && *cat != 0)
+ buffer[pos++] = *cat++;
+
+ if (pos >= bufsize)
+ pos = bufsize-1;
+
+ buffer[pos] = 0;
+ return pos;
+}
+
+static size_t safecatn(char *buffer, size_t bufsize, size_t pos, int n)
+{
+ char number[64];
+ sprintf(number, "%d", n);
+ return safecat(buffer, bufsize, pos, number);
+}
+
+static size_t safecatd(char *buffer, size_t bufsize, size_t pos, double d,
+ int precision)
+{
+ char number[64];
+ sprintf(number, "%.*f", precision, d);
+ return safecat(buffer, bufsize, pos, number);
+}
+
+static PNG_CONST char invalid[] = "invalid";
+static PNG_CONST char sep[] = ": ";
+
+/* NOTE: this is indexed by ln2(bit_depth)! */
+static PNG_CONST char *bit_depths[8] =
+{
+ "1", "2", "4", "8", "16", invalid, invalid, invalid
+};
+
+static PNG_CONST char *colour_types[8] =
+{
+ "greyscale", invalid, "truecolour", "indexed-colour",
+ "greyscale with alpha", invalid, "truecolour with alpha", invalid
+};
+
+/* To get log-bit-depth from bit depth, returns 0 to 7 (7 on error). */
+static unsigned int
+log2depth(png_byte bit_depth)
+{
+ switch (bit_depth)
+ {
+ case 1:
+ return 0;
+
+ case 2:
+ return 1;
+
+ case 4:
+ return 2;
+
+ case 8:
+ return 3;
+
+ case 16:
+ return 4;
+
+ default:
+ return 7;
+ }
+}
+
+/* A numeric ID based on PNG file characteristics. The 'do_interlace' field
+ * simply records whether pngvalid did the interlace itself or whether it
+ * was done by libpng. Width and height must be less than 256.
+ */
+#define FILEID(col, depth, interlace, width, height, do_interlace) \
+ ((png_uint_32)((col) + ((depth)<<3) + ((interlace)<<8) + \
+ (((do_interlace)!=0)<<15) + ((width)<<16) + ((height)<<24)))
+
+#define COL_FROM_ID(id) ((png_byte)((id)& 0x7U))
+#define DEPTH_FROM_ID(id) ((png_byte)(((id) >> 3) & 0x1fU))
+#define INTERLACE_FROM_ID(id) ((int)(((id) >> 8) & 0x3))
+#define DO_INTERLACE_FROM_ID(id) ((int)(((id)>>15) & 1))
+#define WIDTH_FROM_ID(id) (((id)>>16) & 0xff)
+#define HEIGHT_FROM_ID(id) (((id)>>24) & 0xff)
+
+/* Utility to construct a standard name for a standard image. */
+static size_t
+standard_name(char *buffer, size_t bufsize, size_t pos, png_byte colour_type,
+ int log_bit_depth, int interlace_type, png_uint_32 w, png_uint_32 h,
+ int do_interlace)
+{
+ pos = safecat(buffer, bufsize, pos, colour_types[colour_type]);
+ pos = safecat(buffer, bufsize, pos, " ");
+ pos = safecat(buffer, bufsize, pos, bit_depths[log_bit_depth]);
+ pos = safecat(buffer, bufsize, pos, " bit ");
+
+ if (interlace_type != PNG_INTERLACE_NONE)
+ pos = safecat(buffer, bufsize, pos, "interlaced");
+ if (do_interlace)
+ pos = safecat(buffer, bufsize, pos, "(pngvalid)");
+ else
+ pos = safecat(buffer, bufsize, pos, "(libpng)");
+ if (w > 0 || h > 0)
+ {
+ pos = safecat(buffer, bufsize, pos, " ");
+ pos = safecatn(buffer, bufsize, pos, w);
+ pos = safecat(buffer, bufsize, pos, "x");
+ pos = safecatn(buffer, bufsize, pos, h);
+ }
+
+ return pos;
+}
+
+static size_t
+standard_name_from_id(char *buffer, size_t bufsize, size_t pos, png_uint_32 id)
+{
+ return standard_name(buffer, bufsize, pos, COL_FROM_ID(id),
+ log2depth(DEPTH_FROM_ID(id)), INTERLACE_FROM_ID(id),
+ WIDTH_FROM_ID(id), HEIGHT_FROM_ID(id), DO_INTERLACE_FROM_ID(id));
+}
+
+/* Convenience API and defines to list valid formats. Note that 16 bit read and
+ * write support is required to do 16 bit read tests (we must be able to make a
+ * 16 bit image to test!)
+ */
+#ifdef PNG_WRITE_16BIT_SUPPORTED
+# define WRITE_BDHI 4
+# ifdef PNG_READ_16BIT_SUPPORTED
+# define READ_BDHI 4
+# define DO_16BIT
+# endif
+#else
+# define WRITE_BDHI 3
+#endif
+#ifndef DO_16BIT
+# define READ_BDHI 3
+#endif
+
+static int
+next_format(png_bytep colour_type, png_bytep bit_depth)
+{
+ if (*bit_depth == 0)
+ {
+ *colour_type = 0, *bit_depth = 1;
+ return 1;
+ }
+
+ *bit_depth = (png_byte)(*bit_depth << 1);
+
+ /* Palette images are restricted to 8 bit depth */
+ if (*bit_depth <= 8
+# ifdef DO_16BIT
+ || (*colour_type != 3 && *bit_depth <= 16)
+# endif
+ )
+ return 1;
+
+ /* Move to the next color type, or return 0 at the end. */
+ switch (*colour_type)
+ {
+ case 0:
+ *colour_type = 2;
+ *bit_depth = 8;
+ return 1;
+
+ case 2:
+ *colour_type = 3;
+ *bit_depth = 1;
+ return 1;
+
+ case 3:
+ *colour_type = 4;
+ *bit_depth = 8;
+ return 1;
+
+ case 4:
+ *colour_type = 6;
+ *bit_depth = 8;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static unsigned int
+sample(png_const_bytep row, png_byte colour_type, png_byte bit_depth,
+ png_uint_32 x, unsigned int sample_index)
+{
+ png_uint_32 bit_index, result;
+
+ /* Find a sample index for the desired sample: */
+ x *= bit_depth;
+ bit_index = x;
+
+ if ((colour_type & 1) == 0) /* !palette */
+ {
+ if (colour_type & 2)
+ bit_index *= 3;
+
+ if (colour_type & 4)
+ bit_index += x; /* Alpha channel */
+
+ if (colour_type & (2+4))
+ bit_index += sample_index * bit_depth; /* Multiple channels: select one */
+ }
+
+ /* Return the sample from the row as an integer. */
+ row += bit_index >> 3;
+ result = *row;
+
+ if (bit_depth == 8)
+ return result;
+
+ else if (bit_depth > 8)
+ return (result << 8) + *++row;
+
+ /* Less than 8 bits per sample. */
+ bit_index &= 7;
+ return (result >> (8-bit_index-bit_depth)) & ((1U<<bit_depth)-1);
+}
+
+/* Copy a single pixel, of a given size, from one buffer to another -
+ * while this is basically bit addressed there is an implicit assumption
+ * that pixels 8 or more bits in size are byte aligned and that pixels
+ * do not otherwise cross byte boundaries. (This is, so far as I know,
+ * universally true in bitmap computer graphics. [JCB 20101212])
+ *
+ * NOTE: The to and from buffers may be the same.
+ */
+static void
+pixel_copy(png_bytep toBuffer, png_uint_32 toIndex,
+ png_const_bytep fromBuffer, png_uint_32 fromIndex, unsigned int pixelSize)
+{
+ /* Assume we can multiply by 'size' without overflow because we are
+ * just working in a single buffer.
+ */
+ toIndex *= pixelSize;
+ fromIndex *= pixelSize;
+ if (pixelSize < 8) /* Sub-byte */
+ {
+ /* Mask to select the location of the copied pixel: */
+ unsigned int destMask = ((1U<<pixelSize)-1) << (8-pixelSize-(toIndex&7));
+ /* The following read the entire pixels and clears the extra: */
+ unsigned int destByte = toBuffer[toIndex >> 3] & ~destMask;
+ unsigned int sourceByte = fromBuffer[fromIndex >> 3];
+
+ /* Don't rely on << or >> supporting '0' here, just in case: */
+ fromIndex &= 7;
+ if (fromIndex > 0) sourceByte <<= fromIndex;
+ if ((toIndex & 7) > 0) sourceByte >>= toIndex & 7;
+
+ toBuffer[toIndex >> 3] = (png_byte)(destByte | (sourceByte & destMask));
+ }
+ else /* One or more bytes */
+ memmove(toBuffer+(toIndex>>3), fromBuffer+(fromIndex>>3), pixelSize>>3);
+}
+
+/* Compare pixels - they are assumed to start at the first byte in the
+ * given buffers.
+ */
+static int
+pixel_cmp(png_const_bytep pa, png_const_bytep pb, png_uint_32 bit_width)
+{
+ if (memcmp(pa, pb, bit_width>>3) == 0)
+ {
+ png_uint_32 p;
+
+ if ((bit_width & 7) == 0) return 0;
+
+ /* Ok, any differences? */
+ p = pa[bit_width >> 3];
+ p ^= pb[bit_width >> 3];
+
+ if (p == 0) return 0;
+
+ /* There are, but they may not be significant, remove the bits
+ * after the end (the low order bits in PNG.)
+ */
+ bit_width &= 7;
+ p >>= 8-bit_width;
+
+ if (p == 0) return 0;
+ }
+
+ return 1; /* Different */
+}
+
+/*************************** BASIC PNG FILE WRITING ***************************/
+/* A png_store takes data from the sequential writer or provides data
+ * to the sequential reader. It can also store the result of a PNG
+ * write for later retrieval.
+ */
+#define STORE_BUFFER_SIZE 500 /* arbitrary */
+typedef struct png_store_buffer
+{
+ struct png_store_buffer* prev; /* NOTE: stored in reverse order */
+ png_byte buffer[STORE_BUFFER_SIZE];
+} png_store_buffer;
+
+#define FILE_NAME_SIZE 64
+
+typedef struct png_store_file
+{
+ struct png_store_file* next; /* as many as you like... */
+ char name[FILE_NAME_SIZE];
+ png_uint_32 id; /* must be correct (see FILEID) */
+ png_size_t datacount; /* In this (the last) buffer */
+ png_store_buffer data; /* Last buffer in file */
+} png_store_file;
+
+/* The following is a pool of memory allocated by a single libpng read or write
+ * operation.
+ */
+typedef struct store_pool
+{
+ struct png_store *store; /* Back pointer */
+ struct store_memory *list; /* List of allocated memory */
+ png_byte mark[4]; /* Before and after data */
+
+ /* Statistics for this run. */
+ png_alloc_size_t max; /* Maximum single allocation */
+ png_alloc_size_t current; /* Current allocation */
+ png_alloc_size_t limit; /* Highest current allocation */
+ png_alloc_size_t total; /* Total allocation */
+
+ /* Overall statistics (retained across successive runs). */
+ png_alloc_size_t max_max;
+ png_alloc_size_t max_limit;
+ png_alloc_size_t max_total;
+} store_pool;
+
+typedef struct png_store
+{
+ /* For cexcept.h exception handling - simply store one of these;
+ * the context is a self pointer but it may point to a different
+ * png_store (in fact it never does in this program.)
+ */
+ struct exception_context
+ exception_context;
+
+ unsigned int verbose :1;
+ unsigned int treat_warnings_as_errors :1;
+ unsigned int expect_error :1;
+ unsigned int expect_warning :1;
+ unsigned int saw_warning :1;
+ unsigned int speed :1;
+ unsigned int progressive :1; /* use progressive read */
+ unsigned int validated :1; /* used as a temporary flag */
+ int nerrors;
+ int nwarnings;
+ char test[128]; /* Name of test */
+ char error[256];
+
+ /* Read fields */
+ png_structp pread; /* Used to read a saved file */
+ png_infop piread;
+ png_store_file* current; /* Set when reading */
+ png_store_buffer* next; /* Set when reading */
+ png_size_t readpos; /* Position in *next */
+ png_byte* image; /* Buffer for reading interlaced images */
+ size_t cb_image; /* Size of this buffer */
+ store_pool read_memory_pool;
+
+ /* Write fields */
+ png_store_file* saved;
+ png_structp pwrite; /* Used when writing a new file */
+ png_infop piwrite;
+ png_size_t writepos; /* Position in .new */
+ char wname[FILE_NAME_SIZE];
+ png_store_buffer new; /* The end of the new PNG file being written. */
+ store_pool write_memory_pool;
+} png_store;
+
+/* Initialization and cleanup */
+static void
+store_pool_mark(png_byte *mark)
+{
+ /* Generate a new mark. This uses a boring repeatable algorithm and it is
+ * implemented here so that it gives the same set of numbers on every
+ * architecture. It's a linear congruential generator (Knuth or Sedgewick
+ * "Algorithms") but it comes from the 'feedback taps' table in Horowitz and
+ * Hill, "The Art of Electronics".
+ */
+ static png_uint_32 u0 = 0x12345678, u1 = 1;
+
+ /* There are thirty three bits, the next bit in the sequence is bit-33 XOR
+ * bit-20. The top 1 bit is in u1, the bottom 32 are in u0.
+ */
+ int i;
+ for (i=0; i<4; ++i)
+ {
+ /* First generate 8 new bits then shift them in at the end. */
+ png_uint_32 u = ((u0 >> (20-8)) ^ ((u1 << 7) | (u0 >> (32-7)))) & 0xff;
+ u1 <<= 8;
+ u1 |= u0 >> 24;
+ u0 <<= 8;
+ u0 |= u;
+ *mark++ = (png_byte)u;
+ }
+}
+
+/* Use this for random 32 bit values, this function makes sure the result is
+ * non-zero.
+ */
+static png_uint_32
+random_32(void)
+{
+
+ for(;;)
+ {
+ png_byte mark[4];
+ png_uint_32 result;
+
+ store_pool_mark(mark);
+ result = png_get_uint_32(mark);
+
+ if (result != 0)
+ return result;
+ }
+}
+
+static void
+store_pool_init(png_store *ps, store_pool *pool)
+{
+ memset(pool, 0, sizeof *pool);
+
+ pool->store = ps;
+ pool->list = NULL;
+ pool->max = pool->current = pool->limit = pool->total = 0;
+ pool->max_max = pool->max_limit = pool->max_total = 0;
+ store_pool_mark(pool->mark);
+}
+
+static void
+store_init(png_store* ps)
+{
+ memset(ps, 0, sizeof *ps);
+ init_exception_context(&ps->exception_context);
+ store_pool_init(ps, &ps->read_memory_pool);
+ store_pool_init(ps, &ps->write_memory_pool);
+ ps->verbose = 0;
+ ps->treat_warnings_as_errors = 0;
+ ps->expect_error = 0;
+ ps->expect_warning = 0;
+ ps->saw_warning = 0;
+ ps->speed = 0;
+ ps->progressive = 0;
+ ps->validated = 0;
+ ps->nerrors = ps->nwarnings = 0;
+ ps->pread = NULL;
+ ps->piread = NULL;
+ ps->saved = ps->current = NULL;
+ ps->next = NULL;
+ ps->readpos = 0;
+ ps->image = NULL;
+ ps->cb_image = 0;
+ ps->pwrite = NULL;
+ ps->piwrite = NULL;
+ ps->writepos = 0;
+ ps->new.prev = NULL;
+}
+
+/* This somewhat odd function is used when reading an image to ensure that the
+ * buffer is big enough - this is why a png_structp is available.
+ */
+static void
+store_ensure_image(png_store *ps, png_structp pp, size_t cb)
+{
+ if (ps->cb_image < cb)
+ {
+ if (ps->image != NULL)
+ {
+ free(ps->image-1);
+ ps->cb_image = 0;
+ }
+
+ /* The buffer is deliberately mis-aligned. */
+ ps->image = malloc(cb+1);
+ if (ps->image == NULL)
+ png_error(pp, "OOM allocating image buffer");
+
+ ++(ps->image);
+ ps->cb_image = cb;
+ }
+
+ /* And, for error checking, the whole buffer is set to '1' - this
+ * matches what happens with the 'size' test images on write and also
+ * matches the unused bits in the test rows.
+ */
+ memset(ps->image, 0xff, cb);
+}
+
+static void
+store_freebuffer(png_store_buffer* psb)
+{
+ if (psb->prev)
+ {
+ store_freebuffer(psb->prev);
+ free(psb->prev);
+ psb->prev = NULL;
+ }
+}
+
+static void
+store_freenew(png_store *ps)
+{
+ store_freebuffer(&ps->new);
+ ps->writepos = 0;
+}
+
+static void
+store_storenew(png_store *ps)
+{
+ png_store_buffer *pb;
+
+ if (ps->writepos != STORE_BUFFER_SIZE)
+ png_error(ps->pwrite, "invalid store call");
+
+ pb = malloc(sizeof *pb);
+
+ if (pb == NULL)
+ png_error(ps->pwrite, "store new: OOM");
+
+ *pb = ps->new;
+ ps->new.prev = pb;
+ ps->writepos = 0;
+}
+
+static void
+store_freefile(png_store_file **ppf)
+{
+ if (*ppf != NULL)
+ {
+ store_freefile(&(*ppf)->next);
+
+ store_freebuffer(&(*ppf)->data);
+ (*ppf)->datacount = 0;
+ free(*ppf);
+ *ppf = NULL;
+ }
+}
+
+/* Main interface to file storeage, after writing a new PNG file (see the API
+ * below) call store_storefile to store the result with the given name and id.
+ */
+static void
+store_storefile(png_store *ps, png_uint_32 id)
+{
+ png_store_file *pf = malloc(sizeof *pf);
+ if (pf == NULL)
+ png_error(ps->pwrite, "storefile: OOM");
+ safecat(pf->name, sizeof pf->name, 0, ps->wname);
+ pf->id = id;
+ pf->data = ps->new;
+ pf->datacount = ps->writepos;
+ ps->new.prev = NULL;
+ ps->writepos = 0;
+
+ /* And save it. */
+ pf->next = ps->saved;
+ ps->saved = pf;
+}
+
+/* Generate an error message (in the given buffer) */
+static size_t
+store_message(png_store *ps, png_structp pp, char *buffer, size_t bufsize,
+ size_t pos, PNG_CONST char *msg)
+{
+ if (pp != NULL && pp == ps->pread)
+ {
+ /* Reading a file */
+ pos = safecat(buffer, bufsize, pos, "read: ");
+
+ if (ps->current != NULL)
+ {
+ pos = safecat(buffer, bufsize, pos, ps->current->name);
+ pos = safecat(buffer, bufsize, pos, sep);
+ }
+ }
+
+ else if (pp != NULL && pp == ps->pwrite)
+ {
+ /* Writing a file */
+ pos = safecat(buffer, bufsize, pos, "write: ");
+ pos = safecat(buffer, bufsize, pos, ps->wname);
+ pos = safecat(buffer, bufsize, pos, sep);
+ }
+
+ else
+ {
+ /* Neither reading nor writing (or a memory error in struct delete) */
+ pos = safecat(buffer, bufsize, pos, "pngvalid: ");
+ }
+
+ if (ps->test[0] != 0)
+ {
+ pos = safecat(buffer, bufsize, pos, ps->test);
+ pos = safecat(buffer, bufsize, pos, sep);
+ }
+ pos = safecat(buffer, bufsize, pos, msg);
+ return pos;
+}
+
+/* Log an error or warning - the relevant count is always incremented. */
+static void
+store_log(png_store* ps, png_structp pp, png_const_charp message, int is_error)
+{
+ /* The warning is copied to the error buffer if there are no errors and it is
+ * the first warning. The error is copied to the error buffer if it is the
+ * first error (overwriting any prior warnings).
+ */
+ if (is_error ? (ps->nerrors)++ == 0 :
+ (ps->nwarnings)++ == 0 && ps->nerrors == 0)
+ store_message(ps, pp, ps->error, sizeof ps->error, 0, message);
+
+ if (ps->verbose)
+ {
+ char buffer[256];
+ size_t pos;
+
+ if (is_error)
+ pos = safecat(buffer, sizeof buffer, 0, "error: ");
+ else
+ pos = safecat(buffer, sizeof buffer, 0, "warning: ");
+
+ store_message(ps, pp, buffer, sizeof buffer, pos, message);
+ fputs(buffer, stderr);
+ fputc('\n', stderr);
+ }
+}
+
+/* Functions to use as PNG callbacks. */
+static void
+store_error(png_structp pp, png_const_charp message) /* PNG_NORETURN */
+{
+ png_store *ps = png_get_error_ptr(pp);
+
+ if (!ps->expect_error)
+ store_log(ps, pp, message, 1 /* error */);
+
+ /* And finally throw an exception. */
+ {
+ struct exception_context *the_exception_context = &ps->exception_context;
+ Throw ps;
+ }
+}
+
+static void
+store_warning(png_structp pp, png_const_charp message)
+{
+ png_store *ps = png_get_error_ptr(pp);
+
+ if (!ps->expect_warning)
+ store_log(ps, pp, message, 0 /* warning */);
+ else
+ ps->saw_warning = 1;
+}
+
+static void
+store_write(png_structp pp, png_bytep pb, png_size_t st)
+{
+ png_store *ps = png_get_io_ptr(pp);
+
+ if (ps->pwrite != pp)
+ png_error(pp, "store state damaged");
+
+ while (st > 0)
+ {
+ size_t cb;
+
+ if (ps->writepos >= STORE_BUFFER_SIZE)
+ store_storenew(ps);
+
+ cb = st;
+
+ if (cb > STORE_BUFFER_SIZE - ps->writepos)
+ cb = STORE_BUFFER_SIZE - ps->writepos;
+
+ memcpy(ps->new.buffer + ps->writepos, pb, cb);
+ pb += cb;
+ st -= cb;
+ ps->writepos += cb;
+ }
+}
+
+static void
+store_flush(png_structp pp)
+{
+ UNUSED(pp) /*DOES NOTHING*/
+}
+
+static size_t
+store_read_buffer_size(png_store *ps)
+{
+ /* Return the bytes available for read in the current buffer. */
+ if (ps->next != &ps->current->data)
+ return STORE_BUFFER_SIZE;
+
+ return ps->current->datacount;
+}
+
+/* Return total bytes available for read. */
+static size_t
+store_read_buffer_avail(png_store *ps)
+{
+ if (ps->current != NULL && ps->next != NULL)
+ {
+ png_store_buffer *next = &ps->current->data;
+ size_t cbAvail = ps->current->datacount;
+
+ while (next != ps->next && next != NULL)
+ {
+ next = next->prev;
+ cbAvail += STORE_BUFFER_SIZE;
+ }
+
+ if (next != ps->next)
+ png_error(ps->pread, "buffer read error");
+
+ if (cbAvail > ps->readpos)
+ return cbAvail - ps->readpos;
+ }
+
+ return 0;
+}
+
+static int
+store_read_buffer_next(png_store *ps)
+{
+ png_store_buffer *pbOld = ps->next;
+ png_store_buffer *pbNew = &ps->current->data;
+ if (pbOld != pbNew)
+ {
+ while (pbNew != NULL && pbNew->prev != pbOld)
+ pbNew = pbNew->prev;
+
+ if (pbNew != NULL)
+ {
+ ps->next = pbNew;
+ ps->readpos = 0;
+ return 1;
+ }
+
+ png_error(ps->pread, "buffer lost");
+ }
+
+ return 0; /* EOF or error */
+}
+
+/* Need separate implementation and callback to allow use of the same code
+ * during progressive read, where the io_ptr is set internally by libpng.
+ */
+static void
+store_read_imp(png_store *ps, png_bytep pb, png_size_t st)
+{
+ if (ps->current == NULL || ps->next == NULL)
+ png_error(ps->pread, "store state damaged");
+
+ while (st > 0)
+ {
+ size_t cbAvail = store_read_buffer_size(ps) - ps->readpos;
+
+ if (cbAvail > 0)
+ {
+ if (cbAvail > st) cbAvail = st;
+ memcpy(pb, ps->next->buffer + ps->readpos, cbAvail);
+ st -= cbAvail;
+ pb += cbAvail;
+ ps->readpos += cbAvail;
+ }
+
+ else if (!store_read_buffer_next(ps))
+ png_error(ps->pread, "read beyond end of file");
+ }
+}
+
+static void
+store_read(png_structp pp, png_bytep pb, png_size_t st)
+{
+ png_store *ps = png_get_io_ptr(pp);
+
+ if (ps == NULL || ps->pread != pp)
+ png_error(pp, "bad store read call");
+
+ store_read_imp(ps, pb, st);
+}
+
+static void
+store_progressive_read(png_store *ps, png_structp pp, png_infop pi)
+{
+ /* Notice that a call to store_read will cause this function to fail because
+ * readpos will be set.
+ */
+ if (ps->pread != pp || ps->current == NULL || ps->next == NULL)
+ png_error(pp, "store state damaged (progressive)");
+
+ do
+ {
+ if (ps->readpos != 0)
+ png_error(pp, "store_read called during progressive read");
+
+ png_process_data(pp, pi, ps->next->buffer, store_read_buffer_size(ps));
+ }
+ while (store_read_buffer_next(ps));
+}
+
+/***************************** MEMORY MANAGEMENT*** ***************************/
+/* A store_memory is simply the header for an allocated block of memory. The
+ * pointer returned to libpng is just after the end of the header block, the
+ * allocated memory is followed by a second copy of the 'mark'.
+ */
+typedef struct store_memory
+{
+ store_pool *pool; /* Originating pool */
+ struct store_memory *next; /* Singly linked list */
+ png_alloc_size_t size; /* Size of memory allocated */
+ png_byte mark[4]; /* ID marker */
+} store_memory;
+
+/* Handle a fatal error in memory allocation. This calls png_error if the
+ * libpng struct is non-NULL, else it outputs a message and returns. This means
+ * that a memory problem while libpng is running will abort (png_error) the
+ * handling of particular file while one in cleanup (after the destroy of the
+ * struct has returned) will simply keep going and free (or attempt to free)
+ * all the memory.
+ */
+static void
+store_pool_error(png_store *ps, png_structp pp, PNG_CONST char *msg)
+{
+ if (pp != NULL)
+ png_error(pp, msg);
+
+ /* Else we have to do it ourselves. png_error eventually calls store_log,
+ * above. store_log accepts a NULL png_structp - it just changes what gets
+ * output by store_message.
+ */
+ store_log(ps, pp, msg, 1 /* error */);
+}
+
+static void
+store_memory_free(png_structp pp, store_pool *pool, store_memory *memory)
+{
+ /* Note that pp may be NULL (see store_pool_delete below), the caller has
+ * found 'memory' in pool->list *and* unlinked this entry, so this is a valid
+ * pointer (for sure), but the contents may have been trashed.
+ */
+ if (memory->pool != pool)
+ store_pool_error(pool->store, pp, "memory corrupted (pool)");
+
+ else if (memcmp(memory->mark, pool->mark, sizeof memory->mark) != 0)
+ store_pool_error(pool->store, pp, "memory corrupted (start)");
+
+ /* It should be safe to read the size field now. */
+ else
+ {
+ png_alloc_size_t cb = memory->size;
+
+ if (cb > pool->max)
+ store_pool_error(pool->store, pp, "memory corrupted (size)");
+
+ else if (memcmp((png_bytep)(memory+1)+cb, pool->mark, sizeof pool->mark)
+ != 0)
+ store_pool_error(pool->store, pp, "memory corrupted (end)");
+
+ /* Finally give the library a chance to find problems too: */
+ else
+ {
+ pool->current -= cb;
+ free(memory);
+ }
+ }
+}
+
+static void
+store_pool_delete(png_store *ps, store_pool *pool)
+{
+ if (pool->list != NULL)
+ {
+ fprintf(stderr, "%s: %s %s: memory lost (list follows):\n", ps->test,
+ pool == &ps->read_memory_pool ? "read" : "write",
+ pool == &ps->read_memory_pool ? (ps->current != NULL ?
+ ps->current->name : "unknown file") : ps->wname);
+ ++ps->nerrors;
+
+ do
+ {
+ store_memory *next = pool->list;
+ pool->list = next->next;
+ next->next = NULL;
+
+ fprintf(stderr, "\t%lu bytes @ %p\n",
+ (unsigned long)next->size, next+1);
+ /* The NULL means this will always return, even if the memory is
+ * corrupted.
+ */
+ store_memory_free(NULL, pool, next);
+ }
+ while (pool->list != NULL);
+ }
+
+ /* And reset the other fields too for the next time. */
+ if (pool->max > pool->max_max) pool->max_max = pool->max;
+ pool->max = 0;
+ if (pool->current != 0) /* unexpected internal error */
+ fprintf(stderr, "%s: %s %s: memory counter mismatch (internal error)\n",
+ ps->test, pool == &ps->read_memory_pool ? "read" : "write",
+ pool == &ps->read_memory_pool ? (ps->current != NULL ?
+ ps->current->name : "unknown file") : ps->wname);
+ pool->current = 0;
+
+ if (pool->limit > pool->max_limit)
+ pool->max_limit = pool->limit;
+
+ pool->limit = 0;
+
+ if (pool->total > pool->max_total)
+ pool->max_total = pool->total;
+
+ pool->total = 0;
+
+ /* Get a new mark too. */
+ store_pool_mark(pool->mark);
+}
+
+/* The memory callbacks: */
+static png_voidp
+store_malloc(png_structp pp, png_alloc_size_t cb)
+{
+ store_pool *pool = png_get_mem_ptr(pp);
+ store_memory *new = malloc(cb + (sizeof *new) + (sizeof pool->mark));
+
+ if (new != NULL)
+ {
+ if (cb > pool->max)
+ pool->max = cb;
+
+ pool->current += cb;
+
+ if (pool->current > pool->limit)
+ pool->limit = pool->current;
+
+ pool->total += cb;
+
+ new->size = cb;
+ memcpy(new->mark, pool->mark, sizeof new->mark);
+ memcpy((png_byte*)(new+1) + cb, pool->mark, sizeof pool->mark);
+ new->pool = pool;
+ new->next = pool->list;
+ pool->list = new;
+ ++new;
+ }
+
+ else
+ store_pool_error(pool->store, pp, "out of memory");
+
+ return new;
+}
+
+static void
+store_free(png_structp pp, png_voidp memory)
+{
+ store_pool *pool = png_get_mem_ptr(pp);
+ store_memory *this = memory, **test;
+
+ /* First check that this 'memory' really is valid memory - it must be in the
+ * pool list. If it is, use the shared memory_free function to free it.
+ */
+ --this;
+ for (test = &pool->list; *test != this; test = &(*test)->next)
+ {
+ if (*test == NULL)
+ {
+ store_pool_error(pool->store, pp, "bad pointer to free");
+ return;
+ }
+ }
+
+ /* Unlink this entry, *test == this. */
+ *test = this->next;
+ this->next = NULL;
+ store_memory_free(pp, pool, this);
+}
+
+/* Setup functions. */
+/* Cleanup when aborting a write or after storing the new file. */
+static void
+store_write_reset(png_store *ps)
+{
+ if (ps->pwrite != NULL)
+ {
+ anon_context(ps);
+
+ Try
+ png_destroy_write_struct(&ps->pwrite, &ps->piwrite);
+
+ Catch_anonymous
+ {
+ /* memory corruption: continue. */
+ }
+
+ ps->pwrite = NULL;
+ ps->piwrite = NULL;
+ }
+
+ /* And make sure that all the memory has been freed - this will output
+ * spurious errors in the case of memory corruption above, but this is safe.
+ */
+ store_pool_delete(ps, &ps->write_memory_pool);
+
+ store_freenew(ps);
+}
+
+/* The following is the main write function, it returns a png_struct and,
+ * optionally, a png_info suitable for writiing a new PNG file. Use
+ * store_storefile above to record this file after it has been written. The
+ * returned libpng structures as destroyed by store_write_reset above.
+ */
+static png_structp
+set_store_for_write(png_store *ps, png_infopp ppi,
+ PNG_CONST char * volatile name)
+{
+ anon_context(ps);
+
+ Try
+ {
+ if (ps->pwrite != NULL)
+ png_error(ps->pwrite, "write store already in use");
+
+ store_write_reset(ps);
+ safecat(ps->wname, sizeof ps->wname, 0, name);
+
+ /* Don't do the slow memory checks if doing a speed test. */
+ if (ps->speed)
+ ps->pwrite = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ ps, store_error, store_warning);
+
+ else
+ ps->pwrite = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
+ ps, store_error, store_warning, &ps->write_memory_pool,
+ store_malloc, store_free);
+
+ png_set_write_fn(ps->pwrite, ps, store_write, store_flush);
+
+ if (ppi != NULL)
+ *ppi = ps->piwrite = png_create_info_struct(ps->pwrite);
+ }
+
+ Catch_anonymous
+ return NULL;
+
+ return ps->pwrite;
+}
+
+/* Cleanup when finished reading (either due to error or in the success case).
+ */
+static void
+store_read_reset(png_store *ps)
+{
+ if (ps->pread != NULL)
+ {
+ anon_context(ps);
+
+ Try
+ png_destroy_read_struct(&ps->pread, &ps->piread, NULL);
+
+ Catch_anonymous
+ {
+ /* error already output: continue */
+ }
+
+ ps->pread = NULL;
+ ps->piread = NULL;
+ }
+
+ /* Always do this to be safe. */
+ store_pool_delete(ps, &ps->read_memory_pool);
+
+ ps->current = NULL;
+ ps->next = NULL;
+ ps->readpos = 0;
+ ps->validated = 0;
+}
+
+static void
+store_read_set(png_store *ps, png_uint_32 id)
+{
+ png_store_file *pf = ps->saved;
+
+ while (pf != NULL)
+ {
+ if (pf->id == id)
+ {
+ ps->current = pf;
+ ps->next = NULL;
+ store_read_buffer_next(ps);
+ return;
+ }
+
+ pf = pf->next;
+ }
+
+ {
+ size_t pos;
+ char msg[FILE_NAME_SIZE+64];
+
+ pos = standard_name_from_id(msg, sizeof msg, 0, id);
+ pos = safecat(msg, sizeof msg, pos, ": file not found");
+ png_error(ps->pread, msg);
+ }
+}
+
+/* The main interface for reading a saved file - pass the id number of the file
+ * to retrieve. Ids must be unique or the earlier file will be hidden. The API
+ * returns a png_struct and, optionally, a png_info. Both of these will be
+ * destroyed by store_read_reset above.
+ */
+static png_structp
+set_store_for_read(png_store *ps, png_infopp ppi, png_uint_32 id,
+ PNG_CONST char *name)
+{
+ /* Set the name for png_error */
+ safecat(ps->test, sizeof ps->test, 0, name);
+
+ if (ps->pread != NULL)
+ png_error(ps->pread, "read store already in use");
+
+ store_read_reset(ps);
+
+ /* Both the create APIs can return NULL if used in their default mode
+ * (because there is no other way of handling an error because the jmp_buf
+ * by default is stored in png_struct and that has not been allocated!)
+ * However, given that store_error works correctly in these circumstances
+ * we don't ever expect NULL in this program.
+ */
+ if (ps->speed)
+ ps->pread = png_create_read_struct(PNG_LIBPNG_VER_STRING, ps,
+ store_error, store_warning);
+
+ else
+ ps->pread = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, ps,
+ store_error, store_warning, &ps->read_memory_pool, store_malloc,
+ store_free);
+
+ if (ps->pread == NULL)
+ {
+ struct exception_context *the_exception_context = &ps->exception_context;
+
+ store_log(ps, NULL, "png_create_read_struct returned NULL (unexpected)",
+ 1 /*error*/);
+
+ Throw ps;
+ }
+
+ store_read_set(ps, id);
+
+ if (ppi != NULL)
+ *ppi = ps->piread = png_create_info_struct(ps->pread);
+
+ return ps->pread;
+}
+
+/* The overall cleanup of a store simply calls the above then removes all the
+ * saved files. This does not delete the store itself.
+ */
+static void
+store_delete(png_store *ps)
+{
+ store_write_reset(ps);
+ store_read_reset(ps);
+ store_freefile(&ps->saved);
+
+ if (ps->image != NULL)
+ {
+ free(ps->image-1);
+ ps->image = NULL;
+ ps->cb_image = 0;
+ }
+}
+
+/*********************** PNG FILE MODIFICATION ON READ ************************/
+/* Files may be modified on read. The following structure contains a complete
+ * png_store together with extra members to handle modification and a special
+ * read callback for libpng. To use this the 'modifications' field must be set
+ * to a list of png_modification structures that actually perform the
+ * modification, otherwise a png_modifier is functionally equivalent to a
+ * png_store. There is a special read function, set_modifier_for_read, which
+ * replaces set_store_for_read.
+ */
+typedef struct png_modifier
+{
+ png_store this; /* I am a png_store */
+ struct png_modification *modifications; /* Changes to make */
+
+ enum modifier_state
+ {
+ modifier_start, /* Initial value */
+ modifier_signature, /* Have a signature */
+ modifier_IHDR /* Have an IHDR */
+ } state; /* My state */
+
+ /* Information from IHDR: */
+ png_byte bit_depth; /* From IHDR */
+ png_byte colour_type; /* From IHDR */
+
+ /* While handling PLTE, IDAT and IEND these chunks may be pended to allow
+ * other chunks to be inserted.
+ */
+ png_uint_32 pending_len;
+ png_uint_32 pending_chunk;
+
+ /* Test values */
+ double *gammas;
+ unsigned int ngammas;
+
+ /* Lowest sbit to test (libpng fails for sbit < 8) */
+ png_byte sbitlow;
+
+ /* Error control - these are the limits on errors accepted by the gamma tests
+ * below.
+ */
+ double maxout8; /* Maximum output value error */
+ double maxabs8; /* Absolute sample error 0..1 */
+ double maxpc8; /* Percentage sample error 0..100% */
+ double maxout16; /* Maximum output value error */
+ double maxabs16; /* Absolute sample error 0..1 */
+ double maxpc16; /* Percentage sample error 0..100% */
+
+ /* Logged 8 and 16 bit errors ('output' values): */
+ double error_gray_2;
+ double error_gray_4;
+ double error_gray_8;
+ double error_gray_16;
+ double error_color_8;
+ double error_color_16;
+
+ /* Flags: */
+ /* Whether or not to interlace. */
+ int interlace_type :9; /* int, but must store '1' */
+
+ /* Run the standard tests? */
+ unsigned int test_standard :1;
+
+ /* Run the odd-sized image and interlace read/write tests? */
+ unsigned int test_size :1;
+
+ /* Run tests on reading with a combiniation of transforms, */
+ unsigned int test_transform :1;
+
+ /* When to use the use_input_precision option: */
+ unsigned int use_input_precision :1;
+ unsigned int use_input_precision_sbit :1;
+ unsigned int use_input_precision_16to8 :1;
+
+ /* Which gamma tests to run: */
+ unsigned int test_gamma_threshold :1;
+ unsigned int test_gamma_transform :1; /* main tests */
+ unsigned int test_gamma_sbit :1;
+ unsigned int test_gamma_strip16 :1;
+
+ unsigned int log :1; /* Log max error */
+
+ /* Buffer information, the buffer size limits the size of the chunks that can
+ * be modified - they must fit (including header and CRC) into the buffer!
+ */
+ size_t flush; /* Count of bytes to flush */
+ size_t buffer_count; /* Bytes in buffer */
+ size_t buffer_position; /* Position in buffer */
+ png_byte buffer[1024];
+} png_modifier;
+
+static double abserr(png_modifier *pm, png_byte bit_depth)
+{
+ return bit_depth == 16 ? pm->maxabs16 : pm->maxabs8;
+}
+
+static double pcerr(png_modifier *pm, png_byte bit_depth)
+{
+ return (bit_depth == 16 ? pm->maxpc16 : pm->maxpc8) * .01;
+}
+
+static double outerr(png_modifier *pm, png_byte bit_depth)
+{
+ /* There is a serious error in the 2 and 4 bit grayscale transform because
+ * the gamma table value (8 bits) is simply shifted, not rounded, so the
+ * error in 4 bit greyscale gamma is up to the value below. This is a hack
+ * to allow pngvalid to succeed:
+ */
+ if (bit_depth == 2)
+ return .73182-.5;
+
+ if (bit_depth == 4)
+ return .90644-.5;
+
+ if (bit_depth == 16)
+ return pm->maxout16;
+
+ return pm->maxout8;
+}
+
+/* This returns true if the test should be stopped now because it has already
+ * failed and it is running silently.
+ */
+static int fail(png_modifier *pm)
+{
+ return !pm->log && !pm->this.verbose && (pm->this.nerrors > 0 ||
+ (pm->this.treat_warnings_as_errors && pm->this.nwarnings > 0));
+}
+
+static void
+modifier_init(png_modifier *pm)
+{
+ memset(pm, 0, sizeof *pm);
+ store_init(&pm->this);
+ pm->modifications = NULL;
+ pm->state = modifier_start;
+ pm->sbitlow = 1U;
+ pm->maxout8 = pm->maxpc8 = pm->maxabs8 = 0;
+ pm->maxout16 = pm->maxpc16 = pm->maxabs16 = 0;
+ pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 = 0;
+ pm->error_gray_16 = pm->error_color_8 = pm->error_color_16 = 0;
+ pm->interlace_type = PNG_INTERLACE_NONE;
+ pm->test_standard = 0;
+ pm->test_size = 0;
+ pm->test_transform = 0;
+ pm->use_input_precision = 0;
+ pm->use_input_precision_sbit = 0;
+ pm->use_input_precision_16to8 = 0;
+ pm->test_gamma_threshold = 0;
+ pm->test_gamma_transform = 0;
+ pm->test_gamma_sbit = 0;
+ pm->test_gamma_strip16 = 0;
+ pm->log = 0;
+
+ /* Rely on the memset for all the other fields - there are no pointers */
+}
+
+/* One modification structure must be provided for each chunk to be modified (in
+ * fact more than one can be provided if multiple separate changes are desired
+ * for a single chunk.) Modifications include adding a new chunk when a
+ * suitable chunk does not exist.
+ *
+ * The caller of modify_fn will reset the CRC of the chunk and record 'modified'
+ * or 'added' as appropriate if the modify_fn returns 1 (true). If the
+ * modify_fn is NULL the chunk is simply removed.
+ */
+typedef struct png_modification
+{
+ struct png_modification *next;
+ png_uint_32 chunk;
+
+ /* If the following is NULL all matching chunks will be removed: */
+ int (*modify_fn)(struct png_modifier *pm,
+ struct png_modification *me, int add);
+
+ /* If the following is set to PLTE, IDAT or IEND and the chunk has not been
+ * found and modified (and there is a modify_fn) the modify_fn will be called
+ * to add the chunk before the relevant chunk.
+ */
+ png_uint_32 add;
+ unsigned int modified :1; /* Chunk was modified */
+ unsigned int added :1; /* Chunk was added */
+ unsigned int removed :1; /* Chunk was removed */
+} png_modification;
+
+static void modification_reset(png_modification *pmm)
+{
+ if (pmm != NULL)
+ {
+ pmm->modified = 0;
+ pmm->added = 0;
+ pmm->removed = 0;
+ modification_reset(pmm->next);
+ }
+}
+
+static void
+modification_init(png_modification *pmm)
+{
+ memset(pmm, 0, sizeof *pmm);
+ pmm->next = NULL;
+ pmm->chunk = 0;
+ pmm->modify_fn = NULL;
+ pmm->add = 0;
+ modification_reset(pmm);
+}
+
+static void
+modifier_reset(png_modifier *pm)
+{
+ store_read_reset(&pm->this);
+ pm->modifications = NULL;
+ pm->state = modifier_start;
+ pm->bit_depth = pm->colour_type = 0;
+ pm->pending_len = pm->pending_chunk = 0;
+ pm->flush = pm->buffer_count = pm->buffer_position = 0;
+}
+
+/* Convenience macros. */
+#define CHUNK(a,b,c,d) (((a)<<24)+((b)<<16)+((c)<<8)+(d))
+#define CHUNK_IHDR CHUNK(73,72,68,82)
+#define CHUNK_PLTE CHUNK(80,76,84,69)
+#define CHUNK_IDAT CHUNK(73,68,65,84)
+#define CHUNK_IEND CHUNK(73,69,78,68)
+#define CHUNK_cHRM CHUNK(99,72,82,77)
+#define CHUNK_gAMA CHUNK(103,65,77,65)
+#define CHUNK_sBIT CHUNK(115,66,73,84)
+#define CHUNK_sRGB CHUNK(115,82,71,66)
+
+/* The guts of modification are performed during a read. */
+static void
+modifier_crc(png_bytep buffer)
+{
+ /* Recalculate the chunk CRC - a complete chunk must be in
+ * the buffer, at the start.
+ */
+ uInt datalen = png_get_uint_32(buffer);
+ png_save_uint_32(buffer+datalen+8, crc32(0L, buffer+4, datalen+4));
+}
+
+static void
+modifier_setbuffer(png_modifier *pm)
+{
+ modifier_crc(pm->buffer);
+ pm->buffer_count = png_get_uint_32(pm->buffer)+12;
+ pm->buffer_position = 0;
+}
+
+/* Separate the callback into the actual implementation (which is passed the
+ * png_modifier explicitly) and the callback, which gets the modifier from the
+ * png_struct.
+ */
+static void
+modifier_read_imp(png_modifier *pm, png_bytep pb, png_size_t st)
+{
+ while (st > 0)
+ {
+ size_t cb;
+ png_uint_32 len, chunk;
+ png_modification *mod;
+
+ if (pm->buffer_position >= pm->buffer_count) switch (pm->state)
+ {
+ static png_byte sign[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
+ case modifier_start:
+ store_read_imp(&pm->this, pm->buffer, 8); /* size of signature. */
+ pm->buffer_count = 8;
+ pm->buffer_position = 0;
+
+ if (memcmp(pm->buffer, sign, 8) != 0)
+ png_error(pm->this.pread, "invalid PNG file signature");
+ pm->state = modifier_signature;
+ break;
+
+ case modifier_signature:
+ store_read_imp(&pm->this, pm->buffer, 13+12); /* size of IHDR */
+ pm->buffer_count = 13+12;
+ pm->buffer_position = 0;
+
+ if (png_get_uint_32(pm->buffer) != 13 ||
+ png_get_uint_32(pm->buffer+4) != CHUNK_IHDR)
+ png_error(pm->this.pread, "invalid IHDR");
+
+ /* Check the list of modifiers for modifications to the IHDR. */
+ mod = pm->modifications;
+ while (mod != NULL)
+ {
+ if (mod->chunk == CHUNK_IHDR && mod->modify_fn &&
+ (*mod->modify_fn)(pm, mod, 0))
+ {
+ mod->modified = 1;
+ modifier_setbuffer(pm);
+ }
+
+ /* Ignore removal or add if IHDR! */
+ mod = mod->next;
+ }
+
+ /* Cache information from the IHDR (the modified one.) */
+ pm->bit_depth = pm->buffer[8+8];
+ pm->colour_type = pm->buffer[8+8+1];
+
+ pm->state = modifier_IHDR;
+ pm->flush = 0;
+ break;
+
+ case modifier_IHDR:
+ default:
+ /* Read a new chunk and process it until we see PLTE, IDAT or
+ * IEND. 'flush' indicates that there is still some data to
+ * output from the preceding chunk.
+ */
+ if ((cb = pm->flush) > 0)
+ {
+ if (cb > st) cb = st;
+ pm->flush -= cb;
+ store_read_imp(&pm->this, pb, cb);
+ pb += cb;
+ st -= cb;
+ if (st <= 0) return;
+ }
+
+ /* No more bytes to flush, read a header, or handle a pending
+ * chunk.
+ */
+ if (pm->pending_chunk != 0)
+ {
+ png_save_uint_32(pm->buffer, pm->pending_len);
+ png_save_uint_32(pm->buffer+4, pm->pending_chunk);
+ pm->pending_len = 0;
+ pm->pending_chunk = 0;
+ }
+ else
+ store_read_imp(&pm->this, pm->buffer, 8);
+
+ pm->buffer_count = 8;
+ pm->buffer_position = 0;
+
+ /* Check for something to modify or a terminator chunk. */
+ len = png_get_uint_32(pm->buffer);
+ chunk = png_get_uint_32(pm->buffer+4);
+
+ /* Terminators first, they may have to be delayed for added
+ * chunks
+ */
+ if (chunk == CHUNK_PLTE || chunk == CHUNK_IDAT ||
+ chunk == CHUNK_IEND)
+ {
+ mod = pm->modifications;
+
+ while (mod != NULL)
+ {
+ if ((mod->add == chunk ||
+ (mod->add == CHUNK_PLTE && chunk == CHUNK_IDAT)) &&
+ mod->modify_fn != NULL && !mod->modified && !mod->added)
+ {
+ /* Regardless of what the modify function does do not run
+ * this again.
+ */
+ mod->added = 1;
+
+ if ((*mod->modify_fn)(pm, mod, 1 /*add*/))
+ {
+ /* Reset the CRC on a new chunk */
+ if (pm->buffer_count > 0)
+ modifier_setbuffer(pm);
+
+ else
+ {
+ pm->buffer_position = 0;
+ mod->removed = 1;
+ }
+
+ /* The buffer has been filled with something (we assume)
+ * so output this. Pend the current chunk.
+ */
+ pm->pending_len = len;
+ pm->pending_chunk = chunk;
+ break; /* out of while */
+ }
+ }
+
+ mod = mod->next;
+ }
+
+ /* Don't do any further processing if the buffer was modified -
+ * otherwise the code will end up modifying a chunk that was
+ * just added.
+ */
+ if (mod != NULL)
+ break; /* out of switch */
+ }
+
+ /* If we get to here then this chunk may need to be modified. To
+ * do this it must be less than 1024 bytes in total size, otherwise
+ * it just gets flushed.
+ */
+ if (len+12 <= sizeof pm->buffer)
+ {
+ store_read_imp(&pm->this, pm->buffer+pm->buffer_count,
+ len+12-pm->buffer_count);
+ pm->buffer_count = len+12;
+
+ /* Check for a modification, else leave it be. */
+ mod = pm->modifications;
+ while (mod != NULL)
+ {
+ if (mod->chunk == chunk)
+ {
+ if (mod->modify_fn == NULL)
+ {
+ /* Remove this chunk */
+ pm->buffer_count = pm->buffer_position = 0;
+ mod->removed = 1;
+ break; /* Terminate the while loop */
+ }
+
+ else if ((*mod->modify_fn)(pm, mod, 0))
+ {
+ mod->modified = 1;
+ /* The chunk may have been removed: */
+ if (pm->buffer_count == 0)
+ {
+ pm->buffer_position = 0;
+ break;
+ }
+ modifier_setbuffer(pm);
+ }
+ }
+
+ mod = mod->next;
+ }
+ }
+
+ else
+ pm->flush = len+12 - pm->buffer_count; /* data + crc */
+
+ /* Take the data from the buffer (if there is any). */
+ break;
+ }
+
+ /* Here to read from the modifier buffer (not directly from
+ * the store, as in the flush case above.)
+ */
+ cb = pm->buffer_count - pm->buffer_position;
+
+ if (cb > st)
+ cb = st;
+
+ memcpy(pb, pm->buffer + pm->buffer_position, cb);
+ st -= cb;
+ pb += cb;
+ pm->buffer_position += cb;
+ }
+}
+
+/* The callback: */
+static void
+modifier_read(png_structp pp, png_bytep pb, png_size_t st)
+{
+ png_modifier *pm = png_get_io_ptr(pp);
+
+ if (pm == NULL || pm->this.pread != pp)
+ png_error(pp, "bad modifier_read call");
+
+ modifier_read_imp(pm, pb, st);
+}
+
+/* Like store_progressive_read but the data is getting changed as we go so we
+ * need a local buffer.
+ */
+static void
+modifier_progressive_read(png_modifier *pm, png_structp pp, png_infop pi)
+{
+ if (pm->this.pread != pp || pm->this.current == NULL ||
+ pm->this.next == NULL)
+ png_error(pp, "store state damaged (progressive)");
+
+ /* This is another Horowitz and Hill random noise generator. In this case
+ * the aim is to stress the progressive reader with truely horrible variable
+ * buffer sizes in the range 1..500, so a sequence of 9 bit random numbers
+ * is generated. We could probably just count from 1 to 32767 and get as
+ * good a result.
+ */
+ for (;;)
+ {
+ static png_uint_32 noise = 1;
+ png_size_t cb, cbAvail;
+ png_byte buffer[512];
+
+ /* Generate 15 more bits of stuff: */
+ noise = (noise << 9) | ((noise ^ (noise >> (9-5))) & 0x1ff);
+ cb = noise & 0x1ff;
+
+ /* Check that this number of bytes are available (in the current buffer.)
+ * (This doesn't quite work - the modifier might delete a chunk; unlikely
+ * but possible, it doesn't happen at present because the modifier only
+ * adds chunks to standard images.)
+ */
+ cbAvail = store_read_buffer_avail(&pm->this);
+ if (pm->buffer_count > pm->buffer_position)
+ cbAvail += pm->buffer_count - pm->buffer_position;
+
+ if (cb > cbAvail)
+ {
+ /* Check for EOF: */
+ if (cbAvail == 0)
+ break;
+
+ cb = cbAvail;
+ }
+
+ modifier_read_imp(pm, buffer, cb);
+ png_process_data(pp, pi, buffer, cb);
+ }
+
+ /* Check the invariants at the end (if this fails it's a problem in this
+ * file!)
+ */
+ if (pm->buffer_count > pm->buffer_position ||
+ pm->this.next != &pm->this.current->data ||
+ pm->this.readpos < pm->this.current->datacount)
+ png_error(pp, "progressive read implementation error");
+}
+
+/* Set up a modifier. */
+static png_structp
+set_modifier_for_read(png_modifier *pm, png_infopp ppi, png_uint_32 id,
+ PNG_CONST char *name)
+{
+ /* Do this first so that the modifier fields are cleared even if an error
+ * happens allocating the png_struct. No allocation is done here so no
+ * cleanup is required.
+ */
+ pm->state = modifier_start;
+ pm->bit_depth = 0;
+ pm->colour_type = 255;
+
+ pm->pending_len = 0;
+ pm->pending_chunk = 0;
+ pm->flush = 0;
+ pm->buffer_count = 0;
+ pm->buffer_position = 0;
+
+ return set_store_for_read(&pm->this, ppi, id, name);
+}
+
+/***************************** STANDARD PNG FILES *****************************/
+/* Standard files - write and save standard files. */
+/* There are two basic forms of standard images. Those which attempt to have
+ * all the possible pixel values (not possible for 16bpp images, but a range of
+ * values are produced) and those which have a range of image sizes. The former
+ * are used for testing transforms, in particular gamma correction and bit
+ * reduction and increase. The latter are reserved for testing the behavior of
+ * libpng with respect to 'odd' image sizes - particularly small images where
+ * rows become 1 byte and interlace passes disappear.
+ *
+ * The first, most useful, set are the 'transform' images, the second set of
+ * small images are the 'size' images.
+ *
+ * The transform files are constructed with rows which fit into a 1024 byte row
+ * buffer. This makes allocation easier below. Further regardless of the file
+ * format every row has 128 pixels (giving 1024 bytes for 64bpp formats).
+ *
+ * Files are stored with no gAMA or sBIT chunks, with a PLTE only when needed
+ * and with an ID derived from the colour type, bit depth and interlace type
+ * as above (FILEID). The width (128) and height (variable) are not stored in
+ * the FILEID - instead the fields are set to 0, indicating a transform file.
+ *
+ * The size files ar constructed with rows a maximum of 128 bytes wide, allowing
+ * a maximum width of 16 pixels (for the 64bpp case.) They also have a maximum
+ * height of 16 rows. The width and height are stored in the FILEID and, being
+ * non-zero, indicate a size file.
+ */
+
+/* The number of passes is related to the interlace type. There wass no libpng
+ * API to determine this prior to 1.5, so we need an inquiry function:
+ */
+static int
+npasses_from_interlace_type(png_structp pp, int interlace_type)
+{
+ switch (interlace_type)
+ {
+ default:
+ png_error(pp, "invalid interlace type");
+
+ case PNG_INTERLACE_NONE:
+ return 1;
+
+ case PNG_INTERLACE_ADAM7:
+ return PNG_INTERLACE_ADAM7_PASSES;
+ }
+}
+
+static unsigned int
+bit_size(png_structp pp, png_byte colour_type, png_byte bit_depth)
+{
+ switch (colour_type)
+ {
+ case 0: return bit_depth;
+
+ case 2: return 3*bit_depth;
+
+ case 3: return bit_depth;
+
+ case 4: return 2*bit_depth;
+
+ case 6: return 4*bit_depth;
+
+ default: png_error(pp, "invalid color type");
+ }
+}
+
+#define TRANSFORM_WIDTH 128U
+#define TRANSFORM_ROWMAX (TRANSFORM_WIDTH*8U)
+#define SIZE_ROWMAX (16*8U) /* 16 pixels, max 8 bytes each - 128 bytes */
+#define STANDARD_ROWMAX TRANSFORM_ROWMAX /* The larger of the two */
+
+/* So the maximum image sizes are as follows. A 'transform' image may require
+ * more than 65535 bytes. The size images are a maximum of 2046 bytes.
+ */
+#define TRANSFORM_IMAGEMAX (TRANSFORM_ROWMAX * (png_uint_32)2048)
+#define SIZE_IMAGEMAX (SIZE_ROWMAX * 16U)
+
+static size_t
+transform_rowsize(png_structp pp, png_byte colour_type, png_byte bit_depth)
+{
+ return (TRANSFORM_WIDTH * bit_size(pp, colour_type, bit_depth)) / 8;
+}
+
+/* transform_width(pp, colour_type, bit_depth) current returns the same number
+ * every time, so just use a macro:
+ */
+#define transform_width(pp, colour_type, bit_depth) TRANSFORM_WIDTH
+
+static png_uint_32
+transform_height(png_structp pp, png_byte colour_type, png_byte bit_depth)
+{
+ switch (bit_size(pp, colour_type, bit_depth))
+ {
+ case 1:
+ case 2:
+ case 4:
+ return 1; /* Total of 128 pixels */
+
+ case 8:
+ return 2; /* Total of 256 pixels/bytes */
+
+ case 16:
+ return 512; /* Total of 65536 pixels */
+
+ case 24:
+ case 32:
+ return 512; /* 65536 pixels */
+
+ case 48:
+ case 64:
+ return 2048;/* 4 x 65536 pixels. */
+
+ default:
+ return 0; /* Error, will be caught later */
+ }
+}
+
+/* The following can only be defined here, now we have the definitions
+ * of the transform image sizes.
+ */
+static png_uint_32
+standard_width(png_structp pp, png_uint_32 id)
+{
+ png_uint_32 width = WIDTH_FROM_ID(id);
+ UNUSED(pp)
+
+ if (width == 0)
+ width = transform_width(pp, COL_FROM_ID(id), DEPTH_FROM_ID(id));
+
+ return width;
+}
+
+static png_uint_32
+standard_height(png_structp pp, png_uint_32 id)
+{
+ png_uint_32 height = HEIGHT_FROM_ID(id);
+
+ if (height == 0)
+ height = transform_height(pp, COL_FROM_ID(id), DEPTH_FROM_ID(id));
+
+ return height;
+}
+
+static png_uint_32
+standard_rowsize(png_structp pp, png_uint_32 id)
+{
+ png_uint_32 width = standard_width(pp, id);
+
+ /* This won't overflow: */
+ width *= bit_size(pp, COL_FROM_ID(id), DEPTH_FROM_ID(id));
+ return (width + 7) / 8;
+}
+
+static void
+transform_row(png_structp pp, png_byte buffer[TRANSFORM_ROWMAX],
+ png_byte colour_type, png_byte bit_depth, png_uint_32 y)
+{
+ png_uint_32 v = y << 7;
+ png_uint_32 i = 0;
+
+ switch (bit_size(pp, colour_type, bit_depth))
+ {
+ case 1:
+ while (i<128/8) buffer[i] = v & 0xff, v += 17, ++i;
+ return;
+
+ case 2:
+ while (i<128/4) buffer[i] = v & 0xff, v += 33, ++i;
+ return;
+
+ case 4:
+ while (i<128/2) buffer[i] = v & 0xff, v += 65, ++i;
+ return;
+
+ case 8:
+ /* 256 bytes total, 128 bytes in each row set as follows: */
+ while (i<128) buffer[i] = v & 0xff, ++v, ++i;
+ return;
+
+ case 16:
+ /* Generate all 65536 pixel values in order, which includes the 8 bit
+ * GA case as well as the 16 bit G case.
+ */
+ while (i<128)
+ buffer[2*i] = (v>>8) & 0xff, buffer[2*i+1] = v & 0xff, ++v, ++i;
+
+ return;
+
+ case 24:
+ /* 65535 pixels, but rotate the values. */
+ while (i<128)
+ {
+ /* Three bytes per pixel, r, g, b, make b by r^g */
+ buffer[3*i+0] = (v >> 8) & 0xff;
+ buffer[3*i+1] = v & 0xff;
+ buffer[3*i+2] = ((v >> 8) ^ v) & 0xff;
+ ++v;
+ ++i;
+ }
+
+ return;
+
+ case 32:
+ /* 65535 pixels, r, g, b, a; just replicate */
+ while (i<128)
+ {
+ buffer[4*i+0] = (v >> 8) & 0xff;
+ buffer[4*i+1] = v & 0xff;
+ buffer[4*i+2] = (v >> 8) & 0xff;
+ buffer[4*i+3] = v & 0xff;
+ ++v;
+ ++i;
+ }
+
+ return;
+
+ case 48:
+ /* y is maximum 2047, giving 4x65536 pixels, make 'r' increase by 1 at
+ * each pixel, g increase by 257 (0x101) and 'b' by 0x1111:
+ */
+ while (i<128)
+ {
+ png_uint_32 t = v++;
+ buffer[6*i+0] = (t >> 8) & 0xff;
+ buffer[6*i+1] = t & 0xff;
+ t *= 257;
+ buffer[6*i+2] = (t >> 8) & 0xff;
+ buffer[6*i+3] = t & 0xff;
+ t *= 17;
+ buffer[6*i+4] = (t >> 8) & 0xff;
+ buffer[6*i+5] = t & 0xff;
+ ++i;
+ }
+
+ return;
+
+ case 64:
+ /* As above in the 32 bit case. */
+ while (i<128)
+ {
+ png_uint_32 t = v++;
+ buffer[8*i+0] = (t >> 8) & 0xff;
+ buffer[8*i+1] = t & 0xff;
+ buffer[8*i+4] = (t >> 8) & 0xff;
+ buffer[8*i+5] = t & 0xff;
+ t *= 257;
+ buffer[8*i+2] = (t >> 8) & 0xff;
+ buffer[8*i+3] = t & 0xff;
+ buffer[8*i+6] = (t >> 8) & 0xff;
+ buffer[8*i+7] = t & 0xff;
+ ++i;
+ }
+ return;
+
+ default:
+ break;
+ }
+
+ png_error(pp, "internal error");
+}
+
+/* This is just to do the right cast - could be changed to a function to check
+ * 'bd' but there isn't much point.
+ */
+#define DEPTH(bd) ((png_byte)(1U << (bd)))
+
+/* Make a standardized image given a an image colour type, bit depth and
+ * interlace type. The standard images have a very restricted range of
+ * rows and heights and are used for testing transforms rather than image
+ * layout details. See make_size_images below for a way to make images
+ * that test odd sizes along with the libpng interlace handling.
+ */
+static void
+make_transform_image(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type,
+ png_byte PNG_CONST bit_depth, int interlace_type, png_const_charp name)
+{
+ context(ps, fault);
+
+ Try
+ {
+ png_infop pi;
+ png_structp pp = set_store_for_write(ps, &pi, name);
+ png_uint_32 h;
+
+ /* In the event of a problem return control to the Catch statement below
+ * to do the clean up - it is not possible to 'return' directly from a Try
+ * block.
+ */
+ if (pp == NULL)
+ Throw ps;
+
+ h = transform_height(pp, colour_type, bit_depth);
+
+ png_set_IHDR(pp, pi, transform_width(pp, colour_type, bit_depth), h,
+ bit_depth, colour_type, interlace_type,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (colour_type == 3) /* palette */
+ {
+ unsigned int i = 0;
+ png_color pal[256];
+
+ do
+ pal[i].red = pal[i].green = pal[i].blue = (png_byte)i;
+ while(++i < 256U);
+
+ png_set_PLTE(pp, pi, pal, 256);
+ }
+
+ png_write_info(pp, pi);
+
+ if (png_get_rowbytes(pp, pi) !=
+ transform_rowsize(pp, colour_type, bit_depth))
+ png_error(pp, "row size incorrect");
+
+ else
+ {
+ /* Somewhat confusingly this must be called *after* png_write_info
+ * because if it is called before, the information in *pp has not been
+ * updated to reflect the interlaced image.
+ */
+ int npasses = png_set_interlace_handling(pp);
+ int pass;
+
+ if (npasses != npasses_from_interlace_type(pp, interlace_type))
+ png_error(pp, "write: png_set_interlace_handling failed");
+
+ for (pass=0; pass<npasses; ++pass)
+ {
+ png_uint_32 y;
+
+ for (y=0; y<h; ++y)
+ {
+ png_byte buffer[TRANSFORM_ROWMAX];
+
+ transform_row(pp, buffer, colour_type, bit_depth, y);
+ png_write_row(pp, buffer);
+ }
+ }
+ }
+
+ png_write_end(pp, pi);
+
+ /* And store this under the appropriate id, then clean up. */
+ store_storefile(ps, FILEID(colour_type, bit_depth, interlace_type,
+ 0, 0, 0));
+
+ store_write_reset(ps);
+ }
+
+ Catch(fault)
+ {
+ /* Use the png_store returned by the exception. This may help the compiler
+ * because 'ps' is not used in this branch of the setjmp. Note that fault
+ * and ps will always be the same value.
+ */
+ store_write_reset(fault);
+ }
+}
+
+static void
+make_standard(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type, int bdlo,
+ int PNG_CONST bdhi)
+{
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ int interlace_type;
+
+ for (interlace_type = PNG_INTERLACE_NONE;
+ interlace_type < PNG_INTERLACE_LAST; ++interlace_type)
+ {
+ char name[FILE_NAME_SIZE];
+
+ standard_name(name, sizeof name, 0, colour_type, bdlo, interlace_type,
+ 0, 0, 0);
+ make_transform_image(ps, colour_type, DEPTH(bdlo), interlace_type,
+ name);
+ }
+ }
+}
+
+static void
+make_transform_images(png_store *ps)
+{
+ /* This is in case of errors. */
+ safecat(ps->test, sizeof ps->test, 0, "make standard images");
+
+ /* Arguments are colour_type, low bit depth, high bit depth
+ */
+ make_standard(ps, 0, 0, WRITE_BDHI);
+ make_standard(ps, 2, 3, WRITE_BDHI);
+ make_standard(ps, 3, 0, 3 /*palette: max 8 bits*/);
+ make_standard(ps, 4, 3, WRITE_BDHI);
+ make_standard(ps, 6, 3, WRITE_BDHI);
+}
+
+/* The following two routines use the PNG interlace support macros from
+ * png.h to interlace or deinterlace rows.
+ */
+static void
+interlace_row(png_bytep buffer, png_const_bytep imageRow,
+ unsigned int pixel_size, png_uint_32 w, int pass)
+{
+ png_uint_32 xin, xout, xstep;
+
+ /* Note that this can, trivially, be optimized to a memcpy on pass 7, the
+ * code is presented this way to make it easier to understand. In practice
+ * consult the code in the libpng source to see other ways of doing this.
+ */
+ xin = PNG_PASS_START_COL(pass);
+ xstep = 1U<<PNG_PASS_COL_SHIFT(pass);
+
+ for (xout=0; xin<w; xin+=xstep)
+ {
+ pixel_copy(buffer, xout, imageRow, xin, pixel_size);
+ ++xout;
+ }
+}
+
+static void
+deinterlace_row(png_bytep buffer, png_const_bytep row,
+ unsigned int pixel_size, png_uint_32 w, int pass)
+{
+ /* The inverse of the above, 'row' is part of row 'y' of the output image,
+ * in 'buffer'. The image is 'w' wide and this is pass 'pass', distribute
+ * the pixels of row into buffer and return the number written (to allow
+ * this to be checked).
+ */
+ png_uint_32 xin, xout, xstep;
+
+ xout = PNG_PASS_START_COL(pass);
+ xstep = 1U<<PNG_PASS_COL_SHIFT(pass);
+
+ for (xin=0; xout<w; xout+=xstep)
+ {
+ pixel_copy(buffer, xout, row, xin, pixel_size);
+ ++xin;
+ }
+}
+
+/* Build a single row for the 'size' test images, this fills in only the
+ * first bit_width bits of the sample row.
+ */
+static void
+size_row(png_byte buffer[SIZE_ROWMAX], png_uint_32 bit_width, png_uint_32 y)
+{
+ /* height is in the range 1 to 16, so: */
+ y = ((y & 1) << 7) + ((y & 2) << 6) + ((y & 4) << 5) + ((y & 8) << 4);
+ /* the following ensures bits are set in small images: */
+ y ^= 0xA5;
+
+ while (bit_width >= 8)
+ *buffer++ = (png_byte)y++, bit_width -= 8;
+
+ /* There may be up to 7 remaining bits, these go in the most significant
+ * bits of the byte.
+ */
+ if (bit_width > 0)
+ {
+ png_uint_32 mask = (1U<<(8-bit_width))-1;
+ *buffer = (png_byte)((*buffer & mask) | (y & ~mask));
+ }
+}
+
+static void
+make_size_image(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type,
+ png_byte PNG_CONST bit_depth, int PNG_CONST interlace_type,
+ png_uint_32 PNG_CONST w, png_uint_32 PNG_CONST h,
+ int PNG_CONST do_interlace)
+{
+ context(ps, fault);
+
+ Try
+ {
+ png_infop pi;
+ png_structp pp;
+ unsigned int pixel_size;
+
+ /* Make a name and get an appropriate id for the store: */
+ char name[FILE_NAME_SIZE];
+ PNG_CONST png_uint_32 id = FILEID(colour_type, bit_depth, interlace_type,
+ w, h, do_interlace);
+
+ standard_name_from_id(name, sizeof name, 0, id);
+ pp = set_store_for_write(ps, &pi, name);
+
+ /* In the event of a problem return control to the Catch statement below
+ * to do the clean up - it is not possible to 'return' directly from a Try
+ * block.
+ */
+ if (pp == NULL)
+ Throw ps;
+
+ png_set_IHDR(pp, pi, w, h, bit_depth, colour_type, interlace_type,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ /* Same palette as make_transform_image - I don' think there is any
+ * benefit from using a different one (JB 20101211)
+ */
+ if (colour_type == 3) /* palette */
+ {
+ unsigned int i = 0;
+ png_color pal[256];
+
+ do
+ pal[i].red = pal[i].green = pal[i].blue = (png_byte)i;
+ while(++i < 256U);
+
+ png_set_PLTE(pp, pi, pal, 256);
+ }
+
+ png_write_info(pp, pi);
+
+ /* Calculate the bit size, divide by 8 to get the byte size - this won't
+ * overflow because we know the w values are all small enough even for
+ * a system where 'unsigned int' is only 16 bits.
+ */
+ pixel_size = bit_size(pp, colour_type, bit_depth);
+ if (png_get_rowbytes(pp, pi) != ((w * pixel_size) + 7) / 8)
+ png_error(pp, "row size incorrect");
+
+ else
+ {
+ int npasses = npasses_from_interlace_type(pp, interlace_type);
+ png_uint_32 y;
+ int pass;
+ png_byte image[16][SIZE_ROWMAX];
+
+ /* To help consistent error detection make the parts of this buffer
+ * that aren't set below all '1':
+ */
+ memset(image, 0xff, sizeof image);
+
+ if (!do_interlace && npasses != png_set_interlace_handling(pp))
+ png_error(pp, "write: png_set_interlace_handling failed");
+
+ /* Prepare the whole image first to avoid making it 7 times: */
+ for (y=0; y<h; ++y)
+ size_row(image[y], w * pixel_size, y);
+
+ for (pass=0; pass<npasses; ++pass)
+ {
+ /* The following two are for checking the macros: */
+ PNG_CONST png_uint_32 wPass = PNG_PASS_COLS(w, pass);
+
+ /* If do_interlace is set we don't call png_write_row for every
+ * row because some of them are empty. In fact, for a 1x1 image,
+ * most of them are empty!
+ */
+ for (y=0; y<h; ++y)
+ {
+ png_const_bytep row = image[y];
+ png_byte tempRow[SIZE_ROWMAX];
+
+ /* If do_interlace *and* the image is interlaced we
+ * need a reduced interlace row, this may be reduced
+ * to empty.
+ */
+ if (do_interlace && interlace_type == PNG_INTERLACE_ADAM7)
+ {
+ /* The row must not be written if it doesn't exist, notice
+ * that there are two conditions here, either the row isn't
+ * ever in the pass or the row would be but isn't wide
+ * enough to contribute any pixels. In fact the wPass test
+ * can be used to skip the whole y loop in this case.
+ */
+ if (PNG_ROW_IN_INTERLACE_PASS(y, pass) && wPass > 0)
+ {
+ /* Set to all 1's for error detection (libpng tends to
+ * set unset things to 0).
+ */
+ memset(tempRow, 0xff, sizeof tempRow);
+ interlace_row(tempRow, row, pixel_size, w, pass);
+ row = tempRow;
+ }
+ else
+ continue;
+ }
+
+ /* Only get to here if the row has some pixels in it. */
+ png_write_row(pp, row);
+ }
+ }
+ }
+
+ png_write_end(pp, pi);
+
+ /* And store this under the appropriate id, then clean up. */
+ store_storefile(ps, id);
+
+ store_write_reset(ps);
+ }
+
+ Catch(fault)
+ {
+ /* Use the png_store returned by the exception. This may help the compiler
+ * because 'ps' is not used in this branch of the setjmp. Note that fault
+ * and ps will always be the same value.
+ */
+ store_write_reset(fault);
+ }
+}
+
+static void
+make_size(png_store* PNG_CONST ps, png_byte PNG_CONST colour_type, int bdlo,
+ int PNG_CONST bdhi)
+{
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ png_uint_32 width;
+
+ for (width = 1; width <= 16; ++width)
+ {
+ png_uint_32 height;
+
+ for (height = 1; height <= 16; ++height)
+ {
+ /* The four combinations of DIY interlace and interlace or not -
+ * no interlace + DIY should be identical to no interlace with
+ * libpng doing it.
+ */
+ make_size_image(ps, colour_type, DEPTH(bdlo), PNG_INTERLACE_NONE,
+ width, height, 0);
+ make_size_image(ps, colour_type, DEPTH(bdlo), PNG_INTERLACE_NONE,
+ width, height, 1);
+ make_size_image(ps, colour_type, DEPTH(bdlo), PNG_INTERLACE_ADAM7,
+ width, height, 0);
+ make_size_image(ps, colour_type, DEPTH(bdlo), PNG_INTERLACE_ADAM7,
+ width, height, 1);
+ }
+ }
+ }
+}
+
+static void
+make_size_images(png_store *ps)
+{
+ /* This is in case of errors. */
+ safecat(ps->test, sizeof ps->test, 0, "make size images");
+
+ /* Arguments are colour_type, low bit depth, high bit depth
+ */
+ make_size(ps, 0, 0, WRITE_BDHI);
+ make_size(ps, 2, 3, WRITE_BDHI);
+ make_size(ps, 3, 0, 3 /*palette: max 8 bits*/);
+ make_size(ps, 4, 3, WRITE_BDHI);
+ make_size(ps, 6, 3, WRITE_BDHI);
+}
+
+/* Return a row based on image id and 'y' for checking: */
+static void
+standard_row(png_structp pp, png_byte std[STANDARD_ROWMAX], png_uint_32 id,
+ png_uint_32 y)
+{
+ if (WIDTH_FROM_ID(id) == 0)
+ transform_row(pp, std, COL_FROM_ID(id), DEPTH_FROM_ID(id), y);
+ else
+ size_row(std, WIDTH_FROM_ID(id) * bit_size(pp, COL_FROM_ID(id),
+ DEPTH_FROM_ID(id)), y);
+}
+
+/* Tests - individual test cases */
+/* Like 'make_standard' but errors are deliberately introduced into the calls
+ * to ensure that they get detected - it should not be possible to write an
+ * invalid image with libpng!
+ */
+static void
+sBIT0_error_fn(png_structp pp, png_infop pi)
+{
+ /* 0 is invalid... */
+ png_color_8 bad;
+ bad.red = bad.green = bad.blue = bad.gray = bad.alpha = 0;
+ png_set_sBIT(pp, pi, &bad);
+}
+
+static void
+sBIT_error_fn(png_structp pp, png_infop pi)
+{
+ png_byte bit_depth;
+ png_color_8 bad;
+
+ if (png_get_color_type(pp, pi) == PNG_COLOR_TYPE_PALETTE)
+ bit_depth = 8;
+
+ else
+ bit_depth = png_get_bit_depth(pp, pi);
+
+ /* Now we know the bit depth we can easily generate an invalid sBIT entry */
+ bad.red = bad.green = bad.blue = bad.gray = bad.alpha =
+ (png_byte)(bit_depth+1);
+ png_set_sBIT(pp, pi, &bad);
+}
+
+static PNG_CONST struct
+{
+ void (*fn)(png_structp, png_infop);
+ PNG_CONST char *msg;
+ unsigned int warning :1; /* the error is a warning... */
+} error_test[] =
+ {
+ { sBIT0_error_fn, "sBIT(0): failed to detect error", 1 },
+ { sBIT_error_fn, "sBIT(too big): failed to detect error", 1 },
+ };
+
+static void
+make_error(png_store* volatile ps, png_byte PNG_CONST colour_type,
+ png_byte bit_depth, int interlace_type, int test, png_const_charp name)
+{
+ context(ps, fault);
+
+ Try
+ {
+ png_structp pp;
+ png_infop pi;
+
+ pp = set_store_for_write(ps, &pi, name);
+
+ if (pp == NULL)
+ Throw ps;
+
+ png_set_IHDR(pp, pi, transform_width(pp, colour_type, bit_depth),
+ transform_height(pp, colour_type, bit_depth), bit_depth, colour_type,
+ interlace_type, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ if (colour_type == 3) /* palette */
+ {
+ unsigned int i = 0;
+ png_color pal[256];
+
+ do
+ pal[i].red = pal[i].green = pal[i].blue = (png_byte)i;
+ while(++i < 256U);
+
+ png_set_PLTE(pp, pi, pal, 256);
+ }
+
+ /* Time for a few errors, these are in various optional chunks, the
+ * standard tests test the standard chunks pretty well.
+ */
+# define exception__prev exception_prev_1
+# define exception__env exception_env_1
+ Try
+ {
+ /* Expect this to throw: */
+ ps->expect_error = !error_test[test].warning;
+ ps->expect_warning = error_test[test].warning;
+ ps->saw_warning = 0;
+ error_test[test].fn(pp, pi);
+
+ /* Normally the error is only detected here: */
+ png_write_info(pp, pi);
+
+ /* And handle the case where it was only a warning: */
+ if (ps->expect_warning && ps->saw_warning)
+ Throw ps;
+
+ /* If we get here there is a problem, we have success - no error or
+ * no warning - when we shouldn't have success. Log an error.
+ */
+ store_log(ps, pp, error_test[test].msg, 1 /*error*/);
+ }
+
+ Catch (fault)
+ ps = fault; /* expected exit, make sure ps is not clobbered */
+#undef exception__prev
+#undef exception__env
+
+ /* And clear these flags */
+ ps->expect_error = 0;
+ ps->expect_warning = 0;
+
+ /* Now write the whole image, just to make sure that the detected, or
+ * undetected, errro has not created problems inside libpng.
+ */
+ if (png_get_rowbytes(pp, pi) !=
+ transform_rowsize(pp, colour_type, bit_depth))
+ png_error(pp, "row size incorrect");
+
+ else
+ {
+ png_uint_32 h = transform_height(pp, colour_type, bit_depth);
+ int npasses = png_set_interlace_handling(pp);
+ int pass;
+
+ if (npasses != npasses_from_interlace_type(pp, interlace_type))
+ png_error(pp, "write: png_set_interlace_handling failed");
+
+ for (pass=0; pass<npasses; ++pass)
+ {
+ png_uint_32 y;
+
+ for (y=0; y<h; ++y)
+ {
+ png_byte buffer[TRANSFORM_ROWMAX];
+
+ transform_row(pp, buffer, colour_type, bit_depth, y);
+ png_write_row(pp, buffer);
+ }
+ }
+ }
+
+ png_write_end(pp, pi);
+
+ /* The following deletes the file that was just written. */
+ store_write_reset(ps);
+ }
+
+ Catch(fault)
+ {
+ store_write_reset(fault);
+ }
+}
+
+static int
+make_errors(png_modifier* PNG_CONST pm, png_byte PNG_CONST colour_type,
+ int bdlo, int PNG_CONST bdhi)
+{
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ int interlace_type;
+
+ for (interlace_type = PNG_INTERLACE_NONE;
+ interlace_type < PNG_INTERLACE_LAST; ++interlace_type)
+ {
+ unsigned int test;
+ char name[FILE_NAME_SIZE];
+
+ standard_name(name, sizeof name, 0, colour_type, bdlo, interlace_type,
+ 0, 0, 0);
+
+ for (test=0; test<(sizeof error_test)/(sizeof error_test[0]); ++test)
+ {
+ make_error(&pm->this, colour_type, DEPTH(bdlo), interlace_type,
+ test, name);
+
+ if (fail(pm))
+ return 0;
+ }
+ }
+ }
+
+ return 1; /* keep going */
+}
+
+static void
+perform_error_test(png_modifier *pm)
+{
+ /* Need to do this here because we just write in this test. */
+ safecat(pm->this.test, sizeof pm->this.test, 0, "error test");
+
+ if (!make_errors(pm, 0, 0, WRITE_BDHI))
+ return;
+
+ if (!make_errors(pm, 2, 3, WRITE_BDHI))
+ return;
+
+ if (!make_errors(pm, 3, 0, 3))
+ return;
+
+ if (!make_errors(pm, 4, 3, WRITE_BDHI))
+ return;
+
+ if (!make_errors(pm, 6, 3, WRITE_BDHI))
+ return;
+}
+
+/* Because we want to use the same code in both the progressive reader and the
+ * sequential reader it is necessary to deal with the fact that the progressive
+ * reader callbacks only have one parameter (png_get_progressive_ptr()), so this
+ * must contain all the test parameters and all the local variables directly
+ * accessible to the sequential reader implementation.
+ *
+ * The technique adopted is to reinvent part of what Dijkstra termed a
+ * 'display'; an array of pointers to the stack frames of enclosing functions so
+ * that a nested function definition can access the local (C auto) variables of
+ * the functions that contain its definition. In fact C provides the first
+ * pointer (the local variables - the stack frame pointer) and the last (the
+ * global variables - the BCPL global vector typically implemented as global
+ * addresses), this code requires one more pointer to make the display - the
+ * local variables (and function call parameters) of the function that actually
+ * invokes either the progressive or sequential reader.
+ *
+ * Perhaps confusingly this technique is confounded with classes - the
+ * 'standard_display' defined here is sub-classed as the 'gamma_display' below.
+ * A gamma_display is a standard_display, taking advantage of the ANSI-C
+ * requirement that the pointer to the first member of a structure must be the
+ * same as the pointer to the structure. This allows us to reuse standard_
+ * functions in the gamma test code; something that could not be done with
+ * nested funtions!
+ */
+typedef struct standard_palette_entry /* pngvalid format palette! */
+{
+ png_byte red;
+ png_byte green;
+ png_byte blue;
+ png_byte alpha;
+} standard_palette[256];
+
+typedef struct standard_display
+{
+ png_store* ps; /* Test parameters (passed to the function) */
+ png_byte colour_type;
+ png_byte bit_depth;
+ png_byte red_sBIT; /* Input data sBIT values. */
+ png_byte green_sBIT;
+ png_byte blue_sBIT;
+ png_byte alpha_sBIT;
+ int interlace_type;
+ png_uint_32 id; /* Calculated file ID */
+ png_uint_32 w; /* Width of image */
+ png_uint_32 h; /* Height of image */
+ int npasses; /* Number of interlaced passes */
+ png_uint_32 pixel_size; /* Width of one pixel in bits */
+ png_uint_32 bit_width; /* Width of output row in bits */
+ size_t cbRow; /* Bytes in a row of the output image */
+ int do_interlace; /* Do interlacing internally */
+ int is_transparent; /* Transparecy information was present. */
+ struct
+ {
+ png_uint_16 red;
+ png_uint_16 green;
+ png_uint_16 blue;
+ } transparent; /* The transparent color, if set. */
+ standard_palette
+ palette;
+} standard_display;
+
+static void
+standard_display_init(standard_display *dp, png_store* ps, png_uint_32 id,
+ int do_interlace)
+{
+ dp->ps = ps;
+ dp->colour_type = COL_FROM_ID(id);
+ dp->bit_depth = DEPTH_FROM_ID(id);
+ dp->alpha_sBIT = dp->blue_sBIT = dp->green_sBIT = dp->alpha_sBIT =
+ dp->bit_depth;
+ dp->interlace_type = INTERLACE_FROM_ID(id);
+ dp->id = id;
+ /* All the rest are filled in after the read_info: */
+ dp->w = 0;
+ dp->h = 0;
+ dp->npasses = 0;
+ dp->pixel_size = 0;
+ dp->bit_width = 0;
+ dp->cbRow = 0;
+ dp->do_interlace = do_interlace;
+ dp->is_transparent = 0;
+ /* Preset the transparent color to black: */
+ memset(&dp->transparent, 0, sizeof dp->transparent);
+ /* Preset the palette to full intensity/opaque througout: */
+ memset(dp->palette, 0xff, sizeof dp->palette);
+
+}
+
+/* Call this only if the colour type is 3 - PNG_COLOR_TYPE_PALETTE - otherwise
+ * it will png_error out. The API returns true if tRNS information was
+ * present.
+ */
+static int
+standard_palette_init(standard_palette palette, png_structp pp, png_infop pi)
+{
+ png_colorp pal;
+ png_bytep trans_alpha;
+ int num;
+
+ pal = 0;
+ num = -1;
+ if (png_get_PLTE(pp, pi, &pal, &num) & PNG_INFO_PLTE)
+ {
+ int i;
+
+ for (i=0; i<num; ++i)
+ {
+ palette[i].red = pal[i].red;
+ palette[i].green = pal[i].green;
+ palette[i].blue = pal[i].blue;
+ }
+
+ /* Mark the remainder of the entries with a flag value: */
+ for (; i<256; ++i)
+ palette[i].red = palette[i].green = palette[i].blue = 126;
+ }
+
+ else /* !png_get_PLTE */
+ png_error(pp, "validate: missing PLTE with color type 3");
+
+ trans_alpha = 0;
+ num = -1;
+ if (png_get_tRNS(pp, pi, &trans_alpha, &num, 0) & PNG_INFO_tRNS)
+ {
+ int i;
+
+ /* Any of these are crash-worthy - given the implementation of
+ * png_get_tRNS up to 1.5 an app won't crash if it just checks the
+ * result above and fails to check that the variables it passed have
+ * actually been filled in! Note that if the app were to pass the
+ * last, png_color_16p, variable too it couldn't rely on this.
+ */
+ if (trans_alpha == 0 || num <= 0 || num > 256)
+ png_error(pp, "validate: unexpected png_get_tRNS (palette) result");
+
+ for (i=0; i<num; ++i)
+ palette[i].alpha = trans_alpha[i];
+
+ for (; i<256; ++i)
+ palette[i].alpha = 255;
+
+ return 1; /* transparency */
+ }
+
+ else
+ {
+ /* No transparency - just set the alpha channel to opaque. */
+ int i;
+
+ for (i=0; i<256; ++i)
+ palette[i].alpha = 255;
+
+ return 0; /* no transparency */
+ }
+}
+
+/* By passing a 'standard_display' the progressive callbacks can be used
+ * directly by the sequential code, the functions suffixed "_imp" are the
+ * implementations, the functions without the suffix are the callbacks.
+ *
+ * The code for the info callback is split into two because this callback calls
+ * png_read_update_info or png_start_read_image and what gets called depends on
+ * whether the info needs updating (we want to test both calls in pngvalid.)
+ */
+static void
+standard_info_part1(standard_display *dp, png_structp pp, png_infop pi)
+{
+ if (png_get_bit_depth(pp, pi) != dp->bit_depth)
+ png_error(pp, "validate: bit depth changed");
+
+ if (png_get_color_type(pp, pi) != dp->colour_type)
+ png_error(pp, "validate: color type changed");
+
+ if (png_get_filter_type(pp, pi) != PNG_FILTER_TYPE_BASE)
+ png_error(pp, "validate: filter type changed");
+
+ if (png_get_interlace_type(pp, pi) != dp->interlace_type)
+ png_error(pp, "validate: interlacing changed");
+
+ if (png_get_compression_type(pp, pi) != PNG_COMPRESSION_TYPE_BASE)
+ png_error(pp, "validate: compression type changed");
+
+ dp->w = png_get_image_width(pp, pi);
+
+ if (dp->w != standard_width(pp, dp->id))
+ png_error(pp, "validate: image width changed");
+
+ dp->h = png_get_image_height(pp, pi);
+
+ if (dp->h != standard_height(pp, dp->id))
+ png_error(pp, "validate: image height changed");
+
+ /* Record (but don't check at present) the input sBIT according to the colour
+ * type information.
+ */
+ {
+ png_color_8p sBIT = 0;
+
+ if (png_get_sBIT(pp, pi, &sBIT) & PNG_INFO_sBIT)
+ {
+ int sBIT_invalid = 0;
+
+ if (sBIT == 0)
+ png_error(pp, "validate: unexpected png_get_sBIT result");
+
+ if (dp->colour_type & PNG_COLOR_MASK_COLOR)
+ {
+ if (sBIT->red == 0 || sBIT->red > dp->bit_depth)
+ sBIT_invalid = 1;
+ else
+ dp->red_sBIT = sBIT->red;
+
+ if (sBIT->green == 0 || sBIT->green > dp->bit_depth)
+ sBIT_invalid = 1;
+ else
+ dp->green_sBIT = sBIT->green;
+
+ if (sBIT->blue == 0 || sBIT->blue > dp->bit_depth)
+ sBIT_invalid = 1;
+ else
+ dp->blue_sBIT = sBIT->blue;
+ }
+
+ else /* !COLOR */
+ {
+ if (sBIT->gray == 0 || sBIT->gray > dp->bit_depth)
+ sBIT_invalid = 1;
+ else
+ dp->blue_sBIT = dp->green_sBIT = dp->red_sBIT = sBIT->gray;
+ }
+
+ /* All 8 bits in tRNS for a palette image are significant - see the
+ * spec.
+ */
+ if (dp->colour_type & PNG_COLOR_MASK_ALPHA)
+ {
+ if (sBIT->alpha == 0 || sBIT->alpha > dp->bit_depth)
+ sBIT_invalid = 1;
+ else
+ dp->alpha_sBIT = sBIT->alpha;
+ }
+
+ if (sBIT_invalid)
+ png_error(pp, "validate: sBIT value out of range");
+ }
+ }
+
+ /* Important: this is validating the value *before* any transforms have been
+ * put in place. It doesn't matter for the standard tests, where there are
+ * no transforms, but it does for other tests where rowbytes may change after
+ * png_read_update_info.
+ */
+ if (png_get_rowbytes(pp, pi) != standard_rowsize(pp, dp->id))
+ png_error(pp, "validate: row size changed");
+
+ /* The palette is never read for non-palette images, even though it is valid
+ * - this could be changed.
+ */
+ if (dp->colour_type == 3) /* palette */
+ {
+ int i;
+
+ dp->is_transparent = standard_palette_init(dp->palette, pp, pi);
+
+ /* And validate the result. */
+ for (i=0; i<256; ++i)
+ if (dp->palette[i].red != i || dp->palette[i].green != i ||
+ dp->palette[i].blue != i)
+ png_error(pp, "validate: color type 3 PLTE chunk changed");
+ }
+
+ /* In any case always check for a tranparent color: */
+ {
+ png_color_16p trans_color = 0;
+
+ if (png_get_tRNS(pp, pi, 0, 0, &trans_color) & PNG_INFO_tRNS)
+ {
+ if (trans_color == 0)
+ png_error(pp, "validate: unexpected png_get_tRNS (color) result");
+
+ switch (dp->colour_type)
+ {
+ case 0:
+ dp->transparent.red = dp->transparent.green = dp->transparent.blue =
+ trans_color->gray;
+ dp->is_transparent = 1;
+ break;
+
+ case 2:
+ dp->transparent.red = trans_color->red;
+ dp->transparent.green = trans_color->green;
+ dp->transparent.blue = trans_color->blue;
+ dp->is_transparent = 1;
+ break;
+
+ case 3:
+ /* Not expected because it should result in the array case
+ * above.
+ */
+ png_error(pp, "validate: unexpected png_get_tRNS result");
+ break;
+
+ default:
+ png_error(pp, "validate: invalid tRNS chunk with alpha image");
+ }
+ }
+ }
+
+ /* Read the number of passes - expected to match the value used when
+ * creating the image (interlaced or not). This has the side effect of
+ * turning on interlace handling (if do_interlace is not set.)
+ */
+ dp->npasses = npasses_from_interlace_type(pp, dp->interlace_type);
+ if (!dp->do_interlace && dp->npasses != png_set_interlace_handling(pp))
+ png_error(pp, "validate: file changed interlace type");
+
+ /* Caller calls png_read_update_info or png_start_read_image now, then calls
+ * part2.
+ */
+}
+
+/* This must be called *after* the png_read_update_info call to get the correct
+ * 'rowbytes' value, otherwise png_get_rowbytes will refer to the untransformed
+ * image.
+ */
+static void
+standard_info_part2(standard_display *dp, png_structp pp, png_infop pi,
+ int nImages)
+{
+ /* Record cbRow now that it can be found. */
+ dp->pixel_size = bit_size(pp, png_get_color_type(pp, pi),
+ png_get_bit_depth(pp, pi));
+ dp->bit_width = png_get_image_width(pp, pi) * dp->pixel_size;
+ dp->cbRow = png_get_rowbytes(pp, pi);
+
+ /* Validate the rowbytes here again. */
+ if (dp->cbRow != (dp->bit_width+7)/8)
+ png_error(pp, "bad png_get_rowbytes calculation");
+
+ /* Then ensure there is enough space for the output image(s). */
+ store_ensure_image(dp->ps, pp, nImages * dp->cbRow * dp->h);
+}
+
+static void
+standard_info_imp(standard_display *dp, png_structp pp, png_infop pi,
+ int nImages)
+{
+ /* Note that the validation routine has the side effect of turning on
+ * interlace handling in the subsequent code.
+ */
+ standard_info_part1(dp, pp, pi);
+
+ /* And the info callback has to call this (or png_read_update_info - see
+ * below in the png_modifier code for that variant.
+ */
+ png_start_read_image(pp);
+
+ /* Validate the height, width and rowbytes plus ensure that sufficient buffer
+ * exists for decoding the image.
+ */
+ standard_info_part2(dp, pp, pi, nImages);
+}
+
+static void
+standard_info(png_structp pp, png_infop pi)
+{
+ standard_display *dp = png_get_progressive_ptr(pp);
+
+ /* Call with nImages==1 because the progressive reader can only produce one
+ * image.
+ */
+ standard_info_imp(dp, pp, pi, 1 /*only one image*/);
+}
+
+static void
+progressive_row(png_structp pp, png_bytep new_row, png_uint_32 y, int pass)
+{
+ PNG_CONST standard_display *dp = png_get_progressive_ptr(pp);
+
+ /* When handling interlacing some rows will be absent in each pass, the
+ * callback still gets called, but with a NULL pointer. This is checked
+ * in the 'else' clause below. We need our own 'cbRow', but we can't call
+ * png_get_rowbytes because we got no info structure.
+ */
+ if (new_row != NULL)
+ {
+ png_bytep row;
+
+ /* In the case where the reader doesn't do the interlace it gives
+ * us the y in the sub-image:
+ */
+ if (dp->do_interlace && dp->interlace_type == PNG_INTERLACE_ADAM7)
+ y = PNG_ROW_FROM_PASS_ROW(y, pass);
+
+ /* Validate this just in case. */
+ if (y >= dp->h)
+ png_error(pp, "invalid y to progressive row callback");
+
+ row = dp->ps->image + y * dp->cbRow;
+
+ /* Combine the new row into the old: */
+ if (dp->do_interlace)
+ {
+ if (dp->interlace_type == PNG_INTERLACE_ADAM7)
+ deinterlace_row(row, new_row, dp->pixel_size, dp->w, pass);
+ else
+ memcpy(row, new_row, dp->cbRow);
+ }
+ else
+ png_progressive_combine_row(pp, row, new_row);
+ } else if (dp->interlace_type == PNG_INTERLACE_ADAM7 &&
+ PNG_ROW_IN_INTERLACE_PASS(y, pass) &&
+ PNG_PASS_COLS(dp->w, pass) > 0)
+ png_error(pp, "missing row in progressive de-interlacing");
+}
+
+static void
+sequential_row(standard_display *dp, png_structp pp, png_infop pi,
+ PNG_CONST png_bytep pImage, PNG_CONST png_bytep pDisplay)
+{
+ PNG_CONST int npasses = dp->npasses;
+ PNG_CONST int do_interlace = dp->do_interlace &&
+ dp->interlace_type == PNG_INTERLACE_ADAM7;
+ PNG_CONST png_uint_32 height = standard_height(pp, dp->id);
+ PNG_CONST png_uint_32 width = standard_width(pp, dp->id);
+ PNG_CONST size_t cbRow = dp->cbRow;
+ int pass;
+
+ for (pass=0; pass<npasses; ++pass)
+ {
+ png_uint_32 y;
+ png_uint_32 wPass = PNG_PASS_COLS(width, pass);
+ png_bytep pRow1 = pImage;
+ png_bytep pRow2 = pDisplay;
+
+ for (y=0; y<height; ++y)
+ {
+ if (do_interlace)
+ {
+ /* wPass may be zero or this row may not be in this pass.
+ * png_read_row must not be called in either case.
+ */
+ if (wPass > 0 && PNG_ROW_IN_INTERLACE_PASS(y, pass))
+ {
+ /* Read the row into a pair of temporary buffers, then do the
+ * merge here into the output rows.
+ */
+ png_byte row[STANDARD_ROWMAX], display[STANDARD_ROWMAX];
+
+ /* The following aids (to some extent) error detection - we can
+ * see where png_read_row wrote. Use opposite values in row and
+ * display to make this easier.
+ */
+ memset(row, 0xff, sizeof row);
+ memset(display, 0, sizeof display);
+
+ png_read_row(pp, row, display);
+
+ if (pRow1 != NULL)
+ deinterlace_row(pRow1, row, dp->pixel_size, dp->w, pass);
+
+ if (pRow2 != NULL)
+ deinterlace_row(pRow2, display, dp->pixel_size, dp->w, pass);
+ }
+ }
+ else
+ png_read_row(pp, pRow1, pRow2);
+
+ if (pRow1 != NULL)
+ pRow1 += cbRow;
+
+ if (pRow2 != NULL)
+ pRow2 += cbRow;
+ }
+ }
+
+ /* And finish the read operation (only really necessary if the caller wants
+ * to find additional data in png_info from chunks after the last IDAT.)
+ */
+ png_read_end(pp, pi);
+}
+
+static void
+standard_row_validate(standard_display *dp, png_structp pp, png_const_bytep row,
+ png_const_bytep display, png_uint_32 y)
+{
+ png_byte std[STANDARD_ROWMAX];
+
+ memset(std, 0xff, sizeof std);
+ standard_row(pp, std, dp->id, y);
+
+ /* At the end both the 'row' and 'display' arrays should end up identical.
+ * In earlier passes 'row' will be partially filled in, with only the pixels
+ * that have been read so far, but 'display' will have those pixels
+ * replicated to fill the unread pixels while reading an interlaced image.
+ * The side effect inside the libpng sequential reader is that the 'row'
+ * array retains the correct values for unwritten pixels within the row
+ * bytes, while the 'display' array gets bits off the end of the image (in
+ * the last byte) trashed. Unfortunately in the progressive reader the
+ * row bytes are always trashed, so we always do a pixel_cmp here even though
+ * a memcmp of all cbRow bytes will succeed for the sequential reader.
+ */
+ if (row != NULL && pixel_cmp(std, row, dp->bit_width) != 0)
+ {
+ char msg[64];
+ sprintf(msg, "PNG image row %d changed", y);
+ png_error(pp, msg);
+ }
+
+ /* In this case use pixel_cmp because we need to compare a partial
+ * byte at the end of the row if the row is not an exact multiple
+ * of 8 bits wide.
+ */
+ if (display != NULL && pixel_cmp(std, display, dp->bit_width) != 0)
+ {
+ char msg[64];
+ sprintf(msg, "display row %d changed", y);
+ png_error(pp, msg);
+ }
+}
+
+static void
+standard_image_validate(standard_display *dp, png_structp pp,
+ png_const_bytep pImage, png_const_bytep pDisplay)
+{
+ png_uint_32 y;
+
+ for (y=0; y<dp->h; ++y)
+ {
+ standard_row_validate(dp, pp, pImage, pDisplay, y);
+
+ if (pImage != NULL)
+ pImage += dp->cbRow;
+
+ if (pDisplay != NULL)
+ pDisplay += dp->cbRow;
+ }
+
+ /* This avoids false positives if the validation code is never called! */
+ dp->ps->validated = 1;
+}
+
+static void
+standard_end(png_structp pp, png_infop pi)
+{
+ standard_display *dp = png_get_progressive_ptr(pp);
+
+ UNUSED(pi)
+
+ /* Validate the image - progressive reading only produces one variant for
+ * interlaced images.
+ */
+ standard_image_validate(dp, pp, dp->ps->image, NULL);
+}
+
+/* A single test run checking the standard image to ensure it is not damaged. */
+static void
+standard_test(png_store* PNG_CONST psIn, png_uint_32 PNG_CONST id,
+ int do_interlace)
+{
+ standard_display d;
+ context(psIn, fault);
+
+ /* Set up the display (stack frame) variables from the arguments to the
+ * function and initialize the locals that are filled in later.
+ */
+ standard_display_init(&d, psIn, id, do_interlace);
+
+ /* Everything is protected by a Try/Catch. The functions called also
+ * typically have local Try/Catch blocks.
+ */
+ Try
+ {
+ png_structp pp;
+ png_infop pi;
+
+ /* Get a png_struct for reading the image. This will throw an error if it
+ * fails, so we don't need to check the result.
+ */
+ pp = set_store_for_read(d.ps, &pi, d.id,
+ d.do_interlace ? (d.ps->progressive ?
+ "pngvalid progressive deinterlacer" :
+ "pngvalid sequential deinterlacer") : (d.ps->progressive ?
+ "progressive reader" : "sequential reader"));
+
+ /* Introduce the correct read function. */
+ if (d.ps->progressive)
+ {
+ png_set_progressive_read_fn(pp, &d, standard_info, progressive_row,
+ standard_end);
+
+ /* Now feed data into the reader until we reach the end: */
+ store_progressive_read(d.ps, pp, pi);
+ }
+ else
+ {
+ /* Note that this takes the store, not the display. */
+ png_set_read_fn(pp, d.ps, store_read);
+
+ /* Check the header values: */
+ png_read_info(pp, pi);
+
+ /* The code tests both versions of the images that the sequential
+ * reader can produce.
+ */
+ standard_info_imp(&d, pp, pi, 2 /*images*/);
+
+ /* Need the total bytes in the image below; we can't get to this point
+ * unless the PNG file values have been checked against the expected
+ * values.
+ */
+ {
+ PNG_CONST png_bytep pImage = d.ps->image;
+ PNG_CONST png_bytep pDisplay = pImage + d.cbRow * d.h;
+
+ sequential_row(&d, pp, pi, pImage, pDisplay);
+
+ /* After the last pass loop over the rows again to check that the
+ * image is correct.
+ */
+ standard_image_validate(&d, pp, pImage, pDisplay);
+ }
+ }
+
+ /* Check for validation. */
+ if (!d.ps->validated)
+ png_error(pp, "image read failed silently");
+
+ /* Successful completion. */
+ }
+
+ Catch(fault)
+ d.ps = fault; /* make sure this hasn't been clobbered. */
+
+ /* In either case clean up the store. */
+ store_read_reset(d.ps);
+}
+
+static int
+test_standard(png_modifier* PNG_CONST pm, png_byte PNG_CONST colour_type,
+ int bdlo, int PNG_CONST bdhi)
+{
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ int interlace_type;
+
+ for (interlace_type = PNG_INTERLACE_NONE;
+ interlace_type < PNG_INTERLACE_LAST; ++interlace_type)
+ {
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ interlace_type, 0, 0, 0), 0/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+ }
+ }
+
+ return 1; /* keep going */
+}
+
+static void
+perform_standard_test(png_modifier *pm)
+{
+ /* Test each colour type over the valid range of bit depths (expressed as
+ * log2(bit_depth) in turn, stop as soon as any error is detected.
+ */
+ if (!test_standard(pm, 0, 0, READ_BDHI))
+ return;
+
+ if (!test_standard(pm, 2, 3, READ_BDHI))
+ return;
+
+ if (!test_standard(pm, 3, 0, 3))
+ return;
+
+ if (!test_standard(pm, 4, 3, READ_BDHI))
+ return;
+
+ if (!test_standard(pm, 6, 3, READ_BDHI))
+ return;
+}
+
+
+/********************************** SIZE TESTS ********************************/
+static int
+test_size(png_modifier* PNG_CONST pm, png_byte PNG_CONST colour_type,
+ int bdlo, int PNG_CONST bdhi)
+{
+ /* Run the tests on each combination.
+ *
+ * NOTE: on my 32 bit x86 each of the following blocks takes
+ * a total of 3.5 seconds if done across every combo of bit depth
+ * width and height. This is a waste of time in practice, hence the
+ * hinc and winc stuff:
+ */
+ static PNG_CONST png_byte hinc[] = {1, 3, 11, 1, 5};
+ static PNG_CONST png_byte winc[] = {1, 9, 5, 7, 1};
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ png_uint_32 h, w;
+
+ for (h=1; h<=16; h+=hinc[bdlo]) for (w=1; w<=16; w+=winc[bdlo])
+ {
+ /* First test all the 'size' images against the sequential
+ * reader using libpng to deinterlace (where required.) This
+ * validates the write side of libpng. There are four possibilities
+ * to validate.
+ */
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_NONE, w, h, 0), 0/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_NONE, w, h, 1), 0/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_ADAM7, w, h, 0), 0/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_ADAM7, w, h, 1), 0/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+
+ /* Now validate the interlaced read side - do_interlace true,
+ * in the progressive case this does actually make a difference
+ * to the code used in the non-interlaced case too.
+ */
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_NONE, w, h, 0), 1/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+
+ standard_test(&pm->this, FILEID(colour_type, DEPTH(bdlo),
+ PNG_INTERLACE_ADAM7, w, h, 0), 1/*do_interlace*/);
+
+ if (fail(pm))
+ return 0;
+ }
+ }
+
+ return 1; /* keep going */
+}
+
+static void
+perform_size_test(png_modifier *pm)
+{
+ /* Test each colour type over the valid range of bit depths (expressed as
+ * log2(bit_depth) in turn, stop as soon as any error is detected.
+ */
+ if (!test_size(pm, 0, 0, READ_BDHI))
+ return;
+
+ if (!test_size(pm, 2, 3, READ_BDHI))
+ return;
+
+ /* For the moment don't do the palette test - it's a waste of time when
+ * compared to the greyscale test.
+ */
+#if 0
+ if (!test_size(pm, 3, 0, 3))
+ return;
+#endif
+
+ if (!test_size(pm, 4, 3, READ_BDHI))
+ return;
+
+ if (!test_size(pm, 6, 3, READ_BDHI))
+ return;
+}
+
+
+/******************************* TRANSFORM TESTS ******************************/
+/* A set of tests to validate libpng image transforms. The possibilities here
+ * are legion because the transforms can be combined in a combinatorial
+ * fashion. To deal with this some measure of restraint is required, otherwise
+ * the tests would take forever.
+ */
+typedef struct image_pixel
+{
+ /* A local (pngvalid) representation of a PNG pixel, in all its
+ * various forms.
+ */
+ unsigned int red, green, blue, alpha; /* For non-palette images. */
+ unsigned int palette_index; /* For a palette image. */
+ png_byte colour_type; /* As in the spec. */
+ png_byte bit_depth; /* Defines bit size in row */
+ png_byte sample_depth; /* Scale of samples */
+ int have_tRNS; /* tRNS chunk may need processing */
+
+ /* For checking the code calculates double precision floating point values
+ * along with an error value, accumulated from the transforms. Because an
+ * sBIT setting allows larger error bounds (indeed, by the spec, apparently
+ * up to just less than +/-1 in the scaled value) the *lowest* sBIT for each
+ * channel is stored. This sBIT value is folded in to the stored error value
+ * at the end of the application of the transforms to the pixel.
+ */
+ double redf, greenf, bluef, alphaf;
+ double rede, greene, bluee, alphae;
+ png_byte red_sBIT, green_sBIT, blue_sBIT, alpha_sBIT;
+} image_pixel;
+
+/* Shared utility function, see below. */
+static void
+image_pixel_setf(image_pixel *this, unsigned int max)
+{
+ this->redf = this->red / (double)max;
+ this->greenf = this->green / (double)max;
+ this->bluef = this->blue / (double)max;
+ this->alphaf = this->alpha / (double)max;
+
+ if (this->red < max)
+ this->rede = this->redf * DBL_EPSILON;
+ else
+ this->rede = 0;
+ if (this->green < max)
+ this->greene = this->greenf * DBL_EPSILON;
+ else
+ this->greene = 0;
+ if (this->blue < max)
+ this->bluee = this->bluef * DBL_EPSILON;
+ else
+ this->bluee = 0;
+ if (this->alpha < max)
+ this->alphae = this->alphaf * DBL_EPSILON;
+ else
+ this->alphae = 0;
+}
+
+/* Initialize the structure for the next pixel - call this before doing any
+ * transforms and call it for each pixel since all the fields may need to be
+ * reset.
+ */
+static void
+image_pixel_init(image_pixel *this, png_const_bytep row, png_byte colour_type,
+ png_byte bit_depth, png_uint_32 x, standard_palette palette)
+{
+ PNG_CONST png_byte sample_depth = (png_byte)(colour_type ==
+ PNG_COLOR_TYPE_PALETTE ? 8 : bit_depth);
+ PNG_CONST unsigned int max = (1U<<sample_depth)-1;
+
+ /* Initially just set everything to the same number and the alpha to opaque.
+ * Note that this currently assumes a simple palette where entry x has colour
+ * rgb(x,x,x)!
+ */
+ this->palette_index = this->red = this->green = this->blue =
+ sample(row, colour_type, bit_depth, x, 0);
+ this->alpha = max;
+ this->red_sBIT = this->green_sBIT = this->blue_sBIT = this->alpha_sBIT =
+ sample_depth;
+
+ /* Then override as appropriate: */
+ if (colour_type == 3) /* palette */
+ {
+ /* This permits the caller to default to the sample value. */
+ if (palette != 0)
+ {
+ PNG_CONST unsigned int i = this->palette_index;
+
+ this->red = palette[i].red;
+ this->green = palette[i].green;
+ this->blue = palette[i].blue;
+ this->alpha = palette[i].alpha;
+ }
+ }
+
+ else /* not palette */
+ {
+ unsigned int i = 0;
+
+ if (colour_type & 2)
+ {
+ this->green = sample(row, colour_type, bit_depth, x, 1);
+ this->blue = sample(row, colour_type, bit_depth, x, 2);
+ i = 2;
+ }
+ if (colour_type & 4)
+ this->alpha = sample(row, colour_type, bit_depth, x, ++i);
+ }
+
+ /* Calculate the scaled values, these are simply the values divided by
+ * 'max' and the error is initialized to the double precision epsilon value
+ * from the header file.
+ */
+ image_pixel_setf(this, max);
+
+ /* Store the input information for use in the transforms - these will
+ * modify the information.
+ */
+ this->colour_type = colour_type;
+ this->bit_depth = bit_depth;
+ this->sample_depth = sample_depth;
+ this->have_tRNS = 0;
+}
+
+/* Convert a palette image to an rgb image. This necessarily converts the tRNS
+ * chunk at the same time, because the tRNS will be in palette form.
+ */
+static void
+image_pixel_convert_PLTE(image_pixel *this, const standard_display *display)
+{
+ if (this->colour_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ PNG_CONST unsigned int i = this->palette_index;
+
+ this->bit_depth = this->sample_depth;
+ this->red = display->palette[i].red;
+ this->green = display->palette[i].green;
+ this->blue = display->palette[i].blue;
+ this->red_sBIT = display->red_sBIT;
+ this->green_sBIT = display->green_sBIT;
+ this->blue_sBIT = display->blue_sBIT;
+
+ if (this->have_tRNS)
+ {
+ this->alpha = display->palette[i].alpha;
+ this->colour_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ this->have_tRNS = 0;
+ }
+ else
+ {
+ this->alpha = 255;
+ this->colour_type = PNG_COLOR_TYPE_RGB;
+ }
+ this->alpha_sBIT = 8;
+
+ /* And regenerate the scaled values and all the errors, which are now set
+ * back to the initial values.
+ */
+ image_pixel_setf(this, 255);
+ }
+}
+
+/* Add an alpha channel, this will glom in the tRNS information because tRNS is
+ * not valid in an alpha image. The bit depth will invariably be set to at
+ * least 8. Palette images will be converted to alpha (using the above API).
+ */
+static void
+image_pixel_add_alpha(image_pixel *this, const standard_display *display)
+{
+ if (this->colour_type == PNG_COLOR_TYPE_PALETTE)
+ image_pixel_convert_PLTE(this, display);
+
+ if ((this->colour_type & PNG_COLOR_MASK_ALPHA) == 0)
+ {
+ if (this->colour_type == PNG_COLOR_TYPE_GRAY)
+ {
+ if (this->bit_depth < 8)
+ this->bit_depth = 8;
+
+ if (this->have_tRNS)
+ {
+ this->have_tRNS = 0;
+
+ /* Check the input, original, channel value here against the
+ * original tRNS gray chunk valie.
+ */
+ if (this->red == display->transparent.red)
+ this->alphaf = 0;
+ else
+ this->alphaf = 1;
+ }
+ else
+ this->alphaf = 1;
+
+ this->colour_type = PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ else if (this->colour_type == PNG_COLOR_TYPE_RGB)
+ {
+ if (this->have_tRNS)
+ {
+ this->have_tRNS = 0;
+
+ /* Again, check the exact input values, not the current transformed
+ * value!
+ */
+ if (this->red == display->transparent.red &&
+ this->green == display->transparent.green &&
+ this->blue == display->transparent.blue)
+ this->alphaf = 0;
+ else
+ this->alphaf = 1;
+
+ this->colour_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ }
+ }
+
+ /* The error in the alpha is zero and the sBIT value comes from the
+ * original sBIT data (actually it will always be the original bit depth).
+ */
+ this->alphae = 0;
+ this->alpha_sBIT = display->alpha_sBIT;
+ }
+}
+
+struct transform_display;
+typedef struct image_transform
+{
+ /* The name of this transform: a string. */
+ PNG_CONST char *name;
+
+ /* Each transform can be disabled from the command line: */
+ int enable;
+
+ /* The global list of transforms; read only. */
+ struct image_transform *PNG_CONST list;
+
+ /* The global count of the number of times this transform has been set on an
+ * image.
+ */
+ unsigned int global_use;
+
+ /* The local count of the number of times this transform has been set. */
+ unsigned int local_use;
+
+ /* The next transform in the list, each transform must call its own next
+ * transform after it has processed the pixel successfully.
+ */
+ PNG_CONST struct image_transform *next;
+
+ /* A single transform for the image, expressed as a series of function
+ * callbacks and some space for values.
+ *
+ * First a callback to set the transform on the current png_read_struct:
+ */
+ void (*set)(PNG_CONST struct image_transform *this,
+ struct transform_display *that, png_structp pp, png_infop pi);
+
+ /* Then a transform that takes an input pixel in one PNG format or another
+ * and modifies it by a pngvalid implementation of the transform (thus
+ * duplicating the libpng intent without, we hope, duplicating the bugs
+ * in the libpng implementation!) The png_structp is solely to allow error
+ * reporting via png_error and png_warning.
+ */
+ void (*mod)(PNG_CONST struct image_transform *this, image_pixel *that,
+ png_structp pp, PNG_CONST struct transform_display *display);
+
+ /* Add this transform to the list and return true if the transform is
+ * meaningful for this colour type and bit depth - if false then the
+ * transform should have no effect on the image so there's not a lot of
+ * point running it.
+ */
+ int (*add)(struct image_transform *this,
+ PNG_CONST struct image_transform **that, png_byte colour_type,
+ png_byte bit_depth);
+} image_transform;
+
+typedef struct transform_display
+{
+ standard_display this;
+
+ /* Parameters */
+ png_modifier* pm;
+ PNG_CONST image_transform* transform_list;
+
+ /* Local variables */
+ png_byte output_colour_type;
+ png_byte output_bit_depth;
+} transform_display;
+
+/* Two functions to end the list: */
+static void
+image_transform_set_end(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ UNUSED(this)
+ UNUSED(that)
+ UNUSED(pp)
+ UNUSED(pi)
+}
+
+/* At the end of the list recalculate the output image pixel value from the
+ * double precision values set up by the preceding 'mod' calls:
+ */
+static unsigned int
+sample_scale(double sample_value, unsigned int scale)
+{
+ sample_value = floor(sample_value * scale + .5);
+
+ /* Return NaN as 0: */
+ if (!(sample_value > 0))
+ sample_value = 0;
+ else if (sample_value > scale)
+ sample_value = scale;
+
+ return (unsigned int)sample_value;
+}
+
+static void
+image_transform_mod_end(PNG_CONST image_transform *this, image_pixel *that,
+ png_structp pp, PNG_CONST transform_display *display)
+{
+ PNG_CONST unsigned int scale = (1U<<that->sample_depth)-1;
+
+ UNUSED(this)
+ UNUSED(pp)
+ UNUSED(display)
+
+ /* At the end recalculate the digitized red green and blue values according
+ * to the current sample_depth of the pixel.
+ *
+ * The sample value is simply scaled to the maximum, checking for over
+ * and underflow (which can both happen for some image transforms,
+ * including simple size scaling, though libpng doesn't do that at present.
+ */
+ that->red = sample_scale(that->redf, scale);
+
+ /* The error value is increased, at the end, according to the lowest sBIT
+ * value seen. Common sense tells us that the intermediate integer
+ * representations are no more accurate than +/- 0.5 in the integral values,
+ * the sBIT allows the implementation to be worse than this. In addition the
+ * PNG specification actually permits any error within the range (-1..+1),
+ * but that is ignored here. Instead the final digitized value is compared,
+ * below to the digitized value of the error limits - this has the net effect
+ * of allowing (almost) +/-1 in the output value. It's difficult to see how
+ * any algorithm that digitizes intermediate results can be more accurate.
+ */
+ that->rede += 1./(2*((1U<<that->red_sBIT)-1));
+
+ if (that->colour_type & PNG_COLOR_MASK_COLOR)
+ {
+ that->green = sample_scale(that->greenf, scale);
+ that->blue = sample_scale(that->bluef, scale);
+ that->greene += 1./(2*((1U<<that->green_sBIT)-1));
+ that->bluee += 1./(2*((1U<<that->blue_sBIT)-1));
+ }
+ else
+ {
+ that->blue = that->green = that->red;
+ that->bluef = that->greenf = that->redf;
+ that->bluee = that->greene = that->rede;
+ }
+
+ if ((that->colour_type & PNG_COLOR_MASK_ALPHA) ||
+ that->colour_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ that->alpha = sample_scale(that->alphaf, scale);
+ that->alphae += 1./(2*((1U<<that->alpha_sBIT)-1));
+ }
+ else
+ {
+ that->alpha = scale; /* opaque */
+ that->alpha = 1; /* Override this. */
+ that->alphae = 0; /* It's exact ;-) */
+ }
+}
+
+/* Static 'end' structure: */
+static image_transform image_transform_end =
+{
+ "(end)", /* name */
+ 1, /* enable */
+ 0, /* list */
+ 0, /* global_use */
+ 0, /* local_use */
+ 0, /* next */
+ image_transform_set_end,
+ image_transform_mod_end,
+ 0 /* never called, I want it to crash if it is! */
+};
+
+/* Reader callbacks and implementations, where they differ from the standard
+ * ones.
+ */
+static void
+transform_display_init(transform_display *dp, png_modifier *pm, png_uint_32 id,
+ PNG_CONST image_transform *transform_list)
+{
+ /* Standard fields */
+ standard_display_init(&dp->this, &pm->this, id, 0/*do_interlace*/);
+
+ /* Parameter fields */
+ dp->pm = pm;
+ dp->transform_list = transform_list;
+
+ /* Local variable fields */
+ dp->output_colour_type = 255; /* invalid */
+ dp->output_bit_depth = 255; /* invalid */
+}
+
+static void
+transform_info_imp(transform_display *dp, png_structp pp, png_infop pi)
+{
+ /* Reuse the standard stuff as appropriate. */
+ standard_info_part1(&dp->this, pp, pi);
+
+ /* Now set the list of transforms. */
+ dp->transform_list->set(dp->transform_list, dp, pp, pi);
+
+ /* Update the info structure for these transforms: */
+ png_read_update_info(pp, pi);
+
+ /* And get the output information into the standard_display */
+ standard_info_part2(&dp->this, pp, pi, 1/*images*/);
+
+ /* Plus the extra stuff we need for the transform tests: */
+ dp->output_colour_type = png_get_color_type(pp, pi);
+ dp->output_bit_depth = png_get_bit_depth(pp, pi);
+
+ /* Validate the combination of colour type and bit depth that we are getting
+ * out of libpng; the semantics of something not in the PNG spec are, at
+ * best, unclear.
+ */
+ switch (dp->output_colour_type)
+ {
+ case PNG_COLOR_TYPE_PALETTE:
+ if (dp->output_bit_depth > 8) goto error;
+ /*FALL THROUGH*/
+ case PNG_COLOR_TYPE_GRAY:
+ if (dp->output_bit_depth == 1 || dp->output_bit_depth == 2 ||
+ dp->output_bit_depth == 4)
+ break;
+ /*FALL THROUGH*/
+ default:
+ if (dp->output_bit_depth == 8 || dp->output_bit_depth == 16)
+ break;
+ /*FALL THROUGH*/
+ error:
+ {
+ char message[128];
+ size_t pos;
+
+ pos = safecat(message, sizeof message, 0,
+ "invalid final bit depth: colour type(");
+ pos = safecatn(message, sizeof message, pos, dp->output_colour_type);
+ pos = safecat(message, sizeof message, pos, ") with bit depth: ");
+ pos = safecatn(message, sizeof message, pos, dp->output_bit_depth);
+
+ png_error(pp, message);
+ }
+ }
+
+ /* Use a test pixel to check that the output agrees with what we expect -
+ * this avoids running the whole test if the output is unexpected.
+ */
+ {
+ image_pixel test_pixel;
+
+ memset(&test_pixel, 0, sizeof test_pixel);
+ test_pixel.colour_type = dp->this.colour_type; /* input */
+ test_pixel.bit_depth = dp->this.bit_depth;
+ if (test_pixel.colour_type == PNG_COLOR_TYPE_PALETTE)
+ test_pixel.sample_depth = 8;
+ else
+ test_pixel.sample_depth = test_pixel.bit_depth;
+ /* Don't need sBIT here */
+ test_pixel.have_tRNS = dp->this.is_transparent;
+
+ dp->transform_list->mod(dp->transform_list, &test_pixel, pp, dp);
+
+ if (test_pixel.colour_type != dp->output_colour_type)
+ {
+ char message[128];
+ size_t pos = safecat(message, sizeof message, 0, "colour type ");
+
+ pos = safecatn(message, sizeof message, pos, dp->output_colour_type);
+ pos = safecat(message, sizeof message, pos, " expected ");
+ pos = safecatn(message, sizeof message, pos, test_pixel.colour_type);
+
+ png_error(pp, message);
+ }
+
+ if (test_pixel.bit_depth != dp->output_bit_depth)
+ {
+ char message[128];
+ size_t pos = safecat(message, sizeof message, 0, "bit depth ");
+
+ pos = safecatn(message, sizeof message, pos, dp->output_bit_depth);
+ pos = safecat(message, sizeof message, pos, " expected ");
+ pos = safecatn(message, sizeof message, pos, test_pixel.bit_depth);
+
+ png_error(pp, message);
+ }
+
+ /* If both bit depth and colour type are correct check the sample depth.
+ * I believe these are both internal errors.
+ */
+ if (test_pixel.colour_type == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (test_pixel.sample_depth != 8) /* oops - internal error! */
+ png_error(pp, "pngvalid: internal: palette sample depth not 8");
+ }
+ else if (test_pixel.sample_depth != dp->output_bit_depth)
+ {
+ char message[128];
+ size_t pos = safecat(message, sizeof message, 0,
+ "internal: sample depth ");
+
+ pos = safecatn(message, sizeof message, pos, dp->output_bit_depth);
+ pos = safecat(message, sizeof message, pos, " expected ");
+ pos = safecatn(message, sizeof message, pos, test_pixel.sample_depth);
+
+ png_error(pp, message);
+ }
+ }
+}
+
+static void
+transform_info(png_structp pp, png_infop pi)
+{
+ transform_info_imp(png_get_progressive_ptr(pp), pp, pi);
+}
+
+static void
+transform_range_check(png_structp pp, unsigned int r, unsigned int g,
+ unsigned int b, unsigned int a, unsigned int in_digitized, double in,
+ unsigned int out, png_byte sample_depth, double err, PNG_CONST char *name)
+{
+ /* Compare the scaled, digitzed, values of our local calculation (in+-err)
+ * with the digitized values libpng produced; 'sample_depth' is the actual
+ * digitization depth of the libpng output colors (the bit depth except for
+ * palette images where it is always 8.)
+ */
+ unsigned int max = (1U<<sample_depth)-1;
+ double in_min = ceil((in-err)*max - .5);
+ double in_max = floor((in+err)*max + .5);
+ if (!(out >= in_min && out <= in_max))
+ {
+ char message[256];
+ size_t pos;
+
+ pos = safecat(message, sizeof message, 0, name);
+ pos = safecat(message, sizeof message, pos, " output value error: rgba(");
+ pos = safecatn(message, sizeof message, pos, r);
+ pos = safecat(message, sizeof message, pos, ",");
+ pos = safecatn(message, sizeof message, pos, g);
+ pos = safecat(message, sizeof message, pos, ",");
+ pos = safecatn(message, sizeof message, pos, b);
+ pos = safecat(message, sizeof message, pos, ",");
+ pos = safecatn(message, sizeof message, pos, a);
+ pos = safecat(message, sizeof message, pos, "): ");
+ pos = safecatn(message, sizeof message, pos, out);
+ pos = safecat(message, sizeof message, pos, " expected: ");
+ pos = safecatn(message, sizeof message, pos, in_digitized);
+ pos = safecat(message, sizeof message, pos, " (");
+ pos = safecatd(message, sizeof message, pos, (in-err)*max, 3);
+ pos = safecat(message, sizeof message, pos, "..");
+ pos = safecatd(message, sizeof message, pos, (in+err)*max, 3);
+ pos = safecat(message, sizeof message, pos, ")");
+
+ png_error(pp, message);
+ }
+}
+
+static void
+transform_image_validate(transform_display *dp, png_structp pp, png_infop pi,
+ png_const_bytep pRow)
+{
+ /* Constants for the loop below: */
+ PNG_CONST png_byte in_ct = dp->this.colour_type;
+ PNG_CONST png_byte in_bd = dp->this.bit_depth;
+ PNG_CONST png_uint_32 w = dp->this.w;
+ PNG_CONST png_uint_32 h = dp->this.h;
+ PNG_CONST size_t cbRow = dp->this.cbRow;
+ PNG_CONST png_byte out_ct = dp->output_colour_type;
+ PNG_CONST png_byte out_bd = dp->output_bit_depth;
+ PNG_CONST png_byte sample_depth = (png_byte)(out_ct ==
+ PNG_COLOR_TYPE_PALETTE ? 8 : out_bd);
+ PNG_CONST png_byte red_sBIT = dp->this.red_sBIT;
+ PNG_CONST png_byte green_sBIT = dp->this.green_sBIT;
+ PNG_CONST png_byte blue_sBIT = dp->this.blue_sBIT;
+ PNG_CONST png_byte alpha_sBIT = dp->this.alpha_sBIT;
+ PNG_CONST int have_tRNS = dp->this.is_transparent;
+
+ standard_palette out_palette;
+ png_uint_32 y;
+
+ UNUSED(pi)
+
+ /* Read the palette corresponding to the output if the output colour type
+ * indicates a palette, othewise set out_palette to garbage.
+ */
+ if (out_ct == PNG_COLOR_TYPE_PALETTE)
+ (void)standard_palette_init(out_palette, pp, pi);
+ else
+ memset(out_palette, 0x5e, sizeof out_palette);
+
+ for (y=0; y<h; ++y, pRow += cbRow)
+ {
+ png_uint_32 x;
+
+ /* The original, standard, row pre-transforms. */
+ png_byte std[STANDARD_ROWMAX];
+
+ transform_row(pp, std, in_ct, in_bd, y);
+
+ /* Go through each original pixel transforming it and comparing with what
+ * libpng did to the same pixel.
+ */
+ for (x=0; x<w; ++x)
+ {
+ image_pixel in_pixel, out_pixel;
+ unsigned int r, g, b, a;
+
+ /* Find out what we think the pixel should be: */
+ image_pixel_init(&in_pixel, std, in_ct, in_bd, x, dp->this.palette);
+
+ in_pixel.red_sBIT = red_sBIT;
+ in_pixel.green_sBIT = green_sBIT;
+ in_pixel.blue_sBIT = blue_sBIT;
+ in_pixel.alpha_sBIT = alpha_sBIT;
+ in_pixel.have_tRNS = have_tRNS;
+
+ /* For error detection, below. */
+ r = in_pixel.red;
+ g = in_pixel.green;
+ b = in_pixel.blue;
+ a = in_pixel.alpha;
+
+ dp->transform_list->mod(dp->transform_list, &in_pixel, pp, dp);
+
+ /* Read the output pixel and compare it to what we got, we don't
+ * use the error field here, so no need to update sBIT.
+ */
+ image_pixel_init(&out_pixel, pRow, out_ct, out_bd, x, out_palette);
+
+ /* We don't expect changes to the index here even if the bit depth is
+ * changed.
+ */
+ if (in_ct == PNG_COLOR_TYPE_PALETTE &&
+ out_ct == PNG_COLOR_TYPE_PALETTE)
+ {
+ if (in_pixel.palette_index != out_pixel.palette_index)
+ png_error(pp, "unexpected transformed palette index");
+ }
+
+ /* Check the colours for palette images too - in fact the palette could
+ * be separately verified itself in most cases.
+ */
+ if (in_pixel.red != out_pixel.red)
+ transform_range_check(pp, r, g, b, a, in_pixel.red, in_pixel.redf,
+ out_pixel.red, sample_depth, in_pixel.rede, "red/gray");
+
+ if ((out_ct & PNG_COLOR_MASK_COLOR) != 0 &&
+ in_pixel.green != out_pixel.green)
+ transform_range_check(pp, r, g, b, a, in_pixel.green,
+ in_pixel.greenf, out_pixel.green, sample_depth, in_pixel.greene,
+ "green");
+
+ if ((out_ct & PNG_COLOR_MASK_COLOR) != 0 &&
+ in_pixel.blue != out_pixel.blue)
+ transform_range_check(pp, r, g, b, a, in_pixel.blue, in_pixel.bluef,
+ out_pixel.blue, sample_depth, in_pixel.bluee, "blue");
+
+ if ((out_ct & PNG_COLOR_MASK_ALPHA) != 0 &&
+ in_pixel.alpha != out_pixel.alpha)
+ transform_range_check(pp, r, g, b, a, in_pixel.alpha,
+ in_pixel.alphaf, out_pixel.alpha, sample_depth, in_pixel.alphae,
+ "alpha");
+ } /* pixel (x) loop */
+ } /* row (y) loop */
+
+ /* Record that something was actually checked to avoid a false positive. */
+ dp->this.ps->validated = 1;
+}
+
+static void
+transform_end(png_structp pp, png_infop pi)
+{
+ transform_display *dp = png_get_progressive_ptr(pp);
+
+ transform_image_validate(dp, pp, pi, dp->this.ps->image);
+}
+
+/* A single test run. */
+static void
+transform_test(png_modifier *pmIn, PNG_CONST png_uint_32 idIn,
+ PNG_CONST image_transform* transform_listIn, PNG_CONST char *name)
+{
+ transform_display d;
+ context(&pmIn->this, fault);
+
+ transform_display_init(&d, pmIn, idIn, transform_listIn);
+
+ Try
+ {
+ png_structp pp;
+ png_infop pi;
+
+ /* Get a png_struct for writing the image. */
+ pp = set_modifier_for_read(d.pm, &pi, d.this.id, name);
+
+# if 0
+ /* Logging (debugging only) */
+ {
+ char buffer[256];
+
+ (void)store_message(&d.pm->this, pp, buffer, sizeof buffer, 0,
+ "running test");
+
+ fprintf(stderr, "%s\n", buffer);
+ }
+# endif
+
+ /* Introduce the correct read function. */
+ if (d.pm->this.progressive)
+ {
+ /* Share the row function with the standard implementation. */
+ png_set_progressive_read_fn(pp, &d, transform_info, progressive_row,
+ transform_end);
+
+ /* Now feed data into the reader until we reach the end: */
+ modifier_progressive_read(d.pm, pp, pi);
+ }
+ else
+ {
+ /* modifier_read expects a png_modifier* */
+ png_set_read_fn(pp, d.pm, modifier_read);
+
+ /* Check the header values: */
+ png_read_info(pp, pi);
+
+ /* Process the 'info' requirements. Only one image is generated */
+ transform_info_imp(&d, pp, pi);
+
+ sequential_row(&d.this, pp, pi, NULL, d.this.ps->image);
+
+ transform_image_validate(&d, pp, pi, d.this.ps->image);
+ }
+
+ modifier_reset(d.pm);
+ }
+
+ Catch(fault)
+ modifier_reset((png_modifier*)fault);
+}
+
+/* The transforms: */
+#define ITSTRUCT(name) image_transform_##name
+#define IT(name,prev)\
+static image_transform ITSTRUCT(name) =\
+{\
+ #name,\
+ 1, /*enable*/\
+ &ITSTRUCT(prev), /*list*/\
+ 0, /*global_use*/\
+ 0, /*local_use*/\
+ 0, /*next*/\
+ image_transform_png_set_##name##_set,\
+ image_transform_png_set_##name##_mod,\
+ image_transform_png_set_##name##_add\
+}
+
+/* To save code: */
+static int
+image_transform_default_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(colour_type)
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ return 1;
+}
+
+/* png_set_palette_to_rgb */
+static void
+image_transform_png_set_palette_to_rgb_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_palette_to_rgb(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_palette_to_rgb_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ if (that->colour_type == PNG_COLOR_TYPE_PALETTE)
+ image_pixel_convert_PLTE(that, &display->this);
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_palette_to_rgb_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ return colour_type == PNG_COLOR_TYPE_PALETTE;
+}
+
+IT(palette_to_rgb, end);
+
+
+/* png_set_tRNS_to_alpha */
+static void
+image_transform_png_set_tRNS_to_alpha_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_tRNS_to_alpha(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_tRNS_to_alpha_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ /* LIBPNG BUG: this always forces palette images to RGB. */
+ if (that->colour_type == PNG_COLOR_TYPE_PALETTE)
+ image_pixel_convert_PLTE(that, &display->this);
+
+ /* This effectively does an 'expand' only if there is some transparency to
+ * covert to an alpha channel.
+ */
+ if (that->have_tRNS)
+ image_pixel_add_alpha(that, &display->this);
+
+ /* LIBPNG BUG: otherwise libpng still expands to 8 bits! */
+ else
+ {
+ if (that->bit_depth < 8)
+ that->bit_depth =8;
+ if (that->sample_depth < 8)
+ that->sample_depth = 8;
+ }
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_tRNS_to_alpha_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ /* We don't know yet whether there will be a tRNS chunk, but we know that
+ * this transformation should do nothing if there already is an alpha
+ * channel.
+ */
+ return (colour_type & PNG_COLOR_MASK_ALPHA) == 0;
+}
+
+IT(tRNS_to_alpha,palette_to_rgb);
+
+/* png_set_gray_to_rgb */
+static void
+image_transform_png_set_gray_to_rgb_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_gray_to_rgb(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_gray_to_rgb_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ /* NOTE: we can actually pend the tRNS processing at this point because we
+ * can correctly recognize the original pixel value even though we have
+ * mapped the one gray channel to the three RGB ones, but in fact libpng
+ * doesn't do this, so we don't either.
+ */
+ if ((that->colour_type & PNG_COLOR_MASK_COLOR) == 0 && that->have_tRNS)
+ image_pixel_add_alpha(that, &display->this);
+
+ /* Simply expand the bit depth and alter the colour type as required. */
+ if (that->colour_type == PNG_COLOR_TYPE_GRAY)
+ {
+ /* RGB images have a bit depth at least equal to '8' */
+ if (that->bit_depth < 8)
+ that->sample_depth = that->bit_depth = 8;
+
+ /* And just changing the colour type works here because the green and blue
+ * channels are being maintained in lock-step with the red/gray:
+ */
+ that->colour_type = PNG_COLOR_TYPE_RGB;
+ }
+
+ else if (that->colour_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_RGB_ALPHA;
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_gray_to_rgb_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ return (colour_type & PNG_COLOR_MASK_COLOR) == 0;
+}
+
+IT(gray_to_rgb,tRNS_to_alpha);
+
+/* png_set_expand */
+static void
+image_transform_png_set_expand_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_expand(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_expand_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ /* The general expand case depends on what the colour type is: */
+ if (that->colour_type == PNG_COLOR_TYPE_PALETTE)
+ image_pixel_convert_PLTE(that, &display->this);
+ else if (that->bit_depth < 8) /* grayscale */
+ that->sample_depth = that->bit_depth = 8;
+
+ if (that->have_tRNS)
+ image_pixel_add_alpha(that, &display->this);
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_expand_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ /* 'expand' should do nothing for RGBA or GA input - no tRNS and the bit
+ * depth is at least 8 already.
+ */
+ return (colour_type & PNG_COLOR_MASK_ALPHA) == 0;
+}
+
+IT(expand,gray_to_rgb);
+
+/* png_set_expand_gray_1_2_4_to_8
+ * LIBPNG BUG: this just does an 'expand'
+ */
+static void
+image_transform_png_set_expand_gray_1_2_4_to_8_set(
+ PNG_CONST image_transform *this, transform_display *that, png_structp pp,
+ png_infop pi)
+{
+ png_set_expand_gray_1_2_4_to_8(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_expand_gray_1_2_4_to_8_mod(
+ PNG_CONST image_transform *this, image_pixel *that, png_structp pp,
+ PNG_CONST transform_display *display)
+{
+ image_transform_png_set_expand_mod(this, that, pp, display);
+}
+
+static int
+image_transform_png_set_expand_gray_1_2_4_to_8_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ return image_transform_png_set_expand_add(this, that, colour_type,
+ bit_depth);
+}
+
+IT(expand_gray_1_2_4_to_8, expand);
+
+/* png_set_strip_16 */
+static void
+image_transform_png_set_strip_16_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_strip_16(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_strip_16_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ if (that->bit_depth == 16)
+ {
+ that->sample_depth = that->bit_depth = 8;
+ if (that->red_sBIT > 8) that->red_sBIT = 8;
+ if (that->green_sBIT > 8) that->green_sBIT = 8;
+ if (that->blue_sBIT > 8) that->blue_sBIT = 8;
+ if (that->alpha_sBIT > 8) that->alpha_sBIT = 8;
+
+# ifndef PNG_READ_16_TO_8_ACCURATE_SCALE_SUPPORTED
+ /* The strip 16 algoirithm drops the low 8 bits rather than calculating
+ * 1/257, so we need to adjust the permitted errors appropriately:
+ */
+ {
+ PNG_CONST double d = (255-128.5)/65535;
+ that->rede += d;
+ that->greene += d;
+ that->bluee += d;
+ that->alphae += d;
+ }
+# endif
+ }
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_strip_16_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(colour_type)
+
+ this->next = *that;
+ *that = this;
+
+ return bit_depth > 8;
+}
+
+IT(strip_16, expand_gray_1_2_4_to_8);
+
+/* png_set_strip_alpha */
+static void
+image_transform_png_set_strip_alpha_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_strip_alpha(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_strip_alpha_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ if (that->colour_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_GRAY;
+ else if (that->colour_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_RGB;
+
+ that->have_tRNS = 0;
+ that->alphaf = 1;
+ that->alphae = 0;
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_strip_alpha_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ return (colour_type & PNG_COLOR_MASK_ALPHA) != 0;
+}
+
+IT(strip_alpha,strip_16);
+
+/* png_set_rgb_to_gray(png_structp, int err_action, double red, double green)
+ * png_set_rgb_to_gray_fixed(png_structp, int err_action, png_fixed_point red,
+ * png_fixed_point green)
+ * png_get_rgb_to_gray_status
+ *
+ * At present the APIs are simply tested using the 16.16 fixed point conversion
+ * values known to be used inside libpng:
+ *
+ * red: 6968
+ * green: 23434
+ * blue: 2366
+ *
+ * NOTE: this currently ignores the gamma because no gamma is being set, the
+ * tests on gamma need to happen in the gamma test set.
+ */
+static void
+image_transform_png_set_rgb_to_gray_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ PNG_CONST int error_action = 1; /* no error, no defines in png.h */
+
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_rgb_to_gray(pp, error_action, -1, -1);
+# else
+ png_set_rgb_to_gray_fixed(pp, error_action, -1, -1);
+# endif
+
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_rgb_to_gray_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ if ((that->colour_type & PNG_COLOR_MASK_COLOR) != 0)
+ {
+ if (that->colour_type == PNG_COLOR_TYPE_PALETTE)
+ image_pixel_convert_PLTE(that, &display->this);
+
+ /* Image now has RGB channels... */
+ that->bluef = that->greenf = that->redf = (that->redf * 6968 +
+ that->greenf * 23434 + that->bluef * 2366) / 32768;
+ that->bluee = that->greene = that->rede = (that->rede * 6968 +
+ that->greene * 23434 + that->bluee * 2366) / 32768 *
+ (1 + DBL_EPSILON * 6);
+
+ /* The sBIT is the minium of the three colour channel sBITs. */
+ if (that->red_sBIT > that->green_sBIT)
+ that->red_sBIT = that->green_sBIT;
+ if (that->red_sBIT > that->blue_sBIT)
+ that->red_sBIT = that->blue_sBIT;
+ that->blue_sBIT = that->green_sBIT = that->red_sBIT;
+
+ /* And zap the colour bit in the type: */
+ if (that->colour_type == PNG_COLOR_TYPE_RGB)
+ that->colour_type = PNG_COLOR_TYPE_GRAY;
+ else if (that->colour_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_GRAY_ALPHA;
+ }
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_rgb_to_gray_add(image_transform *this,
+ PNG_CONST image_transform **that, png_byte colour_type, png_byte bit_depth)
+{
+ UNUSED(bit_depth)
+
+ this->next = *that;
+ *that = this;
+
+ return (colour_type & PNG_COLOR_MASK_COLOR) != 0;
+}
+
+IT(rgb_to_gray,strip_alpha);
+
+/* png_set_background(png_structp, png_const_color_16p background_color,
+ * int background_gamma_code, int need_expand, double background_gamma)
+ * png_set_background_fixed(png_structp, png_const_color_16p background_color,
+ * int background_gamma_code, int need_expand,
+ * png_fixed_point background_gamma)
+ *
+ * As with rgb_to_gray this ignores the gamma.
+*/
+static void
+image_transform_png_set_background_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_color_16 back;
+
+ /* Since we don't know the output bit depth at this point we must use the
+ * input values and ask libpng to expand the chunk as required.
+ */
+ back.index = 255; /* Should not be used */
+ back.gray = back.blue = back.green = back.red =
+ (png_uint_16)((1U << that->this.bit_depth) >> 1);
+
+# ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_background(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1, 0);
+# else
+ png_set_background_fixed(pp, &back, PNG_BACKGROUND_GAMMA_FILE, 1, 0);
+# endif
+
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_background_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ /* Check for tRNS first: */
+ if (that->have_tRNS && that->colour_type != PNG_COLOR_TYPE_PALETTE)
+ image_pixel_add_alpha(that, &display->this);
+
+ /* This is only necessary if the alpha value is less than 1. */
+ if (that->alphaf < 1)
+ {
+ /* Repeat the calculation above and scale the result: */
+ unsigned int tmp = (1U << display->this.bit_depth);
+ double component = (tmp >> 1)/(double)(tmp-1);
+
+ /* Now we do the background calculation without any gamma correction. */
+ if (that->alphaf <= 0)
+ {
+ that->bluef = that->greenf = that->redf = component;
+ that->bluee = that->greene = that->rede = component * DBL_EPSILON;
+ that->blue_sBIT = that->green_sBIT = that->red_sBIT = that->bit_depth;
+ }
+
+ else
+ {
+ component *= 1-that->alphaf;
+ that->redf = that->redf * that->alphaf + component;
+ that->rede = that->rede * that->alphaf + that->redf * 3 * DBL_EPSILON;
+ that->greenf = that->greenf * that->alphaf + component;
+ that->greene = that->greene * that->alphaf + that->greenf * 3 *
+ DBL_EPSILON;
+ that->bluef = that->bluef * that->alphaf + component;
+ that->bluee = that->bluee * that->alphaf + that->bluef * 3 *
+ DBL_EPSILON;
+ }
+
+ /* Remove the alpha type and set the alpha. */
+ that->alphaf = 1;
+ that->alphae = 0;
+
+ if (that->colour_type == PNG_COLOR_TYPE_RGB_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_RGB;
+ else if (that->colour_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ that->colour_type = PNG_COLOR_TYPE_GRAY;
+ }
+
+ this->next->mod(this->next, that, pp, display);
+}
+
+#define image_transform_png_set_background_add image_transform_default_add
+
+IT(background,rgb_to_gray);
+
+static image_transform *PNG_CONST image_transform_first = &ITSTRUCT(background);
+
+static void
+transform_enable(PNG_CONST char *name)
+{
+ /* Everything starts out enabled, so if we see an 'enable' disabled
+ * everything else the first time round.
+ */
+ static int all_disabled = 0;
+ int found_it = 0;
+ image_transform *list = image_transform_first;
+
+ while (list != &image_transform_end)
+ {
+ if (strcmp(list->name, name) == 0)
+ {
+ list->enable = 1;
+ found_it = 1;
+ }
+ else if (!all_disabled)
+ list->enable = 0;
+
+ list = list->list;
+ }
+
+ all_disabled = 1;
+
+ if (!found_it)
+ {
+ fprintf(stderr, "pngvalid: --transform-enable=%s: unknown transform\n",
+ name);
+ exit(1);
+ }
+}
+
+static void
+transform_disable(PNG_CONST char *name)
+{
+ image_transform *list = image_transform_first;
+
+ while (list != &image_transform_end)
+ {
+ if (strcmp(list->name, name) == 0)
+ {
+ list->enable = 0;
+ return;
+ }
+
+ list = list->list;
+ }
+
+ fprintf(stderr, "pngvalid: --transform-disable=%s: unknown transform\n",
+ name);
+ exit(1);
+}
+
+static void
+image_transform_reset_count(void)
+{
+ image_transform *next = image_transform_first;
+ int count = 0;
+
+ while (next != &image_transform_end)
+ {
+ next->local_use = 0;
+ next->next = 0;
+ next = next->list;
+ ++count;
+ }
+
+ /* This can only happen if we every have more than 32 transforms (excluding
+ * the end) in the list.
+ */
+ if (count > 32) abort();
+}
+
+static int
+image_transform_test_counter(png_uint_32 counter, unsigned int max)
+{
+ /* Test the list to see if there is any point contining, given a current
+ * counter and a 'max' value.
+ */
+ image_transform *next = image_transform_first;
+
+ while (next != &image_transform_end)
+ {
+ /* For max 0 or 1 continue until the counter overflows: */
+ counter >>= 1;
+
+ /* Continue if any entry hasn't reacked the max. */
+ if (max > 1 && next->local_use < max)
+ return 1;
+ next = next->list;
+ }
+
+ return max <= 1 && counter == 0;
+}
+
+static png_uint_32
+image_transform_add(PNG_CONST image_transform **this, unsigned int max,
+ png_uint_32 counter, char *name, size_t sizeof_name, size_t *pos,
+ png_byte colour_type, png_byte bit_depth)
+{
+ for (;;) /* until we manage to add something */
+ {
+ png_uint_32 mask;
+ image_transform *list;
+
+ /* Find the next counter value, if the counter is zero this is the start
+ * of the list. This routine always returns the current counter (not the
+ * next) so it returns 0 at the end and expects 0 at the beginning.
+ */
+ if (counter == 0) /* first time */
+ {
+ image_transform_reset_count();
+ if (max <= 1)
+ counter = 1;
+ else
+ counter = random_32();
+ }
+ else /* advance the counter */
+ {
+ switch (max)
+ {
+ case 0: ++counter; break;
+ case 1: counter <<= 1; break;
+ default: counter = random_32(); break;
+ }
+ }
+
+ /* Now add all these items, if possible */
+ *this = &image_transform_end;
+ list = image_transform_first;
+ mask = 1;
+
+ /* Go through the whole list adding anything that the counter selects: */
+ while (list != &image_transform_end)
+ {
+ if ((counter & mask) != 0 && list->enable &&
+ (max == 0 || list->local_use < max))
+ {
+ /* Candidate to add: */
+ if (list->add(list, this, colour_type, bit_depth) || max == 0)
+ {
+ /* Added, so add to the name too. */
+ *pos = safecat(name, sizeof_name, *pos, " +");
+ *pos = safecat(name, sizeof_name, *pos, list->name);
+ }
+
+ else
+ {
+ /* Not useful and max>0, so remvoe it from *this: */
+ *this = list->next;
+ list->next = 0;
+
+ /* And, since we know it isn't useful, stop it being added again
+ * in this run:
+ */
+ list->local_use = max;
+ }
+ }
+
+ mask <<= 1;
+ list = list->list;
+ }
+
+ /* Now if anything was added we have something to do. */
+ if (*this != &image_transform_end)
+ return counter;
+
+ /* Nothing added, but was there anything in there to add? */
+ if (!image_transform_test_counter(counter, max))
+ return 0;
+ }
+}
+
+#ifdef THIS_IS_THE_PROFORMA
+static void
+image_transform_png_set_@_set(PNG_CONST image_transform *this,
+ transform_display *that, png_structp pp, png_infop pi)
+{
+ png_set_@(pp);
+ this->next->set(this->next, that, pp, pi);
+}
+
+static void
+image_transform_png_set_@_mod(PNG_CONST image_transform *this,
+ image_pixel *that, png_structp pp, PNG_CONST transform_display *display)
+{
+ this->next->mod(this->next, that, pp, display);
+}
+
+static int
+image_transform_png_set_@_add(image_transform *this,
+ PNG_CONST image_transform **that, char *name, size_t sizeof_name,
+ size_t *pos, png_byte colour_type, png_byte bit_depth)
+{
+ this->next = *that;
+ *that = this;
+
+ *pos = safecat(name, sizeof_name, *pos, " +@");
+
+ return 1;
+}
+
+IT(@);
+#endif
+
+/* png_set_quantize(png_structp, png_colorp palette, int num_palette,
+ * int maximum_colors, png_const_uint_16p histogram, int full_quantize)
+ *
+ * Very difficult to validate this!
+ */
+/*NOTE: TBD NYI */
+
+/* The data layout transforms are handled by swapping our own channel data,
+ * necessarily these need to happen at the end of the transform list because the
+ * semantic of the channels changes after these are executed. Some of these,
+ * like set_shift and set_packing, can't be done at present because they change
+ * the layout of the data at the sub-sample level so sample() won't get the
+ * right answer.
+ */
+/* png_set_invert_alpha */
+/*NOTE: TBD NYI */
+
+/* png_set_bgr */
+/*NOTE: TBD NYI */
+
+/* png_set_swap_alpha */
+/*NOTE: TBD NYI */
+
+/* png_set_swap */
+/*NOTE: TBD NYI */
+
+/* png_set_filler, (png_structp png_ptr, png_uint_32 filler, int flags)); */
+/*NOTE: TBD NYI */
+
+/* png_set_add_alpha, (png_structp png_ptr, png_uint_32 filler, int flags)); */
+/*NOTE: TBD NYI */
+
+/* png_set_packing */
+/*NOTE: TBD NYI */
+
+/* png_set_packswap */
+/*NOTE: TBD NYI */
+
+/* png_set_invert_mono */
+/*NOTE: TBD NYI */
+
+/* png_set_shift(png_structp, png_const_color_8p true_bits) */
+/*NOTE: TBD NYI */
+
+static int
+test_transform(png_modifier* PNG_CONST pm, png_byte PNG_CONST colour_type,
+ int bdlo, int PNG_CONST bdhi, png_uint_32 max)
+{
+ for (; bdlo <= bdhi; ++bdlo)
+ {
+ PNG_CONST png_byte bit_depth = DEPTH(bdlo);
+ png_uint_32 counter = 0;
+ size_t base_pos;
+ char name[64];
+
+ base_pos = safecat(name, sizeof name, 0, "transform:");
+
+ for (;;)
+ {
+ size_t pos = base_pos;
+ PNG_CONST image_transform *list = 0;
+
+ counter = image_transform_add(&list, max, counter, name, sizeof name,
+ &pos, colour_type, bit_depth);
+
+ if (counter == 0)
+ break;
+
+ /* The command line can change this to checking interlaced images. */
+ transform_test(pm, FILEID(colour_type, bit_depth, pm->interlace_type,
+ 0, 0, 0), list, name);
+
+ if (fail(pm))
+ return 0;
+ }
+ }
+
+ return 1; /* keep going */
+}
+
+static void
+perform_transform_test(png_modifier *pm)
+{
+ /* Test each colour type over the valid range of bit depths (expressed as
+ * log2(bit_depth) in turn, stop as soon as any error is detected.
+ */
+ if (!test_transform(pm, 0, 0, READ_BDHI, 1))
+ return;
+
+ if (!test_transform(pm, 2, 3, READ_BDHI, 1))
+ return;
+
+ if (!test_transform(pm, 3, 0, 3, 1))
+ return;
+
+ if (!test_transform(pm, 4, 3, READ_BDHI, 1))
+ return;
+
+ if (!test_transform(pm, 6, 3, READ_BDHI, 1))
+ return;
+}
+
+
+/********************************* GAMMA TESTS ********************************/
+/* Gamma test images. */
+typedef struct gamma_modification
+{
+ png_modification this;
+ png_fixed_point gamma;
+} gamma_modification;
+
+static int
+gamma_modify(png_modifier *pm, png_modification *me, int add)
+{
+ UNUSED(add)
+ /* This simply dumps the given gamma value into the buffer. */
+ png_save_uint_32(pm->buffer, 4);
+ png_save_uint_32(pm->buffer+4, CHUNK_gAMA);
+ png_save_uint_32(pm->buffer+8, ((gamma_modification*)me)->gamma);
+ return 1;
+}
+
+static void
+gamma_modification_init(gamma_modification *me, png_modifier *pm, double gammad)
+{
+ double g;
+
+ modification_init(&me->this);
+ me->this.chunk = CHUNK_gAMA;
+ me->this.modify_fn = gamma_modify;
+ me->this.add = CHUNK_PLTE;
+ g = floor(gammad * 100000 + .5);
+ me->gamma = (png_fixed_point)g;
+ me->this.next = pm->modifications;
+ pm->modifications = &me->this;
+}
+
+typedef struct srgb_modification
+{
+ png_modification this;
+ png_byte intent;
+} srgb_modification;
+
+static int
+srgb_modify(png_modifier *pm, png_modification *me, int add)
+{
+ UNUSED(add)
+ /* As above, ignore add and just make a new chunk */
+ png_save_uint_32(pm->buffer, 1);
+ png_save_uint_32(pm->buffer+4, CHUNK_sRGB);
+ pm->buffer[8] = ((srgb_modification*)me)->intent;
+ return 1;
+}
+
+static void
+srgb_modification_init(srgb_modification *me, png_modifier *pm, png_byte intent)
+{
+ modification_init(&me->this);
+ me->this.chunk = CHUNK_sBIT;
+
+ if (intent <= 3) /* if valid, else *delete* sRGB chunks */
+ {
+ me->this.modify_fn = srgb_modify;
+ me->this.add = CHUNK_PLTE;
+ me->intent = intent;
+ }
+
+ else
+ {
+ me->this.modify_fn = 0;
+ me->this.add = 0;
+ me->intent = 0;
+ }
+
+ me->this.next = pm->modifications;
+ pm->modifications = &me->this;
+}
+
+typedef struct sbit_modification
+{
+ png_modification this;
+ png_byte sbit;
+} sbit_modification;
+
+static int
+sbit_modify(png_modifier *pm, png_modification *me, int add)
+{
+ png_byte sbit = ((sbit_modification*)me)->sbit;
+ if (pm->bit_depth > sbit)
+ {
+ int cb = 0;
+ switch (pm->colour_type)
+ {
+ case 0:
+ cb = 1;
+ break;
+
+ case 2:
+ case 3:
+ cb = 3;
+ break;
+
+ case 4:
+ cb = 2;
+ break;
+
+ case 6:
+ cb = 4;
+ break;
+
+ default:
+ png_error(pm->this.pread,
+ "unexpected colour type in sBIT modification");
+ }
+
+ png_save_uint_32(pm->buffer, cb);
+ png_save_uint_32(pm->buffer+4, CHUNK_sBIT);
+
+ while (cb > 0)
+ (pm->buffer+8)[--cb] = sbit;
+
+ return 1;
+ }
+ else if (!add)
+ {
+ /* Remove the sBIT chunk */
+ pm->buffer_count = pm->buffer_position = 0;
+ return 1;
+ }
+ else
+ return 0; /* do nothing */
+}
+
+static void
+sbit_modification_init(sbit_modification *me, png_modifier *pm, png_byte sbit)
+{
+ modification_init(&me->this);
+ me->this.chunk = CHUNK_sBIT;
+ me->this.modify_fn = sbit_modify;
+ me->this.add = CHUNK_PLTE;
+ me->sbit = sbit;
+ me->this.next = pm->modifications;
+ pm->modifications = &me->this;
+}
+
+/* Reader callbacks and implementations, where they differ from the standard
+ * ones.
+ */
+typedef struct gamma_display
+{
+ standard_display this;
+
+ /* Parameters */
+ png_modifier* pm;
+ double file_gamma;
+ double screen_gamma;
+ png_byte sbit;
+ int threshold_test;
+ PNG_CONST char* name;
+ int speed;
+ int use_input_precision;
+ int strip16;
+
+ /* Local variables */
+ double maxerrout;
+ double maxerrpc;
+ double maxerrabs;
+} gamma_display;
+
+static void
+gamma_display_init(gamma_display *dp, png_modifier *pm, png_uint_32 id,
+ double file_gamma, double screen_gamma, png_byte sbit, int threshold_test,
+ int speed, int use_input_precision, int strip16)
+{
+ /* Standard fields */
+ standard_display_init(&dp->this, &pm->this, id, 0/*do_interlace*/);
+
+ /* Parameter fields */
+ dp->pm = pm;
+ dp->file_gamma = file_gamma;
+ dp->screen_gamma = screen_gamma;
+ dp->sbit = sbit;
+ dp->threshold_test = threshold_test;
+ dp->speed = speed;
+ dp->use_input_precision = use_input_precision;
+ dp->strip16 = strip16;
+
+ /* Local variable fields */
+ dp->maxerrout = dp->maxerrpc = dp->maxerrabs = 0;
+}
+
+static void
+gamma_info_imp(gamma_display *dp, png_structp pp, png_infop pi)
+{
+ /* Reuse the standard stuff as appropriate. */
+ standard_info_part1(&dp->this, pp, pi);
+
+ /* If requested strip 16 to 8 bits - this is handled automagically below
+ * because the output bit depth is read from the library. Note that there
+ * are interactions with sBIT but, internally, libpng makes sbit at most
+ * PNG_MAX_GAMMA_8 when doing the following.
+ */
+ if (dp->strip16)
+# ifdef PNG_READ_16_TO_8_SUPPORTED
+ png_set_strip_16(pp);
+# else
+ png_error(pp, "strip16 (16 to 8 bit conversion) not supported");
+# endif
+
+ png_read_update_info(pp, pi);
+
+ /* Now we may get a different cbRow: */
+ standard_info_part2(&dp->this, pp, pi, 1 /*images*/);
+}
+
+static void
+gamma_info(png_structp pp, png_infop pi)
+{
+ gamma_info_imp(png_get_progressive_ptr(pp), pp, pi);
+}
+
+static void
+gamma_image_validate(gamma_display *dp, png_structp pp, png_infop pi,
+ png_const_bytep pRow)
+{
+ /* Get some constants derived from the input and output file formats: */
+ PNG_CONST png_byte sbit = dp->sbit;
+ PNG_CONST double file_gamma = dp->file_gamma;
+ PNG_CONST double screen_gamma = dp->screen_gamma;
+ PNG_CONST int use_input_precision = dp->use_input_precision;
+ PNG_CONST int speed = dp->speed;
+ PNG_CONST png_byte in_ct = dp->this.colour_type;
+ PNG_CONST png_byte in_bd = dp->this.bit_depth;
+ PNG_CONST png_uint_32 w = dp->this.w;
+ PNG_CONST png_uint_32 h = dp->this.h;
+ PNG_CONST size_t cbRow = dp->this.cbRow;
+ PNG_CONST png_byte out_ct = png_get_color_type(pp, pi);
+ PNG_CONST png_byte out_bd = png_get_bit_depth(pp, pi);
+ PNG_CONST unsigned int outmax = (1U<<out_bd)-1;
+ PNG_CONST double maxabs = abserr(dp->pm, out_bd);
+ PNG_CONST double maxout = outerr(dp->pm, out_bd);
+ PNG_CONST double maxpc = pcerr(dp->pm, out_bd);
+
+ /* There are three sources of error, firstly the quantization in the
+ * file encoding, determined by sbit and/or the file depth, secondly
+ * the output (screen) gamma and thirdly the output file encoding.
+ *
+ * Since this API receives the screen and file gamma in double
+ * precision it is possible to calculate an exact answer given an input
+ * pixel value. Therefore we assume that the *input* value is exact -
+ * sample/maxsample - calculate the corresponding gamma corrected
+ * output to the limits of double precision arithmetic and compare with
+ * what libpng returns.
+ *
+ * Since the library must quantize the output to 8 or 16 bits there is
+ * a fundamental limit on the accuracy of the output of +/-.5 - this
+ * quantization limit is included in addition to the other limits
+ * specified by the paramaters to the API. (Effectively, add .5
+ * everywhere.)
+ *
+ * The behavior of the 'sbit' paramter is defined by section 12.5
+ * (sample depth scaling) of the PNG spec. That section forces the
+ * decoder to assume that the PNG values have been scaled if sBIT is
+ * present:
+ *
+ * png-sample = floor( input-sample * (max-out/max-in) + .5);
+ *
+ * This means that only a subset of the possible PNG values should
+ * appear in the input. However, the spec allows the encoder to use a
+ * variety of approximations to the above and doesn't require any
+ * restriction of the values produced.
+ *
+ * Nevertheless the spec requires that the upper 'sBIT' bits of the
+ * value stored in a PNG file be the original sample bits.
+ * Consequently the code below simply scales the top sbit bits by
+ * (1<<sbit)-1 to obtain an original sample value.
+ *
+ * Because there is limited precision in the input it is arguable that
+ * an acceptable result is any valid result from input-.5 to input+.5.
+ * The basic tests below do not do this, however if
+ * 'use_input_precision' is set a subsequent test is performed below.
+ */
+ PNG_CONST int processing = (fabs(screen_gamma*file_gamma-1) >=
+ PNG_GAMMA_THRESHOLD && !dp->threshold_test && !speed && in_ct != 3) ||
+ in_bd != out_bd;
+
+ PNG_CONST unsigned int samples_per_pixel = (out_ct & 2U) ? 3U : 1U;
+
+ PNG_CONST double gamma_correction = 1/(file_gamma*screen_gamma);/* Overall */
+
+ double maxerrout = 0, maxerrabs = 0, maxerrpc = 0;
+ png_uint_32 y;
+
+ for (y=0; y<h; ++y, pRow += cbRow)
+ {
+ unsigned int s, x;
+ png_byte std[STANDARD_ROWMAX];
+
+ transform_row(pp, std, in_ct, in_bd, y);
+
+ if (processing)
+ {
+ for (x=0; x<w; ++x) for (s=0; s<samples_per_pixel; ++s)
+ {
+ /* Input sample values: */
+ PNG_CONST unsigned int
+ id = sample(std, in_ct, in_bd, x, s);
+
+ PNG_CONST unsigned int
+ od = sample(pRow, out_ct, out_bd, x, s);
+
+ PNG_CONST unsigned int
+ isbit = id >> (in_bd-sbit);
+
+ double i, input_sample, encoded_sample, output;
+ double encoded_error, error;
+ double es_lo, es_hi;
+
+ /* First check on the 'perfect' result obtained from the
+ * digitized input value, id, and compare this against the
+ * actual digitized result, 'od'. 'i' is the input result
+ * in the range 0..1:
+ *
+ * NOTE: sBIT should be taken into account here but isn't,
+ * as described above.
+ */
+ i = isbit; i /= (1U<<sbit)-1;
+
+ /* Then get the gamma corrected version of 'i' and compare
+ * to 'od', any error less than .5 is insignificant - just
+ * quantization of the output value to the nearest digital
+ * value (nevertheless the error is still recorded - it's
+ * interesting ;-)
+ */
+ encoded_sample = pow(i, gamma_correction) * outmax;
+ encoded_error = fabs(od-encoded_sample);
+
+ if (encoded_error > maxerrout)
+ maxerrout = encoded_error;
+
+ if (encoded_error < .5+maxout)
+ continue;
+
+ /* There may be an error, so calculate the actual sample
+ * values - unencoded light intensity values. Note that
+ * in practice these are not unencoded because they
+ * include a 'viewing correction' to decrease or
+ * (normally) increase the perceptual contrast of the
+ * image. There's nothing we can do about this - we don't
+ * know what it is - so assume the unencoded value is
+ * perceptually linear.
+ */
+ input_sample = pow(i, 1/file_gamma); /* In range 0..1 */
+ output = od;
+ output /= outmax;
+ output = pow(output, screen_gamma);
+
+ /* Now we have the numbers for real errors, both absolute
+ * values as as a percentage of the correct value (output):
+ */
+ error = fabs(input_sample-output);
+
+ if (error > maxerrabs)
+ maxerrabs = error;
+
+ /* The following is an attempt to ignore the tendency of
+ * quantization to dominate the percentage errors for low
+ * output sample values:
+ */
+ if (input_sample*maxpc > .5+maxabs)
+ {
+ double percentage_error = error/input_sample;
+ if (percentage_error > maxerrpc) maxerrpc = percentage_error;
+ }
+
+ /* Now calculate the digitization limits for
+ * 'encoded_sample' using the 'max' values. Note that
+ * maxout is in the encoded space but maxpc and maxabs are
+ * in linear light space.
+ *
+ * First find the maximum error in linear light space,
+ * range 0..1:
+ */
+ {
+ double tmp = input_sample * maxpc;
+ if (tmp < maxabs) tmp = maxabs;
+
+ /* Low bound - the minimum of the three: */
+ es_lo = encoded_sample - maxout;
+
+ if (es_lo > 0 && input_sample-tmp > 0)
+ {
+ double low_value = outmax * pow(input_sample-tmp,
+ 1/screen_gamma);
+ if (low_value < es_lo) es_lo = low_value;
+ }
+
+ else
+ es_lo = 0;
+
+ es_hi = encoded_sample + maxout;
+
+ if (es_hi < outmax && input_sample+tmp < 1)
+ {
+ double high_value = outmax * pow(input_sample+tmp,
+ 1/screen_gamma);
+ if (high_value > es_hi) es_hi = high_value;
+ }
+
+ else
+ es_hi = outmax;
+ }
+
+ /* The primary test is that the final encoded value
+ * returned by the library should be between the two limits
+ * (inclusive) that were calculated above. At this point
+ * quantization of the output must be taken into account.
+ */
+ if (od+.5 < es_lo || od-.5 > es_hi)
+ {
+ /* There has been an error in processing. */
+ double is_lo, is_hi;
+
+ if (use_input_precision)
+ {
+ /* Ok, something is wrong - this actually happens in
+ * current libpng sbit processing. Assume that the
+ * input value (id, adjusted for sbit) can be
+ * anywhere between value-.5 and value+.5 - quite a
+ * large range if sbit is low.
+ */
+ double tmp = (isbit - .5)/((1U<<sbit)-1);
+
+ if (tmp > 0)
+ {
+ is_lo = outmax * pow(tmp, gamma_correction) - maxout;
+ if (is_lo < 0) is_lo = 0;
+ }
+
+ else
+ is_lo = 0;
+
+ tmp = (isbit + .5)/((1U<<sbit)-1);
+
+ if (tmp < 1)
+ {
+ is_hi = outmax * pow(tmp, gamma_correction) + maxout;
+ if (is_hi > outmax) is_hi = outmax;
+ }
+
+ else
+ is_hi = outmax;
+
+ if (!(od+.5 < is_lo || od-.5 > is_hi))
+ continue;
+ }
+ else
+ is_lo = es_lo, is_hi = es_hi;
+
+ {
+ char msg[256];
+
+ sprintf(msg,
+ "error: %.3f; %u{%u;%u} -> %u not %.2f (%.1f-%.1f)",
+ od-encoded_sample, id, sbit, isbit, od,
+ encoded_sample, is_lo, is_hi);
+
+ png_warning(pp, msg);
+ }
+ }
+ }
+ }
+
+ else if (!speed && memcmp(std, pRow, cbRow) != 0)
+ {
+ char msg[64];
+
+ /* No transform is expected on the threshold tests. */
+ sprintf(msg, "gamma: below threshold row %d changed", y);
+
+ png_error(pp, msg);
+ }
+ } /* row (y) loop */
+
+ dp->maxerrout = maxerrout;
+ dp->maxerrabs = maxerrabs;
+ dp->maxerrpc = maxerrpc;
+ dp->this.ps->validated = 1;
+}
+
+static void
+gamma_end(png_structp pp, png_infop pi)
+{
+ gamma_display *dp = png_get_progressive_ptr(pp);
+
+ gamma_image_validate(dp, pp, pi, dp->this.ps->image);
+}
+
+/* A single test run checking a gamma transformation.
+ *
+ * maxabs: maximum absolute error as a fraction
+ * maxout: maximum output error in the output units
+ * maxpc: maximum percentage error (as a percentage)
+ */
+static void
+gamma_test(png_modifier *pmIn, PNG_CONST png_byte colour_typeIn,
+ PNG_CONST png_byte bit_depthIn, PNG_CONST int interlace_typeIn,
+ PNG_CONST double file_gammaIn, PNG_CONST double screen_gammaIn,
+ PNG_CONST png_byte sbitIn, PNG_CONST int threshold_testIn,
+ PNG_CONST char *name, PNG_CONST int speedIn,
+ PNG_CONST int use_input_precisionIn, PNG_CONST int strip16In)
+{
+ gamma_display d;
+ context(&pmIn->this, fault);
+
+ gamma_display_init(&d, pmIn, FILEID(colour_typeIn, bit_depthIn,
+ interlace_typeIn, 0, 0, 0), file_gammaIn, screen_gammaIn, sbitIn,
+ threshold_testIn, speedIn, use_input_precisionIn, strip16In);
+
+ Try
+ {
+ png_structp pp;
+ png_infop pi;
+ gamma_modification gamma_mod;
+ srgb_modification srgb_mod;
+ sbit_modification sbit_mod;
+
+ /* Make an appropriate modifier to set the PNG file gamma to the
+ * given gamma value and the sBIT chunk to the given precision.
+ */
+ d.pm->modifications = NULL;
+ gamma_modification_init(&gamma_mod, d.pm, d.file_gamma);
+ srgb_modification_init(&srgb_mod, d.pm, 127 /*delete*/);
+ sbit_modification_init(&sbit_mod, d.pm, d.sbit);
+
+ modification_reset(d.pm->modifications);
+
+ /* Get a png_struct for writing the image. */
+ pp = set_modifier_for_read(d.pm, &pi, d.this.id, name);
+
+ /* Set up gamma processing. */
+#ifdef PNG_FLOATING_POINT_SUPPORTED
+ png_set_gamma(pp, d.screen_gamma, d.file_gamma);
+#else
+ {
+ png_fixed_point s = floor(d.screen_gamma*100000+.5);
+ png_fixed_point f = floor(d.file_gamma*100000+.5);
+ png_set_gamma_fixed(pp, s, f);
+ }
+#endif
+
+ /* Introduce the correct read function. */
+ if (d.pm->this.progressive)
+ {
+ /* Share the row function with the standard implementation. */
+ png_set_progressive_read_fn(pp, &d, gamma_info, progressive_row,
+ gamma_end);
+
+ /* Now feed data into the reader until we reach the end: */
+ modifier_progressive_read(d.pm, pp, pi);
+ }
+ else
+ {
+ /* modifier_read expects a png_modifier* */
+ png_set_read_fn(pp, d.pm, modifier_read);
+
+ /* Check the header values: */
+ png_read_info(pp, pi);
+
+ /* Process the 'info' requirements. Only one image is generated */
+ gamma_info_imp(&d, pp, pi);
+
+ sequential_row(&d.this, pp, pi, NULL, d.this.ps->image);
+
+ gamma_image_validate(&d, pp, pi, d.this.ps->image);
+ }
+
+ modifier_reset(d.pm);
+
+ if (d.pm->log && !d.threshold_test && !d.speed)
+ fprintf(stderr, "%d bit %s %s: max error %f (%.2g, %2g%%)\n",
+ d.this.bit_depth, colour_types[d.this.colour_type], d.name,
+ d.maxerrout, d.maxerrabs, 100*d.maxerrpc);
+
+ /* Log the summary values too. */
+ if (d.this.colour_type == 0 || d.this.colour_type == 4)
+ {
+ switch (d.this.bit_depth)
+ {
+ case 1:
+ break;
+
+ case 2:
+ if (d.maxerrout > d.pm->error_gray_2)
+ d.pm->error_gray_2 = d.maxerrout;
+
+ break;
+
+ case 4:
+ if (d.maxerrout > d.pm->error_gray_4)
+ d.pm->error_gray_4 = d.maxerrout;
+
+ break;
+
+ case 8:
+ if (d.maxerrout > d.pm->error_gray_8)
+ d.pm->error_gray_8 = d.maxerrout;
+
+ break;
+
+ case 16:
+ if (d.maxerrout > d.pm->error_gray_16)
+ d.pm->error_gray_16 = d.maxerrout;
+
+ break;
+
+ default:
+ png_error(pp, "bad bit depth (internal: 1)");
+ }
+ }
+
+ else if (d.this.colour_type == 2 || d.this.colour_type == 6)
+ {
+ switch (d.this.bit_depth)
+ {
+ case 8:
+
+ if (d.maxerrout > d.pm->error_color_8)
+ d.pm->error_color_8 = d.maxerrout;
+
+ break;
+
+ case 16:
+
+ if (d.maxerrout > d.pm->error_color_16)
+ d.pm->error_color_16 = d.maxerrout;
+
+ break;
+
+ default:
+ png_error(pp, "bad bit depth (internal: 2)");
+ }
+ }
+ }
+
+ Catch(fault)
+ modifier_reset((png_modifier*)fault);
+}
+
+static void gamma_threshold_test(png_modifier *pm, png_byte colour_type,
+ png_byte bit_depth, int interlace_type, double file_gamma,
+ double screen_gamma)
+{
+ size_t pos = 0;
+ char name[64];
+ pos = safecat(name, sizeof name, pos, "threshold ");
+ pos = safecatd(name, sizeof name, pos, file_gamma, 3);
+ pos = safecat(name, sizeof name, pos, "/");
+ pos = safecatd(name, sizeof name, pos, screen_gamma, 3);
+
+ (void)gamma_test(pm, colour_type, bit_depth, interlace_type, file_gamma,
+ screen_gamma, bit_depth, 1, name, 0 /*speed*/, 0 /*no input precision*/,
+ 0 /*no strip16*/);
+}
+
+static void
+perform_gamma_threshold_tests(png_modifier *pm)
+{
+ png_byte colour_type = 0;
+ png_byte bit_depth = 0;
+
+ while (next_format(&colour_type, &bit_depth))
+ {
+ double test_gamma = 1.0;
+ while (test_gamma >= .4)
+ {
+ /* There's little point testing the interlacing vs non-interlacing,
+ * but this can be set from the command line.
+ */
+ gamma_threshold_test(pm, colour_type, bit_depth, pm->interlace_type,
+ test_gamma, 1/test_gamma);
+ test_gamma *= .95;
+ }
+
+ /* And a special test for sRGB */
+ gamma_threshold_test(pm, colour_type, bit_depth, pm->interlace_type,
+ .45455, 2.2);
+
+ if (fail(pm))
+ return;
+ }
+}
+
+static void gamma_transform_test(png_modifier *pm,
+ PNG_CONST png_byte colour_type, PNG_CONST png_byte bit_depth,
+ PNG_CONST int interlace_type, PNG_CONST double file_gamma,
+ PNG_CONST double screen_gamma, PNG_CONST png_byte sbit, PNG_CONST int speed,
+ PNG_CONST int use_input_precision, PNG_CONST int strip16)
+{
+ size_t pos = 0;
+ char name[64];
+
+ if (sbit != bit_depth)
+ {
+ pos = safecat(name, sizeof name, pos, "sbit(");
+ pos = safecatn(name, sizeof name, pos, sbit);
+ pos = safecat(name, sizeof name, pos, ") ");
+ }
+
+ else
+ pos = safecat(name, sizeof name, pos, "gamma ");
+
+ if (strip16)
+ pos = safecat(name, sizeof name, pos, "16to8 ");
+
+ pos = safecatd(name, sizeof name, pos, file_gamma, 3);
+ pos = safecat(name, sizeof name, pos, "->");
+ pos = safecatd(name, sizeof name, pos, screen_gamma, 3);
+
+ gamma_test(pm, colour_type, bit_depth, interlace_type, file_gamma,
+ screen_gamma, sbit, 0, name, speed, use_input_precision, strip16);
+}
+
+static void perform_gamma_transform_tests(png_modifier *pm, int speed)
+{
+ png_byte colour_type = 0;
+ png_byte bit_depth = 0;
+
+ /* Ignore palette images - the gamma correction happens on the palette entry,
+ * haven't got the tests for this yet.
+ */
+ while (next_format(&colour_type, &bit_depth)) if (colour_type != 3)
+ {
+ unsigned int i, j;
+
+ for (i=0; i<pm->ngammas; ++i) for (j=0; j<pm->ngammas; ++j) if (i != j)
+ {
+ gamma_transform_test(pm, colour_type, bit_depth, pm->interlace_type,
+ 1/pm->gammas[i], pm->gammas[j], bit_depth, speed,
+ pm->use_input_precision, 0 /*do not strip16*/);
+
+ if (fail(pm))
+ return;
+ }
+ }
+}
+
+static void perform_gamma_sbit_tests(png_modifier *pm, int speed)
+{
+ png_byte sbit;
+
+ /* The only interesting cases are colour and grayscale, alpha is ignored here
+ * for overall speed. Only bit depths 8 and 16 are tested.
+ */
+ for (sbit=pm->sbitlow; sbit<(1<<READ_BDHI); ++sbit)
+ {
+ unsigned int i, j;
+
+ for (i=0; i<pm->ngammas; ++i)
+ {
+ for (j=0; j<pm->ngammas; ++j)
+ {
+ if (i != j)
+ {
+ if (sbit < 8)
+ {
+ gamma_transform_test(pm, 0, 8, pm->interlace_type,
+ 1/pm->gammas[i], pm->gammas[j], sbit, speed,
+ pm->use_input_precision_sbit, 0 /*strip16*/);
+
+ if (fail(pm))
+ return;
+
+ gamma_transform_test(pm, 2, 8, pm->interlace_type,
+ 1/pm->gammas[i], pm->gammas[j], sbit, speed,
+ pm->use_input_precision_sbit, 0 /*strip16*/);
+
+ if (fail(pm))
+ return;
+ }
+
+#ifdef DO_16BIT
+ gamma_transform_test(pm, 0, 16, pm->interlace_type,
+ 1/pm->gammas[i], pm->gammas[j], sbit, speed,
+ pm->use_input_precision_sbit, 0 /*strip16*/);
+
+ if (fail(pm))
+ return;
+
+ gamma_transform_test(pm, 2, 16, pm->interlace_type,
+ 1/pm->gammas[i], pm->gammas[j], sbit, speed,
+ pm->use_input_precision_sbit, 0 /*strip16*/);
+
+ if (fail(pm))
+ return;
+#endif
+ }
+ }
+ }
+ }
+}
+
+/* Note that this requires a 16 bit source image but produces 8 bit output, so
+ * we only need the 16bit write support.
+ */
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+static void perform_gamma_strip16_tests(png_modifier *pm, int speed)
+{
+# ifndef PNG_MAX_GAMMA_8
+# define PNG_MAX_GAMMA_8 11
+# endif
+ /* Include the alpha cases here. Note that sbit matches the internal value
+ * used by the library - otherwise we will get spurious errors from the
+ * internal sbit style approximation.
+ *
+ * The threshold test is here because otherwise the 16 to 8 conversion will
+ * proceed *without* gamma correction, and the tests above will fail (but not
+ * by much) - this could be fixed, it only appears with the -g option.
+ */
+ unsigned int i, j;
+ for (i=0; i<pm->ngammas; ++i)
+ {
+ for (j=0; j<pm->ngammas; ++j)
+ {
+ if (i != j &&
+ fabs(pm->gammas[j]/pm->gammas[i]-1) >= PNG_GAMMA_THRESHOLD)
+ {
+ gamma_transform_test(pm, 0, 16, pm->interlace_type, 1/pm->gammas[i],
+ pm->gammas[j], PNG_MAX_GAMMA_8, speed,
+ pm->use_input_precision_16to8, 1 /*strip16*/);
+
+ if (fail(pm))
+ return;
+
+ gamma_transform_test(pm, 2, 16, pm->interlace_type, 1/pm->gammas[i],
+ pm->gammas[j], PNG_MAX_GAMMA_8, speed,
+ pm->use_input_precision_16to8, 1 /*strip16*/);
+
+ if (fail(pm))
+ return;
+
+ gamma_transform_test(pm, 4, 16, pm->interlace_type, 1/pm->gammas[i],
+ pm->gammas[j], PNG_MAX_GAMMA_8, speed,
+ pm->use_input_precision_16to8, 1 /*strip16*/);
+
+ if (fail(pm))
+ return;
+
+ gamma_transform_test(pm, 6, 16, pm->interlace_type, 1/pm->gammas[i],
+ pm->gammas[j], PNG_MAX_GAMMA_8, speed,
+ pm->use_input_precision_16to8, 1 /*strip16*/);
+
+ if (fail(pm))
+ return;
+ }
+ }
+ }
+}
+#endif /* 16 to 8 bit conversion */
+
+static void
+perform_gamma_test(png_modifier *pm, int speed, int summary)
+{
+ /* First some arbitrary no-transform tests: */
+ if (!speed && pm->test_gamma_threshold)
+ {
+ perform_gamma_threshold_tests(pm);
+
+ if (fail(pm))
+ return;
+ }
+
+ /* Now some real transforms. */
+ if (pm->test_gamma_transform)
+ {
+ perform_gamma_transform_tests(pm, speed);
+
+ if (summary)
+ {
+ printf("Gamma correction error summary\n\n");
+ printf("The printed value is the maximum error in the pixel values\n");
+ printf("calculated by the libpng gamma correction code. The error\n");
+ printf("is calculated as the difference between the output pixel\n");
+ printf("value (always an integer) and the ideal value from the\n");
+ printf("libpng specification (typically not an integer).\n\n");
+
+ printf("Expect this value to be less than .5 for 8 bit formats,\n");
+ printf("less than 1 for formats with fewer than 8 bits and a small\n");
+ printf("number (typically less than 5) for the 16 bit formats.\n");
+ printf("For performance reasons the value for 16 bit formats\n");
+ printf("increases when the image file includes an sBIT chunk.\n\n");
+
+ printf(" 2 bit gray: %.5f\n", pm->error_gray_2);
+ printf(" 4 bit gray: %.5f\n", pm->error_gray_4);
+ printf(" 8 bit gray: %.5f\n", pm->error_gray_8);
+ printf(" 8 bit color: %.5f\n", pm->error_color_8);
+#ifdef DO_16BIT
+ printf(" 16 bit gray: %.5f\n", pm->error_gray_16);
+ printf(" 16 bit color: %.5f\n", pm->error_color_16);
+#endif
+ }
+ }
+
+ /* The sbit tests produce much larger errors: */
+ if (pm->test_gamma_sbit)
+ {
+ pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 =
+ pm->error_gray_16 = pm->error_color_8 = pm->error_color_16 = 0;
+ perform_gamma_sbit_tests(pm, speed);
+
+ if (summary)
+ {
+ printf("Gamma correction with sBIT:\n");
+
+ if (pm->sbitlow < 8U)
+ {
+ printf(" 2 bit gray: %.5f\n", pm->error_gray_2);
+ printf(" 4 bit gray: %.5f\n", pm->error_gray_4);
+ printf(" 8 bit gray: %.5f\n", pm->error_gray_8);
+ printf(" 8 bit color: %.5f\n", pm->error_color_8);
+ }
+
+ #ifdef DO_16BIT
+ printf(" 16 bit gray: %.5f\n", pm->error_gray_16);
+ printf(" 16 bit color: %.5f\n", pm->error_color_16);
+ #endif
+ }
+ }
+
+#ifdef PNG_READ_16_TO_8_SUPPORTED
+ if (pm->test_gamma_strip16)
+ {
+ /* The 16 to 8 bit strip operations: */
+ pm->error_gray_2 = pm->error_gray_4 = pm->error_gray_8 =
+ pm->error_gray_16 = pm->error_color_8 = pm->error_color_16 = 0;
+ perform_gamma_strip16_tests(pm, speed);
+
+ if (summary)
+ {
+ printf("Gamma correction with 16 to 8 bit reduction:\n");
+ printf(" 16 bit gray: %.5f\n", pm->error_gray_16);
+ printf(" 16 bit color: %.5f\n", pm->error_color_16);
+ }
+ }
+#endif
+}
+
+/* INTERLACE MACRO VALIDATION */
+/* This is copied verbatim from the specification, it is simply the pass
+ * number in which each pixel in each 8x8 tile appears. The array must
+ * be indexed adam7[y][x] and notice that the pass numbers are based at
+ * 1, not 0 - the base libpng uses.
+ */
+static PNG_CONST
+png_byte adam7[8][8] =
+{
+ { 1,6,4,6,2,6,4,6 },
+ { 7,7,7,7,7,7,7,7 },
+ { 5,6,5,6,5,6,5,6 },
+ { 7,7,7,7,7,7,7,7 },
+ { 3,6,4,6,3,6,4,6 },
+ { 7,7,7,7,7,7,7,7 },
+ { 5,6,5,6,5,6,5,6 },
+ { 7,7,7,7,7,7,7,7 }
+};
+
+/* This routine validates all the interlace support macros in png.h for
+ * a variety of valid PNG widths and heights. It uses a number of similarly
+ * named internal routines that feed off the above array.
+ */
+static png_uint_32
+png_pass_start_row(int pass)
+{
+ int x, y;
+ ++pass;
+ for (y=0; y<8; ++y) for (x=0; x<8; ++x) if (adam7[y][x] == pass)
+ return y;
+ return 0xf;
+}
+
+static png_uint_32
+png_pass_start_col(int pass)
+{
+ int x, y;
+ ++pass;
+ for (x=0; x<8; ++x) for (y=0; y<8; ++y) if (adam7[y][x] == pass)
+ return x;
+ return 0xf;
+}
+
+static int
+png_pass_row_shift(int pass)
+{
+ int x, y, base=(-1), inc=8;
+ ++pass;
+ for (y=0; y<8; ++y) for (x=0; x<8; ++x) if (adam7[y][x] == pass)
+ {
+ if (base == (-1))
+ base = y;
+ else if (base == y)
+ {}
+ else if (inc == y-base)
+ base=y;
+ else if (inc == 8)
+ inc = y-base, base=y;
+ else if (inc != y-base)
+ return 0xff; /* error - more than one 'inc' value! */
+ }
+
+ if (base == (-1)) return 0xfe; /* error - no row in pass! */
+
+ /* The shift is always 1, 2 or 3 - no pass has all the rows! */
+ switch (inc)
+ {
+case 2: return 1;
+case 4: return 2;
+case 8: return 3;
+default: break;
+ }
+
+ /* error - unrecognized 'inc' */
+ return (inc << 8) + 0xfd;
+}
+
+static int
+png_pass_col_shift(int pass)
+{
+ int x, y, base=(-1), inc=8;
+ ++pass;
+ for (x=0; x<8; ++x) for (y=0; y<8; ++y) if (adam7[y][x] == pass)
+ {
+ if (base == (-1))
+ base = x;
+ else if (base == x)
+ {}
+ else if (inc == x-base)
+ base=x;
+ else if (inc == 8)
+ inc = x-base, base=x;
+ else if (inc != x-base)
+ return 0xff; /* error - more than one 'inc' value! */
+ }
+
+ if (base == (-1)) return 0xfe; /* error - no row in pass! */
+
+ /* The shift is always 1, 2 or 3 - no pass has all the rows! */
+ switch (inc)
+ {
+case 1: return 0; /* pass 7 has all the columns */
+case 2: return 1;
+case 4: return 2;
+case 8: return 3;
+default: break;
+ }
+
+ /* error - unrecognized 'inc' */
+ return (inc << 8) + 0xfd;
+}
+
+static png_uint_32
+png_row_from_pass_row(png_uint_32 yIn, int pass)
+{
+ /* By examination of the array: */
+ switch (pass)
+ {
+case 0: return yIn * 8;
+case 1: return yIn * 8;
+case 2: return yIn * 8 + 4;
+case 3: return yIn * 4;
+case 4: return yIn * 4 + 2;
+case 5: return yIn * 2;
+case 6: return yIn * 2 + 1;
+default: break;
+ }
+
+ return 0xff; /* bad pass number */
+}
+
+static png_uint_32
+png_col_from_pass_col(png_uint_32 xIn, int pass)
+{
+ /* By examination of the array: */
+ switch (pass)
+ {
+case 0: return xIn * 8;
+case 1: return xIn * 8 + 4;
+case 2: return xIn * 4;
+case 3: return xIn * 4 + 2;
+case 4: return xIn * 2;
+case 5: return xIn * 2 + 1;
+case 6: return xIn;
+default: break;
+ }
+
+ return 0xff; /* bad pass number */
+}
+
+static int
+png_row_in_interlace_pass(png_uint_32 y, int pass)
+{
+ /* Is row 'y' in pass 'pass'? */
+ int x;
+ y &= 7;
+ ++pass;
+ for (x=0; x<8; ++x) if (adam7[y][x] == pass)
+ return 1;
+
+ return 0;
+}
+
+static int
+png_col_in_interlace_pass(png_uint_32 x, int pass)
+{
+ /* Is column 'x' in pass 'pass'? */
+ int y;
+ x &= 7;
+ ++pass;
+ for (y=0; y<8; ++y) if (adam7[y][x] == pass)
+ return 1;
+
+ return 0;
+}
+
+static png_uint_32
+png_pass_rows(png_uint_32 height, int pass)
+{
+ png_uint_32 tiles = height>>3;
+ png_uint_32 rows = 0;
+ unsigned int x, y;
+
+ height &= 7;
+ ++pass;
+ for (y=0; y<8; ++y) for (x=0; x<8; ++x) if (adam7[y][x] == pass)
+ {
+ rows += tiles;
+ if (y < height) ++rows;
+ break; /* i.e. break the 'x', column, loop. */
+ }
+
+ return rows;
+}
+
+static png_uint_32
+png_pass_cols(png_uint_32 width, int pass)
+{
+ png_uint_32 tiles = width>>3;
+ png_uint_32 cols = 0;
+ unsigned int x, y;
+
+ width &= 7;
+ ++pass;
+ for (x=0; x<8; ++x) for (y=0; y<8; ++y) if (adam7[y][x] == pass)
+ {
+ cols += tiles;
+ if (x < width) ++cols;
+ break; /* i.e. break the 'y', row, loop. */
+ }
+
+ return cols;
+}
+
+static void
+perform_interlace_macro_validation(void)
+{
+ /* The macros to validate, first those that depend only on pass:
+ *
+ * PNG_PASS_START_ROW(pass)
+ * PNG_PASS_START_COL(pass)
+ * PNG_PASS_ROW_SHIFT(pass)
+ * PNG_PASS_COL_SHIFT(pass)
+ */
+ int pass;
+
+ for (pass=0; pass<7; ++pass)
+ {
+ png_uint_32 m, f, v;
+
+ m = PNG_PASS_START_ROW(pass);
+ f = png_pass_start_row(pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_START_ROW(%d) = %u != %x\n", pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_PASS_START_COL(pass);
+ f = png_pass_start_col(pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_START_COL(%d) = %u != %x\n", pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_PASS_ROW_SHIFT(pass);
+ f = png_pass_row_shift(pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_ROW_SHIFT(%d) = %u != %x\n", pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_PASS_COL_SHIFT(pass);
+ f = png_pass_col_shift(pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_COL_SHIFT(%d) = %u != %x\n", pass, m, f);
+ exit(1);
+ }
+
+ /* Macros that depend on the image or sub-image height too:
+ *
+ * PNG_PASS_ROWS(height, pass)
+ * PNG_PASS_COLS(width, pass)
+ * PNG_ROW_FROM_PASS_ROW(yIn, pass)
+ * PNG_COL_FROM_PASS_COL(xIn, pass)
+ * PNG_ROW_IN_INTERLACE_PASS(y, pass)
+ * PNG_COL_IN_INTERLACE_PASS(x, pass)
+ */
+ for (v=0;;)
+ {
+ /* First the base 0 stuff: */
+ m = PNG_ROW_FROM_PASS_ROW(v, pass);
+ f = png_row_from_pass_row(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_ROW_FROM_PASS_ROW(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_COL_FROM_PASS_COL(v, pass);
+ f = png_col_from_pass_col(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_COL_FROM_PASS_COL(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_ROW_IN_INTERLACE_PASS(v, pass);
+ f = png_row_in_interlace_pass(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_ROW_IN_INTERLACE_PASS(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_COL_IN_INTERLACE_PASS(v, pass);
+ f = png_col_in_interlace_pass(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_COL_IN_INTERLACE_PASS(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ /* Then the base 1 stuff: */
+ ++v;
+ m = PNG_PASS_ROWS(v, pass);
+ f = png_pass_rows(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_ROWS(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ m = PNG_PASS_COLS(v, pass);
+ f = png_pass_cols(v, pass);
+ if (m != f)
+ {
+ fprintf(stderr, "PNG_PASS_COLS(%u, %d) = %u != %x\n",
+ v, pass, m, f);
+ exit(1);
+ }
+
+ /* Move to the next v - the stepping algorithm starts skipping
+ * values above 1024.
+ */
+ if (v > 1024)
+ {
+ if (v == PNG_UINT_31_MAX)
+ break;
+
+ v = (v << 1) ^ v;
+ if (v >= PNG_UINT_31_MAX)
+ v = PNG_UINT_31_MAX-1;
+ }
+ }
+ }
+}
+
+/* main program */
+int main(int argc, PNG_CONST char **argv)
+{
+ volatile int summary = 1; /* Print the error summary at the end */
+
+ /* Create the given output file on success: */
+ PNG_CONST char *volatile touch = NULL;
+
+ /* This is an array of standard gamma values (believe it or not I've seen
+ * every one of these mentioned somewhere.)
+ *
+ * In the following list the most useful values are first!
+ */
+ static double
+ gammas[]={2.2, 1.0, 2.2/1.45, 1.8, 1.5, 2.4, 2.5, 2.62, 2.9};
+
+ png_modifier pm;
+ context(&pm.this, fault);
+
+ modifier_init(&pm);
+
+ /* Preallocate the image buffer, because we know how big it needs to be,
+ * note that, for testing purposes, it is deliberately mis-aligned.
+ */
+ pm.this.image = malloc(2*TRANSFORM_IMAGEMAX+1);
+
+ if (pm.this.image != NULL)
+ {
+ /* Ignore OOM at this point - the 'ensure' routine above will allocate
+ * the array appropriately.
+ */
+ ++(pm.this.image);
+ pm.this.cb_image = 2*TRANSFORM_IMAGEMAX;
+ }
+
+ /* Default to error on warning: */
+ pm.this.treat_warnings_as_errors = 1;
+
+ /* Store the test gammas */
+ pm.gammas = gammas;
+ pm.ngammas = 0; /* default to off */
+ pm.sbitlow = 8U; /* because libpng doesn't do sBIT below 8! */
+ pm.use_input_precision_16to8 = 1U; /* Because of the way libpng does it */
+
+ /* Some default values (set the behavior for 'make check' here).
+ * These values simply control the maximum error permitted in the gamma
+ * transformations. The practial limits for human perception are described
+ * below (the setting for maxpc16), however for 8 bit encodings it isn't
+ * possible to meet the accepted capabilities of human vision - i.e. 8 bit
+ * images can never be good enough, regardless of encoding.
+ */
+ pm.maxout8 = .1; /* Arithmetic error in *encoded* value */
+ pm.maxabs8 = .00005; /* 1/20000 */
+ pm.maxpc8 = .499; /* I.e., .499% fractional error */
+ pm.maxout16 = .499; /* Error in *encoded* value */
+ pm.maxabs16 = .00005;/* 1/20000 */
+
+ /* NOTE: this is a reasonable perceptual limit. We assume that humans can
+ * perceive light level differences of 1% over a 100:1 range, so we need to
+ * maintain 1 in 10000 accuracy (in linear light space), which is what the
+ * following guarantees. It also allows significantly higher errors at
+ * higher 16 bit values, which is important for performance. The actual
+ * maximum 16 bit error is about +/-1.9 in the fixed point implementation but
+ * this is only allowed for values >38149 by the following:
+ */
+ pm.maxpc16 = .005; /* I.e., 1/200% - 1/20000 */
+
+ /* Now parse the command line options. */
+ while (--argc >= 1)
+ {
+ if (strcmp(*++argv, "-v") == 0)
+ pm.this.verbose = 1;
+
+ else if (strcmp(*argv, "-l") == 0)
+ pm.log = 1;
+
+ else if (strcmp(*argv, "-q") == 0)
+ summary = pm.this.verbose = pm.log = 0;
+
+ else if (strcmp(*argv, "-w") == 0)
+ pm.this.treat_warnings_as_errors = 0;
+
+ else if (strcmp(*argv, "--speed") == 0)
+ pm.this.speed = 1, pm.ngammas = (sizeof gammas)/(sizeof gammas[0]),
+ pm.test_standard = 0;
+
+ else if (strcmp(*argv, "--size") == 0)
+ pm.test_size = 1;
+
+ else if (strcmp(*argv, "--nosize") == 0)
+ pm.test_size = 0;
+
+ else if (strcmp(*argv, "--standard") == 0)
+ pm.test_standard = 1;
+
+ else if (strcmp(*argv, "--nostandard") == 0)
+ pm.test_standard = 0;
+
+ else if (strcmp(*argv, "--transform") == 0)
+ pm.test_transform = 1;
+
+ else if (strcmp(*argv, "--notransform") == 0)
+ pm.test_transform = 0;
+
+ else if (strncmp(*argv, "--transform-disable=",
+ sizeof "--transform-disable") == 0)
+ {
+ pm.test_transform = 1;
+ transform_disable(*argv + sizeof "--transform-disable");
+ }
+
+ else if (strncmp(*argv, "--transform-enable=",
+ sizeof "--transform-enable") == 0)
+ {
+ pm.test_transform = 1;
+ transform_enable(*argv + sizeof "--transform-enable");
+ }
+
+ else if (strcmp(*argv, "--gamma") == 0)
+ {
+ /* Just do two gamma tests here (2.2 and linear) for speed: */
+ pm.ngammas = 2U;
+ pm.test_gamma_threshold = 1;
+ pm.test_gamma_transform = 1;
+ pm.test_gamma_sbit = 1;
+ pm.test_gamma_strip16 = 1;
+ }
+
+ else if (strcmp(*argv, "--nogamma") == 0)
+ pm.ngammas = 0;
+
+ else if (strcmp(*argv, "--gamma-threshold") == 0)
+ pm.ngammas = 2U, pm.test_gamma_threshold = 1;
+
+ else if (strcmp(*argv, "--nogamma-threshold") == 0)
+ pm.test_gamma_threshold = 0;
+
+ else if (strcmp(*argv, "--gamma-transform") == 0)
+ pm.ngammas = 2U, pm.test_gamma_transform = 1;
+
+ else if (strcmp(*argv, "--nogamma-transform") == 0)
+ pm.test_gamma_transform = 0;
+
+ else if (strcmp(*argv, "--gamma-sbit") == 0)
+ pm.ngammas = 2U, pm.test_gamma_sbit = 1;
+
+ else if (strcmp(*argv, "--nogamma-sbit") == 0)
+ pm.test_gamma_sbit = 0;
+
+ else if (strcmp(*argv, "--gamma-16-to-8") == 0)
+ pm.ngammas = 2U, pm.test_gamma_strip16 = 1;
+
+ else if (strcmp(*argv, "--nogamma-16-to-8") == 0)
+ pm.test_gamma_strip16 = 0;
+
+ else if (strcmp(*argv, "--all-gammas") == 0)
+ pm.ngammas = (sizeof gammas)/(sizeof gammas[0]);
+
+ else if (strcmp(*argv, "--progressive-read") == 0)
+ pm.this.progressive = 1;
+
+ else if (strcmp(*argv, "--interlace") == 0)
+ pm.interlace_type = PNG_INTERLACE_ADAM7;
+
+ else if (argc >= 1 && strcmp(*argv, "--sbitlow") == 0)
+ --argc, pm.sbitlow = (png_byte)atoi(*++argv);
+
+ else if (argc >= 1 && strcmp(*argv, "--touch") == 0)
+ --argc, touch = *++argv;
+
+ else if (argc >= 1 && strncmp(*argv, "--max", 4) == 0)
+ {
+ --argc;
+
+ if (strcmp(4+*argv, "abs8") == 0)
+ pm.maxabs8 = atof(*++argv);
+
+ else if (strcmp(4+*argv, "abs16") == 0)
+ pm.maxabs16 = atof(*++argv);
+
+ else if (strcmp(4+*argv, "out8") == 0)
+ pm.maxout8 = atof(*++argv);
+
+ else if (strcmp(4+*argv, "out16") == 0)
+ pm.maxout16 = atof(*++argv);
+
+ else if (strcmp(4+*argv, "pc8") == 0)
+ pm.maxpc8 = atof(*++argv);
+
+ else if (strcmp(4+*argv, "pc16") == 0)
+ pm.maxpc16 = atof(*++argv);
+
+ else
+ {
+ fprintf(stderr, "pngvalid: %s: unknown 'max' option\n", *argv);
+ exit(1);
+ }
+ }
+
+ else
+ {
+ fprintf(stderr, "pngvalid: %s: unknown argument\n", *argv);
+ exit(1);
+ }
+ }
+
+ /* If pngvalid is run with no arguments default to a reasonable set of the
+ * tests.
+ */
+ if (pm.test_standard == 0 && pm.test_size == 0 && pm.test_transform == 0 &&
+ pm.ngammas == 0)
+ {
+ pm.test_standard = 1;
+ pm.test_size = 1;
+ pm.test_transform = 1;
+ pm.ngammas = 3U;
+ }
+
+ if (pm.ngammas > 0 &&
+ pm.test_gamma_threshold == 0 && pm.test_gamma_transform == 0 &&
+ pm.test_gamma_sbit == 0 && pm.test_gamma_strip16 == 0)
+ {
+ pm.test_gamma_threshold = 1;
+ pm.test_gamma_transform = 1;
+ pm.test_gamma_sbit = 1;
+ pm.test_gamma_strip16 = 1;
+ }
+
+ else if (pm.ngammas == 0)
+ {
+ /* Nothing to test so turn everything off: */
+ pm.test_gamma_threshold = 0;
+ pm.test_gamma_transform = 0;
+ pm.test_gamma_sbit = 0;
+ pm.test_gamma_strip16 = 0;
+ }
+
+ Try
+ {
+ /* Make useful base images */
+ make_transform_images(&pm.this);
+
+ /* Perform the standard and gamma tests. */
+ if (pm.test_standard)
+ {
+ perform_interlace_macro_validation();
+ perform_standard_test(&pm);
+ perform_error_test(&pm);
+ }
+
+ /* Various oddly sized images: */
+ if (pm.test_size)
+ {
+ make_size_images(&pm.this);
+ perform_size_test(&pm);
+ }
+
+ /* Combinatorial transforms: */
+ if (pm.test_transform)
+ perform_transform_test(&pm);
+
+ if (pm.ngammas > 0)
+ perform_gamma_test(&pm, pm.this.speed != 0,
+ summary && !pm.this.speed);
+ }
+
+ Catch(fault)
+ {
+ fprintf(stderr, "pngvalid: test aborted (probably failed in cleanup)\n");
+ if (!pm.this.verbose)
+ {
+ if (pm.this.error[0] != 0)
+ fprintf(stderr, "pngvalid: first error: %s\n", pm.this.error);
+
+ fprintf(stderr, "pngvalid: run with -v to see what happened\n");
+ }
+ exit(1);
+ }
+
+ if (summary && !pm.this.speed)
+ {
+ printf("Results using %s point arithmetic %s\n",
+#if defined(PNG_FLOATING_ARITHMETIC_SUPPORTED) || PNG_LIBPNG_VER < 10500
+ "floating",
+#else
+ "fixed",
+#endif
+ (pm.this.nerrors || (pm.this.treat_warnings_as_errors &&
+ pm.this.nwarnings)) ? "(errors)" : (pm.this.nwarnings ?
+ "(warnings)" : "(no errors or warnings)")
+ );
+ printf("Allocated memory statistics (in bytes):\n"
+ "\tread %lu maximum single, %lu peak, %lu total\n"
+ "\twrite %lu maximum single, %lu peak, %lu total\n",
+ (unsigned long)pm.this.read_memory_pool.max_max,
+ (unsigned long)pm.this.read_memory_pool.max_limit,
+ (unsigned long)pm.this.read_memory_pool.max_total,
+ (unsigned long)pm.this.write_memory_pool.max_max,
+ (unsigned long)pm.this.write_memory_pool.max_limit,
+ (unsigned long)pm.this.write_memory_pool.max_total);
+ }
+
+ /* Do this here to provoke memory corruption errors in memory not directly
+ * allocated by libpng - not a complete test, but better than nothing.
+ */
+ store_delete(&pm.this);
+
+ /* Error exit if there are any errors, and maybe if there are any
+ * warnings.
+ */
+ if (pm.this.nerrors || (pm.this.treat_warnings_as_errors &&
+ pm.this.nwarnings))
+ {
+ if (!pm.this.verbose)
+ fprintf(stderr, "pngvalid: %s\n", pm.this.error);
+
+ fprintf(stderr, "pngvalid: %d errors, %d warnings\n", pm.this.nerrors,
+ pm.this.nwarnings);
+
+ exit(1);
+ }
+
+ /* Success case. */
+ if (touch != NULL)
+ {
+ FILE *fsuccess = fopen(touch, "wt");
+
+ if (fsuccess != NULL)
+ {
+ int error = 0;
+ fprintf(fsuccess, "PNG validation succeeded\n");
+ fflush(fsuccess);
+ error = ferror(fsuccess);
+
+ if (fclose(fsuccess) || error)
+ {
+ fprintf(stderr, "%s: write failed\n", touch);
+ exit(1);
+ }
+ }
+ }
+
+ return 0;
+}