/*
 * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * You can also choose to distribute this program under the terms of
 * the Unmodified Binary Distribution Licence (as given in the file
 * COPYING.UBDL), provided that you have satisfied its requirements.
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/umalloc.h>
#include <ipxe/pixbuf.h>
#include <ipxe/deflate.h>
#include <ipxe/png.h>

/** @file
 *
 * Portable Network Graphics (PNG) format
 *
 * The PNG format is defined in RFC 2083.
 */

/** PNG context */
struct png_context {
	/** Offset within image */
	size_t offset;

	/** Pixel buffer */
	struct pixel_buffer *pixbuf;

	/** Bit depth */
	unsigned int depth;
	/** Colour type */
	unsigned int colour_type;
	/** Number of channels */
	unsigned int channels;
	/** Number of interlace passes */
	unsigned int passes;
	/** Palette, in iPXE's pixel buffer format */
	uint32_t palette[PNG_PALETTE_COUNT];

	/** Decompression buffer for raw PNG data */
	struct deflate_chunk raw;
	/** Decompressor */
	struct deflate deflate;
};

/** A PNG interlace pass */
struct png_interlace {
	/** Pass number */
	unsigned int pass;
	/** X starting indent */
	unsigned int x_indent;
	/** Y starting indent */
	unsigned int y_indent;
	/** X stride */
	unsigned int x_stride;
	/** Y stride */
	unsigned int y_stride;
	/** Width */
	unsigned int width;
	/** Height */
	unsigned int height;
};

/** PNG file signature */
static struct png_signature png_signature = PNG_SIGNATURE;

/** Number of interlacing passes */
static uint8_t png_interlace_passes[] = {
	[PNG_INTERLACE_NONE] = 1,
	[PNG_INTERLACE_ADAM7] = 7,
};

/**
 * Transcribe PNG chunk type name (for debugging)
 *
 * @v type		Chunk type
 * @ret name		Chunk type name
 */
static const char * png_type_name ( uint32_t type ) {
	static union {
		uint32_t type;
		char name[ sizeof ( uint32_t ) + 1 /* NUL */ ];
	} u;

	u.type = type;
	return u.name;
}

/**
 * Calculate PNG interlace pass parameters
 *
 * @v png		PNG context
 * @v pass		Pass number (0=first pass)
 * @v interlace		Interlace pass to fill in
 */
static void png_interlace ( struct png_context *png, unsigned int pass,
			    struct png_interlace *interlace ) {
	unsigned int grid_width_log2;
	unsigned int grid_height_log2;
	unsigned int x_indent;
	unsigned int y_indent;
	unsigned int x_stride_log2;
	unsigned int y_stride_log2;
	unsigned int x_stride;
	unsigned int y_stride;
	unsigned int width;
	unsigned int height;

	/* Sanity check */
	assert ( png->passes > 0 );

	/* Store pass number */
	interlace->pass = pass;

	/* Calculate interlace grid dimensions */
	grid_width_log2 = ( png->passes / 2 );
	grid_height_log2 = ( ( png->passes - 1 ) / 2 );

	/* Calculate starting indents */
	interlace->x_indent = x_indent =
		( ( pass & 1 ) ?
		  ( 1 << ( grid_width_log2 - ( pass / 2 ) - 1 ) ) : 0 );
	interlace->y_indent = y_indent =
		( ( pass && ! ( pass & 1 ) ) ?
		  ( 1 << ( grid_height_log2 - ( ( pass - 1 ) / 2 ) - 1 ) ) : 0);

	/* Calculate strides */
	x_stride_log2 = ( grid_width_log2 - ( pass / 2 ) );
	y_stride_log2 =
		( grid_height_log2 - ( pass ? ( ( pass - 1 ) / 2 ) : 0 ) );
	interlace->x_stride = x_stride = ( 1 << x_stride_log2 );
	interlace->y_stride = y_stride = ( 1 << y_stride_log2 );

	/* Calculate pass dimensions */
	width = png->pixbuf->width;
	height = png->pixbuf->height;
	interlace->width =
		( ( width - x_indent + x_stride - 1 ) >> x_stride_log2 );
	interlace->height =
		( ( height - y_indent + y_stride - 1 ) >> y_stride_log2 );
}

/**
 * Calculate PNG pixel length
 *
 * @v png		PNG context
 * @ret pixel_len	Pixel length
 */
