/* cairo - a vector graphics library with display and print output
 *
 * Copyright © 2003 University of Southern California
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is University of Southern
 * California.
 *
 * Contributor(s):
 *	Carl D. Worth <cworth@cworth.org>
 *	Kristian Høgsberg <krh@redhat.com>
 *	Chris Wilson <chris@chris-wilson.co.uk>
 */

#include "cairoint.h"
#include "cairo-output-stream-private.h"

#include <stdio.h>
#include <errno.h>
#include <png.h>

struct png_read_closure_t {
    cairo_read_func_t		 read_func;
    void			*closure;
    cairo_output_stream_t	*png_data;
};


#ifndef PNG_FILLER_AFTER
#define PNG_FILLER_AFTER 1
#endif


/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
static void
unpremultiply_data (png_structp png, png_row_infop row_info, png_bytep data)
{
    unsigned int i;

    for (i = 0; i < row_info->rowbytes; i += 4) {
        uint8_t *b = &data[i];
        uint32_t pixel;
        uint8_t  alpha;

	memcpy (&pixel, b, sizeof (uint32_t));
	alpha = (pixel & 0xff000000) >> 24;
        if (alpha == 0) {
	    b[0] = b[1] = b[2] = b[3] = 0;
	} else {
            b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
            b[1] = (((pixel & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
            b[2] = (((pixel & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
	    b[3] = alpha;
	}
    }
}

/* Converts native endian xRGB => RGBx bytes */
static void
convert_data_to_bytes (png_structp png, png_row_infop row_info, png_bytep data)
{
    unsigned int i;

    for (i = 0; i < row_info->rowbytes; i += 4) {
        uint8_t *b = &data[i];
        uint32_t pixel;

	memcpy (&pixel, b, sizeof (uint32_t));

	b[0] = (pixel & 0xff0000) >> 16;
	b[1] = (pixel & 0x00ff00) >>  8;
	b[2] = (pixel & 0x0000ff) >>  0;
	b[3] = 0;
    }
}

/* Use a couple of simple error callbacks that do not print anything to
 * stderr and rely on the user to check for errors via the #cairo_status_t
 * return.
 */
static void
png_simple_error_callback (png_structp png,
	                   png_const_charp error_msg)
{
    cairo_status_t *error = png_get_error_ptr (png);

    /* default to the most likely error */
    if (*error == CAIRO_STATUS_SUCCESS)
	*error = _cairo_error (CAIRO_STATUS_NO_MEMORY);

#ifdef PNG_SETJMP_SUPPORTED
    longjmp (png_jmpbuf (png), 1);
#endif

    /* if we get here, then we have to choice but to abort ... */
}

static void
png_simple_warning_callback (png_structp png,
	                     png_const_charp error_msg)
{
    cairo_status_t *error = png_get_error_ptr (png);

    /* default to the most likely error */
    if (*error == CAIRO_STATUS_SUCCESS)
	*error = _cairo_error (CAIRO_STATUS_NO_MEMORY);

    /* png does not expect to abort and will try to tidy up after a warning */
}


/* Starting with libpng-1.2.30, we must explicitly specify an output_flush_fn.
 * Otherwise, we will segfault if we are writing to a stream. */
static void
png_simple_output_flush_fn (png_structp png_ptr)
{
}

static cairo_status_t
write_png (cairo_surface_t	*surface,
	   png_rw_ptr		write_func,
	   void			*closure)
{
    int i;
    cairo_status_t status;
    cairo_image_surface_t *image;
    cairo_image_surface_t * volatile clone;
    void *image_extra;
    png_struct *png;
    png_info *info;
    png_byte **volatile rows = NULL;
    png_color_16 white;
    int png_color_type;
    int depth;

    status = _cairo_surface_acquire_source_image (surface,
						  &image,
						  &image_extra);

    if (status == CAIRO_INT_STATUS_UNSUPPORTED)
	return _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
    else if (unlikely (status))
        return status;

    /* PNG complains about "Image width or height is zero in IHDR" */
    if (image->width == 0 || image->height == 0) {
	status = _cairo_error (CAIRO_STATUS_WRITE_ERROR);
	goto BAIL1;
    }

    /* Handle the various fallback formats (e.g. low bit-depth XServers)
     * by coercing them to a simpler format using pixman.
     */
    if (image->format == CAIRO_FORMAT_INVALID) {
	clone = _cairo_image_surface_coerce (image,
					     _cairo_format_from_content (image->base.content));
	status = clone->base.status;
	if (unlikely (status))
	    goto BAIL1;
    } else
	clone = image;

    rows = _cairo_malloc_ab (clone->height, sizeof (png_byte*));
    if (unlikely (rows == NULL)) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	goto BAIL2;
    }

    for (i = 0; i < clone->height; i++)
	rows[i] = (png_byte *) clone->data + i * clone->stride;

    png = png_create_write_struct (PNG_LIBPNG_VER_STRING, &status,
	                           png_simple_error_callback,
	                           png_simple_warning_callback);
    if (unlikely (png == NULL)) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	goto BAIL3;
    }

    info = png_create_info_struct (png);
    if (unlikely (info == NULL)) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	goto BAIL4;
    }

#ifdef PNG_SETJMP_SUPPORTED
    if (setjmp (png_jmpbuf (png)))
	goto BAIL4;
#endif

    png_set_write_fn (png, closure, write_func, png_simple_output_flush_fn);

    switch (clone->format) {
    case CAIRO_FORMAT_ARGB32:
	depth = 8;
	if (_cairo_image_analyze_transparency (clone) == CAIRO_IMAGE_IS_OPAQUE)
	    png_color_type = PNG_COLOR_TYPE_RGB;
	else
	    png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
	break;
    case CAIRO_FORMAT_RGB24:
	depth = 8;
	png_color_type = PNG_COLOR_TYPE_RGB;
	break;
    case CAIRO_FORMAT_A8:
	depth = 8;
	png_color_type = PNG_COLOR_TYPE_GRAY;
	break;
    case CAIRO_FORMAT_A1:
	depth = 1;
	png_color_type = PNG_COLOR_TYPE_GRAY;
#ifndef WORDS_BIGENDIAN
	png_set_packswap (png);
#endif
	break;
    default:
	status = _cairo_error (CAIRO_STATUS_INVALID_FORMAT);
	goto BAIL4;
    }

    png_set_IHDR (png, info,
		  clone->width,
		  clone->height, depth,
		  png_color_type,
		  PNG_INTERLACE_NONE,
		  PNG_COMPRESSION_TYPE_DEFAULT,
		  PNG_FILTER_TYPE_DEFAULT);

    white.gray = (1 << depth) - 1;
    white.red = white.blue = white.green = white.gray;
    png_set_bKGD (png, info, &white);

    if (0) { /* XXX extract meta-data from surface (i.e. creation date) */
	png_time pt;

	png_convert_from_time_t (&pt, time (NULL));
	png_set_tIME (png, info, &pt);
    }

    /* We have to call png_write_info() before setting up the write
     * transformation, since it stores data internally in 'png'
     * that is needed for the write transformation functions to work.
     */
    png_write_info (png, info);

    if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
	png_set_write_user_transform_fn (png, unpremultiply_data);
    } else if (png_color_type == PNG_COLOR_TYPE_RGB) {
	png_set_write_user_transform_fn (png, convert_data_to_bytes);
	png_set_filler (png, 0, PNG_FILLER_AFTER);
    }

    png_write_image (png, rows);
    png_write_end (png, info);

BAIL4:
    png_destroy_write_struct (&png, &info);
BAIL3:
    free (rows);
BAIL2:
    if (clone != image)
	cairo_surface_destroy (&clone->base);
BAIL1:
    _cairo_surface_release_source_image (surface, image, image_extra);

    return status;
}

static void
stdio_write_func (png_structp png, png_bytep data, png_size_t size)
{
    FILE *fp;

    fp = png_get_io_ptr (png);
    while (size) {
	size_t ret = fwrite (data, 1, size, fp);
	size -= ret;
	data += ret;
	if (size && ferror (fp)) {
	    cairo_status_t *error = png_get_error_ptr (png);
	    if (*error == CAIRO_STATUS_SUCCESS)
		*error = _cairo_error (CAIRO_STATUS_WRITE_ERROR);
	    png_error (png, NULL);
	}
    }
}

/**
 * cairo_surface_write_to_png:
 * @surface: a #cairo_surface_t with pixel contents
 * @filename: the name of a file to write to
 *
 * Writes the contents of @surface to a new file @filename as a PNG
 * image.
 *
 * Return value: %CAIRO_STATUS_SUCCESS if the PNG file was written
 * successfully. Otherwise, %CAIRO_STATUS_NO_MEMORY if memory could not
 * be allocated for the operation or
 * %CAIRO_STATUS_SURFACE_TYPE_MISMATCH if the surface does not have
 * pixel contents, or %CAIRO_STATUS_WRITE_ERROR if an I/O error occurs
 * while attempting to write the file.
 **/
cairo_status_t
cairo_surface_write_to_png (cairo_surface_t	*surface,
			    const char		*filename)
{
    FILE *fp;
    cairo_status_t status;

    if (surface->status)
	return surface->status;

    if (surface->finished)
	return _cairo_error (CAIRO_STATUS_SURFACE_FINISHED);

    fp = fopen (filename, "wb");
    if (fp == NULL) {
	switch (errno) {
	case ENOMEM:
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	default:
	    return _cairo_error (CAIRO_STATUS_WRITE_ERROR);
	}
    }

    status = write_png (surface, stdio_write_func, fp);

    if (fclose (fp) && status == CAIRO_STATUS_SUCCESS)
	status = _cairo_error (CAIRO_STATUS_WRITE_ERROR);

    return status;
}

struct png_write_closure_t {
    cairo_write_func_t		 write_func;
    void			*closure;
};

static void
stream_write_func (png_structp png, png_bytep data, png_size_t size)
{
    cairo_status_t status;
    struct png_write_closure_t *png_closure;

    png_closure = png_get_io_ptr (png);
    status = png_closure->write_func (png_closure->closure, data, size);
    if (unlikely (status)) {
	cairo_status_t *error = png_get_error_ptr (png);
	if (*error == CAIRO_STATUS_SUCCESS)
	    *error = status;
	png_error (png, NULL);
    }
}

/**
 * cairo_surface_write_to_png_stream:
 * @surface: a #cairo_surface_t with pixel contents
 * @write_func: a #cairo_write_func_t
 * @closure: closure data for the write function
 *
 * Writes the image surface to the write function.
 *
 * Return value: %CAIRO_STATUS_SUCCESS if the PNG file was written
 * successfully.  Otherwise, %CAIRO_STATUS_NO_MEMORY is returned if
 * memory could not be allocated for the operation,
 * %CAIRO_STATUS_SURFACE_TYPE_MISMATCH if the surface does not have
 * pixel contents.
 **/
cairo_status_t
cairo_surface_write_to_png_stream (cairo_surface_t	*surface,
				   cairo_write_func_t	write_func,
				   void			*closure)
{
    struct png_write_closure_t png_closure;

    if (surface->status)
	return surface->status;

    if (surface->finished)
	return _cairo_error (CAIRO_STATUS_SURFACE_FINISHED);

    png_closure.write_func = write_func;
    png_closure.closure = closure;

    return write_png (surface, stream_write_func, &png_closure);
}
slim_hidden_def (cairo_surface_write_to_png_stream);

static inline int
multiply_alpha (int alpha, int color)
{
    int temp = (alpha * color) + 0x80;
    return ((temp + (temp >> 8)) >> 8);
}

/* Premultiplies data and converts RGBA bytes => native endian */
static void
premultiply_data (png_structp   png,
                  png_row_infop row_info,
                  png_bytep     data)
{
    unsigned int i;

    for (i = 0; i < row_info->rowbytes; i += 4) {
	uint8_t *base  = &data[i];
	uint8_t  alpha = base[3];
	uint32_t p;

	if (alpha == 0) {
	    p = 0;
	} else {
	    uint8_t  red   = base[0];
	    uint8_t  green = base[1];
	    uint8_t  blue  = base[2];

	    if (alpha != 0xff) {
		red   = multiply_alpha (alpha, red);
		green = multiply_alpha (alpha, green);
		blue  = multiply_alpha (alpha, blue);
	    }
	    p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
	}
	memcpy (base, &p, sizeof (uint32_t));
    }
}

/* Converts RGBx bytes to native endian xRGB */
static void
convert_bytes_to_data (png_structp png, png_row_infop row_info, png_bytep data)
{
    unsigned int i;

    for (i = 0; i < row_info->rowbytes; i += 4) {
	uint8_t *base  = &data[i];
	uint8_t  red   = base[0];
	uint8_t  green = base[1];
	uint8_t  blue  = base[2];
	uint32_t pixel;

	pixel = (0xff << 24) | (red << 16) | (green << 8) | (blue << 0);
	memcpy (base, &pixel, sizeof (uint32_t));
    }
}

static cairo_status_t
stdio_read_func (void *closure, unsigned char *data, unsigned int size)
{
    FILE *file = closure;

    while (size) {
	size_t ret;

	ret = fread (data, 1, size, file);
	size -= ret;
	data += ret;

	if (size && (feof (file) || ferror (file)))
	    return _cairo_error (CAIRO_STATUS_READ_ERROR);
    }

    return CAIRO_STATUS_SUCCESS;
}

static void
stream_read_func (png_structp png, png_bytep data, png_size_t size)
{
    cairo_status_t status;
    struct png_read_closure_t *png_closure;

    png_closure = png_get_io_ptr (png);
    status = png_closure->read_func (png_closure->closure, data, size);
    if (unlikely (status)) {
	cairo_status_t *error = png_get_error_ptr (png);
	if (*error == CAIRO_STATUS_SUCCESS)
	    *error = status;
	png_error (png, NULL);
    }

    _cairo_output_stream_write (png_closure->png_data, data, size);
}

static cairo_surface_t *
read_png (struct png_read_closure_t *png_closure)
{
    cairo_surface_t *surface;
    png_struct *png = NULL;
    png_info *info;
    png_byte *data = NULL;
    png_byte **row_pointers = NULL;
    png_uint_32 png_width, png_height;
    int depth, color_type, interlace, stride;
    unsigned int i;
    cairo_format_t format;
    cairo_status_t status;
    unsigned char *mime_data;
    unsigned int mime_data_length;

    png_closure->png_data = _cairo_memory_stream_create ();

    /* XXX: Perhaps we'll want some other error handlers? */
    png = png_create_read_struct (PNG_LIBPNG_VER_STRING,
                                  &status,
	                          png_simple_error_callback,
	                          png_simple_warning_callback);
    if (unlikely (png == NULL)) {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
	goto BAIL;
    }

    info = png_create_info_struct (png);
    if (unlikely (info == NULL)) {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
	goto BAIL;
    }

    png_set_read_fn (png, png_closure, stream_read_func);

    status = CAIRO_STATUS_SUCCESS;
#ifdef PNG_SETJMP_SUPPORTED
    if (setjmp (png_jmpbuf (png))) {
	surface = _cairo_surface_create_in_error (status);
	goto BAIL;
    }
#endif

    png_read_info (png, info);

    png_get_IHDR (png, info,
                  &png_width, &png_height, &depth,
                  &color_type, &interlace, NULL, NULL);
    if (unlikely (status)) { /* catch any early warnings */
	surface = _cairo_surface_create_in_error (status);
	goto BAIL;
    }

    /* convert palette/gray image to rgb */
    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_palette_to_rgb (png);

    /* expand gray bit depth if needed */
    if (color_type == PNG_COLOR_TYPE_GRAY) {
#if PNG_LIBPNG_VER >= 10209
        png_set_expand_gray_1_2_4_to_8 (png);
#else
        png_set_gray_1_2_4_to_8 (png);
#endif
    }

    /* transform transparency to alpha */
    if (png_get_valid (png, info, PNG_INFO_tRNS))
        png_set_tRNS_to_alpha (png);

    if (depth == 16)
        png_set_strip_16 (png);

    if (depth < 8)
        png_set_packing (png);

    /* convert grayscale to RGB */
    if (color_type == PNG_COLOR_TYPE_GRAY ||
	color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
    {
	png_set_gray_to_rgb (png);
    }

    if (interlace != PNG_INTERLACE_NONE)
        png_set_interlace_handling (png);

    png_set_filler (png, 0xff, PNG_FILLER_AFTER);

    /* recheck header after setting EXPAND options */
    png_read_update_info (png, info);
    png_get_IHDR (png, info,
                  &png_width, &png_height, &depth,
                  &color_type, &interlace, NULL, NULL);
    if (depth != 8 ||
	! (color_type == PNG_COLOR_TYPE_RGB ||
	   color_type == PNG_COLOR_TYPE_RGB_ALPHA))
    {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_READ_ERROR));
	goto BAIL;
    }

    switch (color_type) {
	default:
	    ASSERT_NOT_REACHED;
	    /* fall-through just in case ;-) */

	case PNG_COLOR_TYPE_RGB_ALPHA:
	    format = CAIRO_FORMAT_ARGB32;
	    png_set_read_user_transform_fn (png, premultiply_data);
	    break;

	case PNG_COLOR_TYPE_RGB:
	    format = CAIRO_FORMAT_RGB24;
	    png_set_read_user_transform_fn (png, convert_bytes_to_data);
	    break;
    }

    stride = cairo_format_stride_for_width (format, png_width);
    if (stride < 0) {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_INVALID_STRIDE));
	goto BAIL;
    }

    data = _cairo_malloc_ab (png_height, stride);
    if (unlikely (data == NULL)) {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
	goto BAIL;
    }

    row_pointers = _cairo_malloc_ab (png_height, sizeof (char *));
    if (unlikely (row_pointers == NULL)) {
	surface = _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
	goto BAIL;
    }

    for (i = 0; i < png_height; i++)
        row_pointers[i] = &data[i * stride];

    png_read_image (png, row_pointers);
    png_read_end (png, info);

    if (unlikely (status)) { /* catch any late warnings - probably hit an error already */
	surface = _cairo_surface_create_in_error (status);
	goto BAIL;
    }

    surface = cairo_image_surface_create_for_data (data, format,
						   png_width, png_height,
						   stride);
    if (surface->status)
	goto BAIL;

    _cairo_image_surface_assume_ownership_of_data ((cairo_image_surface_t*)surface);
    data = NULL;

    _cairo_debug_check_image_surface_is_defined (surface);

    status = _cairo_memory_stream_destroy (png_closure->png_data,
					   &mime_data,
					   &mime_data_length);
    png_closure->png_data = NULL;
    if (unlikely (status)) {
	cairo_surface_destroy (surface);
	surface = _cairo_surface_create_in_error (status);
	goto BAIL;
    }

    status = cairo_surface_set_mime_data (surface,
					  CAIRO_MIME_TYPE_PNG,
					  mime_data,
					  mime_data_length,
					  free,
					  mime_data);
    if (unlikely (status)) {
	free (mime_data);
	cairo_surface_destroy (surface);
	surface = _cairo_surface_create_in_error (status);
	goto BAIL;
    }

 BAIL:
    if (row_pointers != NULL)
	free (row_pointers);
    if (data != NULL)
	free (data);
    if (png != NULL)
	png_destroy_read_struct (&png, &info, NULL);
    if (png_closure->png_data != NULL) {
	cairo_status_t status_ignored;

	status_ignored = _cairo_output_stream_destroy (png_closure->png_data);
    }

    return surface;
}

