From 5bc90389bffa3cf3d2b8325bfac5c4344a206bc0 Mon Sep 17 00:00:00 2001 From: John Bowler Date: Mon, 23 Jan 2012 22:43:22 -0600 Subject: [PATCH] [libpng16] Added color-map support to simplified API. This is an initial version for review; the documentation has not yet been updated. --- ANNOUNCE | 2 + CHANGES | 2 + png.h | 230 +++-- pngpriv.h | 34 +- pngread.c | 2095 ++++++++++++++++++++++++++++++++++++++----- pngrtran.c | 2 +- pngwrite.c | 330 +++++-- scripts/symbols.def | 2 - 8 files changed, 2239 insertions(+), 458 deletions(-) diff --git a/ANNOUNCE b/ANNOUNCE index 0ce76cc04..922dd0322 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -114,6 +114,8 @@ Version 1.6.0beta06 [January 24, 2012] changes some of the macro definitions in png.h, app code may need corresponding changes. Increased the formatted warning buffer to 192 bytes. + Added color-map support to simplified API. This is an initial version for + review; the documentation has not yet been updated. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/CHANGES b/CHANGES index 0ceb4039b..0e9b9e3c8 100644 --- a/CHANGES +++ b/CHANGES @@ -3865,6 +3865,8 @@ Version 1.6.0beta06 [January 24, 2012] changes some of the macro definitions in png.h, app code may need corresponding changes. Increased the formatted warning buffer to 192 bytes. + Added color-map support to simplified API. This is an initial version for + review; the documentation has not yet been updated. Send comments/corrections/commendations to png-mng-implement at lists.sf.net (subscription required; visit diff --git a/png.h b/png.h index f99b6d6cf..c423ffac9 100644 --- a/png.h +++ b/png.h @@ -1,7 +1,7 @@ /* png.h - header file for PNG reference library * - * libpng version 1.6.0beta06 - January 16, 2012 + * libpng version 1.6.0beta06 - January 24, 2012 * Copyright (c) 1998-2012 Glenn Randers-Pehrson * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger) * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.) @@ -11,7 +11,7 @@ * Authors and maintainers: * libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat * libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger - * libpng versions 0.97, January 1998, through 1.6.0beta06 - January 16, 2012: Glenn + * libpng versions 0.97, January 1998, through 1.6.0beta06 - January 24, 2012: Glenn * See also "Contributing Authors", below. * * Note about libpng version numbers: @@ -198,7 +198,7 @@ * * This code is released under the libpng license. * - * libpng versions 1.2.6, August 15, 2004, through 1.6.0beta06, January 16, 2012, are + * libpng versions 1.2.6, August 15, 2004, through 1.6.0beta06, January 24, 2012, are * Copyright (c) 2004, 2006-2012 Glenn Randers-Pehrson, and are * distributed according to the same disclaimer and license as libpng-1.2.5 * with the following individual added to the list of Contributing Authors: @@ -310,7 +310,7 @@ * Y2K compliance in libpng: * ========================= * - * January 16, 2012 + * January 24, 2012 * * Since the PNG Development group is an ad-hoc body, we can't make * an official declaration. @@ -376,7 +376,7 @@ /* Version information for png.h - this should match the version in png.c */ #define PNG_LIBPNG_VER_STRING "1.6.0beta06" #define PNG_HEADER_VERSION_STRING \ - " libpng version 1.6.0beta06 - January 16, 2012\n" + " libpng version 1.6.0beta06 - January 24, 2012\n" #define PNG_LIBPNG_VER_SONUM 16 #define PNG_LIBPNG_VER_DLLNUM 16 @@ -2636,48 +2636,47 @@ PNG_EXPORT(207, void, png_save_uint_16, (png_bytep buf, unsigned int i)); * * To read a PNG file using the simplified API: * - * 1) Declare a 'png_image' structure (see below) on the stack and memset() it - * to all zero. + * 1) Declare a 'png_image' structure (see below) on the stack and set the + * version field to PNG_IMAGE_VERSION. * 2) Call the appropriate png_image_begin_read... function. * 3) Set the png_image 'format' member to the required sample format. - * 4) [Optionally] Call png_image_read_colormap to read the image color-map and - * request return of a color-mapped image. - * 5) Allocate a buffer for the image. - * 6) Call png_image_finish_read to read the image into your buffer. + * 4) Allocate a buffer for the image and, if required, the color-map. + * 5) Call png_image_finish_read to read the image and, if required, the + * color-map into your buffers. * * There are no restrictions on the format of the PNG input itself; all valid * color types, bit depths, and interlace methods are acceptable, and the * input image is transformed as necessary to the requested in-memory format * during the png_image_finish_read() step. The only caveat is that if you - * request a color-mapped image from a PNG that was not originally color-mapped - * the transformation is extremely lossy and the result may look terrible. + * request a color-mapped image from a PNG that is full-color or makes + * complex use of an alpha channel the transformation is extremely lossy and the + * result may look terrible. * * To write a PNG file using the simplified API: * * 1) Declare a 'png_image' structure on the stack and memset() it to all zero. * 2) Initialize the members of the structure that describe the image, setting * the 'format' member to the format of the image samples. - * 3) [Optionally] call png_image_write_colormap to set the image color-map if - * the data to be written is color-mapped. - * 4) Call the appropriate png_image_write... function with a pointer to the - * image to write the PNG data. + * 3) Call the appropriate png_image_write... function with a pointer to the + * image and, if necessary, the color-map to write the PNG data. * * png_image is a structure that describes the in-memory format of an image - * when it is being read or define the in-memory format of an image that you + * when it is being read or defines the in-memory format of an image that you * need to write: */ +#define PNG_IMAGE_VERSION 1 typedef struct png_control *png_controlp; typedef struct { - png_uint_32 width; /* Image width in pixels (columns) */ - png_uint_32 height; /* Image height in pixels (rows) */ - png_uint_32 format; /* Image format as defined below */ - png_uint_32 flags; /* A bit mask containing informational flags */ - png_controlp opaque; /* Initialize to NULL, free with png_image_free */ - - /* The following is only used for write; initialize it to NULL */ - png_const_bytep colormap; /* A pointer to the application color-map */ + png_controlp opaque; /* Initialize to NULL, free with png_image_free */ + png_uint_32 version; /* Set to PNG_IMAGE_VERSION */ + png_uint_32 width; /* Image width in pixels (columns) */ + png_uint_32 height; /* Image height in pixels (rows) */ + png_uint_32 format; /* Image format as defined below */ + png_uint_32 flags; /* A bit mask containing informational flags */ + png_uint_32 colormap_entries; + /* Number of entries in the color-map */ /* In the event of an error or warning the following field will be set to a * non-zero value and the 'message' field will contain a '\0' terminated @@ -2706,15 +2705,15 @@ typedef struct char message[64]; } png_image, *png_imagep; -/* The pixels (samples) of the image have one to four channels whose components - * have original values in the range 0 to 1.0: +/* The samples of the image have one to four channels whose components have + * original values in the range 0 to 1.0: * * 1: A single gray or luminance channel (G). * 2: A gray/luminance channel and an alpha channel (GA). * 3: Three red, green, blue color channels (RGB). * 4: Three color channels and an alpha channel (RGBA). * - * The channels are encoded in one of two ways: + * The components are encoded in one of two ways: * * a) As a small integer, value 0..255, contained in a single byte. For the * alpha channel the original value is simply value/255. For the color or @@ -2735,22 +2734,24 @@ typedef struct * channel: the color/gray channels are scaled (pre-multiplied) by the alpha * value. * - * When a color-mapped image is used as a result of calling - * png_image_read_colormap or png_image_write_colormap the channels are encoded - * in the color-map and the descriptions above apply to the color-map entries. - * The image data is encoded as small integers, value 0..255, that index the - * entries in the color-map. One integer (one byte) is stored for each pixel. + * The samples are either contained directly in the image data, between 1 and 8 + * bytes per pixel according to the encoding, or are held in a color-map indexed + * by bytes in the image data. In the case of a color-map the color-map entries + * are individual samples, encoded as above, and the image data has one byte per + * pixel to select the relevant sample from the color-map. */ /* PNG_FORMAT_* * * #defines to be used in png_image::format. Each #define identifies a - * particular layout of channel data and, if present, alpha values. There are - * separate defines for each of the two channel encodings. + * particular layout of sample data and, if present, alpha values. There are + * separate defines for each of the two component encodings. * - * A format is built up using single bit flag values. Not all combinations are - * valid: use the bit flag values below for testing a format returned by the - * read APIs, but set formats from the derived values. + * A format is built up using single bit flag values. All combinations are + * valid. Formats can be built up from the flag values or you can use one of + * the predefined values below. When testing formats always use the FORMAT_FLAG + * macros to test for individual features - future versions of the library may + * add new flags. * * When reading or writing color-mapped images the format should be set to the * format of the entries in the color-map then png_image_{read,write}_colormap @@ -2762,14 +2763,15 @@ typedef struct * compiled out it is because libpng does not have the required support. It is * possible, however, for the libpng configuration to enable the format on just * read or just write; in that case you may see an error at run time. You can - * guard against this by checking for the definition of: + * guard against this by checking for the definition of the appropriate + * "_SUPPORTED" macro, one of: * * PNG_SIMPLIFIED_{READ,WRITE}_{BGR,AFIRST}_SUPPORTED */ #define PNG_FORMAT_FLAG_ALPHA 0x01U /* format with an alpha channel */ #define PNG_FORMAT_FLAG_COLOR 0x02U /* color format: otherwise grayscale */ #define PNG_FORMAT_FLAG_LINEAR 0x04U /* 2 byte channels else 1 byte */ -#define PNG_FORMAT_FLAG_COLORMAP 0x08U /* libpng use only */ +#define PNG_FORMAT_FLAG_COLORMAP 0x08U /* image data is color-mapped */ #ifdef PNG_FORMAT_BGR_SUPPORTED # define PNG_FORMAT_FLAG_BGR 0x10U /* BGR colors, else order is RGB */ @@ -2779,13 +2781,9 @@ typedef struct # define PNG_FORMAT_FLAG_AFIRST 0x20U /* alpha channel comes first */ #endif -/* Supported formats are as follows. Future versions of libpng may support more - * formats; for compatibility with older versions simply check if the format - * macro is defined using #ifdef. These defines describe the in-memory layout - * of the components of the pixels of the image or, for color-mapped images, the - * layout of the entries of the color-map. +/* Commonly used formats have predefined macros. * - * First the single byte formats: + * First the single byte (sRGB) formats: */ #define PNG_FORMAT_GRAY 0 #define PNG_FORMAT_GA PNG_FORMAT_FLAG_ALPHA @@ -2798,9 +2796,7 @@ typedef struct #define PNG_FORMAT_ABGR (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_AFIRST) /* Then the linear 2-byte formats. When naming these "Y" is used to - * indicate a luminance (gray) channel. The component order within the pixel - * is always the same - there is no provision for swapping the order of the - * components in the linear format. + * indicate a luminance (gray) channel. */ #define PNG_FORMAT_LINEAR_Y PNG_FORMAT_FLAG_LINEAR #define PNG_FORMAT_LINEAR_Y_ALPHA (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_ALPHA) @@ -2808,13 +2804,17 @@ typedef struct #define PNG_FORMAT_LINEAR_RGB_ALPHA \ (PNG_FORMAT_FLAG_LINEAR|PNG_FORMAT_FLAG_COLOR|PNG_FORMAT_FLAG_ALPHA) -/* Color-mapped formats are obtained by calling png_image_{read,write}_colormap, - * as appropriate after setting png_image::format to the format of the color-map - * to be read or written. Applications may check the value of - * PNG_FORMAT_FLAG_COLORMAP to see if they have called the colormap API. The - * format of the color-map may be extracted using the following macro. +/* With color-mapped formats the image data is one byte for each pixel, the byte + * is an index into the color-map which is formatted as above. To obtain a + * color-mapped format it is sufficient just to add the PNG_FOMAT_FLAG_COLORMAP + * to one of the above definitions, or you can use one of the definitions below. */ -#define PNG_FORMAT_OF_COLORMAP(fmt) ((fmt) & ~PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_RGB_COLORMAP (PNG_FORMAT_RGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGR_COLORMAP (PNG_FORMAT_BGR|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_RGBA_COLORMAP (PNG_FORMAT_RGBA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ARGB_COLORMAP (PNG_FORMAT_ARGB|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_BGRA_COLORMAP (PNG_FORMAT_BGRA|PNG_FORMAT_FLAG_COLORMAP) +#define PNG_FORMAT_ABGR_COLORMAP (PNG_FORMAT_ABGR|PNG_FORMAT_FLAG_COLORMAP) /* PNG_IMAGE macros * @@ -2822,9 +2822,9 @@ typedef struct * structure. The PNG_IMAGE_SAMPLE_ macros return values appropriate to the * actual image sample values - either the entries in the color-map or the * pixels in the image. The PNG_IMAGE_PIXEL_ macros return corresponding values - * for the pixels and will always return 1 after a call to - * png_image_{read,write}_colormap. The remaining macros return information - * about the rows in the image and the complete image. + * for the pixels and will always return 1 for color-mapped formats. The + * remaining macros return information about the rows in the image and the + * complete image. * * NOTE: All the macros that take a png_image::format parameter are compile time * constants if the format parameter is, itself, a constant. Therefore these @@ -2841,7 +2841,7 @@ typedef struct #define PNG_IMAGE_SAMPLE_COMPONENT_SIZE(fmt)\ ((((fmt) & PNG_FORMAT_FLAG_LINEAR) >> 2)+1) /* Return the size in bytes of a single component of a pixel or color-map - * entry (as appropriate) in the image. + * entry (as appropriate) in the image: 1 or 2. */ #define PNG_IMAGE_SAMPLE_SIZE(fmt)\ @@ -2851,11 +2851,19 @@ typedef struct * one byte in size), otherwise it is the size of one image pixel. */ -#define PNG_IMAGE_COLORMAP_SIZE(fmt) (PNG_IMAGE_SAMPLE_SIZE(format) * 256) - /* The size of the color-map required by the format; this is the size of the - * color-map buffer passed to the png_image_{read,write}_colormap APIs, it is - * a fixed number determined by the format so can easily be allocated on the - * stack if necessary. +#define PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(fmt)\ + (PNG_IMAGE_SAMPLE_CHANNELS(fmt) * 256) + /* The maximum size of the color-map required by the format expressed in a + * count of components. This can be used to compile-time allocate a + * color-map: + * + * png_uint_16 colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(linear_fmt)]; + * + * png_byte colormap[PNG_IMAGE_MAXIMUM_COLORMAP_COMPONENTS(sRGB_fmt)]; + * + * Alternatively use the PNG_IMAGE_COLORMAP_SIZE macro below to use the + * information from one of the png_image_begin_read_ APIs and dynamically + * allocate the required memory. */ /* Corresponding information about the pixels */ @@ -2898,6 +2906,14 @@ typedef struct * the row stride is the minimum stride required for the image. */ +#define PNG_IMAGE_COLORMAP_SIZE(image)\ + (PNG_IMAGE_SAMPLE_SIZE((image).format) * (image).colormap_entries) + /* Return the size, in bytes, of the color-map of this image. If the image + * format is not a color-map format this will return a size sufficient for + * 256 entries in the given format; check PNG_IMAGE_FORMAT_FLAG_COLORMAP if + * you don't want to allocate a color-map in this case. + */ + /* PNG_IMAGE_FLAG_* * * Flags containing additional information about the image are held in the @@ -2908,13 +2924,6 @@ typedef struct * correspond to the red, green and blue end-points defined by sRGB. */ -#define PNG_IMAGE_FLAG_COLORMAP 0x02 - /* The PNG is color-mapped. If this flag is set png_image_read_colormap - * can be used without further loss of image information. If it is not set - * png_image_read_colormap will cause significant loss if the image has any - * colors (if PNG_FORMAT_FLAG_COLOR is set). - */ - #ifdef PNG_SIMPLIFIED_READ_SUPPORTED /* READ APIs * --------- @@ -2938,28 +2947,9 @@ PNG_EXPORT(236, int, png_image_begin_read_from_memory, (png_imagep image, png_const_voidp memory, png_size_t size)); /* The PNG header is read from the given memory buffer. */ -PNG_EXPORT(242, int, png_image_read_colormap, (png_imagep image, - png_bytep colormap, png_colorp background)); - /* Set the png_image to read a color-mapped image. image->format must be set - * to the format required for the color-map, typically PNG_FORMAT_RGBA or - * just PNG_FORMAT_RGB if an alpha channel is to be removed. - * - * The color-map is filled in and the actual number of valid entries - * returned, 0 is returned on error. A subsequent call to - * png_image_finish_read will return the color-mapped image data; one byte - * per pixel. - * - * background is used as described below to remove alpha or transparency - * information from an 8-bit color-map by compositing onto a solid color. - * - * If background is NULL *and* PNG_FORMAT_RGB is requested *and* the input - * has an alpha channel then the call will currently FAIL, however, in the - * future, libpng may be extended to composite onto the buffer in this case - * too. - */ - PNG_EXPORT(237, int, png_image_finish_read, (png_imagep image, - png_colorp background, void *buffer, png_int_32 row_stride)); + png_colorp background, void *buffer, png_int_32 row_stride, + void *colormap)); /* Finish reading the image into the supplied buffer and clean up the * png_image structure. * @@ -2974,12 +2964,22 @@ PNG_EXPORT(237, int, png_image_finish_read, (png_imagep image, * onto the buffer. The value is an sRGB color to use for the background, * for grayscale output the green channel is used. * - * If png_image_read_colormap has been called the value of background must be - * the same as that passed to the colormap call or the resultant image pixels - * are implementation defined and may vary between libpng minor releases. + * background must be supplied when an alpha channel must be removed from a + * single byte color-mapped output format, in other words if: + * + * 1) The original format from png_image_begin_read_from_* had + * PNG_FORMAT_FLAG_ALPHA set. + * 2) The format set by the application does not. + * 3) The format set by the application has PNG_FORMAT_FLAG_COLORMAP set and + * PNG_FORMAT_FLAG_LINEAR *not* set. * * For linear output removing the alpha channel is always done by compositing - * on black. + * on black and background is ignored.: + * + * colormap must be supplied when PNG_FORMAT_FLAG_COLORMAP is set. It must + * be at least the size (in bytes) returned by PNG_IMAGE_COLORMAP_SIZE. + * image->colormap_entries will be updated to the actual number of entries + * written to the colormap; this may be less than the original value. */ PNG_EXPORT(238, void, png_image_free, (png_imagep image)); @@ -2992,42 +2992,38 @@ PNG_EXPORT(238, void, png_image_free, (png_imagep image)); /* WRITE APIS * ---------- * For write you must initialize a png_image structure to describe the image to - * be written: + * be written. To do this use memset to set the whole structure to 0 then + * initialize fields describing your image. * + * version: must be set to PNG_IMAGE_VERSION * opaque: must be initialized to NULL - * colormap: must be initialized to NULL * width: image width in pixels * height: image height in rows - * format: the format of the data you wish to write + * format: the format of the data (image and color-map) you wish to write * flags: set to 0 unless one of the defined flags applies; set * PNG_IMAGE_FLAG_COLORSPACE_NOT_sRGB for color format images where the RGB * values do not correspond to the colors in sRGB. + * colormap_entries: set to the number of entries in the color-map (0 to 256) */ -PNG_EXPORT(243, int, png_image_write_colormap, (png_imagep image, - png_const_bytep colormap)); - /* Optionally write a color-mapped image. 'format' must be set to the format - * of the data in the color-map and must not be changed after the call. The - * colormap *pointer* is retained, the color-map data itself is not copied; - * the data must not be freed until after the called to png_image_write_* - */ - PNG_EXPORT(239, int, png_image_write_to_file, (png_imagep image, const char *file, int convert_to_8bit, const void *buffer, - png_int_32 row_stride)); + png_int_32 row_stride, const void *colormap)); /* Write the image to the named file. */ PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, - int convert_to_8_bit, const void *buffer, png_int_32 row_stride)); + int convert_to_8_bit, const void *buffer, png_int_32 row_stride, + const void *colormap)); /* Write the image to the given (FILE*). */ -/* With all write APIs if image is in one of the linear formats with 16-bit data - * then setting convert_to_8_bit will cause the output to be an 8-bit PNG gamma - * encoded according to the sRGB specification, otherwise a 16-bit linear +/* With both write APIs if image is in one of the linear formats with 16-bit + * data then setting convert_to_8_bit will cause the output to be an 8-bit PNG + * gamma encoded according to the sRGB specification, otherwise a 16-bit linear * encoded PNG file is written. * - * With color-mapped data png_image_write_colormap must be called. The palette - * may contain linear (16-bit) entries, these will be converted to sRGB values - * regardless of the setting of convert_to_8_bit. + * With color-mapped data formats the colormap parameter point to a color-map + * with at least image->colormap_entries encoded in the specified format. If + * the format is linear the written PNG color-map will be converted to sRGB + * regardless of the convert_to_8_bit flag. * * With all APIs row_stride is handled as in the read APIs - it is the spacing * from one row to the next in component sized units (1 or 2 bytes) and if @@ -3049,7 +3045,7 @@ PNG_EXPORT(240, int, png_image_write_to_stdio, (png_imagep image, FILE *file, * scripts/symbols.def as well. */ #ifdef PNG_EXPORT_LAST_ORDINAL - PNG_EXPORT_LAST_ORDINAL(243); + PNG_EXPORT_LAST_ORDINAL(241); #endif #ifdef __cplusplus diff --git a/pngpriv.h b/pngpriv.h index b5e44ae7c..3b0633ba5 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -519,20 +519,31 @@ typedef const png_uint_16p * png_const_uint_16pp; #ifdef PNG_SIMPLIFIED_READ_SUPPORTED extern /*PRIVATE*/ PNG_CONST_DATA png_uint_16 png_sRGB_table[256]; /* Convert from an sRGB encoded value 0..255 to a 16-bit linear value, - * 0..65535. This table gives the closes 16-bit answers (no errors). + * 0..65535. This table gives the closest 16-bit answers (no errors). */ #endif extern /*PRIVATE*/ PNG_CONST_DATA png_uint_16 png_sRGB_base[512]; extern /*PRIVATE*/ PNG_CONST_DATA png_byte png_sRGB_delta[512]; -#define PNG_sRGB_FROM_LINEAR(linear) ((png_sRGB_base[(linear)>>15] +\ - ((((linear)&0x7fff)*png_sRGB_delta[(linear)>>15])>>12)) >> 8) +#define PNG_sRGB_FROM_LINEAR(linear) ((png_byte)((png_sRGB_base[(linear)>>15] +\ + ((((linear)&0x7fff)*png_sRGB_delta[(linear)>>15])>>12)) >> 8)) /* Given a value 'linear' in the range 0..255*65535 calculate the 8-bit sRGB * encoded value with maximum error 0.646365. Note that the input is not a * 16-bit value; it has been multiplied by 255! */ #endif /* PNG_SIMPLIFIED_READ/WRITE */ +/* Added to libpng-1.6.0: scale a 16-bit value in the range 0..65535 to 0..255 + * by dividing by 257 *with rounding*. This macro is exact for the given range. + * See the discourse in pngrtran.c png_do_scale_16_to_8. The values in the + * macro were established by experiment (modifying the added value). The macro + * has a second variant that takes a value already scaled by 255 and divides by + * 65535 - this has a maximum error of .502. Over the range 0..65535*65535 it + * only gives off-by-one errors and only for 0.5% (1 in 200) of the values. + */ +#define PNG_DIV65535(v24) (((v24) + 32895) >> 16) +#define PNG_DIV257(v16) PNG_DIV65535((png_uint_32)(v16) * 255) + /* Added to libpng-1.2.6 JB */ #define PNG_ROWBYTES(pixel_bits, width) \ ((pixel_bits) >= 8 ? \ @@ -1622,17 +1633,20 @@ PNG_EXTERN void png_build_gamma_table PNGARG((png_structrp png_ptr, /* The internal structure that png_image::opaque points to. */ typedef struct png_control { - png_structp png_ptr; - png_infop info_ptr; - png_voidp error_buf; /* Always a jmp_buf at present. */ + png_structp png_ptr; + png_infop info_ptr; + png_voidp error_buf; /* Always a jmp_buf at present. */ - png_const_bytep memory; /* Memory buffer. */ - png_size_t size; /* Size of the memory buffer. */ + png_const_bytep memory; /* Memory buffer. */ + png_size_t size; /* Size of the memory buffer. */ - unsigned int for_write :1; /* Otherwise it is a read structure */ - unsigned int owned_file :1; /* We own the file in io_ptr */ + unsigned int for_write :1; /* Otherwise it is a read structure */ + unsigned int owned_file :1; /* We own the file in io_ptr */ } png_control; +/* This is used to name an sPLT written by the simplified API. */ +#define LIBPNG_SPLT_NAME "libpng " PNG_LIBPNG_VER_STRING + /* Return the pointer to the jmp_buf from a png_control: necessary because C * does not reveal the type of the elements of jmp_buf. */ diff --git a/pngread.c b/pngread.c index d1e8bef03..7ff4d08a4 100644 --- a/pngread.c +++ b/pngread.c @@ -1159,6 +1159,48 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, * This code currently relies on the sequential reader, though it could easily * be made to work with the progressive one. */ +/* Arguments to png_image_finish_read: */ + +/* Encoding of PNG data (used by the color-map code) */ +# define E_NOTSET 0 /* File encoding not yet known */ +# define E_sRGB 1 /* 8-bit encoded to sRGB gamma */ +# define E_LINEAR 2 /* 16-bit linear: not encoded, NOT pre-multiplied! */ +# define E_FILE 3 /* 8-bit encoded to file gamma, not sRGB or linear */ +# define E_LINEAR8 4 /* 8-bit linear: only from a file value */ + +/* Color-map processing: after libpng has run on the PNG image further + * processing may be needed to conver the data to color-map indicies. + */ +#define PNG_CMAP_NONE 0 +#define PNG_CMAP_GA 1 /* Process GA data to a color-map with alpha */ +#define PNG_CMAP_TRANS 2 /* Process GA data to a background index */ +#define PNG_CMAP_RGB 3 /* Process RGB data */ +#define PNG_CMAP_RGB_ALPHA 4 /* Process RGBA data */ + +/* The following document where the background is for each processing case. */ +#define PNG_CMAP_NONE_BACKGROUND 256 +#define PNG_CMAP_GA_BACKGROUND 231 +#define PNG_CMAP_TRANS_BACKGROUND 254 +#define PNG_CMAP_RGB_BACKGROUND 256 +#define PNG_CMAP_RGB_ALPHA_BACKGROUND 216 + +typedef struct +{ + /* Arguments: */ + png_imagep image; + png_voidp buffer; + png_int_32 row_stride; + png_voidp colormap; + png_colorp background; + /* Local variables: */ + png_bytep local_row; + png_bytep first_row; + ptrdiff_t row_bytes; /* step between rows */ + int file_encoding; /* E_ values above */ + png_fixed_point gamma_to_linear; /* For E_FILE, reciprocal of gamma */ + int colormap_processing; /* PNG_CMAP_ values above */ +} png_image_read_control; + /* Do all the *safe* initialization - 'safe' means that png_error won't be * called, so setting up the jmp_buf is not required. This means that anything * called from here must *not* call png_malloc - it has to call png_malloc_warn @@ -1167,43 +1209,54 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, static int png_image_read_init(png_imagep image) { - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, image, + if (image->opaque == NULL) + { + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, image, png_safe_error, png_safe_warning); - if (png_ptr != NULL) - { - png_infop info_ptr = png_create_info_struct(png_ptr); + /* And set the rest of the structure to NULL to ensure that the various + * fields are consistent. + */ + memset(image, 0, sizeof *image); + image->version = PNG_IMAGE_VERSION; - if (info_ptr != NULL) + if (png_ptr != NULL) { - png_controlp control = png_voidcast(png_controlp, - png_malloc_warn(png_ptr, sizeof *control)); + png_infop info_ptr = png_create_info_struct(png_ptr); - if (control != NULL) + if (info_ptr != NULL) { - png_memset(control, 0, sizeof *control); + png_controlp control = png_voidcast(png_controlp, + png_malloc_warn(png_ptr, sizeof *control)); - control->png_ptr = png_ptr; - control->info_ptr = info_ptr; - control->for_write = 0; + if (control != NULL) + { + png_memset(control, 0, sizeof *control); - image->opaque = control; - return 1; + control->png_ptr = png_ptr; + control->info_ptr = info_ptr; + control->for_write = 0; + + image->opaque = control; + return 1; + } + + /* Error clean up */ + png_destroy_info_struct(png_ptr, &info_ptr); } - /* Error clean up */ - png_destroy_info_struct(png_ptr, &info_ptr); + png_destroy_read_struct(&png_ptr, NULL, NULL); } - png_destroy_read_struct(&png_ptr, NULL, NULL); + return png_image_error(image, "png_image_read: out of memory"); } - return png_image_error(image, "png_image_read: out of memory"); + return png_image_error(image, "png_image_read: opaque pointer not NULL"); } /* Utility to find the base format of a PNG file from a png_struct. */ static png_uint_32 -png_image_format(png_structrp png_ptr, png_inforp info_ptr) +png_image_format(png_structrp png_ptr) { png_uint_32 format = 0; @@ -1213,15 +1266,43 @@ png_image_format(png_structrp png_ptr, png_inforp info_ptr) if (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) format |= PNG_FORMAT_FLAG_ALPHA; - else if (info_ptr != NULL && (info_ptr->valid & PNG_INFO_tRNS)) + /* Use png_ptr here, not info_ptr, because by examination png_handle_tRNS + * sets the png_struct fields; that's all we are interested in here. The + * precise interaction with an app call to png_set_tRNS and PNG file reading + * is unclear. + */ + else if (png_ptr->num_trans > 0) format |= PNG_FORMAT_FLAG_ALPHA; if (png_ptr->bit_depth == 16) format |= PNG_FORMAT_FLAG_LINEAR; + if (png_ptr->color_type & PNG_COLOR_MASK_PALETTE) + format |= PNG_FORMAT_FLAG_COLORMAP; + return format; } +/* Is the given gamma significantly different from sRGB? The test is the same + * one used in pngrtran.c when deciding whether to do gamma correction. The + * arithmetic optimizes the division by using the fact that the inverse of the + * file sRGB gamma is 2.2 + */ +static int +png_gamma_not_sRGB(png_fixed_point g) +{ + if (g < PNG_FP_1) + { + /* An uninitialized gamma is assumed to be sRGB for the simplified API. */ + if (g == 0) + return 0; + + return png_gamma_significant((g * 11 + 2)/5 /* i.e. *2.2, rounded */); + } + + return 1; +} + /* Do the main body of a 'png_image_begin_read' function; read the PNG file * header and fill in all the information. This is executed in a safe context, * unlike the init routine above. @@ -1240,18 +1321,10 @@ png_image_read_header(png_voidp argument) image->height = png_ptr->height; { - png_uint_32 format = png_image_format(png_ptr, info_ptr); + png_uint_32 format = png_image_format(png_ptr); image->format = format; - /* If the image is currently color mapped set the color map *flag* (but - * not the format, which is only set on demand.) - */ - if (png_ptr->color_type & PNG_COLOR_MASK_PALETTE) - image->flags = PNG_IMAGE_FLAG_COLORMAP; - else - image->flags = 0; - /* Now try to work out whether the color data does not match sRGB. */ if ((format & PNG_FORMAT_FLAG_COLOR) != 0 && (info_ptr->valid & PNG_INFO_sRGB) == 0) @@ -1289,6 +1362,33 @@ png_image_read_header(png_voidp argument) } } + /* We need the maximum number of entries regardless of the format the + * application sets here. + */ + { + png_uint_32 cmap_entries; + + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + cmap_entries = 1U << png_ptr->bit_depth; + break; + + case PNG_COLOR_TYPE_PALETTE: + cmap_entries = png_ptr->num_palette; + break; + + default: + cmap_entries = 256; + break; + } + + if (cmap_entries > 256) + cmap_entries = 256; + + image->colormap_entries = cmap_entries; + } + return 1; } @@ -1296,7 +1396,7 @@ png_image_read_header(png_voidp argument) int PNGAPI png_image_begin_read_from_stdio(png_imagep image, FILE* file) { - if (image != NULL) + if (image != NULL && image->version == PNG_IMAGE_VERSION) { if (file != NULL) { @@ -1322,7 +1422,7 @@ png_image_begin_read_from_stdio(png_imagep image, FILE* file) int PNGAPI png_image_begin_read_from_file(png_imagep image, const char *file_name) { - if (image != NULL) + if (image != NULL && image->version == PNG_IMAGE_VERSION) { if (file_name != NULL) { @@ -1387,7 +1487,7 @@ png_image_memory_read(png_structp png_ptr, png_bytep out, png_size_t need) int PNGAPI png_image_begin_read_from_memory(png_imagep image, png_const_voidp memory, png_size_t size) { - if (image != NULL) + if (image != NULL && image->version == PNG_IMAGE_VERSION) { if (memory != NULL && size > 0) { @@ -1414,53 +1514,1269 @@ int PNGAPI png_image_begin_read_from_memory(png_imagep image, return 0; } -int PNGAPI -png_image_read_colormap(png_imagep image, png_bytep colormap, - png_colorp background) +/* Utility function to skip chunks that are not used by the simplified image + * read functions and an appropriate macro to call it. + */ +#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED +static void +png_image_skip_unused_chunks(png_structrp png_ptr) { - if (image != NULL) + /* Prepare the reader to ignore all recognized chunks whose data will not + * be used, i.e., all chunks recognized by libpng except for those + * involved in basic image reading: + * + * IHDR, PLTE, IDAT, IEND + * + * Or image data handling: + * + * tRNS, bKGD, gAMA, cHRM, sRGB, iCCP and sBIT. + * + * This provides a small performance improvement and eliminates any + * potential vulnerability to security problems in the unused chunks. + * + * TODO: make it so that this is an explicit list to process, not a list + * to ignore? + */ { - if (colormap != NULL) + static PNG_CONST png_byte chunks_to_ignore[] = { + 104, 73, 83, 84, '\0', /* hIST */ + 105, 84, 88, 116, '\0', /* iTXt */ + 111, 70, 70, 115, '\0', /* oFFs */ + 112, 67, 65, 76, '\0', /* pCAL */ + 112, 72, 89, 115, '\0', /* pHYs */ + 115, 67, 65, 76, '\0', /* sCAL */ + 115, 80, 76, 84, '\0', /* sPLT */ + 116, 69, 88, 116, '\0', /* tEXt */ + 116, 73, 77, 69, '\0', /* tIME */ + 122, 84, 88, 116, '\0' /* zTXt */ + }; + + /* Ignore unknown chunks */ + png_set_keep_unknown_chunks(png_ptr, 1 /* PNG_HANDLE_CHUNK_NEVER */, + NULL, 0); + + /* Ignore known but unused chunks */ + png_set_keep_unknown_chunks(png_ptr, 1 /* PNG_HANDLE_CHUNK_NEVER */, + chunks_to_ignore, (sizeof chunks_to_ignore)/5); + } +} + +# define PNG_SKIP_CHUNKS(p) png_image_skip_unused_chunks(p) +#else +# define PNG_SKIP_CHUNKS(p) ((void)0) +#endif /* PNG_HANDLE_AS_UNKNOWN_SUPPORTED */ + +/* The following macro gives the exact rounded answer for all values in the + * range 0..255 (it actually divides by 51.2, but the rounding still generates + * the correct numbers 0..5 + */ +#define PNG_DIV51(v8) (((v8) * 5 + 130) >> 8) + +/* Utility functions to make particular color-maps */ +static void +set_file_encoding(png_image_read_control *display) +{ + png_fixed_point g = display->image->opaque->png_ptr->gamma; + if (png_gamma_significant(g)) + { + if (png_gamma_not_sRGB(g)) { - /* TODO: NYI: IMPLEMENT ME */ - return png_image_error(image, "png_image_read_colormap: NYI"); + display->file_encoding = E_FILE; + display->gamma_to_linear = png_reciprocal(g); } else - return png_image_error(image, - "png_image_read_colormap: invalid argument"); + display->file_encoding = E_sRGB; } - PNG_UNUSED(background) - return 0; + else + display->file_encoding = E_LINEAR8; } -/* Arguments to png_image_finish_read: */ -typedef struct +static unsigned int +decode_gamma(png_image_read_control *display, png_uint_32 value, int encoding) { - /* Arguments: */ - png_imagep image; - png_voidp buffer; - png_int_32 row_stride; - png_colorp background; - /* Local variables: */ - png_bytep local_row; - png_bytep first_row; - ptrdiff_t row_bytes; /* unsigned arithmetic step between rows */ -} png_image_read_control; + if (encoding == E_FILE) /* double check */ + encoding = display->file_encoding; + + if (encoding == E_NOTSET) /* must be the file encoding */ + { + set_file_encoding(display); + encoding = display->file_encoding; + } + + switch (encoding) + { + case E_FILE: + value = png_gamma_16bit_correct(value*257, display->gamma_to_linear); + break; + + case E_sRGB: + value = png_sRGB_table[value]; + break; + + case E_LINEAR: + break; + + case E_LINEAR8: + value *= 257; + break; + + default: + png_error(display->image->opaque->png_ptr, + "unexpected encoding (internal error)"); + break; + } + + return value; +} + +static png_uint_32 +png_colormap_compose(png_image_read_control *display, + png_uint_32 foreground, int foreground_encoding, png_uint_32 alpha, + png_uint_32 background, int encoding) +{ + /* The file value is composed on the background, the background has the given + * encoding and so does the result, the file is encoded with E_FILE and the + * file and alpha are 8-bit values. The (output) encoding will always be + * E_LINEAR or E_sRGB. + */ + png_uint_32 f = decode_gamma(display, foreground, foreground_encoding); + png_uint_32 b = decode_gamma(display, background, encoding); + + /* The alpha is always an 8-bit value (it comes from the palette), the value + * scaled by 255 is what PNG_sRGB_FROM_LINEAR requires. + */ + f = f * alpha + b * (255-alpha); + + if (encoding == E_LINEAR) + { + /* Scale to 65535; divide by 255, approximately (in fact this is extremely + * accurate, it divides by 255.00000005937181414556, with no overflow.) + */ + f *= 257; /* Now scaled by 65535 */ + f += f >> 16; + f = (f+32768) >> 16; + } + + else /* E_sRGB */ + f = PNG_sRGB_FROM_LINEAR(f); + + return f; +} + +/* NOTE: E_LINEAR values to this routine must be 16-bit, but E_FILE values must + * be 8-bit. + */ +static void +png_create_colormap_entry(png_image_read_control *display, + png_uint_32 ip, png_uint_32 red, png_uint_32 green, png_uint_32 blue, + png_uint_32 alpha, int encoding) +{ + png_imagep image = display->image; + const int output_encoding = (image->format & PNG_FORMAT_FLAG_LINEAR) ? + E_LINEAR : E_sRGB; + const int convert_to_Y = (image->format & PNG_FORMAT_FLAG_COLOR) == 0 && + (red != green || green != blue); + + if (ip > 255) + png_error(image->opaque->png_ptr, "color-map index out of range"); + + /* Update the cache with whether the file gamma is significantly different + * from sRGB. + */ + if (encoding == E_FILE) + { + if (display->file_encoding == E_NOTSET) + set_file_encoding(display); + + /* Note that the cached value may be E_FILE too, but if it is then the + * gamma_to_linear member has been set. + */ + encoding = display->file_encoding; + } + + if (encoding == E_FILE) + { + png_fixed_point g = display->gamma_to_linear; + + red = png_gamma_16bit_correct(red*257, g); + green = png_gamma_16bit_correct(green*257, g); + blue = png_gamma_16bit_correct(blue*257, g); + + if (convert_to_Y || output_encoding == E_LINEAR) + { + alpha *= 257; + encoding = E_LINEAR; + } + + else + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + encoding = E_sRGB; + } + } + + else if (encoding == E_LINEAR8) + { + /* This encoding occurs quite frequently in test cases because PngSuite + * includes a gAMA 1.0 chunk with most images. + */ + red *= 257; + green *= 257; + blue *= 257; + alpha *= 257; + encoding = E_LINEAR; + } + + else if (encoding == E_sRGB && (convert_to_Y || output_encoding == E_LINEAR)) + { + /* The values are 8-bit sRGB values, but must be converted to 16-bit + * linear. + */ + red = png_sRGB_table[red]; + green = png_sRGB_table[green]; + blue = png_sRGB_table[blue]; + alpha *= 257; + encoding = E_LINEAR; + } + + /* This is set if the color isn't gray but the output is. */ + if (encoding == E_LINEAR) + { + if (convert_to_Y) + { + /* NOTE: these values are copied from png_do_rgb_to_gray */ + png_uint_32 y = (png_uint_32)6968 * red + (png_uint_32)23434 * green + + (png_uint_32)2366 * blue; + + if (output_encoding == E_LINEAR) + y = (y + 16384) >> 15; + + else + { + /* y is scaled by 32768, we need it scaled by 255: */ + y = (y + 128) >> 8; + y *= 255; + y = PNG_sRGB_FROM_LINEAR((y + 64) >> 7); + encoding = E_sRGB; + } + + blue = red = green = y; + } + + else if (output_encoding == E_sRGB) + { + red = PNG_sRGB_FROM_LINEAR(red * 255); + green = PNG_sRGB_FROM_LINEAR(green * 255); + blue = PNG_sRGB_FROM_LINEAR(blue * 255); + alpha = PNG_DIV257(alpha); + encoding = E_sRGB; + } + } + + if (encoding != output_encoding) + png_error(image->opaque->png_ptr, "bad encoding (internal error)"); + + /* Store the value. */ + { +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int afirst = (image->format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (image->format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int bgr = (image->format & PNG_FORMAT_FLAG_BGR) ? 2 : 0; +# else +# define bgr 0 +# endif + + if (output_encoding == E_LINEAR) + { + png_uint_16p entry = png_voidcast(png_uint_16p, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + /* The linear 16-bit values must be pre-multiplied by the alpha channel + * value, if less than 65535 (this is, effectively, composite on black + * if the alpha channel is removed.) + */ + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_uint_16)alpha; + /* FALL THROUGH */ + + case 3: + if (alpha < 65535) + { + if (alpha > 0) + { + blue = (blue * alpha + 32767U)/65535U; + green = (green * alpha + 32767U)/65535U; + red = (red * alpha + 32767U)/65535U; + } + + else + red = green = blue = 0; + } + entry[afirst + (2 ^ bgr)] = (png_uint_16)blue; + entry[afirst + 1] = (png_uint_16)green; + entry[afirst + bgr] = (png_uint_16)red; + break; + + case 2: + entry[1 ^ afirst] = (png_uint_16)alpha; + /* FALL THROUGH */ + + case 1: + if (alpha < 65535) + { + if (alpha > 0) + green = (green * alpha + 32767U)/65535U; + + else + green = 0; + } + entry[afirst] = (png_uint_16)green; + break; + + default: + break; + } + } + + else /* output encoding is E_sRGB */ + { + png_bytep entry = png_voidcast(png_bytep, display->colormap); + + entry += ip * PNG_IMAGE_SAMPLE_CHANNELS(image->format); + + switch (PNG_IMAGE_SAMPLE_CHANNELS(image->format)) + { + case 4: + entry[afirst ? 0 : 3] = (png_byte)alpha; + case 3: + entry[afirst + (2 ^ bgr)] = (png_byte)blue; + entry[afirst + 1] = (png_byte)green; + entry[afirst + bgr] = (png_byte)red; + break; + + case 2: + entry[1 ^ afirst] = (png_byte)alpha; + case 1: + entry[afirst] = (png_byte)green; + break; + + default: + break; + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + } +} -/* Just the row reading part of png_image_read. */ static int -png_image_read_composite(png_voidp argument) +make_gray_colormap(png_image_read_control *display) +{ + unsigned int i; + + for (i=0; i<256; ++i) + png_create_colormap_entry(display, i, i, i, i, 255, E_sRGB); + + return i; +} +#define PNG_GRAY_COLORMAP_ENTRIES 256 + +static int +make_ga_colormap(png_image_read_control *display) +{ + unsigned int i, a; + + /* Alpha is retained, the output will be a color-map with entries + * selected by six levels of alpha. One transparent entry, 6 gray + * levels for all the intermediate alpha values, leaving 230 entries + * for the opaque grays. The color-map entries are the six values + * [0..5]*51, the GA processing uses PNG_DIV51(value) to find the + * relevant entry. + * + * if (alpha > 229) // opaque + * { + * // The 231 entries are selected to make the math below work: + * base = 0; + * entry = (231 * gray + 128) >> 8; + * } + * else if (alpha < 26) // transparent + * { + * base = 231; + * entry = 0; + * } + * else // partially opaque + * { + * base = 226 + 6 * PNG_DIV51(alpha); + * entry = PNG_DIV51(gray); + * } + */ + i = 0; + while (i < 231) + { + unsigned int gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, 255, E_sRGB); + } + + /* 255 is used here for the component values for consistency with the code + * that undoes premultiplication in pngwrite.c. + */ + png_create_colormap_entry(display, i++, 255, 255, 255, 0, E_sRGB); + + for (a=1; a<5; ++a) + { + unsigned int g; + + for (g=0; g<6; ++g) + png_create_colormap_entry(display, i++, g*51, g*51, g*51, a*51, + E_sRGB); + } + + return i; +} + +#define PNG_GA_COLORMAP_ENTRIES 256 + +static int +make_rgb_colormap(png_image_read_control *display) +{ + unsigned int i, r; + + /* Build a 6x6x6 opaque RGB cube */ + for (i=r=0; r<6; ++r) + { + unsigned int g; + + for (g=0; g<6; ++g) + { + unsigned int b; + + for (b=0; b<6; ++b) + png_create_colormap_entry(display, i++, r*51, g*51, b*51, 255, + E_sRGB); + } + } + + return i; +} + +#define PNG_RGB_COLORMAP_ENTRIES 216 + +/* Return a palette index to the above palette given three 8-bit sRGB values. */ +#define PNG_RGB_INDEX(r,g,b) \ + ((png_byte)(6 * (6 * PNG_DIV51(r) + PNG_DIV51(g)) + PNG_DIV51(b))) + +static int +png_image_read_colormap(png_voidp argument) +{ + png_image_read_control *display = + png_voidcast(png_image_read_control*, argument); + const png_imagep image = display->image; + + const png_structrp png_ptr = image->opaque->png_ptr; + const png_uint_32 output_format = image->format; + const int output_encoding = (output_format & PNG_FORMAT_FLAG_LINEAR) ? + E_LINEAR : E_sRGB; + + unsigned int cmap_entries; + unsigned int output_processing; /* Output processing option */ + unsigned int data_encoding = E_NOTSET; /* Encoding libpng must produce */ + + /* Background information; the background color and the index of this color + * in the color-map if it exists (else 256). + */ + unsigned int background_index = 256; + png_uint_32 back_r, back_g, back_b; + + /* Flags to accumulate things that need to be done to the input. */ + int expand_tRNS = 0; + + /* Exclude the NYI feature of compositing onto a color-mapped buffer; it is + * very difficult to do, the results look awful, and it is difficult to see + * what possible use it is because the application can't control the + * color-map. + */ + if (((png_ptr->color_type & PNG_COLOR_MASK_ALPHA) != 0 || + png_ptr->num_trans > 0) /* alpha in input */ && + ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) /* no alpha in output */) + { + if (output_encoding == E_LINEAR) /* compose on black */ + back_b = back_g = back_r = 0; + + else if (display->background == NULL /* no way to remove it */) + png_error(png_ptr, + "a background color must be supplied to remove alpha/transparency"); + + /* Get a copy of the background color (this avoids repeating the checks + * below.) The encoding is 8-bit sRGB or 16-bit linear, depending on the + * output format. + */ + else + { + back_g = display->background->green; + if (output_format & PNG_FORMAT_FLAG_COLOR) + { + back_r = display->background->red; + back_b = display->background->blue; + } + else + back_b = back_r = back_g; + } + } + + else if (output_encoding == E_LINEAR) + back_b = back_r = back_g = 65535; + + else + back_b = back_r = back_g = 255; + + /* Decide what to do based on the PNG color type of the input data. The + * utility function png_create_colormap_entry deals with most aspects of the + * output transformations; this code works out how to produce bytes of + * color-map entries from the original format. + */ + switch (png_ptr->color_type) + { + case PNG_COLOR_TYPE_GRAY: + if (png_ptr->bit_depth <= 8) + { + /* There at most 256 colors in the output, regardless of + * transparency. + */ + unsigned int step, i, val, trans = 256/*ignore*/, back_alpha = 0; + + cmap_entries = 1U << png_ptr->bit_depth; + if (cmap_entries > image->colormap_entries) + png_error(png_ptr, "gray[8] color-map: too few entries"); + + step = 255 / (cmap_entries - 1); + output_processing = PNG_CMAP_NONE; + + /* If there is a tRNS chunk then this either selects a transparent + * value or, if the output has no alpha, the background color. + */ + if (png_ptr->num_trans > 0) + { + trans = png_ptr->trans_color.gray; + + if ((output_format & PNG_FORMAT_FLAG_ALPHA) == 0) + back_alpha = output_encoding == E_LINEAR ? 65535 : 255; + } + + /* png_create_colormap_entry just takes an RGBA and writes the + * corresponding color-map entry using the format from 'image', + * including the required convertion to sRGB or linear as + * appropriate. The input values are always either sRGB (if the + * gamma correction flag is 0) or 0..255 scaled file encoded values + * (if the function must gamma correct them). + */ + for (i=val=0; ibit_depth < 8) + png_set_packing(png_ptr); + } + + else /* bit depth is 16 */ + { + /* The 16-bit input values can be converted directly to 8-bit gamma + * encoded values; however, if a tRNS chunk is present 257 color-map + * entries are required. This means that the extra entry requires + * special processing; add an alpha channel, sacrifice gray level + * 254 and convert transparent (alpha==0) entries to that. + * + * Use libpng to chop the data to 8 bits. Convert it to sRGB at the + * same time to minimize quality loss. If a tRNS chunk is present + * this means libpng must handle it too; otherwise it is impossible + * to do the exact match on the 16-bit value. + * + * If the output has no alpha channel *and* the background color is + * gray then it is possible to let libpng handle the substitution by + * ensuring that the corresponding gray level matches the background + * color exactly. + */ + data_encoding = E_sRGB; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray[16] color-map: too few entries"); + + cmap_entries = make_gray_colormap(display); + + if (png_ptr->num_trans > 0) + { + unsigned int back_alpha; + + if (output_format & PNG_FORMAT_FLAG_ALPHA) + back_alpha = 0; + + else + { + if (back_r == back_g && back_g == back_b) + { + /* Background is gray; no special processing will be + * required. + */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry + * matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, E_LINEAR); + } + + /* The background passed to libpng, however, must be the + * sRGB value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: does this work without expanding tRNS to alpha? + * It should be the color->gray case below apparently + * doesn't. + */ + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + break; + } + + back_alpha = output_encoding == E_LINEAR ? 65535 : 255; + } + + /* output_processing means that the libpng-processed row will be + * 8-bit GA and it has to be processing to single byte color-map + * values. Entry 254 is replaced by either a completely + * transparent entry or by the background color at full + * precision (and the background color is not a simple gray leve + * in this case.) + */ + expand_tRNS = 1; + output_processing = PNG_CMAP_TRANS; + background_index = 254; + + /* And set (overwrite) color-map entry 254 to the actual + * background color at full precision. + */ + png_create_colormap_entry(display, 254, back_r, back_g, back_b, + back_alpha, output_encoding); + } + + else + output_processing = PNG_CMAP_NONE; + } + break; + + case PNG_COLOR_TYPE_GRAY_ALPHA: + /* 8-bit or 16-bit PNG with two channels - gray and alpha. A minimum + * of 65536 combinations. If, however, the alpha channel is to be + * removed there are only 256 possibilities if the background is gray. + * (Otherwise there is a subset of the 65536 possibilities defined by + * the triangle between black, white and the background color.) + * + * Reduce 16-bit files to 8-bit and sRGB encode the result. No need to + * worry about tRNS matching - tRNS is ignored if there is an alpha + * channel. + */ + data_encoding = E_sRGB; + + if (output_format & PNG_FORMAT_FLAG_ALPHA) + { + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray+alpha color-map: too few entries"); + + cmap_entries = make_ga_colormap(display); + + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else /* alpha is removed */ + { + /* Alpha must be removed as the PNG data is processed when the + * background is a color because the G and A channels are + * independent and the vector addition (non-parallel vectors) is a + * 2-D problem. + * + * This can be reduced to the same algorithm as above by making a + * colormap containing gray levels (for the opaque grays), a + * background entry (for a transparent pixel) and a set of four six + * level color values, one set for each intermediate alpha value. + * See the comments in make_ga_colormap for how this works in the + * per-pixel processing. + * + * If the background is gray, however, we only need a 256 entry gray + * level color map. It is sufficient to make the entry generated + * for the background color be exactly the color specified. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0 || + (back_r == back_g && back_g == back_b)) + { + /* Background is gray; no special processing will be required. */ + png_color_16 c; + png_uint_32 gray = back_g; + + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "gray-alpha color-map: too few entries"); + + cmap_entries = make_gray_colormap(display); + + if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, E_LINEAR); + } + + /* The background passed to libpng, however, must be the sRGB + * value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_NONE; + } + + else + { + png_uint_32 i, a; + + /* This is the same as png_make_ga_colormap, above, except that + * the entries are all opaque. + */ + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "ga-alpha color-map: too few entries"); + + i = 0; + while (i < 231) + { + png_uint_32 gray = (i * 256 + 115) / 231; + png_create_colormap_entry(display, i++, gray, gray, gray, + 255, E_sRGB); + } + + /* NOTE: this preserves the full precision of the application + * background color. + */ + background_index = i; + png_create_colormap_entry(display, i++, back_r, back_g, back_b, + output_encoding == E_LINEAR ? 65535U : 255U, output_encoding); + + /* For non-opaque input composite on the sRGB background - this + * requires inverting the encoding for each component. The input + * is still converted to the sRGB encoding because this is a + * reasonable approximate to the logarithmic curve of human + * visual sensitivity, at least over the narrow range which PNG + * represents. Consequently 'G' is always sRGB encoded, while + * 'A' is linear. We need the linear background colors. + */ + if (output_encoding == E_sRGB) /* else already linear */ + { + /* This may produce a value not exactly matching the + * background, but that's ok because these numbers are only + * used when alpha != 0 + */ + back_r = png_sRGB_table[back_r]; + back_g = png_sRGB_table[back_g]; + back_b = png_sRGB_table[back_b]; + } + + for (a=1; a<5; ++a) + { + unsigned int g; + + /* PNG_sRGB_FROM_LINEAR expects a 16-bit linear value scaled + * by an 8-bit alpha value (0..255). + */ + png_uint_32 alpha = 51 * a; + png_uint_32 back_rx = (255-alpha) * back_r; + png_uint_32 back_gx = (255-alpha) * back_g; + png_uint_32 back_bx = (255-alpha) * back_b; + + for (g=0; g<6; ++g) + { + png_uint_32 gray = png_sRGB_table[g*51] * alpha; + + png_create_colormap_entry(display, i++, + PNG_sRGB_FROM_LINEAR(gray + back_rx), + PNG_sRGB_FROM_LINEAR(gray + back_gx), + PNG_sRGB_FROM_LINEAR(gray + back_bx), 255, E_sRGB); + } + } + + cmap_entries = i; + output_processing = PNG_CMAP_GA; + } + } + break; + + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_RGB_ALPHA: + /* Exclude the case where the output is gray; we can always handle this + * with the cases above. + */ + if ((output_format & PNG_FORMAT_FLAG_COLOR) == 0) + { + /* The color-map will be grayscale, so we may as well convert the + * input RGB values to a simple grayscale and use the grayscale + * code above. + * + * NOTE: calling this apparently damages the recognition of the + * transparent color in background color handling; call + * png_set_tRNS_to_alpha before png_set_background_fixed. + */ + png_set_rgb_to_gray_fixed(png_ptr, PNG_ERROR_ACTION_NONE, -1, + -1); + data_encoding = E_sRGB; + + /* The output will now be one or two 8-bit gray or gray+alpha + * channels. The more complex case arises when the input has alpha. + */ + if ((png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) && + (output_format & PNG_FORMAT_FLAG_ALPHA) != 0) + { + /* Both input and output have an alpha channel, so no background + * processing is required; just map the GA bytes to the right + * color-map entry. + */ + expand_tRNS = 1; + + if (PNG_GA_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[ga] color-map: too few entries"); + + cmap_entries = make_ga_colormap(display); + background_index = PNG_CMAP_GA_BACKGROUND; + output_processing = PNG_CMAP_GA; + } + + else + { + /* Either the input or the output has no alpha channel, so there + * will be no non-opaque pixels in the color-map; it will just be + * grayscale. + */ + if (PNG_GRAY_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb[gray] color-map: too few entries"); + + cmap_entries = make_gray_colormap(display); + + /* But if the input has alpha or transparency it must be removed + */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + png_color_16 c; + png_uint_32 gray = back_g; + + /* We need to ensure that the application background exists in + * the colormap and that completely transparent pixels map to + * it. Achieve this simply by ensuring that the entry + * selected for the background really is the background color. + */ + if (output_encoding == E_LINEAR) + { + gray = PNG_sRGB_FROM_LINEAR(gray * 255); + + /* And make sure the corresponding palette entry matches. + */ + png_create_colormap_entry(display, gray, back_g, back_g, + back_g, 65535, E_LINEAR); + } + + /* The background passed to libpng, however, must be the sRGB + * value. + */ + c.index = 0; /*unused*/ + c.gray = c.red = c.green = c.blue = (png_uint_16)gray; + + /* NOTE: the following is apparently a bug in libpng. Without + * it the transparent color recognition in + * png_set_background_fixed seems to go wrong. + */ + expand_tRNS = 1; + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + } + + output_processing = PNG_CMAP_NONE; + } + } + + else /* output is color */ + { + /* We could use png_quantize here so long as there is no transparent + * color or alpha; png_quantize ignores alpha. Easier overall just + * to do it once and using PNG_DIV51 on the 6x6x6 reduced RGB cube. + * Consequently we always want libpng to produce sRGB data. + */ + data_encoding = E_sRGB; + + /* Is there any transparency or alpha? */ + if (png_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA || + png_ptr->num_trans > 0) + { + /* Is there alpha in the output too? If so all four channels are + * processed into a special RGB cube with alpha support. + */ + if (output_format & PNG_FORMAT_FLAG_ALPHA) + { + png_uint_32 r; + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb+alpha color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + + /* Add a transparent entry. */ + png_create_colormap_entry(display, cmap_entries, 255, 255, + 255, 0, E_sRGB); + + /* This is stored as the background index for the processing + * algorithm. + */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with alpha 0.5. */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + png_uint_32 g; + + for (g=0; g<256; g = (g << 1) | 0x7f) + { + png_uint_32 b; + + /* This generates components with the values 0, 127 and + * 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + r, g, b, 128, E_sRGB); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else + { + /* Alpha/transparency must be removed. The background must + * exist in the color map (achieved by setting adding it after + * the 666 color-map). If the standard processing code will + * pick up this entry automatically that's all that is + * required; libpng can be called to do the background + * processing. + */ + unsigned int sample_size = + PNG_IMAGE_SAMPLE_SIZE(output_format); + png_uint_32 r, g, b; /* sRGB background */ + + if (PNG_RGB_COLORMAP_ENTRIES+1+27 > image->colormap_entries) + png_error(png_ptr, "rgb-alpha color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + + png_create_colormap_entry(display, cmap_entries, back_r, + back_g, back_b, 0/*unused*/, output_encoding); + + if (output_encoding == E_LINEAR) + { + r = PNG_sRGB_FROM_LINEAR(back_r * 255); + g = PNG_sRGB_FROM_LINEAR(back_g * 255); + b = PNG_sRGB_FROM_LINEAR(back_b * 255); + } + + else + { + r = back_r; + g = back_g; + b = back_g; + } + + /* Compare the newly-created color-map entry with the one the + * PNG_CMAP_RGB algorithm will use. If the two entries don't + * match, add the new one and set this as the background index. + */ + if (memcmp((png_const_bytep)display->colormap + + sample_size * cmap_entries, + (png_const_bytep)display->colormap + + sample_size * PNG_RGB_INDEX(r,g,b), + sample_size) != 0) + { + /* The background color must be added. */ + background_index = cmap_entries++; + + /* Add 27 r,g,b entries each with created by composing with + * the background at alpha 0.5. + */ + for (r=0; r<256; r = (r << 1) | 0x7f) + { + for (g=0; g<256; g = (g << 1) | 0x7f) + { + /* This generates components with the values 0, 127 + * and 255 + */ + for (b=0; b<256; b = (b << 1) | 0x7f) + png_create_colormap_entry(display, cmap_entries++, + png_colormap_compose(display, r, E_sRGB, 128, + back_r, output_encoding), + png_colormap_compose(display, g, E_sRGB, 128, + back_g, output_encoding), + png_colormap_compose(display, b, E_sRGB, 128, + back_b, output_encoding), + 0/*unused*/, output_encoding); + } + } + + expand_tRNS = 1; + output_processing = PNG_CMAP_RGB_ALPHA; + } + + else /* background color is in the standard color-map */ + { + png_color_16 c; + + c.index = 0; /*unused*/ + c.red = (png_uint_16)back_r; + c.gray = c.green = (png_uint_16)back_g; + c.blue = (png_uint_16)back_b; + + png_set_background_fixed(png_ptr, &c, + PNG_BACKGROUND_GAMMA_SCREEN, 0/*need_expand*/, + 0/*gamma: not used*/); + + output_processing = PNG_CMAP_RGB; + } + } + } + + else /* no alpha or transparency in the input */ + { + /* Alpha in the output is irrelevant, simply map the opaque input + * pixels to the 6x6x6 color-map. + */ + if (PNG_RGB_COLORMAP_ENTRIES > image->colormap_entries) + png_error(png_ptr, "rgb color-map: too few entries"); + + cmap_entries = make_rgb_colormap(display); + output_processing = PNG_CMAP_RGB; + } + } + break; + + case PNG_COLOR_TYPE_PALETTE: + /* It's already got a color-map. It may be necessary to eliminate the + * tRNS entries though. + */ + { + unsigned int num_trans = png_ptr->num_trans; + png_const_bytep trans = num_trans > 0 ? png_ptr->trans_alpha : NULL; + png_const_colorp colormap = png_ptr->palette; + const int do_background = trans != NULL && + (output_format & PNG_FORMAT_FLAG_ALPHA) == 0; + unsigned int i; + + /* Just in case: */ + if (trans == NULL) + num_trans = 0; + + output_processing = PNG_CMAP_NONE; + data_encoding = E_FILE; /* Don't change from color-map indicies */ + cmap_entries = png_ptr->num_palette; + if (cmap_entries > 256) + cmap_entries = 256; + + if (cmap_entries > image->colormap_entries) + png_error(png_ptr, "palette color-map: too few entries"); + + for (i=0; i < cmap_entries; ++i) + { + if (do_background && i < num_trans && trans[i] < 255) + { + if (trans[i] == 0) + png_create_colormap_entry(display, i, back_r, back_g, + back_b, 0, output_encoding); + + else + { + /* Must compose the PNG file color in the color-map entry + * on the sRGB color in 'back'. + */ + png_create_colormap_entry(display, i, + png_colormap_compose(display, colormap[i].red, E_FILE, + trans[i], back_r, output_encoding), + png_colormap_compose(display, colormap[i].green, E_FILE, + trans[i], back_g, output_encoding), + png_colormap_compose(display, colormap[i].blue, E_FILE, + trans[i], back_b, output_encoding), + output_encoding == E_LINEAR ? trans[i] * 257U : + trans[i], + output_encoding); + } + } + + else + png_create_colormap_entry(display, i, colormap[i].red, + colormap[i].green, colormap[i].blue, + i < num_trans ? trans[i] : 255U, E_FILE/*8-bit*/); + } + + /* The PNG data may have indicies packed in fewer than 8 bits, it + * must be expanded if so. + */ + if (png_ptr->bit_depth < 8) + png_set_packing(png_ptr); + } + break; + + default: + png_error(png_ptr, "invalid PNG color type"); + /*NOT REACHED*/ + break; + } + + /* Now deal with the output processing */ + if (expand_tRNS && png_ptr->num_trans > 0 && + (png_ptr->color_type & PNG_COLOR_MASK_ALPHA) == 0) + png_set_tRNS_to_alpha(png_ptr); + + switch (data_encoding) + { + default: + png_error(png_ptr, "bad data option (internal error)"); + break; + + case E_FILE: + /* Make no changes */ + break; + + case E_sRGB: + /* Change to 8-bit sRGB */ + png_set_alpha_mode_fixed(png_ptr, PNG_ALPHA_PNG, PNG_GAMMA_sRGB); + if (png_ptr->bit_depth > 8) + png_set_scale_16(png_ptr); + break; + } + + if (cmap_entries > 256 || cmap_entries > image->colormap_entries) + png_error(png_ptr, "color map overflow (BAD internal error)"); + + image->colormap_entries = cmap_entries; + + /* Double check using the recorded background index */ + switch (output_processing) + { + case PNG_CMAP_NONE: + if (background_index != PNG_CMAP_NONE_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_GA: + if (background_index != PNG_CMAP_GA_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_TRANS: + if (background_index >= cmap_entries || + background_index != PNG_CMAP_TRANS_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB: + if (background_index != PNG_CMAP_RGB_BACKGROUND) + goto bad_background; + break; + + case PNG_CMAP_RGB_ALPHA: + if (background_index != PNG_CMAP_RGB_ALPHA_BACKGROUND) + goto bad_background; + break; + + default: + png_error(png_ptr, "bad processing option (internal error)"); + + bad_background: + png_error(png_ptr, "bad background index (internal error)"); + } + + display->colormap_processing = output_processing; + + return 1/*ok*/; +} + +/* The final part of the color-map read called from png_image_finish_read. */ +static int +png_image_read_and_map(png_voidp argument) { png_image_read_control *display = png_voidcast(png_image_read_control*, argument); png_imagep image = display->image; png_structrp png_ptr = image->opaque->png_ptr; - png_byte interlace_type = png_ptr->interlaced; int passes; - switch (interlace_type) + /* Called when the libpng data must be transformed into the color-mapped + * form. There is a local row buffer in display->local and this routine must + * do the interlace handling. + */ + switch (png_ptr->interlaced) { case PNG_INTERLACE_NONE: passes = 1; @@ -1476,18 +2792,19 @@ png_image_read_composite(png_voidp argument) } { - png_uint_32 height = image->height; - png_uint_32 width = image->width; - unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1; + png_uint_32 height = image->height; + png_uint_32 width = image->width; + int proc = display->colormap_processing; + png_bytep first_row = display->first_row; + ptrdiff_t step_row = display->row_bytes; int pass; for (pass = 0; pass < passes; ++pass) { - png_bytep row = display->first_row; unsigned int startx, stepx, stepy; png_uint_32 y; - if (interlace_type == PNG_INTERLACE_ADAM7) + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) { /* The row may be empty for a short image: */ if (PNG_PASS_COLS(width, pass) == 0) @@ -1506,59 +2823,371 @@ png_image_read_composite(png_voidp argument) stepx = stepy = 1; } - /* The following are invariants across all the rows: */ - startx *= channels; - stepx *= channels; - for (; ylocal_row; + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; + + /* Read read the libpng data into the temporary buffer. */ + png_read_row(png_ptr, inrow, NULL); + + /* Now process the row according to the processing option, note + * that the caller verifies that the format of the libpng output + * data is as required. + */ + outrow += startx; + switch (proc) { - png_bytep inrow = display->local_row; - png_bytep outrow = row + startx; - png_const_bytep end_row = row + width * channels; - - /* Read the row, which is packed: */ - png_read_row(png_ptr, inrow, NULL); - - /* Now do the composition on each pixel in this row. */ - for (; outrow < end_row; outrow += stepx) - { - png_byte alpha = inrow[channels]; - - if (alpha > 0) /* else no change to the output */ + case PNG_CMAP_GA: + for (; outrow < end_row; outrow += stepx) { - unsigned int c; + /* The data is always in the PNG order */ + unsigned int gray = *inrow++; + unsigned int alpha = *inrow++; + unsigned int entry; - for (c=0; c 229) /* opaque */ { - png_uint_32 component = inrow[c]; - - if (alpha < 255) /* else just use component */ - { - /* This is PNG_OPTIMIZED_ALPHA, the component value - * is a linear 8-bit value. Combine this with the - * current outrow[c] value which is sRGB encoded. - * Arithmetic here is 16-bits to preserve the output - * values correctly. - */ - component *= 257*255; /* =65535 */ - component += (255-alpha)*png_sRGB_table[outrow[c]]; - - /* So 'component' is scaled by 255*65535 and is - * therefore appropriate for the sRGB to linear - * conversion table. - */ - component = PNG_sRGB_FROM_LINEAR(component); - } - - outrow[c] = (png_byte)component; + entry = (231 * gray + 128) >> 8; + } + else if (alpha < 26) /* transparent */ + { + entry = 231; + } + else /* partially opaque */ + { + entry = 226 + 6 * PNG_DIV51(alpha) + PNG_DIV51(gray); } - } - inrow += channels+1; /* components and alpha channel */ + *outrow = (png_byte)entry; + } + break; + + case PNG_CMAP_TRANS: + for (; outrow < end_row; outrow += stepx) + { + png_byte gray = *inrow++; + png_byte alpha = *inrow++; + + if (alpha == 0) + *outrow = PNG_CMAP_TRANS_BACKGROUND; + + else if (gray != PNG_CMAP_TRANS_BACKGROUND) + *outrow = gray; + + else + *outrow = (png_byte)(PNG_CMAP_TRANS_BACKGROUND+1); + } + break; + + case PNG_CMAP_RGB: + for (; outrow < end_row; outrow += stepx) + { + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], inrow[2]); + inrow += 3; + } + break; + + case PNG_CMAP_RGB_ALPHA: + for (; outrow < end_row; outrow += stepx) + { + unsigned int alpha = inrow[3]; + + /* Because the alpha entries only hold alpha==0.5 values + * split the processing at alpha==0.25 (64) and 0.75 + * (196). + */ + + if (alpha >= 196) + *outrow = PNG_RGB_INDEX(inrow[0], inrow[1], + inrow[2]); + + else if (alpha < 64) + *outrow = PNG_CMAP_RGB_ALPHA_BACKGROUND; + + else + { + /* Likewise there are three entries for each of r, g + * and b. We could select the entry by popcount on + * the top two bits on those architectures that + * support it, this is what the code below does, + * crudely. + */ + unsigned int back_i = PNG_CMAP_RGB_ALPHA_BACKGROUND+1; + + /* Here are how the values map: + * + * 0x00 .. 0x3f -> 0 + * 0x40 .. 0xbf -> 1 + * 0xc0 .. 0xff -> 2 + * + * So, as above with the explicit alpha checks, the + * breakpoints are at 64 and 196. + */ + if (inrow[0] & 0x80) back_i += 9; /* red */ + if (inrow[0] & 0x40) back_i += 9; + if (inrow[0] & 0x80) back_i += 3; /* green */ + if (inrow[0] & 0x40) back_i += 3; + if (inrow[0] & 0x80) back_i += 1; /* blue */ + if (inrow[0] & 0x40) back_i += 1; + + *outrow = (png_byte)back_i; + } + + inrow += 4; + } + break; + + default: + break; + } + } + } + } + + return 1; +} + +static int +png_image_read_colormapped(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_controlp control = image->opaque; + png_structrp png_ptr = control->png_ptr; + png_inforp info_ptr = control->info_ptr; + + int passes = 0; /* As a flag */ + + PNG_SKIP_CHUNKS(png_ptr); + + /* Update the 'info' structure and make sure the result is as required; first + * make sure to turn on the interlace handling if it will be required + * (because it can't be turned on *after* the call to png_read_update_info!) + */ + if (display->colormap_processing == PNG_CMAP_NONE) + passes = png_set_interlace_handling(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + /* The expected output can be deduced from the colormap_processing option. */ + switch (display->colormap_processing) + { + case PNG_CMAP_NONE: + /* Output must be one channel and one byte per pixel, the output + * encoding can be anything. + */ + if ((info_ptr->color_type == PNG_COLOR_TYPE_PALETTE || + info_ptr->color_type == PNG_COLOR_TYPE_GRAY) && + info_ptr->bit_depth == 8) + break; + + goto bad_output; + + case PNG_CMAP_TRANS: + case PNG_CMAP_GA: + /* Output must be two channels and the 'G' one must be sRGB, the latter + * can be checked with an exact number because it should have been set + * to this number above! + */ + if (info_ptr->color_type == PNG_COLOR_TYPE_GRAY_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 256) + break; + + goto bad_output; + + case PNG_CMAP_RGB: + /* Output must be 8-bit sRGB encoded RGB */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 216) + break; + + goto bad_output; + + case PNG_CMAP_RGB_ALPHA: + /* Output must be 8-bit sRGB encoded RGBA */ + if (info_ptr->color_type == PNG_COLOR_TYPE_RGB_ALPHA && + info_ptr->bit_depth == 8 && + png_ptr->screen_gamma == PNG_GAMMA_sRGB && + image->colormap_entries == 244 /* 216 + 1 + 27 */) + break; + + /* goto bad_output; */ + /* FALL THROUGH */ + + default: + bad_output: + png_error(png_ptr, "bad color-map processing (internal error)"); + } + + /* Now read the rows. Do this here if it is possible to read directly into + * the output buffer, otherwise allocate a local row buffer of the maximum + * size libpng requires and call the relevant processing routine safely. + */ + { + png_bytep first_row = png_voidcast(png_bytep, display->buffer); + ptrdiff_t row_bytes = display->row_stride; + + /* The following expression is designed to work correctly whether it gives + * a signed or an unsigned result. + */ + if (row_bytes < 0) + first_row += (image->height-1) * (-row_bytes); + + display->first_row = first_row; + display->row_bytes = row_bytes; + } + + if (passes == 0) + { + int result; + png_bytep row = png_voidcast(png_bytep, png_malloc(png_ptr, + png_get_rowbytes(png_ptr, info_ptr))); + + display->local_row = row; + result = png_safe_execute(image, png_image_read_and_map, display); + display->local_row = NULL; + png_free(png_ptr, row); + + return result; + } + + else + { + png_alloc_size_t row_bytes = display->row_bytes; + + while (--passes >= 0) + { + png_uint_32 y = image->height; + png_bytep row = display->first_row; + + while (y-- > 0) + { + png_read_row(png_ptr, row, NULL); + row += row_bytes; + } + } + + return 1; + } +} + +/* Just the row reading part of png_image_read. */ +static int +png_image_read_composite(png_voidp argument) +{ + png_image_read_control *display = png_voidcast(png_image_read_control*, + argument); + png_imagep image = display->image; + png_structrp png_ptr = image->opaque->png_ptr; + int passes; + + switch (png_ptr->interlaced) + { + case PNG_INTERLACE_NONE: + passes = 1; + break; + + case PNG_INTERLACE_ADAM7: + passes = PNG_INTERLACE_ADAM7_PASSES; + break; + + default: + passes = 0; + png_error(png_ptr, "unknown interlace type"); + } + + { + png_uint_32 height = image->height; + png_uint_32 width = image->width; + png_bytep first_row = display->first_row; + ptrdiff_t step_row = display->row_bytes; + unsigned int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1; + int pass; + + for (pass = 0; pass < passes; ++pass) + { + unsigned int startx, stepx, stepy; + png_uint_32 y; + + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; + + startx = PNG_PASS_START_COL(pass) * channels; + stepx = PNG_PASS_COL_OFFSET(pass) * channels; + y = PNG_PASS_START_ROW(pass); + stepy = PNG_PASS_ROW_OFFSET(pass); + } + + else + { + y = 0; + startx = 0; + stepx = channels; + stepy = 1; + } + + for (; ylocal_row; + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width * channels; + + /* Read the row, which is packed: */ + png_read_row(png_ptr, inrow, NULL); + + /* Now do the composition on each pixel in this row. */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) + { + png_byte alpha = inrow[channels]; + + if (alpha > 0) /* else no change to the output */ + { + unsigned int c; + + for (c=0; crow_bytes; + inrow += channels+1; /* components and alpha channel */ } + } } } @@ -1586,7 +3215,6 @@ png_image_read_background(png_voidp argument) png_imagep image = display->image; png_structrp png_ptr = image->opaque->png_ptr; png_inforp info_ptr = image->opaque->info_ptr; - png_byte interlace_type = png_ptr->interlaced; png_uint_32 height = image->height; png_uint_32 width = image->width; int pass, passes; @@ -1610,7 +3238,7 @@ png_image_read_background(png_voidp argument) if (png_get_channels(png_ptr, info_ptr) != 2) png_error(png_ptr, "lost/gained channels"); - switch (interlace_type) + switch (png_ptr->interlaced) { case PNG_INTERLACE_NONE: passes = 1; @@ -1634,46 +3262,51 @@ png_image_read_background(png_voidp argument) case 8: /* 8-bit sRGB gray values with an alpha channel; the alpha channel is * to be removed by composing on a backgroundi: either the row if - * display->background is NULL or display->background.green if not. + * display->background is NULL or display->background->green if not. * Unlike the code above ALPHA_OPTIMIZED has *not* been done. */ - for (pass = 0; pass < passes; ++pass) { - png_bytep row = display->first_row; - unsigned int startx, stepx, stepy; - png_uint_32 y; + png_bytep first_row = display->first_row; + ptrdiff_t step_row = display->row_bytes; - if (interlace_type == PNG_INTERLACE_ADAM7) + for (pass = 0; pass < passes; ++pass) { - /* The row may be empty for a short image: */ - if (PNG_PASS_COLS(width, pass) == 0) - continue; + png_bytep row = display->first_row; + unsigned int startx, stepx, stepy; + png_uint_32 y; - startx = PNG_PASS_START_COL(pass); - stepx = PNG_PASS_COL_OFFSET(pass); - y = PNG_PASS_START_ROW(pass); - stepy = PNG_PASS_ROW_OFFSET(pass); - } + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) + { + /* The row may be empty for a short image: */ + if (PNG_PASS_COLS(width, pass) == 0) + continue; - else - { - y = 0; - startx = 0; - stepx = stepy = 1; - } - - if (display->background == NULL) - { - for (; ybackground == NULL) + { + for (; ylocal_row; - png_bytep outrow = row + startx; - png_const_bytep end_row = row + width; + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; /* Read the row, which is packed: */ png_read_row(png_ptr, inrow, NULL); /* Now do the composition on each pixel in this row. */ + outrow += startx; for (; outrow < end_row; outrow += stepx) { png_byte alpha = inrow[1]; @@ -1699,26 +3332,25 @@ png_image_read_background(png_voidp argument) inrow += 2; /* gray and alpha channel */ } - - row += display->row_bytes; } - } + } - else /* constant background value */ - { - png_byte background8 = display->background->green; - png_uint_16 background = png_sRGB_table[background8]; + else /* constant background value */ + { + png_byte background8 = display->background->green; + png_uint_16 background = png_sRGB_table[background8]; - for (; ylocal_row; - png_bytep outrow = row + startx; - png_const_bytep end_row = row + width; + png_bytep outrow = first_row + y * step_row; + png_const_bytep end_row = outrow + width; /* Read the row, which is packed: */ png_read_row(png_ptr, inrow, NULL); /* Now do the composition on each pixel in this row. */ + outrow += startx; for (; outrow < end_row; outrow += stepx) { png_byte alpha = inrow[1]; @@ -1745,6 +3377,7 @@ png_image_read_background(png_voidp argument) row += display->row_bytes; } + } } } break; @@ -1755,6 +3388,8 @@ png_image_read_background(png_voidp argument) * handles the alpha-first option. */ { + png_bytep first_row = display->first_row; + ptrdiff_t step_row = display->row_bytes; unsigned int outchannels = png_get_channels(png_ptr, info_ptr); int preserve_alpha = (image->format & PNG_FORMAT_FLAG_ALPHA) != 0; int swap_alpha = 0; @@ -1764,18 +3399,17 @@ png_image_read_background(png_voidp argument) for (pass = 0; pass < passes; ++pass) { - png_uint_16p row = (png_uint_16p)display->first_row; unsigned int startx, stepx, stepy; /* all in pixels */ png_uint_32 y; - if (interlace_type == PNG_INTERLACE_ADAM7) + if (png_ptr->interlaced == PNG_INTERLACE_ADAM7) { /* The row may be empty for a short image: */ if (PNG_PASS_COLS(width, pass) == 0) continue; - startx = PNG_PASS_START_COL(pass); - stepx = PNG_PASS_COL_OFFSET(pass); + startx = PNG_PASS_START_COL(pass) * outchannels; + stepx = PNG_PASS_COL_OFFSET(pass) * outchannels; y = PNG_PASS_START_ROW(pass); stepy = PNG_PASS_ROW_OFFSET(pass); } @@ -1784,51 +3418,48 @@ png_image_read_background(png_voidp argument) { y = 0; startx = 0; - stepx = stepy = 1; + stepx = outchannels; + stepy = 1; } - startx *= outchannels; - stepx *= outchannels; - for (; ylocal_row, NULL); + inrow = (png_const_uint_16p)display->local_row; + + /* Now do the pre-multiplication on each pixel in this row. + */ + outrow += startx; + for (; outrow < end_row; outrow += stepx) { - png_uint_16p inrow; - png_uint_16p outrow = row + startx; - png_uint_16p end_row = row + width * outchannels; + png_uint_32 component = inrow[0]; + png_uint_16 alpha = inrow[1]; - /* Read the row, which is packed: */ - png_read_row(png_ptr, display->local_row, NULL); - inrow = (png_uint_16p)display->local_row; - - /* Now do the pre-multiplication on each pixel in this row. - */ - for (; outrow < end_row; outrow += stepx) + if (alpha > 0) /* else 0 */ { - png_uint_32 component = inrow[0]; - png_uint_16 alpha = inrow[1]; - - if (alpha > 0) /* else 0 */ + if (alpha < 65535) /* else just use component */ { - if (alpha < 65535) /* else just use component */ - { - component *= alpha; - component += 32767; - component /= 65535; - } + component *= alpha; + component += 32767; + component /= 65535; } - - else - component = 0; - - outrow[swap_alpha] = (png_uint_16)component; - if (outchannels > 1) - outrow[1 ^ swap_alpha] = alpha; - - inrow += 2; /* components and alpha channel */ } - row += display->row_bytes; + else + component = 0; + + outrow[swap_alpha] = (png_uint_16)component; + if (outchannels > 1) + outrow[1 ^ swap_alpha] = alpha; + + inrow += 2; /* components and alpha channel */ } + } } } break; @@ -1839,7 +3470,7 @@ png_image_read_background(png_voidp argument) /* The guts of png_image_finish_read as a png_safe_execute callback. */ static int -png_image_read_end(png_voidp argument) +png_image_read_direct(png_voidp argument) { png_image_read_control *display = png_voidcast(png_image_read_control*, argument); @@ -1861,7 +3492,8 @@ png_image_read_end(png_voidp argument) /* Now check the format to see if it was modified. */ { - png_uint_32 base_format = png_image_format(png_ptr, info_ptr); + png_uint_32 base_format = png_image_format(png_ptr) & + ~PNG_FORMAT_FLAG_COLORMAP /* removed by png_set_expand */; png_uint_32 change = format ^ base_format; png_fixed_point output_gamma; int mode; /* alpha mode */ @@ -2125,46 +3757,7 @@ png_image_read_end(png_voidp argument) png_error(png_ptr, "png_read_image: unsupported transformation"); } -# ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED - /* Prepare the reader to ignore all recognized chunks whose data will not - * be used, i.e., all chunks recognized by libpng except for those - * involved in basic image reading: - * - * IHDR, PLTE, IDAT, IEND - * - * Or image data handling: - * - * tRNS, bKGD, gAMA, cHRM, sRGB, iCCP and sBIT. - * - * This provides a small performance improvement and eliminates any - * potential vulnerability to security problems in the unused chunks. - * - * TODO: make it so that this is an explicit list to process, not a list - * to ignore? - */ - { - static PNG_CONST png_byte chunks_to_ignore[] = { - 104, 73, 83, 84, '\0', /* hIST */ - 105, 84, 88, 116, '\0', /* iTXt */ - 111, 70, 70, 115, '\0', /* oFFs */ - 112, 67, 65, 76, '\0', /* pCAL */ - 112, 72, 89, 115, '\0', /* pHYs */ - 115, 67, 65, 76, '\0', /* sCAL */ - 115, 80, 76, 84, '\0', /* sPLT */ - 116, 69, 88, 116, '\0', /* tEXt */ - 116, 73, 77, 69, '\0', /* tIME */ - 122, 84, 88, 116, '\0' /* zTXt */ - }; - - /* Ignore unknown chunks */ - png_set_keep_unknown_chunks(png_ptr, 1 /* PNG_HANDLE_CHUNK_NEVER */, - NULL, 0); - - /* Ignore known but unused chunks */ - png_set_keep_unknown_chunks(png_ptr, 1 /* PNG_HANDLE_CHUNK_NEVER */, - chunks_to_ignore, (sizeof chunks_to_ignore)/5); - } -# endif /* PNG_HANDLE_AS_UNKNOWN_SUPPORTED */ + PNG_SKIP_CHUNKS(png_ptr); /* Update the 'info' structure and make sure the result is as required; first * make sure to turn on the interlace handling if it will be required @@ -2228,7 +3821,7 @@ png_image_read_end(png_voidp argument) ptrdiff_t row_bytes = display->row_stride; if (linear) - row_bytes *= sizeof (png_uint_16); + row_bytes *= 2; /* The following expression is designed to work correctly whether it gives * a signed or an unsigned result. @@ -2290,9 +3883,9 @@ png_image_read_end(png_voidp argument) int PNGAPI png_image_finish_read(png_imagep image, png_colorp background, void *buffer, - png_int_32 row_stride) + png_int_32 row_stride, void *colormap) { - if (image != NULL) + if (image != NULL && image->version == PNG_IMAGE_VERSION) { png_uint_32 check; @@ -2305,20 +3898,42 @@ png_image_finish_read(png_imagep image, png_colorp background, void *buffer, else check = row_stride; - if (buffer != NULL && check >= PNG_IMAGE_ROW_STRIDE(*image)) + if (image->opaque != NULL && buffer != NULL && + check >= PNG_IMAGE_ROW_STRIDE(*image)) { - int result; - png_image_read_control display; + if ((image->format & PNG_FORMAT_FLAG_COLORMAP) == 0 || + (image->colormap_entries > 0 && colormap != NULL)) + { + int result; + png_image_read_control display; - png_memset(&display, 0, sizeof display); - display.image = image; - display.buffer = buffer; - display.row_stride = row_stride; - display.background = background; - display.local_row = NULL; - result = png_safe_execute(image, png_image_read_end, &display); - png_image_free(image); - return result; + png_memset(&display, 0, sizeof display); + display.image = image; + display.buffer = buffer; + display.row_stride = row_stride; + display.colormap = colormap; + display.background = background; + display.local_row = NULL; + + /* Choose the correct 'end' routine; for the color-map case all the + * setup has already been done. + */ + if (image->format & PNG_FORMAT_FLAG_COLORMAP) + result = + png_safe_execute(image, png_image_read_colormap, &display) && + png_safe_execute(image, png_image_read_colormapped, &display); + + else + result = + png_safe_execute(image, png_image_read_direct, &display); + + png_image_free(image); + return result; + } + + else + return png_image_error(image, + "png_image_finish_read[color-map]: no color-map"); } else diff --git a/pngrtran.c b/pngrtran.c index b51270fe8..46067b1ca 100644 --- a/pngrtran.c +++ b/pngrtran.c @@ -1454,7 +1454,7 @@ png_init_read_transformations(png_structrp png_ptr) * NOTE: this discards the low 16 bits of the user supplied background * color, but until expand_16 works properly there is no choice! */ -# define CHOP(x) (x)=((png_uint_16)(((png_uint_32)(x)*255+32895) >> 16)) +# define CHOP(x) (x)=((png_uint_16)PNG_DIV257(x)) CHOP(png_ptr->background.red); CHOP(png_ptr->background.green); CHOP(png_ptr->background.blue); diff --git a/pngwrite.c b/pngwrite.c index 1edac03f9..bf3fa4e65 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -1561,32 +1561,6 @@ png_image_write_init(png_imagep image) return png_image_error(image, "png_image_read: out of memory"); } -int PNGAPI -png_image_write_colormap(png_imagep image, png_const_bytep colormap) -{ - if (image != NULL) - { - if (colormap != NULL) - { - if ((image->format & PNG_FORMAT_FLAG_COLORMAP) == 0) - { - image->colormap = colormap; /* alias, caller must preserve */ - image->format |= PNG_FORMAT_FLAG_COLORMAP; - } - - else - return png_image_error(image, - "png_image_write_colormap: colormap already set"); - } - - else - return png_image_error(image, - "png_image_write_colormap: invalid argument"); - } - - return 0; -} - /* Arguments to png_image_write_main: */ typedef struct { @@ -1594,6 +1568,7 @@ typedef struct png_imagep image; png_const_voidp buffer; png_int_32 row_stride; + png_const_voidp colormap; int convert_to_8bit; /* Local variables: */ png_const_voidp first_row; @@ -1708,7 +1683,58 @@ png_write_image_16bit(png_voidp argument) /* Given 16-bit input (1 to 4 channels) write 8-bit output. If an alpha channel * is present it must be removed from the components, the components are then * written in sRGB encoding. No components are added or removed. + * + * Calculate an alpha reciprocal to reverse pre-multiplication. As above the + * calculation can be done to 15 bits of accuracy; however, the output needs to + * be scaled in the range 0..255*65535, so include that scaling here. */ +#define UNP_RECIPROCAL(alpha) ((((0xffff*0xff)<<7)+(alpha>>1))/alpha) + +static png_byte +png_unpremultiply(png_uint_32 component, png_uint_32 alpha, + png_uint_32 reciprocal/*from the above macro*/) +{ + /* The following gives 1.0 for an alpha of 0, which is fine, otherwise if 0/0 + * is represented as some other value there is more likely to be a + * discontinuity which will probably damage compression when moving from a + * fully transparent area to a nearly transparent one. (The assumption here + * is that opaque areas tend not to be 0 intensity.) + * + * There is a rounding problem here; if alpha is less than 128 it will end up + * as 0 when scaled to 8 bits. To avoid introducing spurious colors into the + * output change for this too. + */ + if (component >= alpha || alpha < 128) + return 255; + + /* component 0) + { + /* The test is that alpha/257 (rounded) is less than 255, the first value + * that becomes 255 is 65407. + * NOTE: this must agree with the PNG_DIV257 macro (which must, therefore, + * be exact!) [Could also test reciprocal != 0] + */ + if (alpha < 65407) + { + component *= reciprocal; + component += 64; /* round to nearest */ + component >>= 7; + } + + else + component *= 255; + + /* Convert the component to sRGB. */ + return (png_byte)PNG_sRGB_FROM_LINEAR(component); + } + + else + return 0; +} + static int png_write_image_8bit(png_voidp argument) { @@ -1749,62 +1775,19 @@ png_write_image_8bit(png_voidp argument) if (aindex != 0) while (out_ptr < row_end) /* Alpha channel case */ { png_uint_16 alpha = in_ptr[aindex]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); png_uint_32 reciprocal = 0; int c; - /* Scale and write the alpha channel. See pngrtran.c - * png_do_scale_16_to_8 for a discussion of this calculation. The - * code here has machine native values, so use: - * - * (V * 255 + 32895) >> 16 - */ - out_ptr[aindex] = (png_byte)((alpha * 255 + 32895) >> 16); + /* Scale and write the alpha channel. */ + out_ptr[aindex] = alphabyte; - /* Calculate a reciprocal. As above the calculation can be done to - * 15 bits of accuracy, however the output needs to be scaled in the - * range 0..255*65535, so include that scaling here. - */ - if (alpha > 0 && alpha < 65535) - reciprocal = (((0xffff*0xff)<<7)+(alpha>>1))/alpha; + if (alphabyte > 0 && alphabyte < 255) + reciprocal = UNP_RECIPROCAL(alpha); c = channels; do /* always at least one channel */ - { - /* Need 32 bit accuracy in the sRGB tables */ - png_uint_32 component = *in_ptr++; - - /* The following gives 1.0 for an alpha of 0, which is fine, - * otherwise if 0/0 is represented as some other value there is - * more likely to be a discontinuity which will probably damage - * compression when moving from a fully transparent area to a - * nearly transparent one. (The assumption here is that opaque - * areas tend not to be 0 intensity.) - */ - if (component >= alpha) - *out_ptr++ = 255; - - /* component 0) - { - if (alpha < 65535) - { - component *= reciprocal; - component += 64; /* round to nearest */ - component >>= 7; - } - - else - component *= 255; - - /* Convert the component to sRGB. */ - *out_ptr++ = (png_byte)PNG_sRGB_FROM_LINEAR(component); - } - - else - *out_ptr++ = 0; - } + *out_ptr++ = png_unpremultiply(*in_ptr++, alpha, reciprocal); while (--c > 0); /* Skip to next component (skip the intervening alpha channel) */ @@ -1846,6 +1829,151 @@ png_write_image_8bit(png_voidp argument) return 1; } +static void +png_image_set_PLTE(png_image_write_control *display) +{ + const png_imagep image = display->image; + const void *cmap = display->colormap; + const int entries = image->colormap_entries > 256 ? 256 : + (int)image->colormap_entries; + + /* NOTE: the caller must check for cmap != NULL and entries != 0 */ + const png_uint_32 format = image->format; + const int channels = PNG_IMAGE_SAMPLE_CHANNELS(format); + +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int afirst = (format & PNG_FORMAT_FLAG_AFIRST) != 0 && + (format & PNG_FORMAT_FLAG_ALPHA) != 0; +# else +# define afirst 0 +# endif + +# ifdef PNG_FORMAT_BGR_SUPPORTED + const int bgr = (format & PNG_FORMAT_FLAG_BGR) ? 2 : 0; +# else +# define bgr 0 +# endif + + int i, num_trans; + png_color palette[256]; + png_byte tRNS[256]; + + memset(tRNS, 255, sizeof tRNS); + memset(palette, 0, sizeof palette); + + for (i=num_trans=0; i= 3) /* RGB */ + { + palette[i].blue = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[(2 ^ bgr)]); + palette[i].green = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[1]); + palette[i].red = (png_byte)PNG_sRGB_FROM_LINEAR(255 * + entry[bgr]); + } + + else /* Gray */ + palette[i].blue = palette[i].red = palette[i].green = + (png_byte)PNG_sRGB_FROM_LINEAR(255 * *entry); + } + + else /* alpha */ + { + png_uint_16 alpha = entry[afirst ? 0 : channels-1]; + png_byte alphabyte = (png_byte)PNG_DIV257(alpha); + png_uint_32 reciprocal = 0; + + /* Calculate a reciprocal, as in the png_write_image_8bit code above + * this is designed to produce a value scaled to 255*65535 when + * divided by 128 (i.e. asr 7). + */ + if (alphabyte > 0 && alphabyte < 255) + reciprocal = (((0xffff*0xff)<<7)+(alpha>>1))/alpha; + + tRNS[i] = alphabyte; + if (alphabyte < 255) + num_trans = i+1; + + if (channels >= 3) /* RGB */ + { + palette[i].blue = png_unpremultiply(entry[afirst + (2 ^ bgr)], + alpha, reciprocal); + palette[i].green = png_unpremultiply(entry[afirst + 1], alpha, + reciprocal); + palette[i].red = png_unpremultiply(entry[afirst + bgr], alpha, + reciprocal); + } + + else /* gray */ + palette[i].blue = palette[i].red = palette[i].green = + png_unpremultiply(entry[afirst], alpha, reciprocal); + } + } + + else /* Color-map has sRGB values */ + { + png_const_bytep entry = png_voidcast(png_const_bytep, cmap); + + entry += i * channels; + + switch (channels) + { + case 4: + tRNS[i] = entry[afirst ? 0 : 3]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALL THROUGH */ + case 3: + palette[i].blue = entry[afirst + (2 ^ bgr)]; + palette[i].green = entry[afirst + 1]; + palette[i].red = entry[afirst + bgr]; + break; + + case 2: + tRNS[i] = entry[1 ^ afirst]; + if (tRNS[i] < 255) + num_trans = i+1; + /* FALL THROUGH */ + case 1: + palette[i].blue = palette[i].red = palette[i].green = + entry[afirst]; + break; + + default: + break; + } + } + } + +# ifdef afirst +# undef afirst +# endif +# ifdef bgr +# undef bgr +# endif + + png_set_PLTE(image->opaque->png_ptr, image->opaque->info_ptr, palette, + entries); + + if (num_trans > 0) + png_set_tRNS(image->opaque->png_ptr, image->opaque->info_ptr, tRNS, + num_trans, NULL); + + image->colormap_entries = entries; +} + static int png_image_write_main(png_voidp argument) { @@ -1856,9 +1984,10 @@ png_image_write_main(png_voidp argument) png_inforp info_ptr = image->opaque->info_ptr; png_uint_32 format = image->format; - int linear = (format & PNG_FORMAT_FLAG_LINEAR) != 0; /* input */ - int alpha = (format & PNG_FORMAT_FLAG_ALPHA) != 0; - int write_16bit = linear && !display->convert_to_8bit; + int colormap = (format & PNG_FORMAT_FLAG_COLORMAP) != 0; + int linear = !colormap && (format & PNG_FORMAT_FLAG_LINEAR) != 0; /* input */ + int alpha = !colormap && (format & PNG_FORMAT_FLAG_ALPHA) != 0; + int write_16bit = linear && !colormap && !display->convert_to_8bit; /* Default the 'row_stride' parameter if required. */ if (display->row_stride == 0) @@ -1866,7 +1995,23 @@ png_image_write_main(png_voidp argument) /* Set the required transforms then write the rows in the correct order. */ if (format & PNG_FORMAT_FLAG_COLORMAP) - return png_image_error(image, "png_image_write: colormap NYI"); + { + if (display->colormap != NULL && image->colormap_entries > 0) + { + png_uint_32 entries = image->colormap_entries; + + png_set_IHDR(png_ptr, info_ptr, image->width, image->height, + entries > 16 ? 8 : (entries > 4 ? 4 : (entries > 2 ? 2 : 1)), + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_image_set_PLTE(display); + } + + else + png_error(image->opaque->png_ptr, + "no color-map for color-mapped image"); + } else png_set_IHDR(png_ptr, info_ptr, image->width, image->height, @@ -1880,6 +2025,7 @@ png_image_write_main(png_voidp argument) * must still be called before. Just set the color space information, never * write an interlaced image. */ + if (write_16bit) { /* The gamma here is 1.0 (linear) and the cHRM chunk matches sRGB. */ @@ -1923,7 +2069,7 @@ png_image_write_main(png_voidp argument) # ifdef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED if (format & PNG_FORMAT_FLAG_BGR) { - if (format & PNG_FORMAT_FLAG_COLOR) + if (!colormap && (format & PNG_FORMAT_FLAG_COLOR) != 0) png_set_bgr(png_ptr); format &= ~PNG_FORMAT_FLAG_BGR; } @@ -1932,15 +2078,21 @@ png_image_write_main(png_voidp argument) # ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED if (format & PNG_FORMAT_FLAG_AFIRST) { - if (format & PNG_FORMAT_FLAG_ALPHA) + if (!colormap && (format & PNG_FORMAT_FLAG_ALPHA) != 0) png_set_swap_alpha(png_ptr); format &= ~PNG_FORMAT_FLAG_AFIRST; } # endif + /* If there are 16 or fewer color-map entries we wrote a lower bit depth + * above, but the application data is still byte packed. + */ + if (colormap && image->colormap_entries <= 16) + png_set_packing(png_ptr); + /* That should have handled all (both) the transforms. */ if ((format & ~(png_uint_32)(PNG_FORMAT_FLAG_COLOR | PNG_FORMAT_FLAG_LINEAR | - PNG_FORMAT_FLAG_ALPHA)) != 0) + PNG_FORMAT_FLAG_ALPHA | PNG_FORMAT_FLAG_COLORMAP)) != 0) png_error(png_ptr, "png_write_image: unsupported transformation"); { @@ -1961,7 +2113,7 @@ png_image_write_main(png_voidp argument) * before it is written. This only applies when the input is 16-bit and * either there is an alpha channel or it is converted to 8-bit. */ - if ((linear && alpha) || display->convert_to_8bit) + if ((linear && alpha) || (!colormap && display->convert_to_8bit)) { png_bytep row = png_voidcast(png_bytep, png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr))); @@ -2003,10 +2155,10 @@ png_image_write_main(png_voidp argument) int PNGAPI png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, - const void *buffer, png_int_32 row_stride) + const void *buffer, png_int_32 row_stride, const void *colormap) { /* Write the image to the given (FILE*). */ - if (image != NULL) + if (image != NULL || image->version != PNG_IMAGE_VERSION) { if (file != NULL) { @@ -2025,6 +2177,7 @@ png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, display.image = image; display.buffer = buffer; display.row_stride = row_stride; + display.colormap = colormap; display.convert_to_8bit = convert_to_8bit; result = png_safe_execute(image, png_image_write_main, &display); @@ -2047,10 +2200,11 @@ png_image_write_to_stdio(png_imagep image, FILE *file, int convert_to_8bit, int PNGAPI png_image_write_to_file(png_imagep image, const char *file_name, - int convert_to_8bit, const void *buffer, png_int_32 row_stride) + int convert_to_8bit, const void *buffer, png_int_32 row_stride, + const void *colormap) { /* Write the image to the named file. */ - if (image != NULL) + if (image != NULL || image->version != PNG_IMAGE_VERSION) { if (file_name != NULL) { @@ -2059,7 +2213,7 @@ png_image_write_to_file(png_imagep image, const char *file_name, if (fp != NULL) { if (png_image_write_to_stdio(image, fp, convert_to_8bit, buffer, - row_stride)) + row_stride, colormap)) { int error; /* from fflush/fclose */ diff --git a/scripts/symbols.def b/scripts/symbols.def index 40c41e6ed..16941b470 100644 --- a/scripts/symbols.def +++ b/scripts/symbols.def @@ -247,5 +247,3 @@ EXPORTS png_image_write_to_file @239 png_image_write_to_stdio @240 png_convert_to_rfc1123_buffer @241 - png_image_read_colormap @242 - png_image_write_colormap @243