Add qemu 2.4.0
[kvmfornfv.git] / qemu / roms / ipxe / src / image / png.c
diff --git a/qemu/roms/ipxe/src/image/png.c b/qemu/roms/ipxe/src/image/png.c
new file mode 100644 (file)
index 0000000..c146085
--- /dev/null
@@ -0,0 +1,1007 @@
+/*
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#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 ) ) {
+                       DBGC ( image, "PNG %s truncated chunk header 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 ( remaining < ( sizeof ( header ) + chunk_len +
+                                  sizeof ( footer ) ) ) {
+                       DBGC ( image, "PNG %s truncated chunk data/footer 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,
+};