blob: 1efab64538a081f3dc1611da944e36524428316a [file] [log] [blame]
/* pngvalid.c - validate libpng by constructing then reading png files.
*
* Last changed in libpng 1.6.18 [July 23, 2015]
* Copyright (c) 2014-2015 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.
*/
#define _POSIX_SOURCE 1
#define _ISOC99_SOURCE 1 /* For floating point */
#define _GNU_SOURCE 1 /* For the floating point exception extension */
#include <signal.h>
#include <stdio.h>
#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H)
# include <config.h>
#endif
#ifdef HAVE_FEENABLEEXCEPT /* from config.h, if included */
# include <fenv.h>
#endif
#ifndef FE_DIVBYZERO
# define FE_DIVBYZERO 0
#endif
#ifndef FE_INVALID
# define FE_INVALID 0
#endif
#ifndef FE_OVERFLOW
# define FE_OVERFLOW 0
#endif
/* Define the following to use this test against your installed libpng, rather
* than the one being built here:
*/
#ifdef PNG_FREESTANDING_TESTS
# include <png.h>
#else
# include "../../png.h"
#endif
#ifdef PNG_ZLIB_HEADER
# include PNG_ZLIB_HEADER
#else
# include <zlib.h> /* For crc32 */
#endif
/* 1.6.1 added support for the configure test harness, which uses 77 to indicate
* a skipped test, in earlier versions we need to succeed on a skipped test, so:
*/
#if PNG_LIBPNG_VER < 10601
# define SKIP 0
#else
# define SKIP 77
#endif
/* pngvalid requires write support and one of the fixed or floating point APIs.
*/
#if defined(PNG_WRITE_SUPPORTED) &&\
(defined(PNG_FIXED_POINT_SUPPORTED) || defined(PNG_FLOATING_POINT_SUPPORTED))
#if PNG_LIBPNG_VER < 10500
/* This deliberately 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 default build: */
#define PNG_WRITE_16BIT_SUPPORTED
#define PNG_READ_16BIT_SUPPORTED
/* This comes from pnglibconf.h afer 1.5: */
#define PNG_FP_1 100000
#define PNG_GAMMA_THRESHOLD_FIXED\
((png_fixed_point)(PNG_GAMMA_THRESHOLD * PNG_FP_1))
#endif
#if PNG_LIBPNG_VER < 10600
/* 1.6.0 constifies many APIs, the following exists to allow pngvalid to be
* compiled against earlier versions.
*/
# define png_const_structp png_structp
#endif
#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 *****************************/
#ifdef PNG_FREESTANDING_TESTS
# include <cexcept.h>
#else
# include "../visupng/cexcept.h"
#endif
#ifdef __cplusplus
# define this not_the_cpp_this
# define new not_the_cpp_new
# define voidcast(type, value) static_cast<type>(value)
#else
# define voidcast(type, value) (value)
#endif /* __cplusplus */
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
/* This macro returns the number of elements in an array as an (unsigned int),
* it is necessary to avoid the inability of certain versions of GCC to use
* the value of a compile-time constant when performing range checks. It must
* be passed an array name.
*/
#define ARRAY_SIZE(a) ((unsigned int)((sizeof (a))/(sizeof (a)[0])))
/******************************* 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);
}
#ifdef PNG_READ_TRANSFORMS_SUPPORTED
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);
}
#endif
static PNG_CONST char invalid[] = "invalid";
static PNG_CONST char sep[] = ": ";
static PNG_CONST char *colour_types[8] =
{
"grayscale", invalid, "truecolour", "indexed-colour",
"grayscale with alpha", invalid, "truecolour with alpha", invalid
};
#ifdef PNG_READ_SUPPORTED
/* Convert a double precision value to fixed point. */
static png_fixed_point
fix(double d)
{
d = floor(d * PNG_FP_1 + .5);
return (png_fixed_point)d;
}
#endif /* PNG_READ_SUPPORTED */
/* Generate random bytes. 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" (Pseudo-Random Bit Sequences and Noise
* Generation.)
*/
static void
make_random_bytes(png_uint_32* seed, void* pv, size_t size)
{
png_uint_32 u0 = seed[0], u1 = seed[1];
png_bytep bytes = voidcast(png_bytep, pv);
/* 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.
*/
size_t i;
for (i=0; i<size; ++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;
*bytes++ = (png_byte)u;
}
seed[0] = u0;
seed[1] = u1;
}
static void
make_four_random_bytes(png_uint_32* seed, png_bytep bytes)
{
make_random_bytes(seed, bytes, 4);
}
#if defined PNG_READ_SUPPORTED || defined PNG_WRITE_tRNS_SUPPORTED
static void
randomize(void *pv, size_t size)
{
static png_uint_32 random_seed[2] = {0x56789abc, 0xd};
make_random_bytes(random_seed, pv, size);
}
#define RANDOMIZE(this) randomize(&(this), sizeof (this))
#endif /* READ || WRITE_tRNS */
#ifdef PNG_READ_SUPPORTED
static unsigned int
random_mod(unsigned int max)
{
unsigned int x;
RANDOMIZE(x);
return x % max; /* 0 .. max-1 */
}
#if (defined PNG_READ_RGB_TO_GRAY_SUPPORTED) ||\
(defined PNG_READ_FILLER_SUPPORTED)
static int
random_choice(void)
{
unsigned char x;
RANDOMIZE(x);
return x & 1;
}
#endif
#endif /* PNG_READ_SUPPORTED */
/* 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. 'palette' is an
* index of the palette to use for formats with a palette otherwise a boolean
* indicating if a tRNS chunk was generated.
*/
#define FILEID(col, depth, palette, interlace, width, height, do_interlace) \
((png_uint_32)((col) + ((depth)<<3) + ((palette)<<8) + ((interlace)<<13) + \
(((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 PALETTE_FROM_ID(id) (((id) >> 8) & 0x1f)
#define INTERLACE_FROM_ID(id) ((png_byte)(((id) >> 13) & 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 bit_depth, unsigned int npalette, int interlace_type,
png_uint_32 w, png_uint_32 h, int do_interlace)
{
pos = safecat(buffer, bufsize, pos, colour_types[colour_type]);
if (colour_type == 3) /* must have a palette */
{
pos = safecat(buffer, bufsize, pos, "[");
pos = safecatn(buffer, bufsize, pos, npalette);
pos = safecat(buffer, bufsize, pos, "]");
}
else if (npalette != 0)
pos = safecat(buffer, bufsize, pos, "+tRNS");
pos = safecat(buffer, bufsize, pos, " ");
pos = safecatn(buffer, bufsize, pos, 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),
DEPTH_FROM_ID(id), PALETTE_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
/* The following defines the number of different palettes to generate for
* each log bit depth of a colour type 3 standard image.
*/
#define PALETTE_COUNT(bit_depth) ((bit_depth) > 4 ? 1U : 16U)
static int
next_format(png_bytep colour_type, png_bytep bit_depth,
unsigned int* palette_number, int low_depth_gray, int tRNS)
{
if (*bit_depth == 0)
{
*colour_type = 0;
if (low_depth_gray)
*bit_depth = 1;
else
*bit_depth = 8;
*palette_number = 0;
return 1;
}
if (*colour_type < 4/*no alpha channel*/)
{
/* Add multiple palettes for colour type 3, one image with tRNS
* and one without for other non-alpha formats:
*/
unsigned int pn = ++*palette_number;
png_byte ct = *colour_type;
if (((ct == 0/*GRAY*/ || ct/*RGB*/ == 2) && tRNS && pn < 2) ||
(ct == 3/*PALETTE*/ && pn < PALETTE_COUNT(*bit_depth)))
return 1;
/* No: next bit depth */
*palette_number = 0;
}
*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;
}
}
#ifdef PNG_READ_TRANSFORMS_SUPPORTED
static unsigned int
sample(png_const_bytep row, png_byte colour_type, png_byte bit_depth,
png_uint_32 x, unsigned int sample_index, int swap16, int littleendian)
{
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 */
/* Multiple channels; select one: */
if (colour_type & (2+4))
bit_index += sample_index * bit_depth;
}
/* 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)
{
if (swap16)
return (*++row << 8) + result;
else
return (result << 8) + *++row;
}
/* Less than 8 bits per sample. By default PNG has the big end of
* the egg on the left of the screen, but if littleendian is set
* then the big end is on the right.
*/
bit_index &= 7;
if (!littleendian)
bit_index = 8-bit_index-bit_depth;
return (result >> bit_index) & ((1U<<bit_depth)-1);
}
#endif /* PNG_READ_TRANSFORMS_SUPPORTED */
/* 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);
}
#ifdef PNG_READ_SUPPORTED
/* Copy a complete row of pixels, taking into account potential partial
* bytes at the end.
*/
static void
row_copy(png_bytep toBuffer, png_const_bytep fromBuffer, unsigned int bitWidth)
{
memcpy(toBuffer, fromBuffer, bitWidth >> 3);
if ((bitWidth & 7) != 0)
{
unsigned int mask;
toBuffer += bitWidth >> 3;
fromBuffer += bitWidth >> 3;
/* The remaining bits are in the top of the byte, the mask is the bits to
* retain.
*/
mask = 0xff >> (bitWidth & 7);
*toBuffer = (png_byte)((*toBuffer & mask) | (*fromBuffer & ~mask));
}
}
/* 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 PNG_LIBPNG_VER < 10506
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;
}
#else
/* From libpng-1.5.6 the overwrite should be fixed, so compare the trailing
* bits too:
*/
if (memcmp(pa, pb, (bit_width+7)>>3) == 0)
return 0;
#endif
/* Return the index of the changed byte. */
{
png_uint_32 where = 0;
while (pa[where] == pb[where]) ++where;
return 1+where;
}
}
#endif /* PNG_READ_SUPPORTED */
/*************************** 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 store_palette_entry /* record of a single palette entry */
{
png_byte red;
png_byte green;
png_byte blue;
png_byte alpha;
} store_palette_entry, store_palette[256];
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 */
int npalette; /* Number of entries in palette */
store_palette_entry* palette; /* May be NULL */
} 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;
int noptions; /* number of options below: */
struct {
unsigned char option; /* option number, 0..30 */
unsigned char setting; /* setting (unset,invalid,on,off) */
} options[16];
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 */
png_size_t cb_image; /* Size of this buffer */
png_size_t cb_row; /* Row size of the image(s) */
png_uint_32 image_h; /* Number of rows in a single image */
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;
store_palette_entry* palette;
int npalette;
} png_store;
/* Initialization and cleanup */
static void
store_pool_mark(png_bytep mark)
{
static png_uint_32 store_seed[2] = { 0x12345678, 1};
make_four_random_bytes(store_seed, mark);
}
#ifdef PNG_READ_SUPPORTED
/* 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;
}
}
#endif /* PNG_READ_SUPPORTED */
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->cb_row = 0;
ps->image_h = 0;
ps->pwrite = NULL;
ps->piwrite = NULL;
ps->writepos = 0;
ps->new.prev = NULL;
ps->palette = NULL;
ps->npalette = 0;
ps->noptions = 0;
}
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;
if (ps->palette != NULL)
{
free(ps->palette);
ps->palette = NULL;
ps->npalette = 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 = voidcast(png_store_buffer*, 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;
if ((*ppf)->palette != NULL)
{
free((*ppf)->palette);
(*ppf)->palette = NULL;
(*ppf)->npalette = 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 = voidcast(png_store_file*, 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;
pf->palette = ps->palette;
pf->npalette = ps->npalette;
ps->palette = 0;
ps->npalette = 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_const_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;
}
/* Verbose output to the error stream: */
static void
store_verbose(png_store *ps, png_const_structp pp, png_const_charp prefix,
png_const_charp message)
{
char buffer[512];
if (prefix)
fputs(prefix, stderr);
(void)store_message(ps, pp, buffer, sizeof buffer, 0, message);
fputs(buffer, stderr);
fputc('\n', stderr);
}
/* Log an error or warning - the relevant count is always incremented. */
static void
store_log(png_store* ps, png_const_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)
store_verbose(ps, pp, is_error ? "error: " : "warning: ", message);
}
#ifdef PNG_READ_SUPPORTED
/* Internal error function, called with a png_store but no libpng stuff. */
static void
internal_error(png_store *ps, png_const_charp message)
{
store_log(ps, NULL, message, 1 /* error */);
/* And finally throw an exception. */
{
struct exception_context *the_exception_context = &ps->exception_context;
Throw ps;
}
}
#endif /* PNG_READ_SUPPORTED */
/* Functions to use as PNG callbacks. */
static void PNGCBAPI
store_error(png_structp ppIn, png_const_charp message) /* PNG_NORETURN */
{
png_const_structp pp = ppIn;
png_store *ps = voidcast(png_store*, 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 PNGCBAPI
store_warning(png_structp ppIn, png_const_charp message)
{
png_const_structp pp = ppIn;
png_store *ps = voidcast(png_store*, png_get_error_ptr(pp));
if (!ps->expect_warning)
store_log(ps, pp, message, 0 /* warning */);
else
ps->saw_warning = 1;
}
/* These somewhat odd functions are used when reading an image to ensure that
* the buffer is big enough, the png_structp is for errors.
*/
/* Return a single row from the correct image. */
static png_bytep
store_image_row(PNG_CONST png_store* ps, png_const_structp pp, int nImage,
png_uint_32 y)
{
png_size_t coffset = (nImage * ps->image_h + y) * (ps->cb_row + 5) + 2;
if (ps->image == NULL)
png_error(pp, "no allocated image");
if (coffset + ps->cb_row + 3 > ps->cb_image)
png_error(pp, "image too small");
return ps->image + coffset;
}
static void
store_image_free(png_store *ps, png_const_structp pp)
{
if (ps->image != NULL)
{
png_bytep image = ps->image;
if (image[-1] != 0xed || image[ps->cb_image] != 0xfe)
{
if (pp != NULL)
png_error(pp, "png_store image overwrite (1)");
else
store_log(ps, NULL, "png_store image overwrite (2)", 1);
}
ps->image = NULL;
ps->cb_image = 0;
--image;
free(image);
}
}
static void
store_ensure_image(png_store *ps, png_const_structp pp, int nImages,
png_size_t cbRow, png_uint_32 cRows)
{
png_size_t cb = nImages * cRows * (cbRow + 5);
if (ps->cb_image < cb)
{
png_bytep image;
store_image_free(ps, pp);
/* The buffer is deliberately mis-aligned. */
image = voidcast(png_bytep, malloc(cb+2));
if (image == NULL)
{
/* Called from the startup - ignore the error for the moment. */
if (pp == NULL)
return;
png_error(pp, "OOM allocating image buffer");
}
/* These magic tags are used to detect overwrites above. */
++image;
image[-1] = 0xed;
image[cb] = 0xfe;
ps->image = image;
ps->cb_image = cb;
}
/* We have an adequate sized image; lay out the rows. There are 2 bytes at
* the start and three at the end of each (this ensures that the row
* alignment starts out odd - 2+1 and changes for larger images on each row.)
*/
ps->cb_row = cbRow;
ps->image_h = cRows;
/* For error checking, the whole buffer is set to 10110010 (0xb2 - 178).
* This deliberately doesn't match the bits in the size test image which are
* outside the image; these are set to 0xff (all 1). To make the row
* comparison work in the 'size' test case the size rows are pre-initialized
* to the same value prior to calling 'standard_row'.
*/
memset(ps->image, 178, cb);
/* Then put in the marks. */
while (--nImages >= 0)
{
png_uint_32 y;
for (y=0; y<cRows; ++y)
{
png_bytep row = store_image_row(ps, pp, nImages, y);
/* The markers: */
row[-2] = 190;
row[-1] = 239;
row[cbRow] = 222;
row[cbRow+1] = 173;
row[cbRow+2] = 17;
}
}
}
#ifdef PNG_READ_SUPPORTED
static void
store_image_check(PNG_CONST png_store* ps, png_const_structp pp, int iImage)
{
png_const_bytep image = ps->image;
if (image[-1] != 0xed || image[ps->cb_image] != 0xfe)
png_error(pp, "image overwrite");
else
{
png_size_t cbRow = ps->cb_row;
png_uint_32 rows = ps->image_h;
image += iImage * (cbRow+5) * ps->image_h;
image += 2; /* skip image first row markers */
while (rows-- > 0)
{
if (image[-2] != 190 || image[-1] != 239)
png_error(pp, "row start overwritten");
if (image[cbRow] != 222 || image[cbRow+1] != 173 ||
image[cbRow+2] != 17)
png_error(pp, "row end overwritten");
image += cbRow+5;
}
}
}
#endif /* PNG_READ_SUPPORTED */
static void PNGCBAPI
store_write(png_structp ppIn, png_bytep pb, png_size_t st)
{
png_const_structp pp = ppIn;
png_store *ps = voidcast(png_store*, 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 PNGCBAPI
store_flush(png_structp ppIn)
{
UNUSED(ppIn) /*DOES NOTHING*/
}
#ifdef PNG_READ_SUPPORTED
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;
}
#ifdef PNG_READ_TRANSFORMS_SUPPORTED
/* 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;
}
#endif
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 PNGCBAPI
store_read(png_structp ppIn, png_bytep pb, png_size_t st)
{
png_const_structp pp = ppIn;
png_store *ps = voidcast(png_store*, 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));
}
#endif /* PNG_READ_SUPPORTED */
/* The caller must fill this in: */
static store_palette_entry *
store_write_palette(png_store *ps, int npalette)
{
if (ps->pwrite == NULL)
store_log(ps, NULL, "attempt to write palette without write stream", 1);
if (ps->palette != NULL)
png_error(ps->pwrite, "multiple store_write_palette calls");
/* This function can only return NULL if called with '0'! */
if (npalette > 0)
{
ps->palette = voidcast(store_palette_entry*, malloc(npalette *
sizeof *ps->palette));
if (ps->palette == NULL)
png_error(ps->pwrite, "store new palette: OOM");
ps->npalette = npalette;
}
return ps->palette;
}
#ifdef PNG_READ_SUPPORTED
static store_palette_entry *
store_current_palette(png_store *ps, int *npalette)
{
/* This is an internal error (the call has been made outside a read
* operation.)
*/
if (ps->current == NULL)
{
store_log(ps, ps->pread, "no current stream for palette", 1);
return NULL;
}
/* The result may be null if there is no palette. */
*npalette = ps->current->npalette;
return ps->current->palette;
}
#endif /* PNG_READ_SUPPORTED */
/***************************** MEMORY MANAGEMENT*** ***************************/
#ifdef PNG_USER_MEM_SUPPORTED
/* 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_const_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_const_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, (PNG_CONST void*)(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 PNGCBAPI
store_malloc(png_structp ppIn, png_alloc_size_t cb)
{
png_const_structp pp = ppIn;
store_pool *pool = voidcast(store_pool*, png_get_mem_ptr(pp));
store_memory *new = voidcast(store_memory*, 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
{
/* NOTE: the PNG user malloc function cannot use the png_ptr it is passed
* other than to retrieve the allocation pointer! libpng calls the
* store_malloc callback in two basic cases:
*
* 1) From png_malloc; png_malloc will do a png_error itself if NULL is
* returned.
* 2) From png_struct or png_info structure creation; png_malloc is
* to return so cleanup can be performed.
*
* To handle this store_malloc can log a message, but can't do anything
* else.
*/
store_log(pool->store, pp, "out of memory", 1 /* is_error */);
}
return new;
}
static void PNGCBAPI
store_free(png_structp ppIn, png_voidp memory)
{
png_const_structp pp = ppIn;
store_pool *pool = voidcast(store_pool*, png_get_mem_ptr(pp));
store_memory *this = voidcast(store_memory*, memory), **test;
/* Because libpng calls store_free with a dummy png_struct when deleting
* png_struct or png_info via png_destroy_struct_2 it is necessary to check
* the passed in png_structp to ensure it is valid, and not pass it to
* png_error if it is not.
*/
if (pp != pool->store->pread && pp != pool->store->pwrite)
pp = NULL;
/* 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);
}
#endif /* PNG_USER_MEM_SUPPORTED */
/* 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.
*/
# ifdef PNG_USER_MEM_SUPPORTED
store_pool_delete(ps, &ps->write_memory_pool);
# endif
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, also if user
* memory is not supported we can't do it anyway.
*/
# ifdef PNG_USER_MEM_SUPPORTED
if (!ps->speed)
ps->pwrite = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
ps, store_error, store_warning, &ps->write_memory_pool,
store_malloc, store_free);
else
# endif
ps->pwrite = png_create_write_struct(PNG_LIBPNG_VER_STRING,
ps, store_error, store_warning);
png_set_write_fn(ps->pwrite, ps, store_write, store_flush);
# ifdef PNG_SET_OPTION_SUPPORTED
{
int opt;
for (opt=0; opt<ps->noptions; ++opt)
if (png_set_option(ps->pwrite, ps->options[opt].option,
ps->options[opt].setting) == PNG_OPTION_INVALID)
png_error(ps->pwrite, "png option invalid");
}
# endif
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).
* This routine exists even when there is no read support to make the code
* tidier (avoid a mass of ifdefs) and so easier to maintain.
*/
static void
store_read_reset(png_store *ps)
{
# ifdef PNG_READ_SUPPORTED
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;
}
# endif
# ifdef PNG_USER_MEM_SUPPORTED
/* Always do this to be safe. */
store_pool_delete(ps, &ps->read_memory_pool);
# endif
ps->current = NULL;
ps->next = NULL;
ps->readpos = 0;
ps->validated = 0;
}
#ifdef PNG_READ_SUPPORTED
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.
*/
# ifdef PNG_USER_MEM_SUPPORTED
if (!ps->speed)
ps->pread = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, ps,
store_error, store_warning, &ps->read_memory_pool, store_malloc,
store_free);
else
# endif
ps->pread = png_create_read_struct(PNG_LIBPNG_VER_STRING, ps, store_error,
store_warning);
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;
}
# ifdef PNG_SET_OPTION_SUPPORTED
{
int opt;
for (opt=0; opt<ps->noptions; ++opt)
if (png_set_option(ps->pread, ps->options[opt].option,
ps->options[opt].setting) == PNG_OPTION_INVALID)
png_error(ps->pread, "png option invalid");
}
# endif
store_read_set(ps, id);
if (ppi != NULL)
*ppi = ps->piread = png_create_info_struct(ps->pread);
return ps->pread;
}
#endif /* PNG_READ_SUPPORTED */
/* 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);
store_image_free(ps, NULL);
}
/*********************** 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 enum modifier_state
{
modifier_start, /* Initial value */
modifier_signature, /* Have a signature */
modifier_IHDR /* Have an IHDR */
} modifier_state;
typedef struct CIE_color
{
/* A single CIE tristimulus value, representing the unique response of a
* standard observer to a variety of light spectra. The observer recognizes
* all spectra that produce this response as the same color, therefore this
* is effectively a description of a color.
*/
double X, Y, Z;
} CIE_color;
typedef struct color_encoding
{
/* A description of an (R,G,B) encoding of color (as defined above); this
* includes the actual colors of the (R,G,B) triples (1,0,0), (0,1,0) and
* (0,0,1) plus an encoding value that is used to encode the linear
* components R, G and B to give the actual values R^gamma, G^gamma and
* B^gamma that are stored.
*/
double gamma; /* Encoding (file) gamma of space */
CIE_color red, green, blue; /* End points */
} color_encoding;
#ifdef PNG_READ_SUPPORTED
static double
chromaticity_x(CIE_color c)
{
return c.X / (c.X + c.Y + c.Z);
}
static double
chromaticity_y(CIE_color c)
{
return c.Y / (c.X + c.Y + c.Z);
}
static CIE_color
white_point(PNG_CONST color_encoding *encoding)
{
CIE_color white;
white.X = encoding->red.X + encoding->green.X + encoding->blue.X;
white.Y = encoding->red.Y + encoding->green.Y + encoding->blue.Y;
white.Z = encoding->red.Z + encoding->green.Z + encoding->blue.Z;
return white;
}
#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
static void
normalize_color_encoding(color_encoding *encoding)
{
PNG_CONST double whiteY = encoding->red.Y + encoding->green.Y +
encoding->blue.Y;
if (whiteY != 1)
{
encoding->red.X /= whiteY;
encoding->red.Y /= whiteY;
encoding->red.Z /= whiteY;
encoding->green.X /= whiteY;
encoding->green.Y /= whiteY;
encoding->green.Z /= whiteY;
encoding->blue.X /= whiteY;
encoding->blue.Y /= whiteY;
encoding->blue.Z /= whiteY;
}
}
#endif
static size_t
safecat_color_encoding(char *buffer, size_t bufsize, size_t pos,
PNG_CONST color_encoding *e, double encoding_gamma)
{
if (e != 0)
{
if (encoding_gamma != 0)
pos = safecat(buffer, bufsize, pos, "(");
pos = safecat(buffer, bufsize, pos, "R(");
pos = safecatd(buffer, bufsize, pos, e->red.X, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->red.Y, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->red.Z, 4);
pos = safecat(buffer, bufsize, pos, "),G(");
pos = safecatd(buffer, bufsize, pos, e->green.X, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->green.Y, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->green.Z, 4);
pos = safecat(buffer, bufsize, pos, "),B(");
pos = safecatd(buffer, bufsize, pos, e->blue.X, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->blue.Y, 4);
pos = safecat(buffer, bufsize, pos, ",");
pos = safecatd(buffer, bufsize, pos, e->blue.Z, 4);
pos = safecat(buffer, bufsize, pos, ")");
if (encoding_gamma != 0)
pos = safecat(buffer, bufsize, pos, ")");
}
if (encoding_gamma != 0)
{
pos = safecat(buffer, bufsize, pos, "^");
pos = safecatd(buffer, bufsize, pos, encoding_gamma, 5);
}
return pos;
}
#endif /* PNG_READ_SUPPORTED */
typedef struct png_modifier
{
png_store this; /* I am a png_store */
struct png_modification *modifications; /* Changes to make */
modifier_state 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;
unsigned int ngamma_tests; /* Number of gamma tests to run*/
double current_gamma; /* 0 if not set */
PNG_CONST color_encoding *encodings;
unsigned int nencodings;
PNG_CONST color_encoding *current_encoding; /* If an encoding has been set */
unsigned int encoding_counter; /* For iteration */
int encoding_ignored; /* Something overwrote it */
/* Control variables used to iterate through possible encodings, the
* following must be set to 0 and tested by the function that uses the
* png_modifier because the modifier only sets it to 1 (true.)
*/
unsigned int repeat :1; /* Repeat this transform test. */
unsigned int test_uses_encoding :1;
/* 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 maxcalc8; /* 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 maxcalc16;/* Absolute sample error 0..1 */
double maxcalcG; /* Absolute sample error 0..1 */
double maxpc16; /* Percentage sample error 0..100% */
/* This is set by transforms that need to allow a higher limit, it is an
* internal check on pngvalid to ensure that the calculated error limits are
* not ridiculous; without this it is too easy to make a mistake in pngvalid
* that allows any value through.
*/
double limit; /* limit on error values, normally 4E-3 */
/* Log limits - values above this are logged, but not necessarily
* warned.
*/
double log8; /* Absolute error in 8 bits to log */
double log16; /* Absolute error in 16 bits to log */
/* 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;
double error_indexed;
/* Flags: */
/* Whether to call png_read_update_info, not png_read_start_image, and how
* many times to call it.
*/
int use_update_info;
/* 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 combination of transforms, */
unsigned int test_transform :1;
unsigned int test_tRNS :1; /* Includes tRNS images */
/* When to use the use_input_precision option, this controls the gamma
* validation code checks. If set any value that is within the transformed
* range input-.5 to input+.5 will be accepted, otherwise the value must be
* within the normal limits. It should not be necessary to set this; the
* result should always be exact within the permitted error limits.
*/
unsigned int use_input_precision :1;
unsigned int use_input_precision_sbit :1;
unsigned int use_input_precision_16to8 :1;
/* If set assume that the calculation bit depth is set by the input
* precision, not the output precision.
*/
unsigned int calculations_use_input_precision :1;
/* If set assume that the calculations are done in 16 bits even if the sample
* depth is 8 bits.
*/
unsigned int assume_16_bit_calculations :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_scale16 :1;
unsigned int test_gamma_background :1;
unsigned int test_gamma_alpha_mode :1;
unsigned int test_gamma_expand16 :1;
unsigned int test_exhaustive :1;
/* Whether or not to run the low-bit-depth grayscale tests. This fail on
* gamma images in some cases because of gross inaccuracies in the grayscale
* gamma handling for low bit depth.
*/
unsigned int test_lbg :1;
unsigned int test_lbg_gamma_threshold :1;
unsigned int test_lbg_gamma_transform :1;
unsigned int test_lbg_gamma_sbit :1;
unsigned int test_lbg_gamma_composition :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;
/* 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->ngammas = 0;
pm->ngamma_tests = 0;
pm->gammas = 0;
pm->current_gamma = 0;
pm->encodings = 0;
pm->nencodings = 0;
pm->current_encoding = 0;
pm->encoding_counter = 0;
pm->encoding_ignored = 0;
pm->repeat = 0;
pm->test_uses_encoding = 0;
pm->maxout8 = pm->maxpc8 = pm->maxabs8 = pm->maxcalc8 = 0;
pm->maxout16 = pm->maxpc16 = pm->maxabs16 = pm->maxcalc16 = 0;
pm->maxcalcG = 0;
pm->limit = 4E-3;
pm->log8 = pm->log16 = 0; /* Means 'off' */
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->error_indexed = 0;
pm->use_update_info = 0;
pm->interlace_type = PNG_INTERLACE_NONE;
pm->test_standard = 0;
pm->test_size = 0;
pm->test_transform = 0;
# ifdef PNG_WRITE_tRNS_SUPPORTED
pm->test_tRNS = 1;
# else
pm->test_tRNS = 0;
# endif
pm->use_input_precision = 0;
pm->use_input_precision_sbit = 0;
pm->use_input_precision_16to8 = 0;
pm->calculations_use_input_precision = 0;
pm->assume_16_bit_calculations = 0;
pm->test_gamma_threshold = 0;
pm->test_gamma_transform = 0;
pm->test_gamma_sbit = 0;
pm->test_gamma_scale16 = 0;
pm->test_gamma_background = 0;
pm->test_gamma_alpha_mode = 0;
pm->test_gamma_expand16 = 0;
pm->test_lbg = 1;
pm->test_lbg_gamma_threshold = 1;
pm->test_lbg_gamma_transform = 1;
pm->test_lbg_gamma_sbit = 1;
pm->test_lbg_gamma_composition = 1;
pm->test_exhaustive = 0;
pm->log = 0;
/* Rely on the memset for all the other fields - there are no pointers */
}
#ifdef PNG_READ_TRANSFORMS_SUPPORTED
/* This controls use of checks that explicitly know how libpng digitizes the
* samples in calculations; setting this circumvents simple error limit checking
* in the rgb_to_gray check, replacing it with an exact copy of the libpng 1.5
* algorithm.
*/
#define DIGITIZE PNG_LIBPNG_VER < 10700
/* If pm->calculations_use_input_precision is set then operations will happen
* with the precision of the input, not the precision of the output depth.
*
* If pm->assume_16_bit_calculations is set then even 8 bit calculations use 16
* bit precision. This only affects those of the following limits that pertain
* to a calculation - not a digitization operation - unless the following API is
* called directly.
*/
#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
#if DIGITIZE
static double digitize(double value, int depth, int do_round)
{
/* 'value' is in the range 0 to 1, the result is the same value rounded to a
* multiple of the digitization factor - 8 or 16 bits depending on both the
* sample depth and the 'assume' setting. Digitization is normally by
* rounding and 'do_round' should be 1, if it is 0 the digitized value will
* be truncated.
*/
PNG_CONST unsigned int digitization_factor = (1U << depth) -1;
/* Limiting the range is done as a convenience to the caller - it's easier to
* do it once here than every time at the call site.
*/
if (value <= 0)
value = 0;
else if (value >= 1)
value = 1;
value *= digitization_factor;
if (do_round) value += .5;
return floor(value)/digitization_factor;
}
#endif
#endif /* RGB_TO_GRAY */
#ifdef PNG_READ_GAMMA_SUPPORTED
static double abserr(PNG_CONST png_modifier *pm, int in_depth, int out_depth)
{
/* Absolute error permitted in linear values - affected by the bit depth of
* the calculations.
*/
if (pm->assume_16_bit_calculations ||
(pm->calculations_use_input_precision ? in_depth : out_depth) == 16)
return pm->maxabs16;
else
return pm->maxabs8;
}
static double calcerr(PNG_CONST png_modifier *pm, int in_depth, int out_depth)
{
/* Error in the linear composition arithmetic - only relevant when
* composition actually happens (0 < alpha < 1).
*/
if ((pm->calculations_use_input_precision ? in_depth : out_depth) == 16)
return pm->maxcalc16;
else if (pm->assume_16_bit_calculations)
return pm->maxcalcG;
else
return pm->maxcalc8;
}
static double pcerr(PNG_CONST png_modifier *pm, int in_depth, int out_depth)
{
/* Percentage error permitted in the linear values. Note that the specified
* value is a percentage but this routine returns a simple number.
*/
if (pm->assume_16_bit_calculations ||
(pm->calculations_use_input_precision ? in_depth : out_depth) == 16)
return pm->maxpc16 * .01;
else
return pm->maxpc8 * .01;
}
/* Output error - the error in the encoded value. This is determined by the
* digitization of the output so can be +/-0.5 in the actual output value. In
* the expand_16 case with the current code in libpng the expand happens after
* all the calculations are done in 8 bit arithmetic, so even though the output
* depth is 16 the output error is determined by the 8 bit calculation.
*
* This limit is not determined by the bit depth of internal calculations.
*
* The specified parameter does *not* include the base .5 digitization error but
* it is added here.
*/
static double outerr(PNG_CONST png_modifier *pm, int in_depth, int out_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 grayscale gamma is up to the value below. This is a hack
* to allow pngvalid to succeed:
*
* TODO: fix this in libpng
*/
if (out_depth == 2)
return .73182-.5;
if (out_depth == 4)
return .90644-.5;
if ((pm->calculations_use_input_precision ? in_depth : out_depth) == 16)
return pm->maxout16;
/* This is the case where the value was calculated at 8-bit precision then
* scaled to 16 bits.
*/
else if (out_depth == 16)
return pm->maxout8 * 257;
else
return pm->maxout8;
}
/* This does the same thing as the above however it returns the value to log,
* rather than raising a warning. This is useful for debugging to track down
* exactly what set of parameters cause high error values.
*/
static double outlog(PNG_CONST png_modifier *pm, int in_depth, int out_depth)
{
/* The command line parameters are either 8 bit (0..255) or 16 bit (0..65535)
* and so must be adjusted for low bit depth grayscale:
*/
if (out_depth <= 8)
{
if (pm->log8 == 0) /* switched off */
return 256;
if (out_depth < 8)
return pm->log8 / 255 * ((1<<out_depth)-1);
return pm->log8;
}
if ((pm->calculations_use_input_precision ? in_depth : out_depth) == 16)
{
if (pm->log16 == 0)
return 65536;
return pm->log16;
}
/* This is the case where the value was calculated at 8-bit precision then
* scaled to 16 bits.
*/
if (pm->log8 == 0)
return 65536;
return pm->log8 * 257;
}
/* This complements the above by providing the appropriate quantization for the
* final value. Normally this would just be quantization to an integral value,
* but in the 8 bit calculation case it's actually quantization to a multiple of
* 257!
*/
static int output_quantization_factor(PNG_CONST png_modifier *pm, int in_depth,
int out_depth)
{
if (out_depth == 16 && in_depth != 16 &&
pm->calculations_use_input_precision)
return 257;
else
return 1;
}
#endif /* PNG_READ_GAMMA_SUPPORTED */
/* 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);
}
#ifdef PNG_READ_RGB_TO_GRAY_SUPPORTED
static void
modifier_current_encoding(PNG_CONST png_modifier *pm, color_encoding *ce)
{
if (pm->current_encoding != 0)
*ce = *pm->current_encoding;
else
memset(ce, 0, sizeof *ce);
ce->gamma = pm->current_gamma;
}
#endif
static size_t
safecat_current_encoding(char *buffer, size_t bufsize, size_t pos,
PNG_CONST png_modifier *pm)
{
pos = safecat_color_encoding(buffer, bufsize, pos, pm->current_encoding,
pm->current_gamma);
if (pm->encoding_ignored)
pos = safecat(buffer, bufsize, pos, "[overridden]");
return pos;
}
/* Iterate through the usefully testable color encodings. An encoding is one
* of:
*
* 1) Nothing (no color space, no gamma).
* 2) Just a gamma value from the gamma array (including 1.0)
* 3) A color space from the encodings array with the corresponding gamma.
* 4) The same, but with gamma 1.0 (only really useful with 16 bit calculations)
*
* The iterator selects these in turn, the randomizer selects one at random,
* which is used depends on the setting of the 'test_exhaustive' flag. Notice
* that this function changes the colour space encoding so it must only be
* called on completion of the previous test. This is what 'modifier_reset'
* does, below.
*
* After the function has been called the 'repeat' flag will still be set; the
* caller of modifier_reset must reset it at the start of each run of the test!
*/
static unsigned int
modifier_total_encodings(PNG_CONST png_modifier *pm)
{
return 1 + /* (1) nothing */
pm->ngammas + /* (2) gamma values to test */
pm->nencodings + /* (3) total number of encodings */
/* The following test only works after the first time through the
* png_modifier code because 'bit_depth' is set when the IHDR is read.
* modifier_reset, below, preserves the setting until after it has called
* the iterate function (also below.)
*
* For this reason do not rely on this function outside a call to
* modifier_reset.
*/
((pm->bit_depth == 16 || pm->assume_16_bit_calculations) ?
pm->nencodings : 0); /* (4) encodings with gamma == 1.0 */
}
static void
modifier_encoding_iterate(png_modifier *pm)
{
if (!pm->repeat && /* Else something needs the current encoding again. */
pm->test_uses_encoding) /* Some transform is encoding dependent */
{
if (pm->test_exhaustive)
{
if (++pm->encoding_counter >= modifier_total_encodings(pm))
pm->encoding_counter = 0; /* This will stop the repeat */
}
else
{
/* Not exhaustive - choose an encoding at random; generate a number in
* the range 1..(max-1), so the result is always non-zero:
*/
if (pm->encoding_counter == 0)
pm->encoding_counter = random_mod(modifier_total_encodings(pm)-1)+1;
else
pm->encoding_counter = 0;
}
if (pm->encoding_counter > 0)
pm->repeat = 1;
}
else if (!pm->repeat)
pm->encoding_counter = 0;
}
static void
modifier_reset(png_modifier *pm)
{
store_read_reset(&pm->this);
pm->limit = 4E-3;
pm->pending_len = pm->pending_chunk = 0;
pm->flush = pm->buffer_count = pm->buffer_position = 0;
pm->modifications = NULL;
pm->state = modifier_start;
modifier_encoding_iterate(pm);
/* The following must be set in the next run. In particular
* test_uses_encodings must be set in the _ini function of each transform
* that looks at the encodings. (Not the 'add' function!)
*/
pm->test_uses_encoding = 0;
pm->current_gamma = 0;
pm->current_encoding = 0;
pm->encoding_ignored = 0;
/* These only become value after IHDR is read: */
pm->bit_depth = pm->colour_type = 0;
}
/* The following must be called before anything else to get the encoding set up
* on the modifier. In particular it must be called before the transform init
* functions are called.
*/
static void
modifier_set_encoding(png_modifier *pm)
{
/* Set the encoding to the one specified by the current encoding counter,
* first clear out all the settings - this corresponds to an encoding_counter
* of 0.
*/
pm->current_gamma = 0;
pm->current_encoding = 0;
pm->encoding_ignored = 0; /* not ignored yet - happens in _ini functions. */
/* Now, if required, set the gamma and encoding fields. */
if (pm->encoding_counter > 0)
{
/* The gammas[] array is an array of screen gammas, not encoding gammas,
* so we need the inverse:
*/
if (pm->encoding_counter <= pm->ngammas)
pm->current_gamma = 1/pm->gammas[pm->encoding_counter-1];
else
{
unsigned int i = pm->encoding_counter - pm->ngammas;
if (i >= pm->nencodings)
{
i %= pm->nencodings;
pm->current_gamma = 1; /* Linear, only in the 16 bit case */
}
else
pm->current_gamma = pm->encodings[i].gamma;
pm->current_encoding = pm->encodings + i;
}
}
}
/* Enquiry functions to find out what is set. Notice that there is an implicit
* assumption below that the first encoding in the list is the one for sRGB.
*/
static int
modifier_color_encoding_is_sRGB(PNG_CONST png_modifier *pm)
{
return pm->current_encoding != 0 && pm->current_encoding == pm->encodings &&
pm->current_encoding->gamma == pm->current_gamma;
}
static int
modifier_color_encoding_is_set(PNG_CONST png_modifier *pm)
{
return pm->current_gamma != 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);
uLong crc = crc32(0, buffer+4, datalen+4);
/* The cast to png_uint_32 is safe because a crc32 is always a 32 bit value.
*/
png_save_uint_32(buffer+datalen+8, (png_uint_32)crc);
}
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 PNGCBAPI
modifier_read(png_structp ppIn, png_bytep pb, png_size_t st)
{
png_const_structp pp = ppIn;
png_modifier *pm = voidcast(png_modifier*, 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 truly 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