[libpng15] Simplified read/write API initial version; basic read/write tested
on a variety of images, limited documentation (in the header file.)
diff --git a/pngwrite.c b/pngwrite.c
index 107aa34..76bdd79 100644
--- a/pngwrite.c
+++ b/pngwrite.c
@@ -12,6 +12,9 @@
*/
#include "pngpriv.h"
+#if defined PNG_SIMPLIFIED_WRITE_SUPPORTED && defined PNG_STDIO_SUPPORTED
+# include <errno.h>
+#endif
#ifdef PNG_WRITE_SUPPORTED
@@ -1651,4 +1654,551 @@
PNG_UNUSED(params)
}
#endif
+
+
+#ifdef PNG_SIMPLIFIED_WRITE_SUPPORTED
+#ifdef PNG_STDIO_SUPPORTED /* currently required for png_image_write_* */
+/* Initialize the write structure - general purpose utility. */
+static int
+png_image_write_init(png_imagep image)
+{
+ png_structp png_ptr = png_create_write_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);
+
+ if (info_ptr != NULL)
+ {
+ png_controlp control = png_malloc_warn(png_ptr, sizeof *control);
+
+ if (control != NULL)
+ {
+ memset(control, 0, sizeof *control);
+
+ control->png_ptr = png_ptr;
+ control->info_ptr = info_ptr;
+ control->for_write = 1;
+
+ image->opaque = control;
+ return 1;
+ }
+
+ /* Error clean up */
+ png_destroy_info_struct(png_ptr, &info_ptr);
+ }
+
+ png_destroy_write_struct(&png_ptr, NULL);
+ }
+
+ return png_image_error(image, "png_image_read: out of memory");
+}
+
+/* Arguments to png_image_write_main: */
+typedef struct
+{
+ /* Arguments: */
+ png_imagep image;
+ png_const_voidp buffer;
+ png_int_32 row_stride;
+ int convert_to_8bit;
+ /* Local variables: */
+ png_const_voidp first_row;
+ ptrdiff_t row_bytes;
+ png_voidp local_row;
+} png_image_write_control;
+
+/* Write png_uint_16 input to a 16-bit PNG, the png_ptr has already been set to
+ * do any necssary byte swapped. The component order is defined by the
+ * png_image format value.
+ */
+static int
+png_write_image_16bit(png_voidp argument)
+{
+ png_image_write_control *display = argument;
+ png_imagep image = display->image;
+ png_structp png_ptr = image->opaque->png_ptr;
+
+ png_const_uint_16p input_row = display->first_row;
+ png_uint_16p output_row = display->local_row;
+ png_uint_16p row_end;
+ int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1;
+ int aindex = 0;
+ png_uint_32 y = image->height;
+
+ if (image->format & PNG_FORMAT_FLAG_ALPHA)
+ {
+ if (image->format & PNG_FORMAT_FLAG_AFIRST)
+ {
+ aindex = -1;
+ ++input_row; /* To point to the first component */
+ ++output_row;
+ }
+
+ else
+ aindex = channels;
+ }
+
+ else
+ png_error(png_ptr, "png_write_image: internal call error");
+
+ /* Work out the output row end and count over this, note that the increment
+ * above to 'row' means that row_end can actually be beyond the end of the
+ * row, this is correct.
+ */
+ row_end = output_row + image->width * (channels+1);
+
+ while (y-- > 0)
+ {
+ png_const_uint_16p in_ptr = input_row;
+ png_uint_16p out_ptr = output_row;
+
+ while (out_ptr < row_end)
+ {
+ png_uint_16 alpha = in_ptr[aindex];
+ png_uint_32 reciprocal = 0;
+ int c;
+
+ out_ptr[aindex] = alpha;
+
+ /* Calculate a reciprocal. The correct calculation is simply
+ * component/alpha*65535 << 15. (I.e. 15 bits of precision), this
+ * allows correct rounding by adding .5 before the shift. 'reciprocal'
+ * is only initialized when required.
+ */
+ if (alpha > 0 && alpha < 65535)
+ reciprocal = ((0xffff<<15)+(alpha>>1))/alpha;
+
+ c = channels;
+ do /* always at least one channel */
+ {
+ png_uint_16 component = *in_ptr++;
+
+ /* The following gives 65535 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)
+ component = 65535;
+
+ /* component<alpha, so component/alpha is less than one and
+ * component*reciprocal is less than 2^31.
+ */
+ else if (component > 0 && alpha < 65535)
+ {
+ png_uint_32 calc = component * reciprocal;
+ calc += 16384; /* round to nearest */
+ component = (png_uint_16)(calc >> 15);
+ }
+
+ *out_ptr++ = component;
+ }
+ while (--c > 0);
+
+ /* Skip to next component (skip the intervening alpha channel) */
+ ++in_ptr;
+ ++out_ptr;
+ }
+
+ png_write_row(png_ptr, (png_bytep)output_row);
+ input_row += display->row_bytes/(sizeof (png_uint_16));
+ }
+
+ return 1;
+}
+
+/* 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.
+ */
+static int
+png_write_image_8bit(png_voidp argument)
+{
+ png_image_write_control *display = argument;
+ png_imagep image = display->image;
+ png_structp png_ptr = image->opaque->png_ptr;
+
+ png_const_uint_16p input_row = display->first_row;
+ png_bytep output_row = display->local_row;
+ png_uint_32 y = image->height;
+ int channels = (image->format & PNG_FORMAT_FLAG_COLOR) ? 3 : 1;
+
+ if (image->format & PNG_FORMAT_FLAG_ALPHA)
+ {
+ png_bytep row_end;
+ int aindex;
+
+ if (image->format & PNG_FORMAT_FLAG_AFIRST)
+ {
+ aindex = -1;
+ ++input_row; /* To point to the first component */
+ ++output_row;
+ }
+
+ else
+ aindex = channels;
+
+ /* Use row_end in place of a loop counter: */
+ row_end = output_row + image->width * (channels+1);
+
+ while (y-- > 0)
+ {
+ png_const_uint_16p in_ptr = input_row;
+ png_bytep out_ptr = output_row;
+
+ if (aindex != 0) while (out_ptr < row_end) /* Alpha channel case */
+ {
+ png_uint_16 alpha = in_ptr[aindex];
+ 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);
+
+ /* 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;
+
+ 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 65535 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<alpha, so component/alpha is less than one and
+ * component*reciprocal is less than 2^31.
+ */
+ else if (component > 0 && alpha < 65535)
+ {
+ component *= reciprocal;
+ component += 64; /* round to nearest */
+ component >>= 7;
+
+ /* Convert the component to sRGB. */
+ *out_ptr++ = (png_byte)PNG_sRGB_FROM_LINEAR(component);
+ }
+
+ else
+ *out_ptr++ = 0;
+ }
+ while (--c > 0);
+
+ /* Skip to next component (skip the intervening alpha channel) */
+ ++in_ptr;
+ ++out_ptr;
+ } /* while out_ptr < row_end */
+ } /* while y */
+ }
+
+ else
+ {
+ /* No alpha channel, so the row_end really is the end of the row and it
+ * is sufficient to loop over the components one by one.
+ */
+ png_bytep row_end = output_row + image->width * channels;
+
+ while (y-- > 0)
+ {
+ png_const_uint_16p in_ptr = input_row;
+ png_bytep out_ptr = output_row;
+
+ while (out_ptr < row_end)
+ {
+ png_uint_32 component = *in_ptr++;
+
+ component *= 255;
+ *out_ptr++ = (png_byte)PNG_sRGB_FROM_LINEAR(component);
+ }
+
+ png_write_row(png_ptr, output_row);
+ input_row += display->row_bytes/(sizeof (png_uint_16));
+ }
+ }
+
+ return 1;
+}
+
+static int
+png_image_write_main(png_voidp argument)
+{
+ png_image_write_control *display = argument;
+ png_imagep image = display->image;
+ png_structp png_ptr = image->opaque->png_ptr;
+ png_infop 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;
+
+ /* Set the required transforms then write the rows in the correct order. */
+ png_set_IHDR(png_ptr, info_ptr, image->width, image->height,
+ write_16bit ? 16 : 8,
+ ((format & PNG_FORMAT_FLAG_COLOR) ? PNG_COLOR_MASK_COLOR : 0) +
+ ((format & PNG_FORMAT_FLAG_ALPHA) ? PNG_COLOR_MASK_ALPHA : 0),
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ /* Counter-intuitively the data transformations must be called *after*
+ * png_write_info, not before as in the read code, but the 'set' functions
+ * 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. */
+ png_set_gAMA_fixed(png_ptr, info_ptr, PNG_GAMMA_LINEAR);
+ png_set_cHRM_fixed(png_ptr, info_ptr,
+ /* color x y */
+ /* white */ 31270, 32900,
+ /* red */ 64000, 33000,
+ /* green */ 30000, 60000,
+ /* blue */ 15000, 6000
+ );
+ }
+
+ else
+ png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
+
+ /* Write the file header. */
+ png_write_info(png_ptr, info_ptr);
+
+ /* Now set up the data transformations (*after* the header is written),
+ * remove the handled transformations from the 'format' flags for checking.
+ */
+ format &= ~(PNG_FORMAT_FLAG_COLOR | PNG_FORMAT_FLAG_LINEAR |
+ PNG_FORMAT_FLAG_ALPHA);
+
+ /* Check for a little endian system if writing 16 bit files. */
+ if (write_16bit)
+ {
+ PNG_CONST png_uint_16 le = 0x0001;
+
+ if (*(png_const_bytep)&le)
+ png_set_swap(png_ptr);
+ }
+
+# ifdef PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED
+ if (format & PNG_FORMAT_FLAG_BGR)
+ {
+ png_set_bgr(png_ptr);
+ format &= ~PNG_FORMAT_FLAG_BGR;
+ }
+# endif
+
+# ifdef PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED
+ if (format & PNG_FORMAT_FLAG_AFIRST)
+ {
+ png_set_swap_alpha(png_ptr);
+ format &= ~PNG_FORMAT_FLAG_AFIRST;
+ }
+# endif
+
+ /* That should have handled all the transforms. */
+ if (format != 0)
+ png_error(png_ptr, "png_write_image: unsupported transformation");
+
+ {
+ png_const_bytep row = display->buffer;
+ ptrdiff_t row_bytes = display->row_stride;
+
+ if (linear)
+ row_bytes *= sizeof (png_uint_16);
+
+ if (row_bytes < 0)
+ row += (image->height-1) * (-row_bytes);
+
+ display->first_row = row;
+ display->row_bytes = row_bytes;
+ }
+
+ /* Check for the cases that currently require a pre-transform on the row
+ * 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)
+ {
+ png_bytep row = png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
+ int result;
+
+ display->local_row = row;
+ if (write_16bit)
+ result = png_safe_execute(image, png_write_image_16bit, display);
+ else
+ result = png_safe_execute(image, png_write_image_8bit, display);
+ display->local_row = NULL;
+
+ png_free(png_ptr, row);
+
+ /* Skip the 'write_end' on error: */
+ if (!result)
+ return 0;
+ }
+
+ /* Otherwise this is the case where the input is in a format currently
+ * supported by the rest of the libpng write code; call it directly.
+ */
+ else
+ {
+ png_const_bytep row = display->first_row;
+ ptrdiff_t row_bytes = display->row_bytes;
+ png_uint_32 y = image->height;
+
+ while (y-- > 0)
+ {
+ png_write_row(png_ptr, row);
+ row += row_bytes;
+ }
+ }
+
+ png_write_end(png_ptr, info_ptr);
+ return 1;
+}
+
+int PNGAPI
+png_image_write_to_stdio (png_imagep image, FILE *file, int convert_to_8bit,
+ const void *buffer, png_int_32 row_stride)
+{
+ /* Write the image to the given (FILE*). */
+ if (image != NULL)
+ {
+ if (file != NULL)
+ {
+ if (png_image_write_init(image))
+ {
+ png_image_write_control display;
+ int result;
+
+ /* This is slightly evil, but png_init_io doesn't do anything other
+ * than this and we haven't changed the standard IO functions so
+ * this saves a 'safe' function.
+ */
+ image->opaque->png_ptr->io_ptr = file;
+
+ memset(&display, 0, sizeof display);
+ display.image = image;
+ display.buffer = buffer;
+ display.row_stride = row_stride;
+ display.convert_to_8bit = convert_to_8bit;
+
+ result = png_safe_execute(image, png_image_write_main, &display);
+ png_image_free(image);
+ return result;
+ }
+
+ else
+ return 0;
+ }
+
+ else
+ return png_image_error(image,
+ "png_image_write_to_stdio: invalid argument");
+ }
+
+ else
+ return 0;
+}
+
+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)
+{
+ /* Write the image to the named file. */
+ if (image != NULL)
+ {
+ if (file_name != NULL)
+ {
+ FILE *fp = fopen(file_name, "wb");
+
+ if (fp != NULL)
+ {
+ if (png_image_write_init(image))
+ {
+ png_image_write_control display;
+
+ image->opaque->png_ptr->io_ptr = fp;
+ image->opaque->owned_file = 1;
+ /* No need to close this file now - png_image_free will do that.
+ */
+
+ memset(&display, 0, sizeof display);
+ display.image = image;
+ display.buffer = buffer;
+ display.row_stride = row_stride;
+ display.convert_to_8bit = convert_to_8bit;
+
+ if (png_safe_execute(image, png_image_write_main, &display))
+ {
+ int error; /* from fflush/fclose */
+
+ /* Make sure the file is flushed correctly. */
+ if (fflush(fp) == 0)
+ {
+ /* Steal the file pointer back to make sure it closes ok.
+ */
+ image->opaque->png_ptr->io_ptr = NULL;
+ image->opaque->owned_file = 0;
+
+ if (fclose(fp) == 0)
+ {
+ png_image_free(image);
+ return 1;
+ }
+
+ error = errno;
+ }
+
+ else
+ error = errno;
+
+ return png_image_error(image, strerror(error));
+ }
+
+ else /* else cleanup has already happened */
+ return 0;
+ }
+
+ else
+ {
+ /* Clean up: just the opened file. */
+ (void)fclose(fp);
+ return 0;
+ }
+ }
+
+ else
+ return png_image_error(image, strerror(errno));
+ }
+
+ else
+ return png_image_error(image,
+ "png_image_write_to_file: invalid argument");
+ }
+
+ else
+ return 0;
+}
+#endif /* PNG_STDIO_SUPPORTED */
+#endif /* SIMPLIFIED_WRITE */
#endif /* PNG_WRITE_SUPPORTED */