static unsigned int png_pixel_len ( struct png_context *png ) {

	return ( ( ( png->channels * png->depth ) + 7 ) / 8 );
}

/**
 * Calculate PNG scanline length
 *
 * @v png		PNG context
 * @v interlace		Interlace pass
 * @ret scanline_len	Scanline length (including filter byte)
 */
static size_t png_scanline_len ( struct png_context *png,
				 struct png_interlace *interlace ) {

	return ( 1 /* Filter byte */ +
		 ( ( interlace->width * png->channels * png->depth ) + 7 ) / 8);
}

/**
 * Handle PNG image header chunk
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v len		Chunk length
 * @ret rc		Return status code
 */
static int png_image_header ( struct image *image, struct png_context *png,
			      size_t len ) {
	struct png_image_header ihdr;
	struct png_interlace interlace;
	unsigned int pass;

	/* Sanity check */
	if ( len != sizeof ( ihdr ) ) {
		DBGC ( image, "PNG %s invalid IHDR length %zd\n",
		       image->name, len );
		return -EINVAL;
	}
	if ( png->pixbuf ) {
		DBGC ( image, "PNG %s duplicate IHDR\n", image->name );
		return -EINVAL;
	}

	/* Extract image header */
	copy_from_user ( &ihdr, image->data, png->offset, len );
	DBGC ( image, "PNG %s %dx%d depth %d type %d compression %d filter %d "
	       "interlace %d\n", image->name, ntohl ( ihdr.width ),
	       ntohl ( ihdr.height ), ihdr.depth, ihdr.colour_type,
	       ihdr.compression, ihdr.filter, ihdr.interlace );

	/* Sanity checks */
	if ( ihdr.compression >= PNG_COMPRESSION_UNKNOWN ) {
		DBGC ( image, "PNG %s unknown compression method %d\n",
		       image->name, ihdr.compression );
		return -ENOTSUP;
	}
	if ( ihdr.filter >= PNG_FILTER_UNKNOWN ) {
		DBGC ( image, "PNG %s unknown filter method %d\n",
		       image->name, ihdr.filter );
		return -ENOTSUP;
	}
	if ( ihdr.interlace >= PNG_INTERLACE_UNKNOWN ) {
		DBGC ( image, "PNG %s unknown interlace method %d\n",
		       image->name, ihdr.interlace );
		return -ENOTSUP;
	}

	/* Allocate pixel buffer */
	png->pixbuf = alloc_pixbuf ( ntohl ( ihdr.width ),
				     ntohl ( ihdr.height ) );
	if ( ! png->pixbuf ) {
		DBGC ( image, "PNG %s could not allocate pixel buffer\n",
		       image->name );
		return -ENOMEM;
	}

	/* Extract bit depth */
	png->depth = ihdr.depth;
	if ( ( png->depth == 0 ) ||
	     ( ( png->depth & ( png->depth - 1 ) ) != 0 ) ) {
		DBGC ( image, "PNG %s invalid depth %d\n",
		       image->name, png->depth );
		return -EINVAL;
	}

	/* Calculate number of channels */
	png->colour_type = ihdr.colour_type;
	png->channels = 1;
	if ( ! ( ihdr.colour_type & PNG_COLOUR_TYPE_PALETTE ) ) {
		if ( ihdr.colour_type & PNG_COLOUR_TYPE_RGB )
			png->channels += 2;
		if ( ihdr.colour_type & PNG_COLOUR_TYPE_ALPHA )
			png->channels += 1;
	}

	/* Calculate number of interlace passes */
	png->passes = png_interlace_passes[ihdr.interlace];

	/* Calculate length of raw data buffer */
	for ( pass = 0 ; pass < png->passes ; pass++ ) {
		png_interlace ( png, pass, &interlace );
		if ( interlace.width == 0 )
			continue;
		png->raw.len += ( interlace.height *
				  png_scanline_len ( png, &interlace ) );
	}

	/* Allocate raw data buffer */
	png->raw.data = umalloc ( png->raw.len );
	if ( ! png->raw.data ) {
		DBGC ( image, "PNG %s could not allocate data buffer\n",
		       image->name );
		return -ENOMEM;
	}

	return 0;
}

/**
 * Handle PNG palette chunk
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v len		Chunk length
 * @ret rc		Return status code
 */