/**
 * cairo_image_surface_create_from_png:
 * @filename: name of PNG file to load
 *
 * Creates a new image surface and initializes the contents to the
 * given PNG file.
 *
 * Return value: a new #cairo_surface_t initialized with the contents
 * of the PNG file, or a "nil" surface if any error occurred. A nil
 * surface can be checked for with cairo_surface_status(surface) which
 * may return one of the following values:
 *
 *	%CAIRO_STATUS_NO_MEMORY
 *	%CAIRO_STATUS_FILE_NOT_FOUND
 *	%CAIRO_STATUS_READ_ERROR
 *
 * Alternatively, you can allow errors to propagate through the drawing
 * operations and check the status on the context upon completion
 * using cairo_status().
 **/
cairo_surface_t *
cairo_image_surface_create_from_png (const char *filename)
{
    struct png_read_closure_t png_closure;
    cairo_surface_t *surface;

    png_closure.closure = fopen (filename, "rb");
    if (png_closure.closure == NULL) {
	cairo_status_t status;
	switch (errno) {
	case ENOMEM:
	    status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	    break;
	case ENOENT:
	    status = _cairo_error (CAIRO_STATUS_FILE_NOT_FOUND);
	    break;
	default:
	    status = _cairo_error (CAIRO_STATUS_READ_ERROR);
	    break;
	}
	return _cairo_surface_create_in_error (status);
    }

    png_closure.read_func = stdio_read_func;

    surface = read_png (&png_closure);

    fclose (png_closure.closure);

    return surface;
}

/**
 * cairo_image_surface_create_from_png_stream:
 * @read_func: function called to read the data of the file
 * @closure: data to pass to @read_func.
 *
 * Creates a new image surface from PNG data read incrementally
 * via the @read_func function.
 *
 * Return value: a new #cairo_surface_t initialized with the contents
 * of the PNG file or a "nil" surface if the data read is not a valid PNG image
 * or memory could not be allocated for the operation.  A nil
 * surface can be checked for with cairo_surface_status(surface) which
 * may return one of the following values:
 *
 *	%CAIRO_STATUS_NO_MEMORY
 *	%CAIRO_STATUS_READ_ERROR
 *
 * Alternatively, you can allow errors to propagate through the drawing
 * operations and check the status on the context upon completion
 * using cairo_status().
 **/
cairo_surface_t *
cairo_image_surface_create_from_png_stream (cairo_read_func_t	read_func,
					    void		*closure)
{
    struct png_read_closure_t png_closure;

    png_closure.read_func = read_func;
    png_closure.closure = closure;

    return read_png (&png_closure);
}
