diff --git a/ANNOUNCE b/ANNOUNCE index 26d93e0ad..8f2dc86d6 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -27,6 +27,33 @@ Other information: Changes since the last public release (1.6.9): Version 1.6.10beta01 [February 6, 2014] + Backported changes from libpng-1.7.0beta30 and beta31: + Fixed a large number of instances where PNGCBAPI was omitted from + function definitions. + Added pngimage test program for png_read_png() and png_write_png() + with two new test scripts. + Removed dependence on !PNG_READ_EXPAND_SUPPORTED for calling + png_set_packing() in png_read_png(). + Fixed combination of ~alpha with shift. On read invert alpha, processing + occurred after shift processing, which causes the final values to be + outside the range that should be produced by the shift. Reversing the + order on read makes the two transforms work together correctly and mirrors + the order used on write. + Do not read invalid sBIT chunks. Previously libpng only checked sBIT + values on write, so a malicious PNG writer could therefore cause + the read code to return an invalid sBIT chunk, which might lead to + application errors or crashes. Such chunks are now skipped (with + chunk_benign_error). + Make png_read_png() and png_write_png() prototypes in png.h depend + upon PNG_READ_SUPPORTED and PNG_WRITE_SUPPORTED. + Support builds with unsupported PNG_TRANSFORM_* values. All of the + PNG_TRANSFORM_* values are always defined in png.h and, because they + are used for both read and write in some cases, it is not reliable + to #if out ones that are totally unsupported. This change adds error + detection in png_read_image() and png_write_image() to do a + png_app_error() if the app requests something that cannot be done + and it adds corresponding code to pngimage.c to handle such options + by not attempting to test them. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit @@ -35,3 +62,4 @@ to subscribe) or to glennrp at users.sourceforge.net Glenn R-P +#endif diff --git a/CHANGES b/CHANGES index 37a9b739a..c55501e56 100644 --- a/CHANGES +++ b/CHANGES @@ -4801,6 +4801,33 @@ Version 1.6.9rc02 [January 30, 2014] Version 1.6.9 [February 6, 2014] Version 1.6.10beta01 [February 6, 2014] + Backported changes from libpng-1.7.0beta30 and beta31: + Fixed a large number of instances where PNGCBAPI was omitted from + function definitions. + Added pngimage test program for png_read_png() and png_write_png() + with two new test scripts. + Removed dependence on !PNG_READ_EXPAND_SUPPORTED for calling + png_set_packing() in png_read_png(). + Fixed combination of ~alpha with shift. On read invert alpha, processing + occurred after shift processing, which causes the final values to be + outside the range that should be produced by the shift. Reversing the + order on read makes the two transforms work together correctly and mirrors + the order used on write. + Do not read invalid sBIT chunks. Previously libpng only checked sBIT + values on write, so a malicious PNG writer could therefore cause + the read code to return an invalid sBIT chunk, which might lead to + application errors or crashes. Such chunks are now skipped (with + chunk_benign_error). + Make png_read_png() and png_write_png() prototypes in png.h depend + upon PNG_READ_SUPPORTED and PNG_WRITE_SUPPORTED. + Support builds with unsupported PNG_TRANSFORM_* values. All of the + PNG_TRANSFORM_* values are always defined in png.h and, because they + are used for both read and write in some cases, it is not reliable + to #if out ones that are totally unsupported. This change adds error + detection in png_read_image() and png_write_image() to do a + png_app_error() if the app requests something that cannot be done + and it adds corresponding code to pngimage.c to handle such options + by not attempting to test them. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/Makefile.am b/Makefile.am index e69519d71..052e596b6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,7 +7,7 @@ PNGLIB_BASENAME= libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@ ACLOCAL_AMFLAGS = -I scripts # test programs - run on make check, make distcheck -check_PROGRAMS= pngtest pngunknown pngstest pngvalid +check_PROGRAMS= pngtest pngunknown pngstest pngvalid pngimage # Utilities - installed bin_PROGRAMS= pngfix png-fix-itxt @@ -34,6 +34,9 @@ pngstest_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la pngunknown_SOURCES = contrib/libtests/pngunknown.c pngunknown_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la +pngimage_SOURCES = contrib/libtests/pngimage.c +pngimage_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la + pngfix_SOURCES = contrib/tools/pngfix.c pngfix_LDADD = libpng@PNGLIB_MAJOR@@PNGLIB_MINOR@.la @@ -59,7 +62,8 @@ TESTS =\ tests/pngstest-4a16 tests/pngstest-6a08 tests/pngstest-6a16\ tests/pngstest-error tests/pngunknown-IDAT\ tests/pngunknown-discard tests/pngunknown-if-safe tests/pngunknown-sAPI\ - tests/pngunknown-sTER tests/pngunknown-save tests/pngunknown-vpAg + tests/pngunknown-sTER tests/pngunknown-save tests/pngunknown-vpAg\ + tests/pngimage-quick tests/pngimage-full # These tests are expected, and required, to fail: XFAIL_TESTS = tests/pngstest-error @@ -212,6 +216,7 @@ pngtest.o: pnglibconf.h contrib/libtests/makepng.o: pnglibconf.h contrib/libtests/pngstest.o: pnglibconf.h contrib/libtests/pngunknown.o: pnglibconf.h +contrib/libtests/pngimage.o: pnglibconf.h contrib/libtests/pngvalid.o: pnglibconf.h contrib/libtests/readpng.o: pnglibconf.h contrib/libtests/tarith.o: pnglibconf.h diff --git a/contrib/libtests/pngimage.c b/contrib/libtests/pngimage.c new file mode 100644 index 000000000..a91fbb488 --- /dev/null +++ b/contrib/libtests/pngimage.c @@ -0,0 +1,1618 @@ +/* pngimage.c + * + * Copyright (c) 2014 John Cunningham Bowler + * + * Last changed in libpng 1.6.9 [February 6, 2014] + * + * This code is released under the libpng license. + * For conditions of distribution and use, see the disclaimer + * and license in png.h + * + * Test the png_read_png and png_write_png interfaces. Given a PNG file load it + * using png_read_png and then write with png_write_png. Test all possible + * transforms. + */ +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H) +# include +#endif + +/* Define the following to use this test against your installed libpng, rather + * than the one being built here: + */ +#ifdef PNG_FREESTANDING_TESTS +# include +#else +# include "../../png.h" +#endif + +#ifndef PNG_SETJMP_SUPPORTED +# include /* because png.h did *not* include this */ +#endif + +#if defined(PNG_INFO_IMAGE_SUPPORTED) && defined(PNG_SEQUENTIAL_READ_SUPPORTED) +/* If a transform is valid on both read and write this implies that if the + * transform is applied to read it must also be applied on write to produce + * meaningful data. This is because these transforms when performed on read + * produce data with a memory format that does not correspond to a PNG format. + * + * Most of these transforms are invertible; after applying the transform on + * write the result is the original PNG data that would have would have been + * read if no transform were applied. + * + * The exception is _SHIFT, which destroys the low order bits marked as not + * significant in a PNG with the sBIT chunk. + * + * The following table lists, for each transform, the conditions under which it + * is expected to do anything. Conditions are defined as follows: + * + * 1) Color mask bits required - simply a mask to AND with color_type; one of + * these must be present for the transform to fire, except that 0 means + * 'always'. + * 2) Color mask bits which must be absent - another mask - none of these must + * be present. + * 3) Bit depths - a mask of component bit depths for the transform to fire. + * 4) 'read' - the transform works in png_read_png. + * 5) 'write' - the transform works in png_write_png. + * 6) PNG_INFO_chunk; a mask of the chunks that must be present for the + * transform to fire. All must be present - the requirement is that + * png_get_valid() & mask == mask, so if mask is 0 there is no requirement. + * + * The condition refers to the original image state - if multiple transforms are + * used together it is possible to cause a transform that wouldn't fire on the + * original image to fire. + */ +static struct transform_info +{ + const char *name; + int transform; + png_uint_32 valid_chunks; +# define CHUNK_NONE 0 +# define CHUNK_sBIT PNG_INFO_sBIT +# define CHUNK_tRNS PNG_INFO_tRNS + png_byte color_mask_required; + png_byte color_mask_absent; +# define COLOR_MASK_X 0 +# define COLOR_MASK_P PNG_COLOR_MASK_PALETTE +# define COLOR_MASK_C PNG_COLOR_MASK_COLOR +# define COLOR_MASK_A PNG_COLOR_MASK_ALPHA +# define COLOR_MASK_ALL (PALETTE+COLOR+ALPHA) /* absent = gray, no alpha */ + png_byte bit_depths; +# define BD_ALL (1 + 2 + 4 + 8 + 16) +# define BD_PAL (1 + 2 + 4 + 8) +# define BD_LOW (1 + 2 + 4) +# define BD_16 16 +# define BD_TRUE (8+16) /* i.e. true-color depths */ + png_byte when; +# define TRANSFORM_R 1 +# define TRANSFORM_W 2 +# define TRANSFORM_RW 3 + png_byte tested; /* the transform was tested somewhere */ +} transform_info[] = +{ + /* List ALL the PNG_TRANSFORM_ macros here. Check for support using the READ + * macros; even if the transform is supported on write it cannot be tested + * without the read support. + */ +# define T(name,chunk,cm_required,cm_absent,bd,when)\ + { #name, PNG_TRANSFORM_ ## name, CHUNK_ ## chunk,\ + COLOR_MASK_ ## cm_required, COLOR_MASK_ ## cm_absent, BD_ ## bd,\ + TRANSFORM_ ## when, 0/*!tested*/ } + +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED + T(STRIP_16, NONE, X, X, 16, R), + /* drops the bottom 8 bits when bit depth is 16 */ +#endif +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED + T(STRIP_ALPHA, NONE, A, X, ALL, R), + /* removes the alpha channel if present */ +#endif +#ifdef PNG_WRITE_PACK_SUPPORTED +# define TRANSFORM_RW_PACK TRANSFORM_RW +#else +# define TRANSFORM_RW_PACK TRANSFORM_R +#endif +#ifdef PNG_READ_PACK_SUPPORTED + T(PACKING, NONE, X, X, LOW, RW_PACK), + /* unpacks low-bit-depth components into 1 byte per component on read, + * reverses this on write. + */ +#endif +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED +# define TRANSFORM_RW_PACKSWAP TRANSFORM_RW +#else +# define TRANSFORM_RW_PACKSWAP TRANSFORM_R +#endif +#ifdef PNG_READ_PACKSWAP_SUPPORTED + T(PACKSWAP, NONE, X, X, LOW, RW_PACKSWAP), + /* reverses the order of low-bit-depth components packed into a byte */ +#endif +#ifdef PNG_READ_EXPAND_SUPPORTED + T(EXPAND, NONE, P, X, ALL, R), + /* expands PLTE PNG files to RGB (no tRNS) or RGBA (tRNS) * + * Note that the 'EXPAND' transform does lots of different things: */ + T(EXPAND, NONE, X, C, ALL, R), + /* expands grayscale PNG files to RGB, or RGBA */ + T(EXPAND, tRNS, X, A, ALL, R), + /* expands the tRNS chunk in files without alpha */ +#endif +#ifdef PNG_WRITE_INVERT_SUPPORTED +# define TRANSFORM_RW_INVERT TRANSFORM_RW +#else +# define TRANSFORM_RW_INVERT TRANSFORM_R +#endif +#ifdef PNG_READ_INVERT_SUPPORTED + T(INVERT_MONO, NONE, X, C, ALL, RW_INVERT), + /* converts gray-scale components to 1..0 from 0..1 */ +#endif +#ifdef PNG_WRITE_SHIFT_SUPPORTED +# define TRANSFORM_RW_SHIFT TRANSFORM_RW +#else +# define TRANSFORM_RW_SHIFT TRANSFORM_R +#endif +#ifdef PNG_READ_SHIFT_SUPPORTED + T(SHIFT, sBIT, X, X, ALL, RW_SHIFT), + /* reduces component values to the original range based on the sBIT chunk, + * this is only partially reversible - the low bits are lost and cannot be + * recovered on write. In fact write code replicates the bits to generate + * new low-order bits. + */ +#endif +#ifdef PNG_WRITE_BGR_SUPPORTED +# define TRANSFORM_RW_BGR TRANSFORM_RW +#else +# define TRANSFORM_RW_BGR TRANSFORM_R +#endif +#ifdef PNG_READ_BGR_SUPPORTED + T(BGR, NONE, C, P, TRUE, RW_BGR), + /* reverses the rgb component values of true-color pixels */ +#endif +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED +# define TRANSFORM_RW_SWAP_ALPHA TRANSFORM_RW +#else +# define TRANSFORM_RW_SWAP_ALPHA TRANSFORM_R +#endif +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED + T(SWAP_ALPHA, NONE, A, X, TRUE, RW_SWAP_ALPHA), + /* swaps the alpha channel of RGBA or GA pixels to the front - ARGB or + * AG, on write reverses the process. + */ +#endif +#ifdef PNG_WRITE_SWAP_SUPPORTED +# define TRANSFORM_RW_SWAP TRANSFORM_RW +#else +# define TRANSFORM_RW_SWAP TRANSFORM_R +#endif +#ifdef PNG_READ_SWAP_SUPPORTED + T(SWAP_ENDIAN, NONE, X, P, 16, RW_SWAP), + /* byte-swaps 16-bit component values */ +#endif +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED +# define TRANSFORM_RW_INVERT_ALPHA TRANSFORM_RW +#else +# define TRANSFORM_RW_INVERT_ALPHA TRANSFORM_R +#endif +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + T(INVERT_ALPHA, NONE, A, X, TRUE, RW_INVERT_ALPHA), + /* converts an alpha channel from 0..1 to 1..0 */ +#endif +#ifdef PNG_WRITE_FILLER_SUPPORTED + T(STRIP_FILLER_BEFORE, NONE, A, P, TRUE, W), /* 'A' for a filler! */ + /* on write skips a leading filler channel; testing requires data with a + * filler channel so this is produced from RGBA or GA images by removing + * the 'alpha' flag from the color type in place. + */ + T(STRIP_FILLER_AFTER, NONE, A, P, TRUE, W), + /* on write strips a trailing filler channel */ +#endif +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED + T(GRAY_TO_RGB, NONE, X, C, ALL, R), + /* expands grayscale images to RGB, also causes the palette part of + * 'EXPAND' to happen. Low bit depth grayscale images are expanded to + * 8-bits per component and no attempt is made to convert the image to a + * palette image. While this transform is partially reversible + * png_write_png does not currently support this. + */ + T(GRAY_TO_RGB, NONE, P, X, ALL, R), + /* The 'palette' side effect mentioned above; a bit bogus but this is the + * way the libpng code works. + */ +#endif +#ifdef PNG_READ_EXPAND_16_SUPPORTED + T(EXPAND_16, NONE, X, X, PAL, R), + /* expands images to 16-bits per component, as a side effect expands + * palette images to RGB and expands the tRNS chunk if present, so it can + * modify 16-bit per component images as well: + */ + T(EXPAND_16, tRNS, X, A, 16, R), + /* side effect of EXPAND_16 - expands the tRNS chunk in an RGB or G 16-bit + * image. + */ +#endif +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED + T(SCALE_16, NONE, X, X, 16, R) + /* scales 16-bit components to 8-bits. */ +#endif + +#undef T +}; + +#define ARRAY_SIZE(a) ((sizeof a)/(sizeof a[0])) +#define TTABLE_SIZE ARRAY_SIZE(transform_info) + +/* Some combinations of options that should be reversible are not; these cases + * are bugs. + */ +static int known_bad_combos[][2] = +{ + /* problem, antidote */ + { PNG_TRANSFORM_SHIFT | PNG_TRANSFORM_INVERT_ALPHA, 0/*antidote*/ } +}; + +static int +is_combo(int transforms) +{ + return transforms & (transforms-1); /* non-zero if more than one set bit */ +} + +static int +first_transform(int transforms) +{ + return transforms & -transforms; /* lowest set bit */ +} + +static int +is_bad_combo(int transforms) +{ + unsigned int i; + + for (i=0; ifirst.next = NULL; + buffer->last = NULL; + buffer->current = NULL; +} + +#ifdef PNG_WRITE_SUPPORTED +static void +buffer_start_write(struct buffer *buffer) +{ + buffer->last = &buffer->first; + buffer->end_count = 0; + buffer->current = NULL; +} +#endif + +static void +buffer_start_read(struct buffer *buffer) +{ + buffer->current = &buffer->first; + buffer->read_count = 0; +} + +#ifdef ENOMEM /* required by POSIX 1003.1 */ +# define MEMORY ENOMEM +#else +# define MEMORY ERANGE /* required by ANSI-C */ +#endif +static struct buffer * +get_buffer(png_structp pp) + /* Used from libpng callbacks to get the current buffer */ +{ + return (struct buffer*)png_get_io_ptr(pp); +} + +#define NEW(type) ((type *)malloc(sizeof (type))) + +static struct buffer_list * +buffer_extend(struct buffer_list *current) +{ + struct buffer_list *add; + + assert(current->next == NULL); + + add = NEW(struct buffer_list); + if (add == NULL) + return NULL; + + add->next = NULL; + current->next = add; + + return add; +} + +/* Load a buffer from a file; does the equivalent of buffer_start_write. On a + * read error returns an errno value, else returns 0. + */ +static int +buffer_from_file(struct buffer *buffer, FILE *fp) +{ + struct buffer_list *last = &buffer->first; + size_t count = 0; + + for (;;) + { + size_t r = fread(last->buffer+count, 1/*size*/, + (sizeof last->buffer)-count, fp); + + if (r > 0) + { + count += r; + + if (count >= sizeof last->buffer) + { + assert(count == sizeof last->buffer); + count = 0; + + if (last->next == NULL) + { + last = buffer_extend(last); + if (last == NULL) + return MEMORY; + } + + else + last = last->next; + } + } + + else /* fread failed - probably end of file */ + { + if (feof(fp)) + { + buffer->last = last; + buffer->end_count = count; + return 0; /* no error */ + } + + /* Some kind of funky error; errno should be non-zero */ + return errno == 0 ? ERANGE : errno; + } + } +} + +/* This structure is used to control the test of a single file. */ +typedef enum +{ + VERBOSE, /* switches on all messages */ + INFORMATION, + WARNINGS, /* switches on warnings */ + LIBPNG_WARNING, + APP_WARNING, + ERRORS, /* just errors */ + APP_FAIL, /* continuable error - no need to longjmp */ + LIBPNG_ERROR, /* this and higher cause a longjmp */ + LIBPNG_BUG, /* erroneous behavior in libpng */ + APP_ERROR, /* such as out-of-memory in a callback */ + QUIET, /* no normal messages */ + USER_ERROR, /* such as file-not-found */ + INTERNAL_ERROR +} error_level; +#define LEVEL_MASK 0xf /* where the level is in 'options' */ + +#define EXHAUSTIVE 0x010 /* Test all combinations of active options */ +#define STRICT 0x020 /* Fail on warnings as well as errors */ +#define LOG 0x040 /* Log pass/fail to stdout */ +#define CONTINUE 0x080 /* Continue on APP_FAIL errors */ +#define SKIP_BUGS 0x100 /* Skip over known bugs */ +#define LOG_SKIPPED 0x200 /* Log skipped bugs */ +#define FIND_BAD_COMBOS 0x400 /* Attempt to deduce bad combos */ + +/* Result masks apply to the result bits in the 'results' field below; these + * bits are simple 1U<options = WARNINGS; /* default to !verbose, !quiet */ + dp->filename = NULL; + dp->operation = NULL; + dp->original_pp = NULL; + dp->original_ip = NULL; + dp->original_rows = NULL; + dp->read_pp = NULL; + dp->read_ip = NULL; + buffer_init(&dp->original_file); + +# ifdef PNG_WRITE_SUPPORTED + dp->write_pp = NULL; + buffer_init(&dp->written_file); +# endif +} + +static void +display_clean_read(struct display *dp) +{ + if (dp->read_pp != NULL) + png_destroy_read_struct(&dp->read_pp, &dp->read_ip, NULL); +} + +#ifdef PNG_WRITE_SUPPORTED +static void +display_clean_write(struct display *dp) +{ + if (dp->write_pp != NULL) + png_destroy_write_struct(&dp->write_pp, NULL); +} +#endif + +static void +display_clean(struct display *dp) +{ +# ifdef PNG_WRITE_SUPPORTED + display_clean_write(dp); +# endif + display_clean_read(dp); + + dp->original_rowbytes = 0; + dp->original_rows = NULL; + dp->chunks = 0; + + png_destroy_read_struct(&dp->original_pp, &dp->original_ip, NULL); + /* leave the filename for error detection */ + dp->results = 0; /* reset for next time */ +} + +static struct display * +get_dp(png_structp pp) + /* The display pointer is always stored in the png_struct error pointer */ +{ + struct display *dp = (struct display*)png_get_error_ptr(pp); + + if (dp == NULL) + { + fprintf(stderr, "pngimage: internal error (no display)\n"); + exit(99); /* prevents a crash */ + } + + return dp; +} + +/* error handling */ +#ifdef __GNUC__ +# define VGATTR __attribute__((__format__ (__printf__,3,4))) + /* Required to quiet GNUC warnings when the compiler sees a stdarg function + * that calls one of the stdio v APIs. + */ +#else +# define VGATTR +#endif +static void VGATTR +display_log(struct display *dp, error_level level, const char *fmt, ...) + /* 'level' is as above, fmt is a stdio style format string. This routine + * does not return if level is above LIBPNG_WARNING + */ +{ + dp->results |= 1U << level; + + if (level > (error_level)(dp->options & LEVEL_MASK)) + { + const char *lp; + va_list ap; + + switch (level) + { + case INFORMATION: lp = "information"; break; + case LIBPNG_WARNING: lp = "warning(libpng)"; break; + case APP_WARNING: lp = "warning(pngimage)"; break; + case APP_FAIL: lp = "error(continuable)"; break; + case LIBPNG_ERROR: lp = "error(libpng)"; break; + case LIBPNG_BUG: lp = "bug(libpng)"; break; + case APP_ERROR: lp = "error(pngimage)"; break; + case USER_ERROR: lp = "error(user)"; break; + + case INTERNAL_ERROR: /* anything unexpected is an internal error: */ + case VERBOSE: case WARNINGS: case ERRORS: case QUIET: + default: lp = "bug(pngimage)"; break; + } + + fprintf(stderr, "%s: %s: %s", + dp->filename != NULL ? dp->filename : "", lp, dp->operation); + + if (dp->transforms != 0) + { + int tr = dp->transforms; + + if (is_combo(tr)) + fprintf(stderr, "(0x%x)", tr); + + else + fprintf(stderr, "(%s)", transform_name(tr)); + } + + fprintf(stderr, ": "); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputc('\n', stderr); + } + /* else do not output any message */ + + /* Errors cause this routine to exit to the fail code */ + if (level > APP_FAIL || (level > ERRORS && !(dp->options & CONTINUE))) + longjmp(dp->error_return, level); +} + +/* error handler callbacks for libpng */ +static void PNGCBAPI +display_warning(png_structp pp, png_const_charp warning) +{ + display_log(get_dp(pp), LIBPNG_WARNING, "%s", warning); +} + +static void PNGCBAPI +display_error(png_structp pp, png_const_charp error) +{ + struct display *dp = get_dp(pp); + + display_log(dp, LIBPNG_ERROR, "%s", error); +} + +static void +display_cache_file(struct display *dp, const char *filename) + /* Does the initial cache of the file. */ +{ + FILE *fp; + int ret; + + dp->filename = filename; + + if (filename != NULL) + { + fp = fopen(filename, "rb"); + if (fp == NULL) + display_log(dp, USER_ERROR, "open failed: %s", strerror(errno)); + } + + else + fp = stdin; + + ret = buffer_from_file(&dp->original_file, fp); + + fclose(fp); + + if (ret != 0) + display_log(dp, APP_ERROR, "read failed: %s", strerror(ret)); +} + +static void +buffer_read(struct display *dp, struct buffer *bp, png_bytep data, + png_size_t size) +{ + struct buffer_list *last = bp->current; + size_t read_count = bp->read_count; + + while (size > 0) + { + size_t avail; + + if (last == NULL || + (last == bp->last && read_count >= bp->end_count)) + { + display_log(dp, USER_ERROR, "file truncated (%lu bytes)", + (unsigned long)size); + /*NOTREACHED*/ + break; + } + + else if (read_count >= sizeof last->buffer) + { + /* Move to the next buffer: */ + last = last->next; + read_count = 0; + bp->current = last; /* Avoid update outside the loop */ + + /* And do a sanity check (the EOF case is caught above) */ + if (last == NULL) + { + display_log(dp, INTERNAL_ERROR, "damaged buffer list"); + /*NOTREACHED*/ + break; + } + } + + avail = (sizeof last->buffer) - read_count; + if (avail > size) + avail = size; + + memcpy(data, last->buffer + read_count, avail); + read_count += avail; + size -= avail; + data += avail; + } + + bp->read_count = read_count; +} + +static void PNGCBAPI +read_function(png_structp pp, png_bytep data, png_size_t size) +{ + buffer_read(get_dp(pp), get_buffer(pp), data, size); +} + +static void +read_png(struct display *dp, struct buffer *bp, const char *operation, + int transforms) +{ + png_structp pp; + png_infop ip; + + /* This cleans out any previous read and sets operation and transforms to + * empty. + */ + display_clean_read(dp); + + if (operation != NULL) /* else this is a verify and do not overwrite info */ + { + dp->operation = operation; + dp->transforms = transforms; + } + + dp->read_pp = pp = png_create_read_struct(PNG_LIBPNG_VER_STRING, dp, + display_error, display_warning); + if (pp == NULL) + display_log(dp, LIBPNG_ERROR, "failed to create read struct"); + + /* The png_read_png API requires us to make the info struct, but it does the + * call to png_read_info. + */ + dp->read_ip = ip = png_create_info_struct(pp); + if (ip == NULL) + display_log(dp, LIBPNG_ERROR, "failed to create info struct"); + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + /* Remove the user limits, if any */ + png_set_user_limits(pp, 0x7fffffff, 0x7fffffff); +# endif + + /* Set the IO handling */ + buffer_start_read(bp); + png_set_read_fn(pp, bp, read_function); + + png_read_png(pp, ip, transforms, NULL/*params*/); + +#if 0 /* crazy debugging */ + { + png_bytep pr = png_get_rows(pp, ip)[0]; + size_t rb = png_get_rowbytes(pp, ip); + size_t cb; + char c = ' '; + + fprintf(stderr, "%.4x %2d (%3lu bytes):", transforms, png_get_bit_depth(pp,ip), (unsigned long)rb); + + for (cb=0; cboriginal_file, "original read", 0/*no transform*/); + + /* Move the result to the 'original' fields */ + dp->original_pp = pp = dp->read_pp, dp->read_pp = NULL; + dp->original_ip = ip = dp->read_ip, dp->read_ip = NULL; + + dp->original_rowbytes = png_get_rowbytes(pp, ip); + if (dp->original_rowbytes == 0) + display_log(dp, LIBPNG_BUG, "png_get_rowbytes returned 0"); + + dp->chunks = png_get_valid(pp, ip, 0xffffffff); + if ((dp->chunks & PNG_INFO_IDAT) == 0) /* set by png_read_png */ + display_log(dp, LIBPNG_BUG, "png_read_png did not set IDAT flag"); + + dp->original_rows = png_get_rows(pp, ip); + if (dp->original_rows == NULL) + display_log(dp, LIBPNG_BUG, "png_read_png did not create row buffers"); + + if (!png_get_IHDR(pp, ip, + &dp->width, &dp->height, &dp->bit_depth, &dp->color_type, + &dp->interlace_method, &dp->compression_method, &dp->filter_method)) + display_log(dp, LIBPNG_BUG, "png_get_IHDR failed"); + + /* 'active' transforms are discovered based on the original image format; + * running one active transform can activate others. At present the code + * does not attempt to determine the closure. + */ + { + png_uint_32 chunks = dp->chunks; + int active = 0, inactive = 0; + int ct = dp->color_type; + int bd = dp->bit_depth; + unsigned int i; + + for (i=0; iactive_transforms = active; + dp->ignored_transforms = inactive; /* excluding write-only transforms */ + + if (active == 0) + display_log(dp, INTERNAL_ERROR, "bad transform table"); + } +} + +static int +compare_read(struct display *dp, int applied_transforms) +{ + /* Compare the png_info from read_ip with original_info */ + size_t rowbytes; + png_uint_32 width, height; + int bit_depth, color_type; + int interlace_method, compression_method, filter_method; + const char *e = NULL; + + png_get_IHDR(dp->read_pp, dp->read_ip, &width, &height, &bit_depth, + &color_type, &interlace_method, &compression_method, &filter_method); + +# define C(item) if (item != dp->item) \ + display_log(dp, APP_WARNING, "IHDR " #item "(%lu) changed to %lu",\ + (unsigned long)dp->item, (unsigned long)item), e = #item + + /* The IHDR should be identical: */ + C(width); + C(height); + C(bit_depth); + C(color_type); + C(interlace_method); + C(compression_method); + C(filter_method); + + /* 'e' remains set to the name of the last thing changed: */ + if (e) + display_log(dp, APP_ERROR, "IHDR changed (%s)", e); + + /* All the chunks from the original PNG should be preserved in the output PNG + * because the PNG format has not been changed. + */ + { + unsigned long chunks = + png_get_valid(dp->read_pp, dp->read_ip, 0xffffffff); + + if (chunks != dp->chunks) + display_log(dp, APP_FAIL, "PNG chunks changed from 0x%lx to 0x%lx", + (unsigned long)dp->chunks, chunks); + } + + /* rowbytes should be the same */ + rowbytes = png_get_rowbytes(dp->read_pp, dp->read_ip); + + /* NOTE: on 64-bit systems this may trash the top bits of rowbytes, + * which could lead to weird error messages. + */ + if (rowbytes != dp->original_rowbytes) + display_log(dp, APP_ERROR, "PNG rowbytes changed from %lu to %lu", + (unsigned long)dp->original_rowbytes, (unsigned long)rowbytes); + + /* The rows should be the same too, unless the applied transforms includes + * the shift transform, in which case low bits may have been lost. + */ + { + png_bytepp rows = png_get_rows(dp->read_pp, dp->read_ip); + unsigned int mask; /* mask (if not zero) for the final byte */ + + if (bit_depth < 8) + { + /* Need the stray bits at the end, this depends only on the low bits + * of the image width; overflow does not matter. If the width is an + * exact multiple of 8 bits this gives a mask of 0, not 0xff. + */ + mask = 0xff & (0xff00 >> ((bit_depth * width) & 7)); + } + + else + mask = 0; + + if (rows == NULL) + display_log(dp, LIBPNG_BUG, "png_get_rows returned NULL"); + + if ((applied_transforms & PNG_TRANSFORM_SHIFT) == 0 || + (dp->active_transforms & PNG_TRANSFORM_SHIFT) == 0 || + color_type == PNG_COLOR_TYPE_PALETTE) + { + unsigned long y; + + for (y=0; yoriginal_rows[y]; + + if (memcmp(row, orig, rowbytes-(mask != 0)) != 0 || (mask != 0 && + ((row[rowbytes-1] & mask) != (orig[rowbytes-1] & mask)))) + { + size_t x; + + /* Find the first error */ + for (x=0; x 0x%.2x", + (unsigned long)x, (unsigned long)y, orig[x], row[x]); + return 0; /* don't keep reporting failed rows on 'continue' */ + } + } + } + + else + { + unsigned long y; + int bpp; /* bits-per-pixel then bytes-per-pixel */ + /* components are up to 8 bytes in size */ + png_byte sig_bits[8]; + png_color_8p sBIT; + + if (png_get_sBIT(dp->read_pp, dp->read_ip, &sBIT) != PNG_INFO_sBIT) + display_log(dp, INTERNAL_ERROR, + "active shift transform but no sBIT in file"); + + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY: + sig_bits[0] = sBIT->gray; + bpp = bit_depth; + break; + + case PNG_COLOR_TYPE_GA: + sig_bits[0] = sBIT->gray; + sig_bits[1] = sBIT->alpha; + bpp = 2 * bit_depth; + break; + + case PNG_COLOR_TYPE_RGB: + sig_bits[0] = sBIT->red; + sig_bits[1] = sBIT->green; + sig_bits[2] = sBIT->blue; + bpp = 3 * bit_depth; + break; + + case PNG_COLOR_TYPE_RGBA: + sig_bits[0] = sBIT->red; + sig_bits[1] = sBIT->green; + sig_bits[2] = sBIT->blue; + sig_bits[3] = sBIT->alpha; + bpp = 4 * bit_depth; + break; + + default: + display_log(dp, LIBPNG_ERROR, "invalid colour type %d", + color_type); + /*NOTREACHED*/ + bpp = 0; + break; + } + + { + int b; + + for (b=0; 8*b bit_depth/*!palette*/) + display_log(dp, LIBPNG_BUG, + "invalid sBIT[%u] value %d returned for PNG bit depth %d", + b, sig_bits[b], bit_depth); + } + } + + if (bpp < 8 && bpp != bit_depth) + { + /* sanity check; this is a grayscale PNG; something is wrong in the + * code above. + */ + display_log(dp, INTERNAL_ERROR, "invalid bpp %u for bit_depth %u", + bpp, bit_depth); + } + + switch (bit_depth) + { + int b; + + case 16: /* Two bytes per component, bit-endian */ + for (b = (bpp >> 4); b > 0; ) + { + unsigned int sig = (unsigned int)(0xffff0000 >> sig_bits[b]); + + sig_bits[2*b+1] = (png_byte)sig; + sig_bits[2*b+0] = (png_byte)(sig >> 8); /* big-endian */ + } + break; + + case 8: /* One byte per component */ + for (b=0; b*8 < bpp; ++b) + sig_bits[b] = (png_byte)(0xff00 >> sig_bits[b]); + break; + + case 1: /* allowed, but dumb */ + /* Value is 1 */ + sig_bits[0] = 0xff; + break; + + case 2: /* Replicate 4 times */ + /* Value is 1 or 2 */ + b = 0x3 & ((0x3<<2) >> sig_bits[0]); + b |= b << 2; + b |= b << 4; + sig_bits[0] = (png_byte)b; + break; + + case 4: /* Relicate twice */ + /* Value is 1, 2, 3 or 4 */ + b = 0xf & ((0xf << 4) >> sig_bits[0]); + b |= b << 4; + sig_bits[0] = (png_byte)b; + break; + + default: + display_log(dp, LIBPNG_BUG, "invalid bit depth %d", bit_depth); + break; + } + + /* Convert bpp to bytes; this gives '1' for low-bit depth grayscale, + * where there are multiple pixels per byte. + */ + bpp = (bpp+7) >> 3; + + /* The mask can be combined with sig_bits[0] */ + if (mask != 0) + { + mask &= sig_bits[0]; + + if (bpp != 1 || mask == 0) + display_log(dp, INTERNAL_ERROR, "mask calculation error %u, %u", + bpp, mask); + } + + for (y=0; yoriginal_rows[y]; + unsigned long x; + + for (x=0; x<(width-(mask!=0)); ++x) + { + int b; + + for (b=0; b%.2x", + x, b, y, orig[-1], row[-1]); + return 0; + } + } + } + + if (mask != 0 && (*row & mask) != (*orig & mask)) + { + display_log(dp, APP_FAIL, + "significant bits at (%lu[end],%lu) changed", x, y); + return 0; + } + } /* for y */ + } + } + + return 1; /* compare succeeded */ +} + +#ifdef PNG_WRITE_SUPPORTED +static void +buffer_write(struct display *dp, struct buffer *buffer, png_bytep data, + png_size_t size) + /* Generic write function used both from the write callback provided to + * libpng and from the generic read code. + */ +{ + /* Write the data into the buffer, adding buffers as required */ + struct buffer_list *last = buffer->last; + size_t end_count = buffer->end_count; + + while (size > 0) + { + size_t avail; + + if (end_count >= sizeof last->buffer) + { + if (last->next == NULL) + { + last = buffer_extend(last); + + if (last == NULL) + display_log(dp, APP_ERROR, "out of memory saving file"); + } + + else + last = last->next; + + buffer->last = last; /* avoid the need to rewrite every time */ + end_count = 0; + } + + avail = (sizeof last->buffer) - end_count; + if (avail > size) + avail = size; + + memcpy(last->buffer + end_count, data, avail); + end_count += avail; + size -= avail; + data += avail; + } + + buffer->end_count = end_count; +} + +static void PNGCBAPI +write_function(png_structp pp, png_bytep data, png_size_t size) +{ + buffer_write(get_dp(pp), get_buffer(pp), data, size); +} + +static void +write_png(struct display *dp, png_infop ip, int transforms) +{ + display_clean_write(dp); /* safety */ + + buffer_start_write(&dp->written_file); + dp->operation = "write"; + dp->transforms = transforms; + + dp->write_pp = png_create_write_struct(PNG_LIBPNG_VER_STRING, dp, + display_error, display_warning); + + if (dp->write_pp == NULL) + display_log(dp, APP_ERROR, "failed to create write png_struct"); + + png_set_write_fn(dp->write_pp, &dp->written_file, write_function, + NULL/*flush*/); + +# ifdef PNG_SET_USER_LIMITS_SUPPORTED + /* Remove the user limits, if any */ + png_set_user_limits(dp->write_pp, 0x7fffffff, 0x7fffffff); +# endif + + /* Certain transforms require the png_info to be zapped to allow the + * transform to work correctly. + */ + if (transforms & (PNG_TRANSFORM_PACKING| + PNG_TRANSFORM_STRIP_FILLER| + PNG_TRANSFORM_STRIP_FILLER_BEFORE)) + { + int ct = dp->color_type; + + if (transforms & (PNG_TRANSFORM_STRIP_FILLER| + PNG_TRANSFORM_STRIP_FILLER_BEFORE)) + ct &= ~PNG_COLOR_MASK_ALPHA; + + png_set_IHDR(dp->write_pp, ip, dp->width, dp->height, dp->bit_depth, ct, + dp->interlace_method, dp->compression_method, dp->filter_method); + } + + png_write_png(dp->write_pp, ip, transforms, NULL/*params*/); + + /* Clean it on the way out - if control returns to the caller then the + * written_file contains the required data. + */ + display_clean_write(dp); +} +#endif /* WRITE_SUPPORTED */ + +static int +skip_transform(struct display *dp, int tr) + /* Helper to test for a bad combo and log it if it is skipped */ +{ + if ((dp->options & SKIP_BUGS) != 0 && is_bad_combo(tr)) + { + /* Log this to stdout if logging is on, otherwise just do an information + * display_log. + */ + if ((dp->options & LOG_SKIPPED) != 0) + { + printf("SKIP: %s transforms ", dp->filename); + + while (tr != 0) + { + int next = first_transform(tr); + tr &= ~next; + + printf("%s", transform_name(next)); + if (tr != 0) + putchar('+'); + } + + putchar('\n'); + } + + else + display_log(dp, INFORMATION, "%s: skipped known bad combo 0x%x", + dp->filename, tr); + + return 1; /* skip */ + } + + return 0; /* don't skip */ +} + +static void +test_one_file(struct display *dp, const char *filename) +{ + /* First cache the file and update the display original file + * information for the new file. + */ + dp->operation = "cache file"; + dp->transforms = 0; + display_cache_file(dp, filename); + update_display(dp); + + /* First test: if there are options that should be ignored for this file + * verify that they really are ignored. + */ + if (dp->ignored_transforms != 0) + { + read_png(dp, &dp->original_file, "ignored transforms", + dp->ignored_transforms); + + /* The result should be identical to the original_rows */ + if (!compare_read(dp, 0/*transforms applied*/)) + return; /* no point testing more */ + } + +#ifdef PNG_WRITE_SUPPORTED + /* Second test: write the original PNG data out to a new file (to test the + * write side) then read the result back in and make sure that it hasn't + * changed. + */ + dp->operation = "write"; + write_png(dp, dp->original_ip, 0/*transforms*/); + read_png(dp, &dp->written_file, NULL, 0/*transforms*/); + if (!compare_read(dp, 0/*transforms applied*/)) + return; +#endif + + /* Third test: the active options. Test each in turn, or, with the + * EXHAUSTIVE option, test all possible combinations. + */ + { + /* Use unsigned int here because the code below to increment through all + * the possibilities exhaustively has to use a compare and that must be + * unsigned, because some transforms are negative on a 16-bit system. + */ + unsigned int active = dp->active_transforms; + const int exhaustive = (dp->options & EXHAUSTIVE) != 0; + unsigned int current = first_transform(active); + unsigned int bad_transforms = 0; + unsigned int bad_combo = ~0U; /* bitwise AND of failing transforms */ + unsigned int bad_combo_list = 0; /* bitwise OR of failures */ + + for (;;) + { + read_png(dp, &dp->original_file, "active transforms", current); + + /* If this involved any irreversible transformations then if we write + * it out with just the reversible transformations and read it in again + * with the same transforms we should get the same thing. At present + * this isn't done - it just seems like a waste of time and it would + * require two sets of read png_struct/png_info. + * + * If there were no irreversible transformations then if we write it + * out and read it back in again (without the reversible transforms) + * we should get back to the place where we started. + */ +#ifdef PNG_WRITE_SUPPORTED + if ((current & write_transforms) == current) + { + /* All transforms reversible: write the PNG with the transformations + * reversed, then read it back in with no transformations. The + * result should be the same as the original apart from the loss of + * low order bits because of the SHIFT/sBIT transform. + */ + dp->operation = "reversible transforms"; + write_png(dp, dp->read_ip, current); + + /* And if this is read back in, because all the transformations were + * reversible, the result should be the same. + */ + read_png(dp, &dp->written_file, NULL, 0); + if (!compare_read(dp, current/*for the SHIFT/sBIT transform*/)) + { + /* This set of transforms failed. If a single bit is set - if + * there is just one transform - don't include this in further + * 'exhaustive' tests. Notice that each transform is tested on + * its own before testing combos in the exhaustive case. + */ + if (is_combo(current)) + { + bad_combo &= current; + bad_combo_list |= current; + } + + else + bad_transforms |= current; + } + } +#endif + + /* Now move to the next transform */ + if (exhaustive) /* all combinations */ + { + unsigned int next = current; + + do + { + if (next == read_transforms) /* Everything tested */ + goto combo; + + ++next; + } /* skip known bad combos if the relevant option is set; skip + * combos involving known bad single transforms in all cases. + */ + while ( (next & read_transforms) <= current + || (next & active) == 0 /* skip cases that do nothing */ + || (next & bad_transforms) != 0 + || skip_transform(dp, next)); + + assert((next & read_transforms) == next); + current = next; + } + + else /* one at a time */ + { + active &= ~current; + + if (active == 0) + goto combo; + + current = first_transform(active); + } + } + +combo: + if (dp->options & FIND_BAD_COMBOS) + { + /* bad_combos identifies the combos that occur in all failing cases; + * bad_combo_list identifies transforms that do not prevent the + * failure. + */ + if (bad_combo != ~0U) + printf("%s[0x%x]: PROBLEM: 0x%x[0x%x] ANTIDOTE: 0x%x\n", + dp->filename, active, bad_combo, bad_combo_list, + rw_transforms & ~bad_combo_list); + + else + printf("%s: no %sbad combos found\n", dp->filename, + (dp->options & SKIP_BUGS) ? "additional " : ""); + } + } +} + +static int +do_test(struct display *dp, const char *file) + /* Exists solely to isolate the setjmp clobbers */ +{ + int ret = setjmp(dp->error_return); + + if (ret == 0) + { + test_one_file(dp, file); + return 0; + } + + else if (ret < ERRORS) /* shouldn't longjmp on warnings */ + display_log(dp, INTERNAL_ERROR, "unexpected return code %d", ret); + + return ret; +} + +int +main(const int argc, const char * const * const argv) +{ + /* For each file on the command line test it with a range of transforms */ + int option_end, ilog = 0; + struct display d; + + validate_T(); + display_init(&d); + + for (option_end=1; option_end QUIET) /* abort on user or internal error */ + return 99; + } + + /* Here on any return, including failures, except user/internal issues + */ + { + const int pass = (d.options & STRICT) ? + RESULT_STRICT(d.results) : RESULT_RELAXED(d.results); + + if (!pass) + ++errors; + + if (d.options & LOG) + { + int j; + + printf("%s: pngimage ", pass ? "PASS" : "FAIL"); + + for (j=1; jps->validated = 1; } -static void +static void PNGCBAPI standard_end(png_structp ppIn, png_infop pi) { png_const_structp pp = ppIn; @@ -5841,7 +5841,7 @@ transform_info_imp(transform_display *dp, png_structp pp, png_infop pi) } } -static void +static void PNGCBAPI transform_info(png_structp pp, png_infop pi) { transform_info_imp(voidcast(transform_display*, png_get_progressive_ptr(pp)), @@ -6048,7 +6048,7 @@ transform_image_validate(transform_display *dp, png_const_structp pp, dp->this.ps->validated = 1; } -static void +static void PNGCBAPI transform_end(png_structp ppIn, png_infop pi) { png_const_structp pp = ppIn; @@ -7865,7 +7865,7 @@ gamma_info_imp(gamma_display *dp, png_structp pp, png_infop pi) standard_info_part2(&dp->this, pp, pi, 1 /*images*/); } -static void +static void PNGCBAPI gamma_info(png_structp pp, png_infop pi) { gamma_info_imp(voidcast(gamma_display*, png_get_progressive_ptr(pp)), pp, @@ -8829,7 +8829,7 @@ gamma_image_validate(gamma_display *dp, png_const_structp pp, dp->this.ps->validated = 1; } -static void +static void PNGCBAPI gamma_end(png_structp ppIn, png_infop pi) { png_const_structp pp = ppIn; diff --git a/contrib/tools/pngfix.c b/contrib/tools/pngfix.c index 08b23e73b..6d7d584a3 100644 --- a/contrib/tools/pngfix.c +++ b/contrib/tools/pngfix.c @@ -3159,13 +3159,13 @@ read_chunk(struct file *file) /* This returns a file* from a png_struct in an implementation specific way. */ static struct file *get_control(png_const_structrp png_ptr); -static void +static void PNGCBAPI error_handler(png_structp png_ptr, png_const_charp message) { stop(get_control(png_ptr), LIBPNG_ERROR_CODE, message); } -static void +static void PNGCBAPI warning_handler(png_structp png_ptr, png_const_charp message) { struct file *file = get_control(png_ptr); @@ -3177,7 +3177,7 @@ warning_handler(png_structp png_ptr, png_const_charp message) /* Read callback - this is where the work gets done to check the stream before * passing it to libpng */ -static void +static void PNGCBAPI read_callback(png_structp png_ptr, png_bytep buffer, size_t count) /* Return 'count' bytes to libpng in 'buffer' */ { diff --git a/png.h b/png.h index 63365d557..2d056af3f 100644 --- a/png.h +++ b/png.h @@ -2520,11 +2520,15 @@ PNG_EXPORT(177, void, png_set_invalid, (png_const_structrp png_ptr, #ifdef PNG_INFO_IMAGE_SUPPORTED /* The "params" pointer is currently not used and is for future expansion. */ +#ifdef PNG_SEQUENTIAL_READ_SUPPORTED PNG_EXPORT(178, void, png_read_png, (png_structrp png_ptr, png_inforp info_ptr, int transforms, png_voidp params)); +#endif +#ifdef PNG_WRITE_SUPPORTED PNG_EXPORT(179, void, png_write_png, (png_structrp png_ptr, png_inforp info_ptr, int transforms, png_voidp params)); #endif +#endif PNG_EXPORT(180, png_const_charp, png_get_copyright, (png_const_structrp png_ptr)); diff --git a/pngerror.c b/pngerror.c index 3faf27319..ac9d1ffaf 100644 --- a/pngerror.c +++ b/pngerror.c @@ -876,8 +876,8 @@ png_set_strip_error_numbers(png_structrp png_ptr, png_uint_32 strip_mode) * possible to implement without setjmp support just so long as there is some * way to handle the error return here: */ -PNG_FUNCTION(void /* PRIVATE */, -png_safe_error,(png_structp png_nonconst_ptr, png_const_charp error_message), +PNG_FUNCTION(void /* PRIVATE */, (PNGCBAPI +png_safe_error),(png_structp png_nonconst_ptr, png_const_charp error_message), PNG_NORETURN) { const png_const_structrp png_ptr = png_nonconst_ptr; @@ -912,7 +912,7 @@ png_safe_error,(png_structp png_nonconst_ptr, png_const_charp error_message), } #ifdef PNG_WARNINGS_SUPPORTED -void /* PRIVATE */ +void /* PRIVATE */ PNGCBAPI png_safe_warning(png_structp png_nonconst_ptr, png_const_charp warning_message) { const png_const_structrp png_ptr = png_nonconst_ptr; diff --git a/pngpriv.h b/pngpriv.h index a9aa1a2de..b37820af4 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -254,6 +254,11 @@ extern PNG_FUNCTION(type, name, args, PNG_EMPTY attributes) #endif +#ifndef PNG_INTERNAL_CALLBACK +# define PNG_INTERNAL_CALLBACK(type, name, args, attributes)\ + extern PNG_FUNCTION(type, (PNGCBAPI name), args, PNG_EMPTY attributes) +#endif + /* If floating or fixed point APIs are disabled they may still be compiled * internally. To handle this make sure they are declared as the appropriate * internal extern function (otherwise the symbol prefixing stuff won't work and @@ -1883,11 +1888,11 @@ typedef struct png_control * errors that might occur. Returns true on success, false on failure (either * of the function or as a result of a png_error.) */ -PNG_INTERNAL_FUNCTION(void,png_safe_error,(png_structp png_ptr, +PNG_INTERNAL_CALLBACK(void,png_safe_error,(png_structp png_ptr, png_const_charp error_message),PNG_NORETURN); #ifdef PNG_WARNINGS_SUPPORTED -PNG_INTERNAL_FUNCTION(void,png_safe_warning,(png_structp png_ptr, +PNG_INTERNAL_CALLBACK(void,png_safe_warning,(png_structp png_ptr, png_const_charp warning_message),PNG_EMPTY); #else # define png_safe_warning 0/*dummy argument*/ diff --git a/pngread.c b/pngread.c index 9ff639de2..b24247fcb 100644 --- a/pngread.c +++ b/pngread.c @@ -1001,8 +1001,6 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, int transforms, voidp params) { - int row; - if (png_ptr == NULL || info_ptr == NULL) return; @@ -1014,120 +1012,149 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, png_error(png_ptr, "Image is too high to process with png_read_png()"); /* -------------- image transformations start here ------------------- */ + /* libpng 1.6.10: add code to cause a png_app_error if a selected TRANSFORM + * is not implemented. This will only happen in de-configured (non-default) + * libpng builds. The results can be unexpected - png_read_png may return + * short or mal-formed rows because the transform is skipped. + */ -#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED /* Tell libpng to strip 16-bit/color files down to 8 bits per color. */ if (transforms & PNG_TRANSFORM_SCALE_16) - { /* Added at libpng-1.5.4. "strip_16" produces the same result that it * did in earlier versions, while "scale_16" is now more accurate. */ +#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED png_set_scale_16(png_ptr); - } +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SCALE_16 not supported"); #endif -#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED /* If both SCALE and STRIP are required pngrtran will effectively cancel the * latter by doing SCALE first. This is ok and allows apps not to check for * which is supported to get the right answer. */ if (transforms & PNG_TRANSFORM_STRIP_16) +#ifdef PNG_READ_STRIP_16_TO_8_SUPPORTED png_set_strip_16(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_16 not supported"); #endif -#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED /* Strip alpha bytes from the input data without combining with * the background (not recommended). */ if (transforms & PNG_TRANSFORM_STRIP_ALPHA) +#ifdef PNG_READ_STRIP_ALPHA_SUPPORTED png_set_strip_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_ALPHA not supported"); #endif -#if defined(PNG_READ_PACK_SUPPORTED) && !defined(PNG_READ_EXPAND_SUPPORTED) /* Extract multiple pixels with bit depths of 1, 2, or 4 from a single * byte into separate bytes (useful for paletted and grayscale images). */ if (transforms & PNG_TRANSFORM_PACKING) +#ifdef PNG_READ_PACK_SUPPORTED png_set_packing(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKING not supported"); #endif -#ifdef PNG_READ_PACKSWAP_SUPPORTED /* Change the order of packed pixels to least significant bit first * (not useful if you are using png_set_packing). */ if (transforms & PNG_TRANSFORM_PACKSWAP) +#ifdef PNG_READ_PACKSWAP_SUPPORTED png_set_packswap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKSWAP not supported"); #endif -#ifdef PNG_READ_EXPAND_SUPPORTED /* Expand paletted colors into true RGB triplets * Expand grayscale images to full 8 bits from 1, 2, or 4 bits/pixel * Expand paletted or RGB images with transparency to full alpha * channels so the data will be available as RGBA quartets. */ if (transforms & PNG_TRANSFORM_EXPAND) - if ((png_ptr->bit_depth < 8) || - (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) || - (info_ptr->valid & PNG_INFO_tRNS)) - png_set_expand(png_ptr); +#ifdef PNG_READ_EXPAND_SUPPORTED + png_set_expand(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_EXPAND not supported"); #endif /* We don't handle background color or gamma transformation or quantizing. */ -#ifdef PNG_READ_INVERT_SUPPORTED /* Invert monochrome files to have 0 as white and 1 as black */ if (transforms & PNG_TRANSFORM_INVERT_MONO) +#ifdef PNG_READ_INVERT_SUPPORTED png_set_invert_mono(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_MONO not supported"); #endif -#ifdef PNG_READ_SHIFT_SUPPORTED /* If you want to shift the pixel values from the range [0,255] or * [0,65535] to the original [0,7] or [0,31], or whatever range the * colors were originally in: */ - if ((transforms & PNG_TRANSFORM_SHIFT) && (info_ptr->valid & PNG_INFO_sBIT)) - png_set_shift(png_ptr, &info_ptr->sig_bit); + if (transforms & PNG_TRANSFORM_SHIFT) +#ifdef PNG_READ_SHIFT_SUPPORTED + if (info_ptr->valid & PNG_INFO_sBIT) + png_set_shift(png_ptr, &info_ptr->sig_bit); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SHIFT not supported"); #endif -#ifdef PNG_READ_BGR_SUPPORTED /* Flip the RGB pixels to BGR (or RGBA to BGRA) */ if (transforms & PNG_TRANSFORM_BGR) +#ifdef PNG_READ_BGR_SUPPORTED png_set_bgr(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_BGR not supported"); #endif -#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED /* Swap the RGBA or GA data to ARGB or AG (or BGRA to ABGR) */ if (transforms & PNG_TRANSFORM_SWAP_ALPHA) +#ifdef PNG_READ_SWAP_ALPHA_SUPPORTED png_set_swap_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ALPHA not supported"); #endif -#ifdef PNG_READ_SWAP_SUPPORTED /* Swap bytes of 16-bit files to least significant byte first */ if (transforms & PNG_TRANSFORM_SWAP_ENDIAN) +#ifdef PNG_READ_SWAP_SUPPORTED png_set_swap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ENDIAN not supported"); #endif /* Added at libpng-1.2.41 */ -#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED /* Invert the alpha channel from opacity to transparency */ if (transforms & PNG_TRANSFORM_INVERT_ALPHA) +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED png_set_invert_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_ALPHA not supported"); #endif /* Added at libpng-1.2.41 */ -#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED /* Expand grayscale image to RGB */ if (transforms & PNG_TRANSFORM_GRAY_TO_RGB) +#ifdef PNG_READ_GRAY_TO_RGB_SUPPORTED png_set_gray_to_rgb(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_GRAY_TO_RGB not supported"); #endif /* Added at libpng-1.5.4 */ -#ifdef PNG_READ_EXPAND_16_SUPPORTED if (transforms & PNG_TRANSFORM_EXPAND_16) +#ifdef PNG_READ_EXPAND_16_SUPPORTED png_set_expand_16(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_EXPAND_16 not supported"); #endif /* We don't handle adding filler bytes */ @@ -1150,16 +1177,17 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, { png_uint_32 iptr; - info_ptr->row_pointers = (png_bytepp)png_malloc(png_ptr, - info_ptr->height * (sizeof (png_bytep))); + info_ptr->row_pointers = png_voidcast(png_bytepp, png_malloc(png_ptr, + info_ptr->height * (sizeof (png_bytep)))); + for (iptr=0; iptrheight; iptr++) info_ptr->row_pointers[iptr] = NULL; info_ptr->free_me |= PNG_FREE_ROWS; - for (row = 0; row < (int)info_ptr->height; row++) - info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr, - png_get_rowbytes(png_ptr, info_ptr)); + for (iptr = 0; iptr < info_ptr->height; iptr++) + info_ptr->row_pointers[iptr] = png_voidcast(png_bytep, + png_malloc(png_ptr, info_ptr->rowbytes)); } png_read_image(png_ptr, info_ptr->row_pointers); @@ -1168,9 +1196,7 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, /* Read rest of file, and get additional chunks in info_ptr - REQUIRED */ png_read_end(png_ptr, info_ptr); - PNG_UNUSED(transforms) /* Quiet compiler warnings */ PNG_UNUSED(params) - } #endif /* PNG_INFO_IMAGE_SUPPORTED */ #endif /* PNG_SEQUENTIAL_READ_SUPPORTED */ diff --git a/pngrtran.c b/pngrtran.c index f0c016478..de57ce4c4 100644 --- a/pngrtran.c +++ b/pngrtran.c @@ -1363,12 +1363,12 @@ png_init_read_transformations(png_structrp png_ptr) * 12) PNG_EXPAND_16 * 13) PNG_GRAY_TO_RGB iff PNG_BACKGROUND_IS_GRAY * 14) PNG_INVERT_MONO - * 15) PNG_SHIFT - * 16) PNG_PACK - * 17) PNG_BGR - * 18) PNG_PACKSWAP - * 19) PNG_FILLER (includes PNG_ADD_ALPHA) - * 20) PNG_INVERT_ALPHA + * 15) PNG_INVERT_ALPHA + * 16) PNG_SHIFT + * 17) PNG_PACK + * 18) PNG_BGR + * 19) PNG_PACKSWAP + * 20) PNG_FILLER (includes PNG_ADD_ALPHA) * 21) PNG_SWAP_ALPHA * 22) PNG_SWAP_BYTES * 23) PNG_USER_TRANSFORM [must be last] @@ -4907,6 +4907,11 @@ png_do_read_transformations(png_structrp png_ptr, png_row_infop row_info) png_do_invert(row_info, png_ptr->row_buf + 1); #endif +#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED + if (png_ptr->transformations & PNG_INVERT_ALPHA) + png_do_read_invert_alpha(row_info, png_ptr->row_buf + 1); +#endif + #ifdef PNG_READ_SHIFT_SUPPORTED if (png_ptr->transformations & PNG_SHIFT) png_do_unshift(row_info, png_ptr->row_buf + 1, @@ -4941,11 +4946,6 @@ png_do_read_transformations(png_structrp png_ptr, png_row_infop row_info) (png_uint_32)png_ptr->filler, png_ptr->flags); #endif -#ifdef PNG_READ_INVERT_ALPHA_SUPPORTED - if (png_ptr->transformations & PNG_INVERT_ALPHA) - png_do_read_invert_alpha(row_info, png_ptr->row_buf + 1); -#endif - #ifdef PNG_READ_SWAP_ALPHA_SUPPORTED if (png_ptr->transformations & PNG_SWAP_ALPHA) png_do_read_swap_alpha(row_info, png_ptr->row_buf + 1); diff --git a/pngrutil.c b/pngrutil.c index 062f287ed..050006905 100644 --- a/pngrutil.c +++ b/pngrutil.c @@ -1112,13 +1112,12 @@ png_handle_gAMA(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) void /* PRIVATE */ png_handle_sBIT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) { - unsigned int truelen; + unsigned int truelen, i; + png_byte sample_depth; png_byte buf[4]; png_debug(1, "in png_handle_sBIT"); - buf[0] = buf[1] = buf[2] = buf[3] = 0; - if (!(png_ptr->mode & PNG_HAVE_IHDR)) png_chunk_error(png_ptr, "missing IHDR"); @@ -1137,10 +1136,16 @@ png_handle_sBIT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) } if (png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) + { truelen = 3; + sample_depth = 8; + } else + { truelen = png_ptr->channels; + sample_depth = png_ptr->bit_depth; + } if (length != truelen || length > 4) { @@ -1149,11 +1154,19 @@ png_handle_sBIT(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length) return; } + buf[0] = buf[1] = buf[2] = buf[3] = sample_depth; png_crc_read(png_ptr, buf, truelen); if (png_crc_finish(png_ptr, 0)) return; + for (i=0; i sample_depth) + { + png_chunk_benign_error(png_ptr, "invalid"); + return; + } + if (png_ptr->color_type & PNG_COLOR_MASK_COLOR) { png_ptr->sig_bit.red = buf[0]; diff --git a/pngwrite.c b/pngwrite.c index c3cb6070d..33ac58bca 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -1546,81 +1546,117 @@ png_write_png(png_structrp png_ptr, png_inforp info_ptr, if (png_ptr == NULL || info_ptr == NULL) return; + if ((info_ptr->valid & PNG_INFO_IDAT) == 0) + { + png_app_error(png_ptr, "no rows for png_write_image to write"); + return; + } + /* Write the file header information. */ png_write_info(png_ptr, info_ptr); /* ------ these transformations don't touch the info structure ------- */ -#ifdef PNG_WRITE_INVERT_SUPPORTED /* Invert monochrome pixels */ if (transforms & PNG_TRANSFORM_INVERT_MONO) +#ifdef PNG_WRITE_INVERT_SUPPORTED png_set_invert_mono(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_MONO not supported"); #endif -#ifdef PNG_WRITE_SHIFT_SUPPORTED /* Shift the pixels up to a legal bit depth and fill in * as appropriate to correctly scale the image. */ - if ((transforms & PNG_TRANSFORM_SHIFT) - && (info_ptr->valid & PNG_INFO_sBIT)) - png_set_shift(png_ptr, &info_ptr->sig_bit); + if (transforms & PNG_TRANSFORM_SHIFT) +#ifdef PNG_WRITE_SHIFT_SUPPORTED + if (info_ptr->valid & PNG_INFO_sBIT) + png_set_shift(png_ptr, &info_ptr->sig_bit); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SHIFT not supported"); #endif -#ifdef PNG_WRITE_PACK_SUPPORTED /* Pack pixels into bytes */ if (transforms & PNG_TRANSFORM_PACKING) - png_set_packing(png_ptr); +#ifdef PNG_WRITE_PACK_SUPPORTED + png_set_packing(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKING not supported"); #endif -#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED /* Swap location of alpha bytes from ARGB to RGBA */ if (transforms & PNG_TRANSFORM_SWAP_ALPHA) +#ifdef PNG_WRITE_SWAP_ALPHA_SUPPORTED png_set_swap_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ALPHA not supported"); #endif + /* Remove a filler (X) from XRGB/RGBX/AG/GA into to convert it into + * RGB, note that the code expects the input color type to be G or RGB; no + * alpha channel. + */ + if (transforms & + (PNG_TRANSFORM_STRIP_FILLER_AFTER|PNG_TRANSFORM_STRIP_FILLER_BEFORE)) + { #ifdef PNG_WRITE_FILLER_SUPPORTED - /* Pack XRGB/RGBX/ARGB/RGBA into RGB (4 channels -> 3 channels) */ - if (transforms & PNG_TRANSFORM_STRIP_FILLER_AFTER) - png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + if (transforms & PNG_TRANSFORM_STRIP_FILLER_AFTER) + { + if (transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) + png_app_error(png_ptr, + "PNG_TRANSFORM_STRIP_FILLER: BEFORE+AFTER not supported"); - else if (transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) - png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE); + /* Continue if ignored - this is the pre-1.6.10 behavior */ + png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); + } + + else if (transforms & PNG_TRANSFORM_STRIP_FILLER_BEFORE) + png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_STRIP_FILLER not supported"); #endif + } -#ifdef PNG_WRITE_BGR_SUPPORTED /* Flip BGR pixels to RGB */ if (transforms & PNG_TRANSFORM_BGR) +#ifdef PNG_WRITE_BGR_SUPPORTED png_set_bgr(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_BGR not supported"); #endif -#ifdef PNG_WRITE_SWAP_SUPPORTED /* Swap bytes of 16-bit files to most significant byte first */ if (transforms & PNG_TRANSFORM_SWAP_ENDIAN) +#ifdef PNG_WRITE_SWAP_SUPPORTED png_set_swap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_SWAP_ENDIAN not supported"); #endif -#ifdef PNG_WRITE_PACKSWAP_SUPPORTED /* Swap bits of 1, 2, 4 bit packed pixel formats */ if (transforms & PNG_TRANSFORM_PACKSWAP) +#ifdef PNG_WRITE_PACKSWAP_SUPPORTED png_set_packswap(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_PACKSWAP not supported"); #endif -#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED /* Invert the alpha channel from opacity to transparency */ if (transforms & PNG_TRANSFORM_INVERT_ALPHA) +#ifdef PNG_WRITE_INVERT_ALPHA_SUPPORTED png_set_invert_alpha(png_ptr); +#else + png_app_error(png_ptr, "PNG_TRANSFORM_INVERT_ALPHA not supported"); #endif /* ----------------------- end of transformations ------------------- */ /* Write the bits */ - if (info_ptr->valid & PNG_INFO_IDAT) - png_write_image(png_ptr, info_ptr->row_pointers); + png_write_image(png_ptr, info_ptr->row_pointers); /* It is REQUIRED to call this to finish writing the rest of the file */ png_write_end(png_ptr, info_ptr); - PNG_UNUSED(transforms) /* Quiet compiler warnings */ PNG_UNUSED(params) } #endif diff --git a/scripts/intprefix.dfn b/scripts/intprefix.dfn index 790bdfee9..fa59f88e9 100644 --- a/scripts/intprefix.dfn +++ b/scripts/intprefix.dfn @@ -15,5 +15,8 @@ #define PNG_INTERNAL_FUNCTION(type, name, args, attributes)\ PNG_DFN "@" name "@" +#define PNG_INTERNAL_CALLBACK(type, name, args, attributes)\ + PNG_DFN "@" name "@" + #define PNGPREFIX_H /* self generation */ #include "../pngpriv.h" diff --git a/tests/pngimage-full b/tests/pngimage-full new file mode 100755 index 000000000..f5eb9895e --- /dev/null +++ b/tests/pngimage-full @@ -0,0 +1,2 @@ +#!/bin/sh +exec ./pngimage --exhaustive --log "${srcdir}/contrib/pngsuite/"*.png diff --git a/tests/pngimage-quick b/tests/pngimage-quick new file mode 100755 index 000000000..2d4b5db22 --- /dev/null +++ b/tests/pngimage-quick @@ -0,0 +1,2 @@ +#!/bin/sh +exec ./pngimage --log "${srcdir}/contrib/pngsuite/"*.png