static int png_palette ( struct image *image, struct png_context *png,
			 size_t len ) {
	size_t offset = png->offset;
	struct png_palette_entry palette;
	unsigned int i;

	/* Populate palette */
	for ( i = 0 ; i < ( sizeof ( png->palette ) /
			    sizeof ( png->palette[0] ) ) ; i++ ) {

		/* Stop when we run out of palette data */
		if ( len < sizeof ( palette ) )
			break;

		/* Extract palette entry */
		copy_from_user ( &palette, image->data, offset,
				 sizeof ( palette ) );
		png->palette[i] = ( ( palette.red << 16 ) |
				    ( palette.green << 8 ) |
				    ( palette.blue << 0 ) );
		DBGC2 ( image, "PNG %s palette entry %d is %#06x\n",
			image->name, i, png->palette[i] );

		/* Move to next entry */
		offset += sizeof ( palette );
		len -= sizeof ( palette );
	}

	return 0;
}

/**
 * Handle PNG image data chunk
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v len		Chunk length
 * @ret rc		Return status code
 */
static int png_image_data ( struct image *image, struct png_context *png,
			    size_t len ) {
	struct deflate_chunk in;
	int rc;

	/* Deflate this chunk */
	deflate_chunk_init ( &in, image->data, png->offset,
			     ( png->offset + len ) );
	if ( ( rc = deflate_inflate ( &png->deflate, &in, &png->raw ) ) != 0 ) {
		DBGC ( image, "PNG %s could not decompress: %s\n",
		       image->name, strerror ( rc ) );
		return rc;
	}

	return 0;
}

/**
 * Unfilter byte using the "None" filter
 *
 * @v current		Filtered current byte
 * @v left		Unfiltered left byte
 * @v above		Unfiltered above byte
 * @v above_left	Unfiltered above-left byte
 * @ret current		Unfiltered current byte
 */
static unsigned int png_unfilter_none ( unsigned int current,
					unsigned int left __unused,
					unsigned int above __unused,
					unsigned int above_left __unused ) {

	return current;
}

/**
 * Unfilter byte using the "Sub" filter
 *
 * @v current		Filtered current byte
 * @v left		Unfiltered left byte
 * @v above		Unfiltered above byte
 * @v above_left	Unfiltered above-left byte
 * @ret current		Unfiltered current byte
 */
static unsigned int png_unfilter_sub ( unsigned int current,
				       unsigned int left,
				       unsigned int above __unused,
				       unsigned int above_left __unused ) {

	return ( current + left );
}

/**
 * Unfilter byte using the "Up" filter
 *
 * @v current		Filtered current byte
 * @v left		Unfiltered left byte
 * @v above		Unfiltered above byte
 * @v above_left	Unfiltered above-left byte
 * @ret current		Unfiltered current byte
 */
static unsigned int png_unfilter_up ( unsigned int current,
				      unsigned int left __unused,
				      unsigned int above,
				      unsigned int above_left __unused ) {

	return ( current + above );
}

/**
 * Unfilter byte using the "Average" filter
 *
 * @v current		Filtered current byte
 * @v left		Unfiltered left byte
 * @v above		Unfiltered above byte
 * @v above_left	Unfiltered above-left byte
 * @ret current		Unfiltered current byte
 */
static unsigned int png_unfilter_average ( unsigned int current,
					   unsigned int left,
					   unsigned int above,
					   unsigned int above_left __unused ) {

	return ( current + ( ( above + left ) >> 1 ) );
}

/**
 * Paeth predictor function (defined in RFC 2083)
 *
 * @v a			Pixel A
 * @v b			Pixel B
 * @v c			Pixel C
 * @ret predictor	Predictor pixel
 */
static unsigned int png_paeth_predictor ( unsigned int a, unsigned int b,
					  unsigned int c ) {
	unsigned int p;
	unsigned int pa;
	unsigned int pb;
	unsigned int pc;

	/* Algorithm as defined in RFC 2083 section 6.6 */
	p = ( a + b - c );
	pa = abs ( p - a );
	pb = abs ( p - b );
	pc = abs ( p - c );
	if ( ( pa <= pb ) && ( pa <= pc ) ) {
		return a;
	} else if ( pb <= pc ) {
		return b;
	} else {
		return c;
	}
}

/**
 * Unfilter byte using the "Paeth" filter
 *
 * @v current		Filtered current byte
 * @v above_left	Unfiltered above-left byte
 * @v above		Unfiltered above byte
 * @v left		Unfiltered left byte
 * @ret current		Unfiltered current byte
 */
