diff options
author | 2017-08-12 02:43:53 +1000 | |
---|---|---|
committer | 2017-08-12 02:43:53 +1000 | |
commit | a7b15fc928ea5713bd19b68d9992de6c3b93be51 (patch) | |
tree | 2b0a124cfb367c6a0d2bdb3fae12df8934362a21 | |
parent | ac6762603cb1f482eeeb425b2bf6f29dcadbbd86 (diff) |
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.
-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; |