add minimal support for reading Adam7 interlaced PNG

(nw) This is the path of least resistance, and I plan to fix it up
later, I just wanted to get it to actually work first.  Decompression
and unfiltering is fully supported, at least for all the pixel formats
that previously worked.  Expanding 1/2/4bpp to 8bpp should work
properly, too.  Bitmap mapping for Adam7 is only implemented in
rendutil.cpp which is whate everything in MAME uses.  The function in
png.cpp (used by pngcmp) has not been updated.  At some point I'll unify
at least one of the functions in rendutil.cpp with the one in png.cpp
and we can go from three functions that need to do the mapping down to
two at the most.
This commit is contained in:
Vas Crabb 2017-08-12 02:43:53 +10:00
parent ac6762603c
commit a7b15fc928
3 changed files with 544 additions and 482 deletions

View File

@ -655,7 +655,7 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname,
png_free(&png); png_free(&png);
return false; return false;
} }
if (png.interlace_method != 0) if (png.interlace_method != 0 && png.interlace_method != 1)
{ {
osd_printf_error("%s: Interlace unsupported\n", filename); osd_printf_error("%s: Interlace unsupported\n", filename);
png_free(&png); png_free(&png);
@ -696,59 +696,86 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname,
static bool copy_png_to_bitmap(bitmap_argb32 &bitmap, const png_info *png) static bool copy_png_to_bitmap(bitmap_argb32 &bitmap, const png_info *png)
{ {
// FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it
u8 accumalpha = 0xff; u8 accumalpha = 0xff;
u8 *src;
int x, y;
/* handle 8bpp palettized case */ // colour format table
static constexpr unsigned samples[] = { 1, 0, 3, 1, 2, 0, 4 };
// adam7 interlace tables
static constexpr unsigned x_bias[7] = { 7, 3, 3, 1, 1, 0, 0 };
static constexpr unsigned y_bias[7] = { 7, 7, 3, 3, 1, 1, 0 };
static constexpr unsigned x_shift[7] = { 3, 3, 2, 2, 1, 1, 0 };
static constexpr unsigned y_shift[7] = { 3, 3, 3, 2, 2, 1, 1 };
unsigned const pass_count(png->interlace_method ? 7 : 1);
u32 pass_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
for (unsigned pass = 0; pass_count > pass; ++pass)
{
// calculate offset for next interlace pass
u32 const width(png->interlace_method ? ((png->width + x_bias[pass]) >> x_shift[pass]) : png->width);
u32 const height(png->interlace_method ? ((png->height + y_bias[pass]) >> y_shift[pass]) : png->height);
u32 const rowbytes(((width * samples[png->color_type] * png->bit_depth) + 7) >> 3);
pass_offset[pass + 1] = pass_offset[pass] + (height * (rowbytes + 1));
u8 const *src(png->image + pass_offset[pass]);
auto const x_trans = [offs = (1 << x_shift[pass]) - x_bias[pass] - 1, shift = x_shift[pass]] (u32 x) { return (x << shift) + offs; };
auto const y_trans = [offs = (1 << y_shift[pass]) - y_bias[pass] - 1, shift = y_shift[pass]] (u32 y) { return (y << shift) + offs; };
if (png->color_type == 3) if (png->color_type == 3)
{ {
/* loop over width/height */ // handle 8bpp palettized case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++)
for (x = 0; x < png->width; x++, src++)
{ {
/* determine alpha and expand to 32bpp */ for (u32 x = 0; width > x; ++x, ++src)
{
// determine alpha and expand to 32bpp
u8 alpha = (*src < png->num_trans) ? png->trans[*src] : 0xff; u8 alpha = (*src < png->num_trans) ? png->trans[*src] : 0xff;
accumalpha &= alpha; accumalpha &= alpha;
bitmap.pix32(y, x) = rgb_t(alpha, png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]); rgb_t const pix(alpha, png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]);
(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x)) = pix;
}
} }
} }
/* handle 8bpp grayscale case */
else if (png->color_type == 0) else if (png->color_type == 0)
{ {
/* loop over width/height */ // handle 8bpp grayscale case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++) {
for (x = 0; x < png->width; x++, src++) for (u32 x = 0; width > x; ++x, ++src)
bitmap.pix32(y, x) = rgb_t(0xff, *src, *src, *src); {
rgb_t const pix(0xff, *src, *src, *src);
(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x)) = pix;
}
}
} }
/* handle 32bpp non-alpha case */
else if (png->color_type == 2) else if (png->color_type == 2)
{ {
/* loop over width/height */ // handle 32bpp non-alpha case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++) {
for (x = 0; x < png->width; x++, src += 3) for (u32 x = 0; width > x; ++x, src += 3)
bitmap.pix32(y, x) = rgb_t(0xff, src[0], src[1], src[2]); {
rgb_t const pix(0xff, src[0], src[1], src[2]);
(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x)) = pix;
}
}
} }
/* handle 32bpp alpha case */
else else
{ {
/* loop over width/height */ // handle 32bpp alpha case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++) {
for (x = 0; x < png->width; x++, src += 4) for (u32 x = 0; width > x; ++x, src += 4)
{ {
accumalpha &= src[3]; accumalpha &= src[3];
bitmap.pix32(y, x) = rgb_t(src[3], src[0], src[1], src[2]); rgb_t const pix(src[3], src[0], src[1], src[2]);
(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x)) = pix;
}
}
} }
} }
/* set the hasalpha flag */ // set the hasalpha flag
return (accumalpha != 0xff); return (accumalpha != 0xff);
} }
@ -760,69 +787,92 @@ static bool copy_png_to_bitmap(bitmap_argb32 &bitmap, const png_info *png)
static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const png_info *png) static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const png_info *png)
{ {
// FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it
u8 accumalpha = 0xff; u8 accumalpha = 0xff;
u8 *src;
int x, y;
/* handle 8bpp palettized case */ // colour format table
static constexpr unsigned samples[] = { 1, 0, 3, 1, 2, 0, 4 };
// adam7 interlace tables
static constexpr unsigned x_bias[7] = { 7, 3, 3, 1, 1, 0, 0 };
static constexpr unsigned y_bias[7] = { 7, 7, 3, 3, 1, 1, 0 };
static constexpr unsigned x_shift[7] = { 3, 3, 2, 2, 1, 1, 0 };
static constexpr unsigned y_shift[7] = { 3, 3, 3, 2, 2, 1, 1 };
unsigned const pass_count(png->interlace_method ? 7 : 1);
u32 pass_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
for (unsigned pass = 0; pass_count > pass; ++pass)
{
// calculate offset for next interlace pass
u32 const width(png->interlace_method ? ((png->width + x_bias[pass]) >> x_shift[pass]) : png->width);
u32 const height(png->interlace_method ? ((png->height + y_bias[pass]) >> y_shift[pass]) : png->height);
u32 const rowbytes(((width * samples[png->color_type] * png->bit_depth) + 7) >> 3);
pass_offset[pass + 1] = pass_offset[pass] + (height * (rowbytes + 1));
u8 const *src(png->image + pass_offset[pass]);
auto const x_trans = [offs = (1 << x_shift[pass]) - x_bias[pass] - 1, shift = x_shift[pass]] (u32 x) { return (x << shift) + offs; };
auto const y_trans = [offs = (1 << y_shift[pass]) - y_bias[pass] - 1, shift = y_shift[pass]] (u32 y) { return (y << shift) + offs; };
if (png->color_type == 3) if (png->color_type == 3)
{ {
/* loop over width/height */ // handle 8bpp palettized case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++)
for (x = 0; x < png->width; x++, src++)
{ {
rgb_t pixel = bitmap.pix32(y, x); for (u32 x = 0; width > x; ++x, ++src)
u8 alpha = rgb_t(png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]).brightness(); {
bitmap_argb32::pixel_t &dest(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x));
rgb_t const pixel(dest);
u8 const alpha(rgb_t(png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]).brightness());
accumalpha &= alpha; accumalpha &= alpha;
bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b());
}
} }
} }
/* handle 8bpp grayscale case */
else if (png->color_type == 0) else if (png->color_type == 0)
{ {
/* loop over width/height */ // handle 8bpp grayscale case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++)
for (x = 0; x < png->width; x++, src++)
{ {
rgb_t pixel = bitmap.pix32(y, x); for (u32 x = 0; width > x; ++x, ++src)
{
bitmap_argb32::pixel_t &dest(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x));
rgb_t const pixel(dest);
accumalpha &= *src; accumalpha &= *src;
bitmap.pix32(y, x) = rgb_t(*src, pixel.r(), pixel.g(), pixel.b()); dest = rgb_t(*src, pixel.r(), pixel.g(), pixel.b());
}
} }
} }
/* handle 32bpp non-alpha case */
else if (png->color_type == 2) else if (png->color_type == 2)
{ {
/* loop over width/height */ // handle 32bpp non-alpha case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++)
for (x = 0; x < png->width; x++, src += 3)
{ {
rgb_t pixel = bitmap.pix32(y, x); for (u32 x = 0; width > x; ++x, src += 3)
u8 alpha = rgb_t(src[0], src[1], src[2]).brightness(); {
bitmap_argb32::pixel_t &dest(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x));
rgb_t const pixel(dest);
u8 const alpha(rgb_t(src[0], src[1], src[2]).brightness());
accumalpha &= alpha; accumalpha &= alpha;
bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b());
}
} }
} }
/* handle 32bpp alpha case */
else else
{ {
/* loop over width/height */ // handle 32bpp alpha case
src = png->image; for (u32 y = 0; height > y; ++y)
for (y = 0; y < png->height; y++)
for (x = 0; x < png->width; x++, src += 4)
{ {
rgb_t pixel = bitmap.pix32(y, x); for (u32 x = 0; width > x; ++x, src += 4)
u8 alpha = rgb_t(src[0], src[1], src[2]).brightness(); {
bitmap_argb32::pixel_t &dest(png->interlace_method ? bitmap.pix32(y_trans(y), x_trans(x)) : bitmap.pix32(y, x));
rgb_t const pixel(dest);
u8 const alpha(rgb_t(src[0], src[1], src[2]).brightness());
accumalpha &= alpha; accumalpha &= alpha;
bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b());
}
}
} }
} }
/* set the hasalpha flag */ // set the hasalpha flag
return (accumalpha != 0xff); return (accumalpha != 0xff);
} }