static unsigned int png_unfilter_paeth ( unsigned int current,
					 unsigned int left,
					 unsigned int above,
					 unsigned int above_left ) {

	return ( current + png_paeth_predictor ( left, above, above_left ) );
}

/** A PNG filter */
struct png_filter {
	/**
	 * Unfilter byte
	 *
	 * @v current		Filtered current byte
	 * @v left		Unfiltered left byte
	 * @v above		Unfiltered above byte
	 * @v above_left	Unfiltered above-left byte
	 * @ret current		Unfiltered current byte
	 */
	unsigned int ( * unfilter ) ( unsigned int current,
				      unsigned int left,
				      unsigned int above,
				      unsigned int above_left );
};

/** PNG filter types */
static struct png_filter png_filters[] = {
	[PNG_FILTER_BASIC_NONE] = { png_unfilter_none },
	[PNG_FILTER_BASIC_SUB] = { png_unfilter_sub },
	[PNG_FILTER_BASIC_UP] = { png_unfilter_up },
	[PNG_FILTER_BASIC_AVERAGE] = { png_unfilter_average },
	[PNG_FILTER_BASIC_PAETH] = { png_unfilter_paeth },
};

/**
 * Unfilter one interlace pass of PNG raw data
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v interlace		Interlace pass
 * @ret rc		Return status code
 *
 * This routine may assume that it is impossible to overrun the raw
 * data buffer, since the size is determined by the image dimensions.
 */
static int png_unfilter_pass ( struct image *image, struct png_context *png,
			       struct png_interlace *interlace ) {
	size_t offset = png->raw.offset;
	size_t pixel_len = png_pixel_len ( png );
	size_t scanline_len = png_scanline_len ( png, interlace );
	struct png_filter *filter;
	unsigned int scanline;
	unsigned int byte;
	uint8_t filter_type;
	uint8_t left;
	uint8_t above;
	uint8_t above_left;
	uint8_t current;

	/* On the first scanline of a pass, above bytes are assumed to
	 * be zero.
	 */
	above = 0;

	/* Iterate over each scanline in turn */
	for ( scanline = 0 ; scanline < interlace->height ; scanline++ ) {

		/* Extract filter byte and determine filter type */
		copy_from_user ( &filter_type, png->raw.data, offset++,
				 sizeof ( filter_type ) );
		if ( filter_type >= ( sizeof ( png_filters ) /
				      sizeof ( png_filters[0] ) ) ) {
			DBGC ( image, "PNG %s unknown filter type %d\n",
			       image->name, filter_type );
			return -ENOTSUP;
		}
		filter = &png_filters[filter_type];
		assert ( filter->unfilter != NULL );
		DBGC2 ( image, "PNG %s pass %d scanline %d filter type %d\n",
			image->name, interlace->pass, scanline, filter_type );

		/* At the start of a line, both above-left and left
		 * bytes are taken to be zero.
		 */
		left = 0;
		above_left = 0;

		/* Iterate over each byte (not pixel) in turn */
		for ( byte = 0 ; byte < ( scanline_len - 1 ) ; byte++ ) {

			/* Extract predictor bytes, if applicable */
			if ( byte >= pixel_len ) {
				copy_from_user ( &left, png->raw.data,
						 ( offset - pixel_len ),
						 sizeof ( left ) );
			}
			if ( scanline > 0 ) {
				copy_from_user ( &above, png->raw.data,
						 ( offset - scanline_len ),
						 sizeof ( above ) );
			}
			if ( ( scanline > 0 ) && ( byte >= pixel_len ) ) {
				copy_from_user ( &above_left, png->raw.data,
						 ( offset - scanline_len -
						   pixel_len ),
						 sizeof ( above_left ) );
			}

			/* Unfilter current byte */
			copy_from_user ( &current, png->raw.data,
					 offset, sizeof ( current ) );
			current = filter->unfilter ( current, left, above,
						     above_left );
			copy_to_user ( png->raw.data, offset++,
				       &current, sizeof ( current ) );
		}
	}

	/* Update offset */
	png->raw.offset = offset;

	return 0;
}

/**
 * Unfilter PNG raw data
 *
 * @v image		PNG image
 * @v png		PNG context
 * @ret rc		Return status code
 *
 * This routine may assume that it is impossible to overrun the raw
 * data buffer, since the size is determined by the image dimensions.
 */
