diff options
-rw-r--r-- | src/lib/util/png.cpp | 261 | ||||
-rw-r--r-- | src/lib/util/png.h | 49 |
2 files changed, 168 insertions, 142 deletions
diff --git a/src/lib/util/png.cpp b/src/lib/util/png.cpp index 340ec91b8af..ab0a3160518 100644 --- a/src/lib/util/png.cpp +++ b/src/lib/util/png.cpp @@ -10,6 +10,8 @@ #include "png.h" +#include "unicode.h" + #include <zlib.h> #include <algorithm> @@ -17,9 +19,7 @@ #include <cstdint> #include <cstring> #include <cmath> -#include <list> #include <new> -#include <utility> #include <math.h> #include <stdlib.h> @@ -38,44 +38,6 @@ static const int samples[] = { 1, 0, 3, 1, 2, 0, 4 }; INLINE FUNCTIONS ***************************************************************************/ -static inline uint8_t fetch_8bit(uint8_t *v) -{ - return *v; -} - - -#ifdef UNUSED_FUNCTION -static inline uint16_t fetch_16bit(uint8_t *v) -{ - return big_endianize_int16(*(uint16_t *)v); -} -#endif - -static inline uint32_t fetch_32bit(uint8_t *v) -{ - return big_endianize_int32(*(uint32_t *)v); -} - - -static inline void put_8bit(uint8_t *v, uint8_t data) -{ - *v = data; -} - - -#ifdef UNUSED_FUNCTION -static inline void put_16bit(uint8_t *v, uint16_t data) -{ - *(uint16_t *)v = big_endianize_int16(data); -} -#endif - -static inline void put_32bit(uint8_t *v, uint32_t data) -{ - *(uint32_t *)v = big_endianize_int32(data); -} - - static inline int compute_rowbytes(const png_info &pnginfo) { return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8; @@ -94,15 +56,7 @@ static inline int compute_rowbytes(const png_info &pnginfo) void png_info::free_data() { - while (textlist) - { - png_text *const temp = textlist; - textlist = temp->next; - if (temp->keyword) - delete[] (std::uint8_t *)temp->keyword; - delete temp; - } - + textlist.clear(); palette.reset(); trans.reset(); image.reset(); @@ -112,6 +66,50 @@ void png_info::free_data() namespace { +#define PNG_Signature "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" +#define MNG_Signature "\x8A\x4D\x4E\x47\x0D\x0A\x1A\x0A" + +// Chunk names +constexpr std::uint32_t PNG_CN_IHDR = 0x49484452L; +constexpr std::uint32_t PNG_CN_PLTE = 0x504C5445L; +constexpr std::uint32_t PNG_CN_IDAT = 0x49444154L; +constexpr std::uint32_t PNG_CN_IEND = 0x49454E44L; +constexpr std::uint32_t PNG_CN_gAMA = 0x67414D41L; +//constexpr std::uint32_t PNG_CN_sBIT = 0x73424954L; +//constexpr std::uint32_t PNG_CN_cHRM = 0x6348524DL; +constexpr std::uint32_t PNG_CN_tRNS = 0x74524E53L; +//constexpr std::uint32_t PNG_CN_bKGD = 0x624B4744L; +//constexpr std::uint32_t PNG_CN_hIST = 0x68495354L; +constexpr std::uint32_t PNG_CN_tEXt = 0x74455874L; +//constexpr std::uint32_t PNG_CN_zTXt = 0x7A545874L; +constexpr std::uint32_t PNG_CN_pHYs = 0x70485973L; +//constexpr std::uint32_t PNG_CN_oFFs = 0x6F464673L; +//constexpr std::uint32_t PNG_CN_tIME = 0x74494D45L; +//constexpr std::uint32_t PNG_CN_sCAL = 0x7343414CL; + +// MNG Chunk names +constexpr std::uint32_t MNG_CN_MHDR = 0x4D484452L; +constexpr std::uint32_t MNG_CN_MEND = 0x4D454E44L; +//constexpr std::uint32_t MNG_CN_TERM = 0x5445524DL; +//constexpr std::uint32_t MNG_CN_BACK = 0x4241434BL; + +// Prediction filters +constexpr std::uint8_t PNG_PF_None = 0; +constexpr std::uint8_t PNG_PF_Sub = 1; +constexpr std::uint8_t PNG_PF_Up = 2; +constexpr std::uint8_t PNG_PF_Average = 3; +constexpr std::uint8_t PNG_PF_Paeth = 4; + + +inline uint8_t fetch_8bit(uint8_t const *v) { return *v; } +inline uint16_t fetch_16bit(uint8_t const *v) { return big_endianize_int16(*reinterpret_cast<uint16_t const *>(v)); } +inline uint32_t fetch_32bit(uint8_t const *v) { return big_endianize_int32(*reinterpret_cast<uint32_t const *>(v)); } + +inline void put_8bit(uint8_t *v, uint8_t data) { *v = data; } +inline void put_16bit(uint8_t *v, uint16_t data) { *reinterpret_cast<uint16_t *>(v) = big_endianize_int16(data); } +inline void put_32bit(uint8_t *v, uint32_t data) { *reinterpret_cast<uint32_t *>(v) = big_endianize_int32(data); } + + class png_private { private: @@ -137,6 +135,8 @@ private: return PNGERR_UNSUPPORTED_FORMAT; // unknown colour format if ((0 != pnginfo.interlace_method) && (1 != pnginfo.interlace_method)) return PNGERR_UNSUPPORTED_FORMAT; // unknown interlace method + if ((3 == pnginfo.color_type) && (!pnginfo.num_palette || !pnginfo.palette)) + return PNGERR_FILE_CORRUPT; // indexed colour with no palette // calculate the offset for each pass of the interlace unsigned const pass_count(get_pass_count()); @@ -306,10 +306,14 @@ private: case PNG_CN_PLTE: // palette pnginfo.num_palette = length / 3; + if ((length % 3) || ((3 == pnginfo.color_type) && ((1 << pnginfo.bit_depth) < pnginfo.num_palette))) + return PNGERR_FILE_CORRUPT; pnginfo.palette = std::move(data); break; case PNG_CN_tRNS: // transparency information + if (((0 == pnginfo.color_type) && (2 > length)) || ((2 == pnginfo.color_type) && (6 > length))) + return PNGERR_FILE_CORRUPT; pnginfo.num_trans = length; pnginfo.trans = std::move(data); break; @@ -334,24 +338,32 @@ private: break; case PNG_CN_tEXt: // text + try { - // allocate a new text item - png_text *text; - try { text = new png_text; } - catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } - - // set the elements - text->keyword = (char *)data.release(); - text->text = text->keyword + strlen(text->keyword) + 1; - text->next = nullptr; + // split into keyword and string + std::uint8_t const *kwbegin(&data[0]); + std::uint8_t const *const textend(kwbegin + length); + std::uint8_t const *const kwend(std::find(kwbegin, textend, '\0')); + std::uint8_t const *textbegin(kwend + ((textend == kwend) ? 0 : 1)); + + // text is ISO-8859-1 but MAME likes UTF-8 + std::size_t buflen(2 * std::max(kwend - kwbegin, textend - textbegin)); + std::unique_ptr<char []> utf8buf(new char [buflen]); + char const *const bufend(utf8buf.get() + buflen); + char *dst; + for (dst = utf8buf.get(); kwend > kwbegin; dst += utf8_from_uchar(dst, bufend - dst, *kwbegin++)) { } + std::string keyword(utf8buf.get(), dst); + for (dst = utf8buf.get(); textend > textbegin; dst += utf8_from_uchar(dst, bufend - dst, *textbegin++)) { } + std::string text(utf8buf.get(), dst); - // add to the end of the list - png_text *pt, *ct; - for (pt = nullptr, ct = pnginfo.textlist; ct != nullptr; pt = ct, ct = ct->next) { } - (pt ? pt->next : pnginfo.textlist) = text; - - break; + // allocate a new text item + pnginfo.textlist.emplace_back(std::move(keyword), std::move(text)); + } + catch (std::bad_alloc const &) + { + return PNGERR_OUT_OF_MEMORY; } + break; /* anything else */ default: @@ -530,12 +542,16 @@ public: { // handle grayscale non-alpha case uint32_t const bpp(pnginfo.bit_depth >> 3); + std::uint16_t const transpen(pnginfo.trans ? fetch_16bit(pnginfo.trans.get()) : 0U); for (std::uint32_t y = 0; dimensions.second > y; ++y) { for (std::uint32_t x = 0; dimensions.first > x; ++x, src += bpp) { - rgb_t const pix(0xff, src[0], src[0], src[0]); - bitmap.pix32((y << y_shift) + y_offs, (x << x_shift) + x_offs) = pix; + std::uint16_t i((8 < pnginfo.bit_depth) ? fetch_16bit(src) : fetch_8bit(src)); + std::uint8_t const a((pnginfo.trans && (transpen == i)) ? 0x00 : 0xff); + i >>= (8 < pnginfo.bit_depth) ? 8 : 0; + accumalpha &= a; + bitmap.pix32((y << y_shift) + y_offs, (x << x_shift) + x_offs) = rgb_t(a, i, i, i); } } } @@ -622,6 +638,7 @@ public: try { outbuf.reset(new std::uint8_t [outp_offset[pass_count]]); } catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } + // upsample bitmap std::uint8_t const bytesamples(8 / pnginfo.bit_depth); for (unsigned pass = 0; pass_count > pass; ++pass) { @@ -662,6 +679,15 @@ public: } } + // upsample transparent pen as well + for (std::uint32_t i = 0; (pnginfo.num_trans >> 1) > i; ++i) + { + std::uint16_t pen(fetch_16bit(&pnginfo.trans[i << 1])); + for (unsigned k = 4; pnginfo.bit_depth <= k; k >>= 1) + pen |= pen << k; + put_16bit(&pnginfo.trans[i << 1], pen); + } + pnginfo.image = std::move(outbuf); pnginfo.bit_depth = 8; return PNGERR_NONE; @@ -790,31 +816,37 @@ png_error png_info::expand_buffer_8bit() png_error png_info::add_text(const char *keyword, const char *text) { - // allocate a new text element - std::unique_ptr<png_text> newtext; - try { newtext.reset(new png_text); } - catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } - - // allocate a string long enough to hold both - std::size_t const keylen(std::strlen(keyword)); - char *textdata; - try { textdata = reinterpret_cast<char *>(new std::uint8_t [keylen + 1 + std::strlen(text) + 1]); } - catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } - - // copy in the data - std::strcpy(textdata, keyword); - std::strcpy(textdata + keylen + 1, text); - - // text follows a trailing nullptr - newtext->keyword = textdata; - newtext->text = textdata + keylen + 1; - newtext->next = nullptr; + // apply rules to keyword + char32_t prev(0); + std::size_t cnt(0); + char const *const kwend(keyword + std::strlen(keyword)); + for (char const *ptr = keyword; kwend > ptr; ) + { + char32_t ch; + int const len(uchar_from_utf8(&ch, ptr, kwend - ptr)); + if ((0 >= len) || (32 > ch) || (255 < ch) || ((126 < ch) && (161 > ch)) || (((32 == prev) || (keyword == ptr)) && (32 == ch))) + return PNGERR_UNSUPPORTED_FORMAT; + prev = ch; + ++cnt; + ptr += len; + } + if ((32 == prev) || (1 > cnt) || (79 < cnt)) + return PNGERR_UNSUPPORTED_FORMAT; - // add us to the end of the linked list - png_text *pt, *ct; - for (pt = nullptr, ct = textlist; ct != nullptr; pt = ct, ct = ct->next) { } - (pt ? pt->next : textlist) = newtext.release(); + // apply rules to text + char const *const textend(text + std::strlen(text)); + for (char const *ptr = text; textend > ptr; ) + { + char32_t ch; + int const len(uchar_from_utf8(&ch, ptr, textend - ptr)); + if ((0 >= len) || (1 > ch) || (255 < ch)) + return PNGERR_UNSUPPORTED_FORMAT; + ptr += len; + } + // allocate a new text element + try { textlist.emplace_back(std::piecewise_construct, std::forward_as_tuple(keyword, kwend), std::forward_as_tuple(text, textend)); } + catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } return PNGERR_NONE; } @@ -1087,10 +1119,9 @@ static png_error convert_bitmap_to_image_rgb(png_info &pnginfo, const bitmap_t & static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette) { uint8_t tempbuff[16]; - png_text *text; png_error error; - /* create an unfiltered image in either palette or RGB form */ + // create an unfiltered image in either palette or RGB form if (bitmap.format() == BITMAP_FORMAT_IND16 && palette_length <= 256) error = convert_bitmap_to_image_palette(pnginfo, bitmap, palette_length, palette); else @@ -1098,9 +1129,9 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const if (error != PNGERR_NONE) return error; - /* if we wanted to get clever and do filtering, we would do it here */ + // if we wanted to get clever and do filtering, we would do it here - /* write the IHDR chunk */ + // write the IHDR chunk put_32bit(tempbuff + 0, pnginfo.width); put_32bit(tempbuff + 4, pnginfo.height); put_8bit(tempbuff + 8, pnginfo.bit_depth); @@ -1112,26 +1143,58 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const if (error != PNGERR_NONE) return error; - /* write the PLTE chunk */ + // write the PLTE chunk if (pnginfo.num_palette > 0) error = write_chunk(fp, pnginfo.palette.get(), PNG_CN_PLTE, pnginfo.num_palette * 3); if (error != PNGERR_NONE) return error; - /* write a single IDAT chunk */ + // write a single IDAT chunk */ error = write_deflated_chunk(fp, pnginfo.image.get(), PNG_CN_IDAT, pnginfo.height * (compute_rowbytes(pnginfo) + 1)); if (error != PNGERR_NONE) return error; - /* write TEXT chunks */ - for (text = pnginfo.textlist; text != nullptr; text = text->next) + // write TEXT chunks + std::vector<std::uint8_t> textbuf; + for (png_info::png_text const &text : pnginfo.textlist) { - error = write_chunk(fp, (uint8_t *)text->keyword, PNG_CN_tEXt, (uint32_t)strlen(text->keyword) + 1 + (uint32_t)strlen(text->text)); + try { textbuf.resize(text.first.length() + 1 + text.second.length()); } + catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; } + std::uint8_t *dst(&textbuf[0]); + + // convert keyword to ISO-8859-1 + char const *const kwend(text.first.c_str() + text.first.length()); + for (char const *src = text.first.c_str(); kwend > src; ++dst) + { + char32_t ch; + int const len(uchar_from_utf8(&ch, src, kwend - src)); + if (0 >= len) + break; + *dst = std::uint8_t(ch); + src += len; + } + + // NUL separator between keword and text + *dst++ = 0; + + // convert text to ISO-8859-1 + char const *const textend(text.second.c_str() + text.second.length()); + for (char const *src = text.second.c_str(); textend > src; ++dst) + { + char32_t ch; + int const len(uchar_from_utf8(&ch, src, textend - src)); + if (0 >= len) + break; + *dst = std::uint8_t(ch); + src += len; + } + + error = write_chunk(fp, &textbuf[0], PNG_CN_tEXt, dst - &textbuf[0]); if (error != PNGERR_NONE) return error; } - /* write an IEND chunk */ + // write an IEND chunk return write_chunk(fp, nullptr, PNG_CN_IEND, 0); } diff --git a/src/lib/util/png.h b/src/lib/util/png.h index 9d54b2d30b5..9721df4ca94 100644 --- a/src/lib/util/png.h +++ b/src/lib/util/png.h @@ -17,7 +17,10 @@ #include "corefile.h" #include "osdcore.h" +#include <list> #include <memory> +#include <string> +#include <utility> @@ -25,40 +28,6 @@ CONSTANTS ***************************************************************************/ -#define PNG_Signature "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" -#define MNG_Signature "\x8A\x4D\x4E\x47\x0D\x0A\x1A\x0A" - -/* Chunk names */ -#define PNG_CN_IHDR 0x49484452L -#define PNG_CN_PLTE 0x504C5445L -#define PNG_CN_IDAT 0x49444154L -#define PNG_CN_IEND 0x49454E44L -#define PNG_CN_gAMA 0x67414D41L -#define PNG_CN_sBIT 0x73424954L -#define PNG_CN_cHRM 0x6348524DL -#define PNG_CN_tRNS 0x74524E53L -#define PNG_CN_bKGD 0x624B4744L -#define PNG_CN_hIST 0x68495354L -#define PNG_CN_tEXt 0x74455874L -#define PNG_CN_zTXt 0x7A545874L -#define PNG_CN_pHYs 0x70485973L -#define PNG_CN_oFFs 0x6F464673L -#define PNG_CN_tIME 0x74494D45L -#define PNG_CN_sCAL 0x7343414CL - -/* MNG Chunk names */ -#define MNG_CN_MHDR 0x4D484452L -#define MNG_CN_MEND 0x4D454E44L -#define MNG_CN_TERM 0x5445524DL -#define MNG_CN_BACK 0x4241434BL - -/* Prediction filters */ -#define PNG_PF_None 0 -#define PNG_PF_Sub 1 -#define PNG_PF_Up 2 -#define PNG_PF_Average 3 -#define PNG_PF_Paeth 4 - /* Error types */ enum png_error { @@ -81,17 +50,11 @@ enum png_error TYPE DEFINITIONS ***************************************************************************/ -struct png_text -{ - png_text * next; - const char * keyword; /* this is allocated */ - const char * text; /* this points to a part of keyword */ -}; - - class png_info { public: + using png_text = std::pair<std::string, std::string>; + ~png_info() { free_data(); } png_error read_file(util::core_file &fp); @@ -122,7 +85,7 @@ public: std::unique_ptr<std::uint8_t []> trans = 0; std::uint32_t num_trans = 0; - png_text * textlist = nullptr; + std::list<png_text> textlist; private: png_info &operator=(png_info &&) = default; |