View File

@ -2059,7 +2059,7 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f
game_driver const *driver; game_driver const *driver;
get_selection(software, driver); get_selection(software, driver);
if (software && ((software->startempty != 1) || !driver)) if (software && (!software->startempty || !driver))
{ {
m_cache->set_snapx_driver(nullptr); m_cache->set_snapx_driver(nullptr);

View File

@ -8,15 +8,20 @@
***************************************************************************/ ***************************************************************************/
#include "png.h"
#include <zlib.h>
#include <cstdint>
#include <list>
#include <memory>
#include <new>
#include <utility>
#include <math.h> #include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <zlib.h>
#include "png.h"
#include <new>
/*************************************************************************** /***************************************************************************
TYPE DEFINITIONS TYPE DEFINITIONS
@ -24,19 +29,10 @@
struct image_data_chunk struct image_data_chunk
{ {
image_data_chunk * next; image_data_chunk(std::uint32_t l, std::unique_ptr<std::uint8_t []> &&d) : length(l), data(std::move(d)) { }
int length;
uint8_t * data;
};
std::uint32_t length = 0;
struct png_private std::unique_ptr<std::uint8_t []> data;
{
png_info * pnginfo;
image_data_chunk * idata;
image_data_chunk ** idata_next;
uint8_t bpp;
uint32_t rowbytes;
}; };
@ -91,15 +87,9 @@ static inline void put_32bit(uint8_t *v, uint32_t data)
} }
static inline int compute_bpp(const png_info *pnginfo) static inline int compute_rowbytes(const png_info &pnginfo)
{ {
return samples[pnginfo->color_type] * pnginfo->bit_depth / 8; return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8;
}
static inline int compute_rowbytes(const png_info *pnginfo)
{
return (pnginfo->width * samples[pnginfo->color_type] * pnginfo->bit_depth + 7) / 8;
} }
@ -113,212 +103,303 @@ static inline int compute_rowbytes(const png_info *pnginfo)
pnginfo structure pnginfo structure
-------------------------------------------------*/ -------------------------------------------------*/
void png_free(png_info *pnginfo) static void png_free(png_info &pnginfo)
{ {
while (pnginfo->textlist != nullptr) while (pnginfo.textlist)
{ {
png_text *temp = pnginfo->textlist; png_text *const temp = pnginfo.textlist;
pnginfo->textlist = temp->next; pnginfo.textlist = temp->next;
if (temp->keyword != nullptr) if (temp->keyword)
free((void *)temp->keyword); delete[] (std::uint8_t *)temp->keyword;
free(temp); free(temp);
} }
if (pnginfo->palette != nullptr) if (pnginfo.palette)
free(pnginfo->palette); delete[] pnginfo.palette;
pnginfo->palette = nullptr; pnginfo.palette = nullptr;
if (pnginfo->trans != nullptr) if (pnginfo.trans)
free(pnginfo->trans); delete[] pnginfo.trans;
pnginfo->trans = nullptr; pnginfo.trans = nullptr;
if (pnginfo->image != nullptr) if (pnginfo.image)
free(pnginfo->image); delete[] pnginfo.image;
pnginfo->image = nullptr; pnginfo.image = nullptr;
} }
void png_free(png_info *pnginfo) { png_free(*pnginfo); } // TODO: make external interface use reference
/***************************************************************************
PNG READING FUNCTIONS
***************************************************************************/
/*------------------------------------------------- namespace {
verify_header - verify the PNG
header at the current file location
-------------------------------------------------*/
static png_error verify_header(util::core_file &fp) class png_private
{ {
uint8_t signature[8]; public:
png_private(png_info &info) : pnginfo(info)
{
}
/* read 8 bytes */ png_error expand_buffer_8bit()
if (fp.read(signature, 8) != 8) {
return PNGERR_FILE_TRUNCATED; /* nothing to do if we're at 8 or greater already */
if (pnginfo.bit_depth >= 8)
return PNGERR_NONE;
/* return an error if we don't match */ /* calculate the offset for each pass of the interlace on the input and output */
if (memcmp(signature, PNG_Signature, 8) != 0) unsigned const pass_count(get_pass_count());
return PNGERR_BAD_SIGNATURE; std::uint32_t inp_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
std::uint32_t outp_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
for (unsigned pass = 0; pass_count > pass; ++pass)
{
inp_offset[pass + 1] = inp_offset[pass] + get_pass_bytes(pass);
outp_offset[pass + 1] = outp_offset[pass] + get_pass_bytes(pass, 8);
}
/* allocate a new buffer at 8-bit */
std::unique_ptr<std::uint8_t []> outbuf;
try { outbuf.reset(new std::uint8_t [outp_offset[pass_count]]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
for (unsigned pass = 0; pass_count > pass; ++pass)
{
std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
std::uint8_t const *inp(&pnginfo.image[inp_offset[pass]]);
std::uint8_t *outp(&outbuf[outp_offset[pass]]);
for (std::uint32_t y = 0; dimensions.second > y; ++y)
{
for (std::uint32_t i = 0; (dimensions.first / (8 / pnginfo.bit_depth)); ++i, ++inp)
{
for (std::int8_t j = (8 / pnginfo.bit_depth) - 1; 0 <= j; --j)
*outp++ = (*inp >> (j * pnginfo.bit_depth)) & (0xffU >> (8 - pnginfo.bit_depth));
}
if (pnginfo.width % (8 / pnginfo.bit_depth))
{
for (std::int8_t j = (pnginfo.width % (8 / pnginfo.bit_depth)) - 1; 0 <= j; --j)
*outp++ = (*inp >> (j * pnginfo.bit_depth)) & (0xffU >> (8 - pnginfo.bit_depth));
inp++;
}
}
}
delete[] pnginfo.image;
pnginfo.image = outbuf.release();
pnginfo.bit_depth = 8;
return PNGERR_NONE; return PNGERR_NONE;
} }
png_error read_file(util::core_file &fp)
/*-------------------------------------------------
read_chunk - read the next PNG chunk
-------------------------------------------------*/
static png_error read_chunk(util::core_file &fp, uint8_t **data, uint32_t *type, uint32_t *length)
{ {
uint32_t crc, chunk_crc; /* initialize the data structures */
uint8_t tempbuff[4]; png_error error = PNGERR_NONE;
std::memset(&pnginfo, 0, sizeof(pnginfo));
/* fetch the length of this chunk */ /* verify the signature at the start of the file */
if (fp.read(tempbuff, 4) != 4) error = verify_header(fp);
return PNGERR_FILE_TRUNCATED; if (error != PNGERR_NONE)
*length = fetch_32bit(tempbuff); goto handle_error;
/* fetch the type of this chunk */ /* loop until we hit an IEND chunk */
if (fp.read(tempbuff, 4) != 4) for ( ; ; )
return PNGERR_FILE_TRUNCATED; {
*type = fetch_32bit(tempbuff); /* read a chunk */
std::unique_ptr<std::uint8_t []> chunk_data;
std::uint32_t chunk_type, chunk_length;
error = read_chunk(fp, chunk_data, chunk_type, chunk_length);
if (error != PNGERR_NONE)
goto handle_error;
/* stop when we hit an IEND chunk */ /* stop when we hit an IEND chunk */
if (*type == PNG_CN_IEND) if (chunk_type == PNG_CN_IEND)
break;
/* process the chunk */
error = process_chunk(std::move(chunk_data), chunk_type, chunk_length);
if (error != PNGERR_NONE)
goto handle_error;
}
/* finish processing the image */
error = process();
handle_error:
/* no need for the intermediate data any more */
idata.clear();
/* if we have an error, free all the other data as well */
if (error != PNGERR_NONE)
{
png_free(pnginfo);
memset(&pnginfo, 0, sizeof(pnginfo));
}
return error;
}
png_info & pnginfo;
private:
png_error process()
{
png_error error = PNGERR_NONE;
/* calculate the offset for each pass of the interlace */
unsigned const pass_count(get_pass_count());
std::uint32_t pass_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
for (unsigned pass = 0; pass_count > pass; ++pass)
pass_offset[pass + 1] = pass_offset[pass] + get_pass_bytes(pass);
/* allocate memory for the filtered image */
try { pnginfo.image = new std::uint8_t [pass_offset[pass_count]]; }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
/* decompress image data */
error = decompress(pass_offset[pass_count]);
for (unsigned pass = 0; (pass_count > pass) && (PNGERR_NONE == error); ++pass)
{
/* compute some basic parameters */
std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
std::uint32_t const bpp(get_bytes_per_pixel());
std::uint32_t const rowbytes(get_row_bytes(dimensions.first));
/* we de-filter in place */
uint8_t *dst = pnginfo.image + pass_offset[pass];
uint8_t const *src = dst;
/* iterate over rows */
for (std::uint32_t y = 0; (dimensions.second > y) && (PNGERR_NONE == error); ++y)
{
/* first byte of each row is the filter type */
uint8_t const filter(*src++);
error = unfilter_row(filter, src, dst, y ? &dst[-rowbytes] : nullptr, bpp, rowbytes);
src += rowbytes;
dst += rowbytes;
}
}
/* if we errored, free the image data */
if (error != PNGERR_NONE)
{
delete[] pnginfo.image;
pnginfo.image = nullptr;
}
return error;
}
png_error decompress(std::uint32_t expected)
{
/* only deflate is permitted */
if (pnginfo.compression_method != 0)
return PNGERR_DECOMPRESS_ERROR;
/* allocate zlib stream */
z_stream stream;
int zerr;
std::memset(&stream, 0, sizeof(stream));
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = 0;
stream.next_in = Z_NULL;
zerr = inflateInit(&stream);
if (Z_OK != zerr)
return PNGERR_DECOMPRESS_ERROR;
/* decompress IDAT blocks */
stream.next_out = pnginfo.image;
stream.avail_out = expected;
stream.avail_in = 0;
std::list<image_data_chunk>::const_iterator it = idata.begin();
while ((idata.end() != it) && ((Z_OK == zerr) || (Z_BUF_ERROR == zerr)) && !stream.avail_in)
{
stream.avail_in = it->length;
stream.next_in = it->data.get();
do
{
zerr = inflate(&stream, Z_NO_FLUSH);
}
while (stream.avail_in && (Z_OK == zerr));
if (!stream.avail_in)
++it;
}
/* it's all good if we got end-of-stream or we have with no data remaining */
if ((Z_OK == inflateEnd(&stream)) && ((Z_STREAM_END == zerr) || ((Z_OK == zerr) && (idata.end() == it) && !stream.avail_in)))
return PNGERR_NONE; return PNGERR_NONE;
else
/* start the CRC with the chunk type (but not the length) */ return PNGERR_DECOMPRESS_ERROR;
crc = crc32(0, tempbuff, 4);
/* read the chunk itself into an allocated memory buffer */
*data = nullptr;
if (*length != 0)
{
/* allocate memory for this chunk */
*data = (uint8_t *)malloc(*length);
if (*data == nullptr)
return PNGERR_OUT_OF_MEMORY;
/* read the data from the file */
if (fp.read(*data, *length) != *length)
{
free(*data);
*data = nullptr;
return PNGERR_FILE_TRUNCATED;
} }
/* update the CRC */ png_error process_chunk(std::unique_ptr<std::uint8_t []> &&data, uint32_t type, uint32_t length)
crc = crc32(crc, *data, *length);
}
/* read the CRC */
if (fp.read(tempbuff, 4) != 4)
{ {
free(*data);
*data = nullptr;
return PNGERR_FILE_TRUNCATED;
}
chunk_crc = fetch_32bit(tempbuff);
/* validate the CRC */
if (crc != chunk_crc)
{
free(*data);
*data = nullptr;
return PNGERR_FILE_CORRUPT;
}
return PNGERR_NONE;
}
/*-------------------------------------------------
process_chunk - process a PNG chunk
-------------------------------------------------*/
static png_error process_chunk(png_private *png, uint8_t *data, uint32_t type, uint32_t length, bool *keepmem)
{
/* default to not keeping memory */
*keepmem = false;
/* switch off of the type */ /* switch off of the type */
switch (type) switch (type)
{ {
/* image header */ /* image header */
case PNG_CN_IHDR: case PNG_CN_IHDR:
png->pnginfo->width = fetch_32bit(data); pnginfo.width = fetch_32bit(&data[0]);
png->pnginfo->height = fetch_32bit(data + 4); pnginfo.height = fetch_32bit(&data[4]);
png->pnginfo->bit_depth = fetch_8bit(data + 8); pnginfo.bit_depth = fetch_8bit(&data[8]);
png->pnginfo->color_type = fetch_8bit(data + 9); pnginfo.color_type = fetch_8bit(&data[9]);
png->pnginfo->compression_method = fetch_8bit(data + 10); pnginfo.compression_method = fetch_8bit(&data[10]);
png->pnginfo->filter_method = fetch_8bit(data + 11); pnginfo.filter_method = fetch_8bit(&data[11]);
png->pnginfo->interlace_method = fetch_8bit(data + 12); pnginfo.interlace_method = fetch_8bit(&data[12]);
break; break;
/* palette */ /* palette */
case PNG_CN_PLTE: case PNG_CN_PLTE:
png->pnginfo->num_palette = length / 3; pnginfo.num_palette = length / 3;
png->pnginfo->palette = data; pnginfo.palette = data.release();
*keepmem = true;
break; break;
/* transparency information */ /* transparency information */
case PNG_CN_tRNS: case PNG_CN_tRNS:
png->pnginfo->num_trans = length; pnginfo.num_trans = length;
png->pnginfo->trans = data; pnginfo.trans = data.release();
*keepmem = true;
break; break;
/* image data */ /* image data */
case PNG_CN_IDAT: case PNG_CN_IDAT:
/* allocate a new image data descriptor */ /* allocate a new image data descriptor and add it to the tail of the list */
*png->idata_next = (image_data_chunk *)malloc(sizeof(**png->idata_next)); try { idata.emplace_back(length, std::move(data)); }
if (*png->idata_next == nullptr) catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
return PNGERR_OUT_OF_MEMORY;
/* add it to the tail of the list */
(*png->idata_next)->next = nullptr;
(*png->idata_next)->length = length;
(*png->idata_next)->data = data;
png->idata_next = &(*png->idata_next)->next;
*keepmem = true;
break; break;
/* gamma */ /* gamma */
case PNG_CN_gAMA: case PNG_CN_gAMA:
png->pnginfo->source_gamma = fetch_32bit(data) / 100000.0; pnginfo.source_gamma = fetch_32bit(data.get()) / 100000.0;
break; break;
/* physical information */ /* physical information */
case PNG_CN_pHYs: case PNG_CN_pHYs:
png->pnginfo->xres = fetch_32bit(data); pnginfo.xres = fetch_32bit(&data[0]);
png->pnginfo->yres = fetch_32bit(data + 4); pnginfo.yres = fetch_32bit(&data[4]);
png->pnginfo->resolution_unit = fetch_8bit(data + 8); pnginfo.resolution_unit = fetch_8bit(&data[8]);
break; break;
/* text */ /* text */
case PNG_CN_tEXt: case PNG_CN_tEXt:
{ {
png_text *text, *pt, *ct;
/* allocate a new text item */ /* allocate a new text item */
text = (png_text *)malloc(sizeof(*text)); png_text *const text = (png_text *)malloc(sizeof(*text));
if (text == nullptr) if (!text)
return PNGERR_OUT_OF_MEMORY; return PNGERR_OUT_OF_MEMORY;
/* set the elements */ /* set the elements */
text->keyword = (char *)data; text->keyword = (char *)data.release();
text->text = text->keyword + strlen(text->keyword) + 1; text->text = text->keyword + strlen(text->keyword) + 1;
text->next = nullptr; text->next = nullptr;
/* add to the end of the list */ /* add to the end of the list */
for (pt = nullptr, ct = png->pnginfo->textlist; ct != nullptr; pt = ct, ct = ct->next) { } png_text *pt, *ct;
for (pt = nullptr, ct = pnginfo.textlist; ct != nullptr; pt = ct, ct = ct->next) { }
if (pt == nullptr) if (pt == nullptr)
png->pnginfo->textlist = text; pnginfo.textlist = text;
else else
pt->next = text; pt->next = text;
*keepmem = true;
break; break;
} }
@ -331,29 +412,65 @@ static png_error process_chunk(png_private *png, uint8_t *data, uint32_t type, u
return PNGERR_NONE; return PNGERR_NONE;
} }
unsigned get_pass_count() const
/*-------------------------------------------------
unfilter_row - unfilter a single row of pixels
-------------------------------------------------*/
static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dstprev, int bpp, int rowbytes)
{ {
int x; return (1 == pnginfo.interlace_method) ? 7 : 1;
}
std::pair<uint32_t, uint32_t> get_pass_dimensions(unsigned pass) const
{
static constexpr unsigned x_bias[7] = { 7, 3, 3, 1, 1, 0, 0 };
static constexpr unsigned y_bias[7] = { 7, 7, 3, 3, 1, 1, 0 };
static constexpr unsigned x_shift[7] = { 3, 3, 2, 2, 1, 1, 0 };
static constexpr unsigned y_shift[7] = { 3, 3, 3, 2, 2, 1, 1 };
if (0 == pnginfo.interlace_method)
return std::make_pair(pnginfo.width, pnginfo.height);
else
return std::make_pair((pnginfo.width + x_bias[pass]) >> x_shift[pass], (pnginfo.height + y_bias[pass]) >> y_shift[pass]);
}
std::uint32_t get_pass_bytes(unsigned pass) const
{
return get_pass_bytes(pass, pnginfo.bit_depth);
}
std::uint32_t get_pass_bytes(unsigned pass, uint8_t bit_depth) const
{
std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
return (get_row_bytes(dimensions.first, bit_depth) + 1) * dimensions.second;
}
std::uint32_t get_row_bytes(std::uint32_t width) const
{
return get_row_bytes(width, pnginfo.bit_depth);
}
std::uint32_t get_row_bytes(std::uint32_t width, uint8_t bit_depth) const
{
return ((width * samples[pnginfo.color_type] * bit_depth) + 7) >> 3;
}
std::uint32_t get_bytes_per_pixel() const
{
return (samples[pnginfo.color_type] * pnginfo.bit_depth) / 8; // FIXME: won't work for 4bpp greyscale etc.
}
static png_error unfilter_row(int type, uint8_t const *src, uint8_t *dst, uint8_t const *dstprev, int bpp, int rowbytes)
{
/* switch off of it */ /* switch off of it */
switch (type) switch (type)
{ {
/* no filter, just copy */ /* no filter, just copy */
case PNG_PF_None: case PNG_PF_None:
for (x = 0; x < rowbytes; x++) for (int x = 0; x < rowbytes; x++)
*dst++ = *src++; *dst++ = *src++;
break; break;
/* SUB = previous pixel */ /* SUB = previous pixel */
case PNG_PF_Sub: case PNG_PF_Sub:
for (x = 0; x < bpp; x++) for (int x = 0; x < bpp; x++)
*dst++ = *src++; *dst++ = *src++;
for (x = bpp; x < rowbytes; x++, dst++) for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + dst[-bpp]; *dst = *src++ + dst[-bpp];
break; break;
@ -361,7 +478,7 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
case PNG_PF_Up: case PNG_PF_Up:
if (dstprev == nullptr) if (dstprev == nullptr)
return unfilter_row(PNG_PF_None, src, dst, dstprev, bpp, rowbytes); return unfilter_row(PNG_PF_None, src, dst, dstprev, bpp, rowbytes);
for (x = 0; x < rowbytes; x++, dst++) for (int x = 0; x < rowbytes; x++, dst++)
*dst = *src++ + *dstprev++; *dst = *src++ + *dstprev++;
break; break;
@ -369,23 +486,23 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
case PNG_PF_Average: case PNG_PF_Average:
if (dstprev == nullptr) if (dstprev == nullptr)
{ {
for (x = 0; x < bpp; x++) for (int x = 0; x < bpp; x++)
*dst++ = *src++; *dst++ = *src++;
for (x = bpp; x < rowbytes; x++, dst++) for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + dst[-bpp] / 2; *dst = *src++ + dst[-bpp] / 2;
} }
else else
{ {
for (x = 0; x < bpp; x++, dst++) for (int x = 0; x < bpp; x++, dst++)
*dst = *src++ + *dstprev++ / 2; *dst = *src++ + *dstprev++ / 2;
for (x = bpp; x < rowbytes; x++, dst++) for (int x = bpp; x < rowbytes; x++, dst++)
*dst = *src++ + (*dstprev++ + dst[-bpp]) / 2; *dst = *src++ + (*dstprev++ + dst[-bpp]) / 2;
} }
break; break;
/* PAETH = special filter */ /* PAETH = special filter */
case PNG_PF_Paeth: case PNG_PF_Paeth:
for (x = 0; x < rowbytes; x++) for (int x = 0; x < rowbytes; x++)
{ {
int32_t pa = (x < bpp) ? 0 : dst[-bpp]; int32_t pa = (x < bpp) ? 0 : dst[-bpp];
int32_t pc = (x < bpp || dstprev == nullptr) ? 0 : dstprev[-bpp]; int32_t pc = (x < bpp || dstprev == nullptr) ? 0 : dstprev[-bpp];
@ -411,92 +528,85 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst
return PNGERR_NONE; return PNGERR_NONE;
} }
static png_error verify_header(util::core_file &fp)
/*-------------------------------------------------
process_image - post-process a loaded iamge
-------------------------------------------------*/
static png_error process_image(png_private *png)
{ {
int rowbytes, bpp, imagesize; uint8_t signature[8];
png_error error = PNGERR_NONE;
image_data_chunk *idat;
uint8_t *src, *dst;
z_stream stream;
int zerr, y;
/* compute some basic parameters */ /* read 8 bytes */
bpp = compute_bpp(png->pnginfo); if (fp.read(signature, 8) != 8)
rowbytes = compute_rowbytes(png->pnginfo); return PNGERR_FILE_TRUNCATED;
imagesize = png->pnginfo->height * (rowbytes + 1);
/* allocate memory for the filtered image */ /* return an error if we don't match */
png->pnginfo->image = (uint8_t *)malloc(imagesize); if (memcmp(signature, PNG_Signature, 8) != 0)
if (png->pnginfo->image == nullptr) return PNGERR_BAD_SIGNATURE;
return PNGERR_OUT_OF_MEMORY;
/* initialize the stream */ return PNGERR_NONE;
memset(&stream, 0, sizeof(stream));
stream.next_out = png->pnginfo->image;
stream.avail_out = imagesize;
zerr = inflateInit(&stream);
if (zerr != Z_OK)
{
error = PNGERR_DECOMPRESS_ERROR;
goto handle_error;
} }
/* loop over IDAT and decompress each as part of a larger stream */ static png_error read_chunk(util::core_file &fp, std::unique_ptr<std::uint8_t []> &data, std::uint32_t &type, std::uint32_t &length)
for (idat = png->idata; idat != nullptr; idat = idat->next)
{ {
/* decompress this chunk */ std::uint8_t tempbuff[4];
stream.next_in = idat->data;
stream.avail_in = idat->length;
zerr = inflate(&stream, Z_NO_FLUSH);
/* stop at the end of the stream */ /* fetch the length of this chunk */
if (zerr == Z_STREAM_END) if (fp.read(tempbuff, 4) != 4)
break; return PNGERR_FILE_TRUNCATED;
length = fetch_32bit(tempbuff);
/* other errors are fatal */ /* fetch the type of this chunk */
if (zerr != Z_OK) if (fp.read(tempbuff, 4) != 4)
return PNGERR_FILE_TRUNCATED;
type = fetch_32bit(tempbuff);
/* stop when we hit an IEND chunk */
if (type == PNG_CN_IEND)
return PNGERR_NONE;
/* start the CRC with the chunk type (but not the length) */
std::uint32_t crc = crc32(0, tempbuff, 4);
/* read the chunk itself into an allocated memory buffer */
if (length)
{ {
error = PNGERR_DECOMPRESS_ERROR; /* allocate memory for this chunk */
goto handle_error; try { data.reset(new std::uint8_t [length]); }
} catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
}
/* clean up */ /* read the data from the file */
zerr = inflateEnd(&stream); if (fp.read(data.get(), length) != length)
if (zerr != Z_OK)
{ {
error = PNGERR_DECOMPRESS_ERROR; data.reset();
goto handle_error; return PNGERR_FILE_TRUNCATED;
} }
/* we de-filter in place */ /* update the CRC */
src = dst = png->pnginfo->image; crc = crc32(crc, data.get(), length);
}
/* iterate over rows */ /* read the CRC */
for (y = 0; y < png->pnginfo->height && error == PNGERR_NONE; y++) if (fp.read(tempbuff, 4) != 4)
{ {
/* first byte of each row is the filter type */ data.reset();
int filter = *src++; return PNGERR_FILE_TRUNCATED;
error = unfilter_row(filter, src, dst, (y == 0) ? nullptr : &dst[-rowbytes], bpp, rowbytes); }
src += rowbytes; std::uint32_t const chunk_crc = fetch_32bit(tempbuff);
dst += rowbytes;
/* validate the CRC */
if (crc != chunk_crc)
{
data.reset();
return PNGERR_FILE_CORRUPT;
} }
handle_error: return PNGERR_NONE;
/* if we errored, free the image data */
if (error != PNGERR_NONE)
{
free(png->pnginfo->image);
png->pnginfo->image = nullptr;
}
return error;
} }
std::list<image_data_chunk> idata; // FIXME: this could really be a local
};
} // anonymous namespace
/*------------------------------------------------- /*-------------------------------------------------
png_read_file - read a PNG from a core stream png_read_file - read a PNG from a core stream
@ -504,73 +614,8 @@ handle_error:
png_error png_read_file(util::core_file &fp, png_info *pnginfo) png_error png_read_file(util::core_file &fp, png_info *pnginfo)
{ {
uint8_t *chunk_data = nullptr; png_private png(*pnginfo);
png_private png; return png.read_file(fp);
png_error error;
/* initialize the data structures */
memset(&png, 0, sizeof(png));
memset(pnginfo, 0, sizeof(*pnginfo));
png.pnginfo = pnginfo;
png.idata_next = &png.idata;
/* verify the signature at the start of the file */
error = verify_header(fp);
if (error != PNGERR_NONE)
goto handle_error;
/* loop until we hit an IEND chunk */
for ( ; ; )
{
uint32_t chunk_type, chunk_length;
bool keepmem;
/* read a chunk */
error = read_chunk(fp, &chunk_data, &chunk_type, &chunk_length);
if (error != PNGERR_NONE)
goto handle_error;
/* stop when we hit an IEND chunk */
if (chunk_type == PNG_CN_IEND)
break;
/* process the chunk */
error = process_chunk(&png, chunk_data, chunk_type, chunk_length, &keepmem);
if (error != PNGERR_NONE)
goto handle_error;
/* free memory if we didn't want to keep it */
if (!keepmem)
free(chunk_data);
chunk_data = nullptr;
}
/* finish processing the image */
error = process_image(&png);
if (error != PNGERR_NONE)
goto handle_error;
handle_error:
/* free all intermediate data */
while (png.idata != nullptr)
{
image_data_chunk *next = png.idata->next;
if (png.idata->data != nullptr)
free(png.idata->data);
free(png.idata);
png.idata = next;
}
if (chunk_data != nullptr)
free(chunk_data);
/* if we have an error, free all the other data as well */
if (error != PNGERR_NONE)
{
png_free(pnginfo);
memset(pnginfo, 0, sizeof(*pnginfo));
}
return error;
} }
@ -656,40 +701,7 @@ png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap)
png_error png_expand_buffer_8bit(png_info *pnginfo) png_error png_expand_buffer_8bit(png_info *pnginfo)
{ {
int i,j, k; return png_private(*pnginfo).expand_buffer_8bit();
uint8_t *inp, *outp, *outbuf;
/* nothing to do if we're at 8 or greater already */
if (pnginfo->bit_depth >= 8)
return PNGERR_NONE;
/* allocate a new buffer at 8-bit */
outbuf = (uint8_t *)malloc(pnginfo->width * pnginfo->height);
if (outbuf == nullptr)
return PNGERR_OUT_OF_MEMORY;
inp = pnginfo->image;
outp = outbuf;
for (i = 0; i < pnginfo->height; i++)
{
for(j = 0; j < pnginfo->width / ( 8 / pnginfo->bit_depth); j++)
{
for (k = 8 / pnginfo->bit_depth-1; k >= 0; k--)
*outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
inp++;
}
if (pnginfo->width % (8 / pnginfo->bit_depth))
{
for (k = pnginfo->width % (8 / pnginfo->bit_depth)-1; k >= 0; k--)
*outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
inp++;
}
}
free (pnginfo->image);
pnginfo->image = outbuf;
return PNGERR_NONE;
} }
@ -1041,7 +1053,7 @@ static png_error write_png_stream(util::core_file &fp, png_info *pnginfo, const
goto handle_error; goto handle_error;
/* write a single IDAT chunk */ /* write a single IDAT chunk */
error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(pnginfo) + 1)); error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(*pnginfo) + 1));
if (error != PNGERR_NONE) if (error != PNGERR_NONE)
goto handle_error; goto handle_error;