static int png_unfilter ( struct image *image, struct png_context *png ) {
	struct png_interlace interlace;
	unsigned int pass;
	int rc;

	/* Process each interlace pass */
	png->raw.offset = 0;
	for ( pass = 0 ; pass < png->passes ; pass++ ) {

		/* Calculate interlace pass parameters */
		png_interlace ( png, pass, &interlace );

		/* Skip zero-width rows (which have no filter bytes) */
		if ( interlace.width == 0 )
			continue;

		/* Unfilter this pass */
		if ( ( rc = png_unfilter_pass ( image, png,
						&interlace ) ) != 0 )
			return rc;
	}
	assert ( png->raw.offset == png->raw.len );

	return 0;
}

/**
 * Calculate PNG pixel component value
 *
 * @v raw		Raw component value
 * @v alpha		Alpha value
 * @v max		Maximum raw/alpha value
 * @ret value		Component value in range 0-255
 */
static inline unsigned int png_pixel ( unsigned int raw, unsigned int alpha,
				       unsigned int max ) {

	/* The basic calculation is 255*(raw/max)*(value/max).  We use
	 * fixed-point arithmetic (scaling up to the maximum range for
	 * a 32-bit integer), in order to get the same results for
	 * alpha blending as the test cases (produced using
	 * ImageMagick).
	 */
	return ( ( ( ( ( 0xff00 * raw * alpha ) / max ) / max ) + 0x80 ) >> 8 );
}

/**
 * Fill one interlace pass of PNG pixels
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v interlace		Interlace pass
 *
 * This routine may assume that it is impossible to overrun either the
 * raw data buffer or the pixel buffer, since the sizes of both are
 * determined by the image dimensions.
 */
static void png_pixels_pass ( struct image *image,
			      struct png_context *png,
			      struct png_interlace *interlace ) {
	size_t raw_offset = png->raw.offset;
	uint8_t channel[png->channels];
	int is_indexed = ( png->colour_type & PNG_COLOUR_TYPE_PALETTE );
	int is_rgb = ( png->colour_type & PNG_COLOUR_TYPE_RGB );
	int has_alpha = ( png->colour_type & PNG_COLOUR_TYPE_ALPHA );
	size_t pixbuf_y_offset;
	size_t pixbuf_offset;
	size_t pixbuf_x_stride;
	size_t pixbuf_y_stride;
	size_t raw_stride;
	unsigned int y;
	unsigned int x;
	unsigned int c;
	unsigned int bits;
	unsigned int depth;
	unsigned int max;
	unsigned int alpha;
	unsigned int raw;
	unsigned int value;
	uint8_t current = 0;
	uint32_t pixel;

	/* We only ever use the top byte of 16-bit pixels.  Model this
	 * as a bit depth of 8 with a stride of more than one.
	 */
	depth = png->depth;
	raw_stride = ( ( depth + 7 ) / 8 );
	if ( depth > 8 )
		depth = 8;
	max = ( ( 1 << depth ) - 1 );

	/* Calculate pixel buffer offset and strides */
	pixbuf_y_offset = ( ( ( interlace->y_indent * png->pixbuf->width ) +
			      interlace->x_indent ) * sizeof ( pixel ) );
	pixbuf_x_stride = ( interlace->x_stride * sizeof ( pixel ) );
	pixbuf_y_stride = ( interlace->y_stride * png->pixbuf->width *
			    sizeof ( pixel ) );
	DBGC2 ( image, "PNG %s pass %d %dx%d at (%d,%d) stride (%d,%d)\n",
		image->name, interlace->pass, interlace->width,
		interlace->height, interlace->x_indent, interlace->y_indent,
		interlace->x_stride, interlace->y_stride );

	/* Iterate over each scanline in turn */
	for ( y = 0 ; y < interlace->height ; y++ ) {

		/* Skip filter byte */
		raw_offset++;

		/* Iterate over each pixel in turn */
		bits = depth;
		pixbuf_offset = pixbuf_y_offset;
		for ( x = 0 ; x < interlace->width ; x++ ) {

			/* Extract sample value */
			for ( c = 0 ; c < png->channels ; c++ ) {

				/* Get sample value into high bits of current */
				current <<= depth;
				bits -= depth;
				if ( ! bits ) {
					copy_from_user ( &current,
							 png->raw.data,
							 raw_offset,
							 sizeof ( current ) );
					raw_offset += raw_stride;
					bits = 8;
				}

				/* Extract sample value */
				channel[c] = ( current >> ( 8 - depth ) );
			}

			/* Convert to native pixel format */
			if ( is_indexed ) {

				/* Indexed */
				pixel = png->palette[channel[0]];

			} else {

				/* Determine alpha value */
				alpha = ( has_alpha ?
					  channel[ png->channels - 1 ] : max );

				/* Convert to RGB value */
				pixel = 0;
				for ( c = 0 ; c < 3 ; c++ ) {
					raw = channel[ is_rgb ? c : 0 ];
					value = png_pixel ( raw, alpha, max );
					assert ( value <= 255 );
					pixel = ( ( pixel << 8 ) | value );
				}
			}

			/* Store pixel */
			copy_to_user ( png->pixbuf->data, pixbuf_offset,
				       &pixel, sizeof ( pixel ) );
			pixbuf_offset += pixbuf_x_stride;
		}

		/* Move to next output row */
		pixbuf_y_offset += pixbuf_y_stride;
	}

	/* Update offset */
	png->raw.offset = raw_offset;
}

