diff options
-rw-r--r-- | src/emu/rendutil.cpp | 242 | ||||
-rw-r--r-- | src/frontend/mame/ui/selmenu.cpp | 2 | ||||
-rw-r--r-- | src/lib/util/png.cpp | 732 |
3 files changed, 519 insertions, 457 deletions
diff --git a/src/emu/rendutil.cpp b/src/emu/rendutil.cpp index 015a08e4e11..f35332436fb 100644 --- a/src/emu/rendutil.cpp +++ b/src/emu/rendutil.cpp @@ -655,7 +655,7 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, png_free(&png); return false; } - if (png.interlace_method != 0) + if (png.interlace_method != 0 && png.interlace_method != 1) { osd_printf_error("%s: Interlace unsupported\n", filename); 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) { + // FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it u8 accumalpha = 0xff; - u8 *src; - int x, y; - /* handle 8bpp palettized case */ - if (png->color_type == 3) - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src++) - { - /* determine alpha and expand to 32bpp */ - u8 alpha = (*src < png->num_trans) ? png->trans[*src] : 0xff; - accumalpha &= alpha; - bitmap.pix32(y, x) = rgb_t(alpha, png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]); - } - } + // colour format table + static constexpr unsigned samples[] = { 1, 0, 3, 1, 2, 0, 4 }; - /* handle 8bpp grayscale case */ - else if (png->color_type == 0) - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src++) - bitmap.pix32(y, x) = rgb_t(0xff, *src, *src, *src); - } - - /* handle 32bpp non-alpha case */ - else if (png->color_type == 2) - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src += 3) - bitmap.pix32(y, x) = rgb_t(0xff, src[0], src[1], src[2]); - } + // 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 }; - /* handle 32bpp alpha case */ - else + 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) { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src += 4) + // 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) + { + // handle 8bpp palettized case + for (u32 y = 0; height > y; ++y) { - accumalpha &= src[3]; - bitmap.pix32(y, x) = rgb_t(src[3], src[0], src[1], src[2]); + for (u32 x = 0; width > x; ++x, ++src) + { + // determine alpha and expand to 32bpp + u8 alpha = (*src < png->num_trans) ? png->trans[*src] : 0xff; + accumalpha &= alpha; + 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; + } + } + } + else if (png->color_type == 0) + { + // handle 8bpp grayscale case + for (u32 y = 0; height > y; ++y) + { + for (u32 x = 0; width > x; ++x, ++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; + } + } + } + else if (png->color_type == 2) + { + // handle 32bpp non-alpha case + for (u32 y = 0; height > y; ++y) + { + for (u32 x = 0; width > x; ++x, src += 3) + { + 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; + } + } + } + else + { + // handle 32bpp alpha case + for (u32 y = 0; height > y; ++y) + { + for (u32 x = 0; width > x; ++x, src += 4) + { + accumalpha &= src[3]; + 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); } @@ -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) { + // FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it u8 accumalpha = 0xff; - u8 *src; - int x, y; - /* handle 8bpp palettized case */ - if (png->color_type == 3) + // 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) { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src++) + // 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) + { + // handle 8bpp palettized case + for (u32 y = 0; height > y; ++y) { - rgb_t pixel = bitmap.pix32(y, x); - u8 alpha = rgb_t(png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]).brightness(); - accumalpha &= alpha; - bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + 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); + u8 const alpha(rgb_t(png->palette[*src * 3], png->palette[*src * 3 + 1], png->palette[*src * 3 + 2]).brightness()); + accumalpha &= alpha; + dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + } } - } - - /* handle 8bpp grayscale case */ - else if (png->color_type == 0) - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src++) + } + else if (png->color_type == 0) + { + // handle 8bpp grayscale case + for (u32 y = 0; height > y; ++y) { - rgb_t pixel = bitmap.pix32(y, x); - accumalpha &= *src; - bitmap.pix32(y, x) = rgb_t(*src, pixel.r(), pixel.g(), pixel.b()); + 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; + dest = rgb_t(*src, pixel.r(), pixel.g(), pixel.b()); + } } - } - - /* handle 32bpp non-alpha case */ - else if (png->color_type == 2) - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src += 3) + } + else if (png->color_type == 2) + { + // handle 32bpp non-alpha case + for (u32 y = 0; height > y; ++y) { - rgb_t pixel = bitmap.pix32(y, x); - u8 alpha = rgb_t(src[0], src[1], src[2]).brightness(); - accumalpha &= alpha; - bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + for (u32 x = 0; width > x; ++x, src += 3) + { + 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; + dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + } } - } - - /* handle 32bpp alpha case */ - else - { - /* loop over width/height */ - src = png->image; - for (y = 0; y < png->height; y++) - for (x = 0; x < png->width; x++, src += 4) + } + else + { + // handle 32bpp alpha case + for (u32 y = 0; height > y; ++y) { - rgb_t pixel = bitmap.pix32(y, x); - u8 alpha = rgb_t(src[0], src[1], src[2]).brightness(); - accumalpha &= alpha; - bitmap.pix32(y, x) = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + for (u32 x = 0; width > x; ++x, src += 4) + { + 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; + dest = rgb_t(alpha, pixel.r(), pixel.g(), pixel.b()); + } } + } } - /* set the hasalpha flag */ + // set the hasalpha flag return (accumalpha != 0xff); } diff --git a/src/frontend/mame/ui/selmenu.cpp b/src/frontend/mame/ui/selmenu.cpp index f66554768ef..86a07564cf9 100644 --- a/src/frontend/mame/ui/selmenu.cpp +++ b/src/frontend/mame/ui/selmenu.cpp @@ -2059,7 +2059,7 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f game_driver const *driver; get_selection(software, driver); - if (software && ((software->startempty != 1) || !driver)) + if (software && (!software->startempty || !driver)) { m_cache->set_snapx_driver(nullptr); diff --git a/src/lib/util/png.cpp b/src/lib/util/png.cpp index 9069348c843..17574481aea 100644 --- a/src/lib/util/png.cpp +++ b/src/lib/util/png.cpp @@ -8,14 +8,19 @@ ***************************************************************************/ -#include <math.h> -#include <stdlib.h> -#include <assert.h> +#include "png.h" #include <zlib.h> -#include "png.h" +#include <cstdint> +#include <list> +#include <memory> #include <new> +#include <utility> + +#include <math.h> +#include <stdlib.h> +#include <assert.h> /*************************************************************************** @@ -24,19 +29,10 @@ struct image_data_chunk { - image_data_chunk * next; - int length; - uint8_t * data; -}; - + image_data_chunk(std::uint32_t l, std::unique_ptr<std::uint8_t []> &&d) : length(l), data(std::move(d)) { } -struct png_private -{ - png_info * pnginfo; - image_data_chunk * idata; - image_data_chunk ** idata_next; - uint8_t bpp; - uint32_t rowbytes; + std::uint32_t length = 0; + std::unique_ptr<std::uint8_t []> data; }; @@ -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; -} - - -static inline int compute_rowbytes(const png_info *pnginfo) -{ - return (pnginfo->width * samples[pnginfo->color_type] * pnginfo->bit_depth + 7) / 8; + return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8; } @@ -113,247 +103,374 @@ static inline int compute_rowbytes(const png_info *pnginfo) 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; - pnginfo->textlist = temp->next; - if (temp->keyword != nullptr) - free((void *)temp->keyword); + png_text *const temp = pnginfo.textlist; + pnginfo.textlist = temp->next; + if (temp->keyword) + delete[] (std::uint8_t *)temp->keyword; free(temp); } - if (pnginfo->palette != nullptr) - free(pnginfo->palette); - pnginfo->palette = nullptr; + if (pnginfo.palette) + delete[] pnginfo.palette; + pnginfo.palette = nullptr; - if (pnginfo->trans != nullptr) - free(pnginfo->trans); - pnginfo->trans = nullptr; + if (pnginfo.trans) + delete[] pnginfo.trans; + pnginfo.trans = nullptr; - if (pnginfo->image != nullptr) - free(pnginfo->image); - pnginfo->image = nullptr; + if (pnginfo.image) + delete[] pnginfo.image; + pnginfo.image = nullptr; } +void png_free(png_info *pnginfo) { png_free(*pnginfo); } // TODO: make external interface use reference -/*************************************************************************** - PNG READING FUNCTIONS -***************************************************************************/ -/*------------------------------------------------- - verify_header - verify the PNG - header at the current file location --------------------------------------------------*/ +namespace { -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 */ - if (fp.read(signature, 8) != 8) - return PNGERR_FILE_TRUNCATED; + png_error expand_buffer_8bit() + { + /* nothing to do if we're at 8 or greater already */ + if (pnginfo.bit_depth >= 8) + return PNGERR_NONE; + + /* calculate the offset for each pass of the interlace on the input and output */ + unsigned const pass_count(get_pass_count()); + 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); + } - /* return an error if we don't match */ - if (memcmp(signature, PNG_Signature, 8) != 0) - return PNGERR_BAD_SIGNATURE; + /* 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; } - return PNGERR_NONE; -} + 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++; + } + } + } -/*------------------------------------------------- - read_chunk - read the next PNG chunk --------------------------------------------------*/ + delete[] pnginfo.image; + pnginfo.image = outbuf.release(); + pnginfo.bit_depth = 8; + return PNGERR_NONE; + } -static png_error read_chunk(util::core_file &fp, uint8_t **data, uint32_t *type, uint32_t *length) -{ - uint32_t crc, chunk_crc; - uint8_t tempbuff[4]; + png_error read_file(util::core_file &fp) + { + /* initialize the data structures */ + png_error error = PNGERR_NONE; + std::memset(&pnginfo, 0, sizeof(pnginfo)); - /* fetch the length of this chunk */ - if (fp.read(tempbuff, 4) != 4) - return PNGERR_FILE_TRUNCATED; - *length = fetch_32bit(tempbuff); + /* verify the signature at the start of the file */ + error = verify_header(fp); + if (error != PNGERR_NONE) + goto handle_error; - /* fetch the type of this chunk */ - if (fp.read(tempbuff, 4) != 4) - return PNGERR_FILE_TRUNCATED; - *type = fetch_32bit(tempbuff); + /* loop until we hit an IEND chunk */ + for ( ; ; ) + { + /* 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 */ + 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; + } - /* stop when we hit an IEND chunk */ - if (*type == PNG_CN_IEND) - return PNGERR_NONE; + /* finish processing the image */ + error = process(); - /* start the CRC with the chunk type (but not the length) */ - crc = crc32(0, tempbuff, 4); + handle_error: + /* no need for the intermediate data any more */ + idata.clear(); - /* read the chunk itself into an allocated memory buffer */ - *data = nullptr; - if (*length != 0) + /* 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() { - /* allocate memory for this chunk */ - *data = (uint8_t *)malloc(*length); - if (*data == nullptr) - return PNGERR_OUT_OF_MEMORY; + 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); - /* read the data from the file */ - if (fp.read(*data, *length) != *length) + /* 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) { - free(*data); - *data = nullptr; - return PNGERR_FILE_TRUNCATED; + /* 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; + } } - /* update the CRC */ - crc = crc32(crc, *data, *length); + /* if we errored, free the image data */ + if (error != PNGERR_NONE) + { + delete[] pnginfo.image; + pnginfo.image = nullptr; + } + return error; } - /* read the CRC */ - if (fp.read(tempbuff, 4) != 4) + png_error decompress(std::uint32_t expected) { - free(*data); - *data = nullptr; - return PNGERR_FILE_TRUNCATED; - } - chunk_crc = fetch_32bit(tempbuff); + /* 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; + } - /* validate the CRC */ - if (crc != chunk_crc) - { - free(*data); - *data = nullptr; - return PNGERR_FILE_CORRUPT; + /* 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; + else + return PNGERR_DECOMPRESS_ERROR; } - 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 (type) + png_error process_chunk(std::unique_ptr<std::uint8_t []> &&data, uint32_t type, uint32_t length) { + /* switch off of the type */ + switch (type) + { /* image header */ case PNG_CN_IHDR: - png->pnginfo->width = fetch_32bit(data); - png->pnginfo->height = fetch_32bit(data + 4); - png->pnginfo->bit_depth = fetch_8bit(data + 8); - png->pnginfo->color_type = fetch_8bit(data + 9); - png->pnginfo->compression_method = fetch_8bit(data + 10); - png->pnginfo->filter_method = fetch_8bit(data + 11); - png->pnginfo->interlace_method = fetch_8bit(data + 12); + pnginfo.width = fetch_32bit(&data[0]); + pnginfo.height = fetch_32bit(&data[4]); + pnginfo.bit_depth = fetch_8bit(&data[8]); + pnginfo.color_type = fetch_8bit(&data[9]); + pnginfo.compression_method = fetch_8bit(&data[10]); + pnginfo.filter_method = fetch_8bit(&data[11]); + pnginfo.interlace_method = fetch_8bit(&data[12]); break; /* palette */ case PNG_CN_PLTE: - png->pnginfo->num_palette = length / 3; - png->pnginfo->palette = data; - *keepmem = true; + pnginfo.num_palette = length / 3; + pnginfo.palette = data.release(); break; /* transparency information */ case PNG_CN_tRNS: - png->pnginfo->num_trans = length; - png->pnginfo->trans = data; - *keepmem = true; + pnginfo.num_trans = length; + pnginfo.trans = data.release(); break; /* image data */ case PNG_CN_IDAT: - /* allocate a new image data descriptor */ - *png->idata_next = (image_data_chunk *)malloc(sizeof(**png->idata_next)); - if (*png->idata_next == nullptr) - 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; + /* allocate a new image data descriptor and add it to the tail of the list */ + try { idata.emplace_back(length, std::move(data)); } + catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } + break; /* gamma */ case PNG_CN_gAMA: - png->pnginfo->source_gamma = fetch_32bit(data) / 100000.0; + pnginfo.source_gamma = fetch_32bit(data.get()) / 100000.0; break; /* physical information */ case PNG_CN_pHYs: - png->pnginfo->xres = fetch_32bit(data); - png->pnginfo->yres = fetch_32bit(data + 4); - png->pnginfo->resolution_unit = fetch_8bit(data + 8); + pnginfo.xres = fetch_32bit(&data[0]); + pnginfo.yres = fetch_32bit(&data[4]); + pnginfo.resolution_unit = fetch_8bit(&data[8]); break; /* text */ case PNG_CN_tEXt: - { - png_text *text, *pt, *ct; - - /* allocate a new text item */ - text = (png_text *)malloc(sizeof(*text)); - if (text == nullptr) - return PNGERR_OUT_OF_MEMORY; - - /* set the elements */ - text->keyword = (char *)data; - text->text = text->keyword + strlen(text->keyword) + 1; - text->next = nullptr; - - /* add to the end of the list */ - for (pt = nullptr, ct = png->pnginfo->textlist; ct != nullptr; pt = ct, ct = ct->next) { } - if (pt == nullptr) - png->pnginfo->textlist = text; - else - pt->next = text; + { + /* allocate a new text item */ + png_text *const text = (png_text *)malloc(sizeof(*text)); + if (!text) + return PNGERR_OUT_OF_MEMORY; + + /* set the elements */ + text->keyword = (char *)data.release(); + text->text = text->keyword + strlen(text->keyword) + 1; + text->next = nullptr; + + /* add to the end of the list */ + png_text *pt, *ct; + for (pt = nullptr, ct = pnginfo.textlist; ct != nullptr; pt = ct, ct = ct->next) { } + if (pt == nullptr) + pnginfo.textlist = text; + else + pt->next = text; - *keepmem = true; - break; - } + break; + } /* anything else */ default: if ((type & 0x20000000) == 0) return PNGERR_UNKNOWN_CHUNK; break; + } + return PNGERR_NONE; } - return PNGERR_NONE; -} + unsigned get_pass_count() const + { + return (1 == pnginfo.interlace_method) ? 7 : 1; + } -/*------------------------------------------------- - unfilter_row - unfilter a single row of pixels --------------------------------------------------*/ + 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]); + } -static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dstprev, int bpp, int rowbytes) -{ - int x; + 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; + } - /* switch off of it */ - switch (type) + 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 (type) + { /* no filter, just copy */ case PNG_PF_None: - for (x = 0; x < rowbytes; x++) + for (int x = 0; x < rowbytes; x++) *dst++ = *src++; break; /* SUB = previous pixel */ case PNG_PF_Sub: - for (x = 0; x < bpp; x++) + for (int x = 0; x < bpp; x++) *dst++ = *src++; - for (x = bpp; x < rowbytes; x++, dst++) + for (int x = bpp; x < rowbytes; x++, dst++) *dst = *src++ + dst[-bpp]; 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: if (dstprev == nullptr) 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++; 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: if (dstprev == nullptr) { - for (x = 0; x < bpp; x++) + for (int x = 0; x < bpp; x++) *dst++ = *src++; - for (x = bpp; x < rowbytes; x++, dst++) + for (int x = bpp; x < rowbytes; x++, dst++) *dst = *src++ + dst[-bpp] / 2; } else { - for (x = 0; x < bpp; x++, dst++) + for (int x = 0; x < bpp; x++, dst++) *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; } break; /* PAETH = special filter */ 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 pc = (x < bpp || dstprev == nullptr) ? 0 : dstprev[-bpp]; @@ -406,171 +523,99 @@ static png_error unfilter_row(int type, uint8_t *src, uint8_t *dst, uint8_t *dst /* unknown filter type */ default: return PNGERR_UNKNOWN_FILTER; - } - - return PNGERR_NONE; -} - - -/*------------------------------------------------- - process_image - post-process a loaded iamge --------------------------------------------------*/ - -static png_error process_image(png_private *png) -{ - int rowbytes, bpp, imagesize; - png_error error = PNGERR_NONE; - image_data_chunk *idat; - uint8_t *src, *dst; - z_stream stream; - int zerr, y; - - /* compute some basic parameters */ - bpp = compute_bpp(png->pnginfo); - rowbytes = compute_rowbytes(png->pnginfo); - imagesize = png->pnginfo->height * (rowbytes + 1); - - /* allocate memory for the filtered image */ - png->pnginfo->image = (uint8_t *)malloc(imagesize); - if (png->pnginfo->image == nullptr) - return PNGERR_OUT_OF_MEMORY; + } - /* initialize the stream */ - 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; + return PNGERR_NONE; } - /* loop over IDAT and decompress each as part of a larger stream */ - for (idat = png->idata; idat != nullptr; idat = idat->next) + static png_error verify_header(util::core_file &fp) { - /* decompress this chunk */ - stream.next_in = idat->data; - stream.avail_in = idat->length; - zerr = inflate(&stream, Z_NO_FLUSH); + uint8_t signature[8]; - /* stop at the end of the stream */ - if (zerr == Z_STREAM_END) - break; + /* read 8 bytes */ + if (fp.read(signature, 8) != 8) + return PNGERR_FILE_TRUNCATED; - /* other errors are fatal */ - if (zerr != Z_OK) - { - error = PNGERR_DECOMPRESS_ERROR; - goto handle_error; - } + /* return an error if we don't match */ + if (memcmp(signature, PNG_Signature, 8) != 0) + return PNGERR_BAD_SIGNATURE; + + return PNGERR_NONE; } - /* clean up */ - zerr = inflateEnd(&stream); - if (zerr != Z_OK) + static png_error read_chunk(util::core_file &fp, std::unique_ptr<std::uint8_t []> &data, std::uint32_t &type, std::uint32_t &length) { - error = PNGERR_DECOMPRESS_ERROR; - goto handle_error; - } + std::uint8_t tempbuff[4]; - /* we de-filter in place */ - src = dst = png->pnginfo->image; + /* fetch the length of this chunk */ + if (fp.read(tempbuff, 4) != 4) + return PNGERR_FILE_TRUNCATED; + length = fetch_32bit(tempbuff); - /* iterate over rows */ - for (y = 0; y < png->pnginfo->height && error == PNGERR_NONE; y++) - { - /* first byte of each row is the filter type */ - int filter = *src++; - error = unfilter_row(filter, src, dst, (y == 0) ? nullptr : &dst[-rowbytes], bpp, rowbytes); - src += rowbytes; - dst += rowbytes; - } + /* fetch the type of this chunk */ + if (fp.read(tempbuff, 4) != 4) + return PNGERR_FILE_TRUNCATED; + type = fetch_32bit(tempbuff); -handle_error: - /* if we errored, free the image data */ - if (error != PNGERR_NONE) - { - free(png->pnginfo->image); - png->pnginfo->image = nullptr; - } - return error; -} + /* 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); -/*------------------------------------------------- - png_read_file - read a PNG from a core stream --------------------------------------------------*/ + /* read the chunk itself into an allocated memory buffer */ + if (length) + { + /* allocate memory for this chunk */ + try { data.reset(new std::uint8_t [length]); } + catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } -png_error png_read_file(util::core_file &fp, png_info *pnginfo) -{ - uint8_t *chunk_data = nullptr; - png_private png; - png_error error; + /* read the data from the file */ + if (fp.read(data.get(), length) != length) + { + data.reset(); + return PNGERR_FILE_TRUNCATED; + } - /* initialize the data structures */ - memset(&png, 0, sizeof(png)); - memset(pnginfo, 0, sizeof(*pnginfo)); - png.pnginfo = pnginfo; - png.idata_next = &png.idata; + /* update the CRC */ + crc = crc32(crc, data.get(), length); + } - /* verify the signature at the start of the file */ - error = verify_header(fp); - if (error != PNGERR_NONE) - goto handle_error; + /* read the CRC */ + if (fp.read(tempbuff, 4) != 4) + { + data.reset(); + return PNGERR_FILE_TRUNCATED; + } + std::uint32_t const chunk_crc = fetch_32bit(tempbuff); - /* loop until we hit an IEND chunk */ - for ( ; ; ) - { - uint32_t chunk_type, chunk_length; - bool keepmem; + /* validate the CRC */ + if (crc != chunk_crc) + { + data.reset(); + return PNGERR_FILE_CORRUPT; + } - /* read a chunk */ - error = read_chunk(fp, &chunk_data, &chunk_type, &chunk_length); - if (error != PNGERR_NONE) - goto handle_error; + return PNGERR_NONE; + } - /* stop when we hit an IEND chunk */ - if (chunk_type == PNG_CN_IEND) - break; + std::list<image_data_chunk> idata; // FIXME: this could really be a local +}; - /* process the chunk */ - error = process_chunk(&png, chunk_data, chunk_type, chunk_length, &keepmem); - if (error != PNGERR_NONE) - goto handle_error; +} // anonymous namespace - /* 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); +/*------------------------------------------------- + png_read_file - read a PNG from a core stream +-------------------------------------------------*/ - /* 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_error png_read_file(util::core_file &fp, png_info *pnginfo) +{ + png_private png(*pnginfo); + return png.read_file(fp); } @@ -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) { - int i,j, k; - 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; + return png_private(*pnginfo).expand_buffer_8bit(); } @@ -1041,7 +1053,7 @@ static png_error write_png_stream(util::core_file &fp, png_info *pnginfo, const goto handle_error; /* 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) goto handle_error; |