mirror of https://github.com/ipxe/ipxe.git
1012 lines
26 KiB
C
1012 lines
26 KiB
C
/*
|
|
* 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 ( ¤t, png->raw.data,
|
|
offset, sizeof ( current ) );
|
|
current = filter->unfilter ( current, left, above,
|
|
above_left );
|
|
copy_to_user ( png->raw.data, offset++,
|
|
¤t, 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 ( ¤t,
|
|
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,
|
|
};
|