/**
 * Fill PNG pixels
 *
 * @v image		PNG image
 * @v png		PNG context
 *
 * This routine may assume that it is impossible to overrun either the
 * raw data buffer or the pixel buffer, since the sizes of both are
 * determined by the image dimensions.
 */
static void png_pixels ( struct image *image, struct png_context *png ) {
	struct png_interlace interlace;
	unsigned int pass;

	/* Process each interlace pass */
	png->raw.offset = 0;
	for ( pass = 0 ; pass < png->passes ; pass++ ) {

		/* Calculate interlace pass parameters */
		png_interlace ( png, pass, &interlace );

		/* Skip zero-width rows (which have no filter bytes) */
		if ( interlace.width == 0 )
			continue;

		/* Unfilter this pass */
		png_pixels_pass ( image, png, &interlace );
	}
	assert ( png->raw.offset == png->raw.len );
}

/**
 * Handle PNG image end chunk
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v len		Chunk length
 * @ret rc		Return status code
 */
static int png_image_end ( struct image *image, struct png_context *png,
			   size_t len ) {
	int rc;

	/* Sanity checks */
	if ( len != 0 ) {
		DBGC ( image, "PNG %s invalid IEND length %zd\n",
		       image->name, len );
		return -EINVAL;
	}
	if ( ! png->pixbuf ) {
		DBGC ( image, "PNG %s missing pixel buffer (no IHDR?)\n",
		       image->name );
		return -EINVAL;
	}
	if ( ! deflate_finished ( &png->deflate ) ) {
		DBGC ( image, "PNG %s decompression not complete\n",
		       image->name );
		return -EINVAL;
	}
	if ( png->raw.offset != png->raw.len ) {
		DBGC ( image, "PNG %s incorrect decompressed length (expected "
		       "%zd, got %zd)\n", image->name, png->raw.len,
		       png->raw.offset );
		return -EINVAL;
	}

	/* Unfilter raw data */
	if ( ( rc = png_unfilter ( image, png ) ) != 0 )
		return rc;

	/* Fill pixel buffer */
	png_pixels ( image, png );

	return 0;
}

/** A PNG chunk handler */
struct png_chunk_handler {
	/** Chunk type */
	uint32_t type;
	/**
	 * Handle chunk
	 *
	 * @v image		PNG image
	 * @v png		PNG context
	 * @v len		Chunk length
	 * @ret rc		Return status code
	 */
	int ( * handle ) ( struct image *image, struct png_context *png,
			   size_t len );
};

/** PNG chunk handlers */
static struct png_chunk_handler png_chunk_handlers[] = {
	{ htonl ( PNG_TYPE_IHDR ), png_image_header },
	{ htonl ( PNG_TYPE_PLTE ), png_palette },
	{ htonl ( PNG_TYPE_IDAT ), png_image_data },
	{ htonl ( PNG_TYPE_IEND ), png_image_end },
};

/**
 * Handle PNG chunk
 *
 * @v image		PNG image
 * @v png		PNG context
 * @v type		Chunk type
 * @v len		Chunk length
 * @ret rc		Return status code
 */
static int png_chunk ( struct image *image, struct png_context *png,
		       uint32_t type, size_t len ) {
	struct png_chunk_handler *handler;
	unsigned int i;

	DBGC ( image, "PNG %s chunk type %s offset %zd length %zd\n",
	       image->name, png_type_name ( type ), png->offset, len );

	/* Handle according to chunk type */
	for ( i = 0 ; i < ( sizeof ( png_chunk_handlers ) /
			    sizeof ( png_chunk_handlers[0] ) ) ; i++ ) {
		handler = &png_chunk_handlers[i];
		if ( handler->type == type )
			return handler->handle ( image, png, len );
	}

	/* Fail if unknown chunk type is critical */
	if ( ! ( type & htonl ( PNG_CHUNK_ANCILLARY ) ) ) {
		DBGC ( image, "PNG %s unknown critical chunk type %s\n",
		       image->name, png_type_name ( type ) );
		return -ENOTSUP;
	}

	/* Ignore non-critical unknown chunk types */
	return 0;
}

/**
 * Convert PNG image to pixel buffer
 *
 * @v image		PNG image
 * @v pixbuf		Pixel buffer to fill in
 * @ret rc		Return status code
 */
static int png_pixbuf ( struct image *image, struct pixel_buffer **pixbuf ) {
	struct png_context *png;
	struct png_chunk_header header;
	struct png_chunk_footer footer;
	size_t remaining;
	size_t chunk_len;
	int rc;

	/* Allocate and initialise context */
	png = zalloc ( sizeof ( *png ) );
	if ( ! png ) {
		rc = -ENOMEM;
		goto err_alloc;
	}
	png->offset = sizeof ( struct png_signature );
	deflate_init ( &png->deflate, DEFLATE_ZLIB );

	/* Process chunks */
	do {

		/* Extract chunk header */
		remaining = ( image->len - png->offset );
		if ( remaining < ( sizeof ( header ) + sizeof ( footer ) ) ) {
			DBGC ( image, "PNG %s truncated chunk header/footer "
			       "at offset %zd\n", image->name, png->offset );
			rc = -EINVAL;
			goto err_truncated;
		}
		copy_from_user ( &header, image->data, png->offset,
				 sizeof ( header ) );
		png->offset += sizeof ( header );

		/* Validate chunk length */
		chunk_len = ntohl ( header.len );
		if ( chunk_len > ( remaining - sizeof ( header ) -
				   sizeof ( footer ) ) ) {
			DBGC ( image, "PNG %s truncated chunk data at offset "
			       "%zd\n", image->name, png->offset );
			rc = -EINVAL;
			goto err_truncated;
		}

		/* Handle chunk */
		if ( ( rc = png_chunk ( image, png, header.type,
					chunk_len ) ) != 0 )
			goto err_chunk;

		/* Move to next chunk */
		png->offset += ( chunk_len + sizeof ( footer ) );

	} while ( png->offset < image->len );

	/* Check that we finished with an IEND chunk */
	if ( header.type != htonl ( PNG_TYPE_IEND ) ) {
		DBGC ( image, "PNG %s did not finish with IEND\n",
		       image->name );
		rc = -EINVAL;
		goto err_iend;
	}

	/* Return pixel buffer */
	*pixbuf = pixbuf_get ( png->pixbuf );

	/* Success */
	rc = 0;

 err_iend:
 err_chunk:
 err_truncated:
	pixbuf_put ( png->pixbuf );
	ufree ( png->raw.data );
	free ( png );
 err_alloc:
	return rc;
}

/**
 * Probe PNG image
 *
 * @v image		PNG image
 * @ret rc		Return status code
 */
static int png_probe ( struct image *image ) {
	struct png_signature signature;

	/* Sanity check */
	if ( image->len < sizeof ( signature ) ) {
		DBGC ( image, "PNG %s is too short\n", image->name );
		return -ENOEXEC;
	}

	/* Check signature */
	copy_from_user ( &signature, image->data, 0, sizeof ( signature ) );
	if ( memcmp ( &signature, &png_signature, sizeof ( signature ) ) != 0 ){
		DBGC ( image, "PNG %s has invalid signature\n", image->name );
		return -ENOEXEC;
	}

	return 0;
}

/** PNG image type */
struct image_type png_image_type __image_type ( PROBE_NORMAL ) = {
	.name = "PNG",
	.probe = png_probe,
	.pixbuf = png_pixbuf,
};
