| // pngreader.cpp - Public Domain - see unlicense at bottom of file. |
| // |
| // Notes: |
| // This is ancient code from ~1995 ported to C++. It was originally written for a |
| // DOS app with very limited memory. It's not as fast as it should be, but it works. |
| // The low-level PNG reader class was written assuming the PNG file could not fit |
| // entirely into memory, which dictated how it was written/structured. |
| // It has been modified to use either zlib or miniz. |
| // It supports all PNG color types/bit depths/interlacing, however 16-bit/component |
| // images are converted to 8-bit. |
| // TRNS chunks are converted to alpha as needed. |
| // GAMA chunk is read, but not applied. |
| |
| #include "../transcoder/basisu.h" |
| |
| #define MINIZ_HEADER_FILE_ONLY |
| #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES |
| #include "basisu_miniz.h" |
| |
| #include "pvpngreader.h" |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <math.h> |
| #include <string.h> |
| #include <vector> |
| #include <assert.h> |
| |
| #define PVPNG_IDAT_CRC_CHECKING (1) |
| #define PVPNG_ADLER32_CHECKING (1) |
| |
| namespace pv_png |
| { |
| |
| const uint32_t MIN_PNG_SIZE = 8 + 13 + 8 + 1 + 4 + 12; |
| |
| template <typename S> inline S maximum(S a, S b) { return (a > b) ? a : b; } |
| template <typename S> inline S minimum(S a, S b) { return (a < b) ? a : b; } |
| |
| template <typename T> inline void clear_obj(T& obj) { memset(&obj, 0, sizeof(obj)); } |
| |
| #define MAX_SUPPORTED_RES (32768) |
| #define FALSE (0) |
| #define TRUE (1) |
| #define PNG_MAX_ALLOC_BLOCKS (16) |
| |
| enum |
| { |
| PNG_DECERROR = -3, |
| PNG_ALLDONE = -5, |
| PNG_READPASTEOF = -11, |
| PNG_UNKNOWNTYPE = -16, |
| PNG_FILEREADERROR = -17, |
| PNG_NOTENOUGHMEM = -108, |
| PNG_BAD_CHUNK_CRC32 = -13000, |
| PNG_NO_IHDR = -13001, |
| PNG_BAD_WIDTH = -13002, |
| PNG_BAD_HEIGHT = -13003, |
| PNG_UNS_COMPRESSION = -13004, |
| PNG_UNS_FILTER = -13005, |
| PNG_UNS_ILACE = -13006, |
| PNG_UNS_COLOR_TYPE = -13007, |
| PNG_BAD_BIT_DEPTH = -13008, |
| PNG_BAD_CHUNK_SIZE = -13009, |
| PNG_UNS_CRITICAL_CHUNK = -13010, |
| PNG_BAD_TRNS_CHUNK = -13011, |
| PNG_BAD_PLTE_CHUNK = -13012, |
| PNG_UNS_RESOLUTION = -13013, |
| PNG_INVALID_DATA_STREAM = -13014, |
| PNG_MISSING_PALETTE = -13015, |
| PNG_UNS_PREDICTOR = -13016, |
| PNG_INCOMPLETE_IMAGE = -13017, |
| PNG_TOO_MUCH_DATA = -13018 |
| }; |
| |
| #define PNG_COLOR_TYPE_PAL_MASK (1) |
| #define PNG_COLOR_TYPE_COL_MASK (2) |
| #define PNG_COLOR_TYPE_ALP_MASK (4) |
| |
| #define PNG_INFLATE_SRC_BUF_SIZE (4096) |
| |
| struct ihdr_struct |
| { |
| uint32_t m_width; |
| uint32_t m_height; |
| uint8_t m_bit_depth; |
| uint8_t m_color_type; |
| uint8_t m_comp_type; |
| uint8_t m_filter_type; |
| uint8_t m_ilace_type; |
| }; |
| |
| class png_file |
| { |
| public: |
| png_file() { } |
| virtual ~png_file() { } |
| |
| virtual bool resize(uint64_t new_size) = 0; |
| virtual uint64_t get_size() = 0; |
| virtual uint64_t tell() = 0; |
| virtual bool seek(uint64_t ofs) = 0; |
| virtual size_t write(const void* pBuf, size_t len) = 0; |
| virtual size_t read(void* pBuf, size_t len) = 0; |
| }; |
| |
| class png_memory_file : public png_file |
| { |
| public: |
| std::vector<uint8_t> m_buf; |
| uint64_t m_ofs; |
| |
| png_memory_file() : |
| png_file(), |
| m_ofs(0) |
| { |
| } |
| |
| virtual ~png_memory_file() |
| { |
| } |
| |
| std::vector<uint8_t>& get_buf() { return m_buf; } |
| const std::vector<uint8_t>& get_buf() const { return m_buf; } |
| |
| void init() |
| { |
| m_ofs = 0; |
| m_buf.resize(0); |
| } |
| |
| virtual bool resize(uint64_t new_size) |
| { |
| if ((sizeof(size_t) == sizeof(uint32_t)) && (new_size >= 0x7FFFFFFF)) |
| return false; |
| |
| m_buf.resize((size_t)new_size); |
| m_ofs = m_buf.size(); |
| |
| return true; |
| } |
| |
| virtual uint64_t get_size() |
| { |
| return m_buf.size(); |
| } |
| |
| virtual uint64_t tell() |
| { |
| return m_ofs; |
| } |
| |
| virtual bool seek(uint64_t ofs) |
| { |
| m_ofs = ofs; |
| return true; |
| } |
| |
| virtual size_t write(const void* pBuf, size_t len) |
| { |
| uint64_t new_size = m_ofs + len; |
| if (new_size > m_buf.size()) |
| { |
| if ((sizeof(size_t) == sizeof(uint32_t)) && (new_size > 0x7FFFFFFFUL)) |
| return 0; |
| m_buf.resize(new_size); |
| } |
| |
| memcpy(&m_buf[(size_t)m_ofs], pBuf, len); |
| m_ofs += len; |
| |
| return len; |
| } |
| |
| virtual size_t read(void* pBuf, size_t len) |
| { |
| if (m_ofs >= m_buf.size()) |
| return 0; |
| |
| uint64_t max_bytes = minimum<uint64_t>(len, m_buf.size() - m_ofs); |
| memcpy(pBuf, &m_buf[(size_t)m_ofs], max_bytes); |
| |
| m_ofs += max_bytes; |
| |
| return max_bytes; |
| } |
| }; |
| |
| class png_readonly_memory_file : public png_file |
| { |
| public: |
| const uint8_t* m_pBuf; |
| size_t m_buf_size; |
| uint64_t m_ofs; |
| |
| png_readonly_memory_file() : |
| png_file(), |
| m_pBuf(nullptr), |
| m_buf_size(0), |
| m_ofs(0) |
| { |
| } |
| |
| virtual ~png_readonly_memory_file() |
| { |
| } |
| |
| void init(const void *pBuf, size_t buf_size) |
| { |
| m_pBuf = static_cast<const uint8_t*>(pBuf); |
| m_buf_size = buf_size; |
| m_ofs = 0; |
| } |
| |
| virtual bool resize(uint64_t new_size) |
| { |
| (void)new_size; |
| assert(0); |
| return false; |
| } |
| |
| virtual uint64_t get_size() |
| { |
| return m_buf_size; |
| } |
| |
| virtual uint64_t tell() |
| { |
| return m_ofs; |
| } |
| |
| virtual bool seek(uint64_t ofs) |
| { |
| m_ofs = ofs; |
| return true; |
| } |
| |
| virtual size_t write(const void* pBuf, size_t len) |
| { |
| (void)pBuf; |
| (void)len; |
| assert(0); |
| return 0; |
| } |
| |
| virtual size_t read(void* pBuf, size_t len) |
| { |
| if (m_ofs >= m_buf_size) |
| return 0; |
| |
| uint64_t max_bytes = minimum<uint64_t>(len, m_buf_size - m_ofs); |
| memcpy(pBuf, &m_pBuf[(size_t)m_ofs], max_bytes); |
| |
| m_ofs += max_bytes; |
| |
| return max_bytes; |
| } |
| }; |
| |
| #ifdef _MSC_VER |
| #define ftell64 _ftelli64 |
| #define fseek64 _fseeki64 |
| #else |
| #define ftell64 ftello |
| #define fseek64 fseeko |
| #endif |
| |
| class png_cfile : public png_file |
| { |
| public: |
| FILE* m_pFile; |
| |
| png_cfile() : |
| png_file(), |
| m_pFile(nullptr) |
| { |
| } |
| |
| virtual ~png_cfile() |
| { |
| close(); |
| } |
| |
| bool init(const char *pFilename, const char *pMode) |
| { |
| close(); |
| |
| m_pFile = nullptr; |
| |
| #ifdef _MSC_VER |
| fopen_s(&m_pFile, pFilename, pMode); |
| #else |
| m_pFile = fopen(pFilename, pMode); |
| #endif |
| |
| return m_pFile != nullptr; |
| } |
| |
| bool close() |
| { |
| bool status = true; |
| if (m_pFile) |
| { |
| if (fclose(m_pFile) == EOF) |
| status = false; |
| m_pFile = nullptr; |
| } |
| return status; |
| } |
| |
| virtual bool resize(uint64_t new_size) |
| { |
| if (new_size) |
| { |
| if (!seek(new_size - 1)) |
| return false; |
| |
| int v = 0; |
| if (write(&v, 1) != 1) |
| return false; |
| } |
| else |
| { |
| if (!seek(0)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| virtual uint64_t get_size() |
| { |
| int64_t cur_ofs = ftell64(m_pFile); |
| if (cur_ofs < 0) |
| return 0; |
| |
| if (fseek64(m_pFile, 0, SEEK_END) != 0) |
| return 0; |
| |
| const int64_t cur_size = ftell64(m_pFile); |
| if (cur_size < 0) |
| return 0; |
| |
| if (fseek64(m_pFile, cur_ofs, SEEK_SET) != 0) |
| return 0; |
| |
| return cur_size; |
| } |
| |
| virtual uint64_t tell() |
| { |
| int64_t cur_ofs = ftell64(m_pFile); |
| if (cur_ofs < 0) |
| return 0; |
| |
| return cur_ofs; |
| } |
| |
| virtual bool seek(uint64_t ofs) |
| { |
| return fseek64(m_pFile, ofs, SEEK_SET) == 0; |
| } |
| |
| virtual size_t write(const void* pBuf, size_t len) |
| { |
| return (size_t)fwrite(pBuf, 1, len, m_pFile); |
| } |
| |
| virtual size_t read(void* pBuf, size_t len) |
| { |
| return (size_t)fread(pBuf, 1, len, m_pFile); |
| } |
| }; |
| |
| // This low-level helper class handles the actual decoding of PNG files. |
| class png_decoder |
| { |
| public: |
| png_decoder(); |
| ~png_decoder(); |
| |
| // Scans the PNG file, but doesn't decode the IDAT data. |
| // Returns 0 on success, or an error code. |
| // If the returned status is non-zero, or m_img_supported_flag==FALSE the image either the image is corrupted/not PNG or is unsupported in some way. |
| int png_scan(png_file *pFile); |
| |
| // Decodes a single scanline of PNG image data. |
| // Returns a pointer to the scanline's pixel data and its size in bytes. |
| // This data is only minimally processed from the internal PNG pixel data. |
| // The caller must use the ihdr, trns_flag and values, and the palette to actually decode the pixel data. |
| // |
| // Possible returned pixel formats is somewhat complex due to the history of this code: |
| // 8-bit RGBA, always 4 bytes/pixel - 24bpp PNG's are converted to 32bpp and TRNS processing is done automatically (8/16bpp RGB or RGBA PNG files) |
| // 1/2/4/8-bit grayscale, 1 byte per pixel - must convert to [0,255] using the palette or some other means, must optionally use the TRNS chunk for alpha (1/2/4/8 Grayscale PNG files - not 16bpp though!) |
| // 1/2/4/8-bit palettized, 1 byte per pixel - must convert to RGB using the 24bpp palette and optionally the TRNS chunk for alpha (1/2/4/8bpp palettized PNG files) |
| // 8-bit grayscale with alpha, 2 bytes per pixel - TRNS processing will be done for you on 16bpp images (there's a special case here for 16bpp Grey files) (8/16bpp Gray-Alpha *or 16bpp Grayscale* PNG files) |
| // |
| // Returns 0 on success, a non-zero error code, or PNG_ALLDONE. |
| int png_decode(void** ppImg_ptr, uint32_t* pImg_len); |
| |
| // Starts decoding. Returns 0 on success, otherwise an error code. |
| int png_decode_start(); |
| |
| // Deinitializes the decoder, freeing all allocations. |
| void png_decode_end(); |
| |
| png_file* m_pFile; |
| |
| // Image's 24bpp palette - 3 bytes per entry |
| uint8_t m_plte_flag; |
| uint8_t m_img_pal[768]; |
| |
| int m_img_supported_flag; |
| |
| ihdr_struct m_ihdr; |
| |
| uint8_t m_chunk_flag; |
| uint32_t m_chunk_size; |
| uint32_t m_chunk_left; |
| uint32_t m_chunk_crc32; |
| uint8_t m_chunk_name[4]; |
| |
| uint8_t m_end_of_idat_chunks; |
| |
| void* m_pMalloc_blocks[PNG_MAX_ALLOC_BLOCKS]; |
| |
| uint32_t m_dec_bytes_per_pixel; // bytes per pixel decoded from the PNG file (minimum 1 for 1/2/4 bpp), factors in the PNG 8/16 bit/component bit depth, may be up to 8 bytes (2*4) |
| uint32_t m_dst_bytes_per_pixel; // bytes per pixel returned to the caller (1-4), always has alpha if the PNG has alpha, 16-bit components always converted to 8-bits/component |
| |
| uint32_t m_dec_bytes_per_line; // bytes per line decoded from the PNG file (before 1/2/4 expansion), +1 for the filter byte |
| uint32_t m_src_bytes_per_line; // decoded PNG bytes per line, before 1/2/4 bpp expansion, not counting the filter byte, updated during adam7 deinterlacing |
| uint32_t m_dst_bytes_per_line; // bytes per line returned to the caller (1-4 times width) |
| |
| int (*m_pProcess_func)(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi); |
| |
| uint8_t* m_pPre_line_buf; |
| uint8_t* m_pCur_line_buf; |
| uint8_t* m_pPro_line_buf; |
| |
| uint8_t m_bkgd_flag; |
| uint32_t m_bkgd_value[3]; |
| |
| uint8_t m_gama_flag; |
| uint32_t m_gama_value; |
| |
| uint8_t m_trns_flag; |
| uint32_t m_trns_value[256]; |
| |
| buminiz::mz_stream m_inflator; |
| |
| uint8_t inflate_src_buf[PNG_INFLATE_SRC_BUF_SIZE]; |
| |
| uint32_t m_inflate_src_buf_ofs; |
| uint32_t m_inflate_src_buf_size; |
| uint32_t m_inflate_dst_buf_ofs; |
| |
| int m_inflate_eof_flag; |
| |
| uint8_t m_gamma_table[256]; |
| |
| int m_pass_x_size; |
| int m_pass_y_left; |
| |
| int m_adam7_pass_num; |
| int m_adam7_pass_y; |
| int m_adam7_pass_size_x[7]; |
| int m_adam7_pass_size_y[7]; |
| |
| std::vector<uint8_t> m_adam7_image_buf; |
| |
| int m_adam7_decoded_flag; |
| |
| bool m_scanned_flag; |
| |
| int m_terminate_status; |
| |
| #define TEMP_BUF_SIZE (384) |
| uint8_t m_temp_buf[TEMP_BUF_SIZE * 4]; |
| |
| void clear(); |
| void uninitialize(); |
| int terminate(int status); |
| void* png_malloc(uint32_t i); |
| void* png_calloc(uint32_t i); |
| int block_read(void* buf, uint32_t len); |
| int64_t block_read_dword(); |
| int fetch_next_chunk_data(uint8_t* buf, int bytes); |
| int fetch_next_chunk_byte(); |
| int fetch_next_chunk_word(); |
| int64_t fetch_next_chunk_dword(); |
| int fetch_next_chunk_init(); |
| int unchunk_data(uint8_t* buf, uint32_t bytes, uint32_t* ptr_bytes_read); |
| inline void adam7_write_pixel_8(int x, int y, int c); |
| inline void adam7_write_pixel_16(int x, int y, int r, int g); |
| inline void adam7_write_pixel_24(int x, int y, int r, int g, int b); |
| inline void adam7_write_pixel_32(int x, int y, int r, int g, int b, int a); |
| void unpredict_sub(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp); |
| void unpredict_up(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp); |
| void unpredict_average(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp); |
| inline uint8_t paeth_predictor(int a, int b, int c); |
| void unpredict_paeth(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp); |
| int adam7_pass_size(int size, int start, int step); |
| int decompress_line(uint32_t* bytes_decoded); |
| int find_iend_chunk(); |
| void calc_gamma_table(); |
| void create_grey_palette(); |
| int read_signature(); |
| int read_ihdr_chunk(); |
| int read_bkgd_chunk(); |
| int read_gama_chunk(); |
| int read_trns_chunk(); |
| int read_plte_chunk(); |
| int find_idat_chunk(); |
| }; |
| |
| void png_decoder::uninitialize() |
| { |
| m_pFile = nullptr; |
| |
| for (int i = 0; i < PNG_MAX_ALLOC_BLOCKS; i++) |
| { |
| free(m_pMalloc_blocks[i]); |
| m_pMalloc_blocks[i] = nullptr; |
| } |
| |
| mz_inflateEnd(&m_inflator); |
| } |
| |
| int png_decoder::terminate(int status) |
| { |
| if (m_terminate_status == 0) |
| m_terminate_status = status; |
| |
| uninitialize(); |
| return status; |
| } |
| |
| void* png_decoder::png_malloc(uint32_t len) |
| { |
| if (!len) |
| len++; |
| |
| void* p = malloc(len); |
| |
| if (!p) |
| return nullptr; |
| |
| int j; |
| for (j = 0; j < PNG_MAX_ALLOC_BLOCKS; j++) |
| if (!m_pMalloc_blocks[j]) |
| break; |
| |
| if (j == PNG_MAX_ALLOC_BLOCKS) |
| return nullptr; |
| |
| m_pMalloc_blocks[j] = p; |
| |
| return p; |
| } |
| |
| void* png_decoder::png_calloc(uint32_t len) |
| { |
| void* p = png_malloc(len); |
| if (!p) |
| return nullptr; |
| |
| if (p) |
| memset(p, 0, len); |
| |
| return p; |
| } |
| |
| int png_decoder::block_read(void* buf, uint32_t len) |
| { |
| size_t bytes_read = m_pFile->read(buf, len); |
| if (bytes_read != len) |
| return terminate(PNG_READPASTEOF); |
| return 0; |
| } |
| |
| int64_t png_decoder::block_read_dword() |
| { |
| uint8_t buf[4]; |
| |
| int status = block_read(buf, 4); |
| if (status != 0) |
| return status; |
| |
| uint32_t v = buf[3] + ((uint32_t)buf[2] << 8) + ((uint32_t)buf[1] << 16) + ((uint32_t)buf[0] << 24); |
| return (int64_t)v; |
| } |
| |
| int png_decoder::fetch_next_chunk_data(uint8_t* buf, int bytes) |
| { |
| if (!m_chunk_flag) |
| return 0; |
| |
| bytes = minimum<int>(bytes, m_chunk_left); |
| |
| int status = block_read(buf, bytes); |
| if (status != 0) |
| return status; |
| |
| #if PVPNG_IDAT_CRC_CHECKING |
| bool check_crc32 = true; |
| #else |
| const bool is_idat = (m_chunk_name[0] == 'I') && (m_chunk_name[1] == 'D') && (m_chunk_name[2] == 'A') && (m_chunk_name[3] == 'T'); |
| bool check_crc32 = !is_idat; |
| #endif |
| |
| if (check_crc32) |
| m_chunk_crc32 = buminiz::mz_crc32(m_chunk_crc32, buf, bytes); |
| |
| if ((m_chunk_left -= bytes) == 0) |
| { |
| int64_t res = block_read_dword(); |
| if (res < 0) |
| return (int)res; |
| |
| if (check_crc32) |
| { |
| if (m_chunk_crc32 != (uint32_t)res) |
| return terminate(PNG_BAD_CHUNK_CRC32); |
| } |
| |
| m_chunk_flag = FALSE; |
| } |
| |
| return bytes; |
| } |
| |
| int png_decoder::fetch_next_chunk_byte() |
| { |
| uint8_t buf[1]; |
| |
| int status = fetch_next_chunk_data(buf, 1); |
| if (status < 0) |
| return status; |
| |
| if (status != 1) |
| return terminate(PNG_BAD_CHUNK_SIZE); |
| |
| return buf[0]; |
| } |
| |
| int png_decoder::fetch_next_chunk_word() |
| { |
| uint8_t buf[2]; |
| |
| int status = fetch_next_chunk_data(buf, 2); |
| if (status < 0) |
| return status; |
| |
| if (status != 2) |
| return terminate(PNG_BAD_CHUNK_SIZE); |
| |
| return buf[1] + ((uint32_t)buf[0] << 8); |
| } |
| |
| int64_t png_decoder::fetch_next_chunk_dword() |
| { |
| uint8_t buf[4]; |
| |
| int status = fetch_next_chunk_data(buf, 4); |
| if (status < 0) |
| return status; |
| |
| if (status != 4) |
| terminate(PNG_BAD_CHUNK_SIZE); |
| |
| uint32_t v = buf[3] + ((uint32_t)buf[2] << 8) + ((uint32_t)buf[1] << 16) + ((uint32_t)buf[0] << 24); |
| return (int64_t)v; |
| } |
| |
| int png_decoder::fetch_next_chunk_init() |
| { |
| while (m_chunk_flag) |
| { |
| int status = fetch_next_chunk_data(m_temp_buf, TEMP_BUF_SIZE * 4); |
| if (status != 0) |
| return status; |
| } |
| |
| int64_t n = block_read_dword(); |
| if (n < 0) |
| return (int)n; |
| |
| m_chunk_size = (uint32_t)n; |
| |
| m_chunk_flag = TRUE; |
| m_chunk_left = m_chunk_size + 4; |
| m_chunk_crc32 = 0; |
| |
| int status = fetch_next_chunk_data(m_chunk_name, 4); |
| if (status < 0) |
| return status; |
| |
| return 0; |
| } |
| |
| int png_decoder::unchunk_data(uint8_t* buf, uint32_t bytes, uint32_t* ptr_bytes_read) |
| { |
| uint32_t bytes_read = 0; |
| |
| if ((!bytes) || (m_end_of_idat_chunks)) |
| { |
| *ptr_bytes_read = 0; |
| return TRUE; |
| } |
| |
| while (bytes_read != bytes) |
| { |
| if (!m_chunk_flag) |
| { |
| int res = fetch_next_chunk_init(); |
| if (res < 0) |
| return res; |
| |
| if ((m_chunk_name[0] != 'I') || |
| (m_chunk_name[1] != 'D') || |
| (m_chunk_name[2] != 'A') || |
| (m_chunk_name[3] != 'T')) |
| { |
| *ptr_bytes_read = bytes_read; |
| m_end_of_idat_chunks = TRUE; |
| return TRUE; |
| } |
| } |
| |
| int res = fetch_next_chunk_data(buf + bytes_read, bytes - bytes_read); |
| if (res < 0) |
| return res; |
| |
| bytes_read += (uint32_t)res; |
| } |
| |
| *ptr_bytes_read = bytes_read; |
| |
| return FALSE; |
| } |
| |
| inline void png_decoder::adam7_write_pixel_8(int x, int y, int c) |
| { |
| m_adam7_image_buf[x + y * m_dst_bytes_per_line] = (uint8_t)c; |
| } |
| |
| inline void png_decoder::adam7_write_pixel_16(int x, int y, int r, int g) |
| { |
| uint32_t ofs = x * 2 + y * m_dst_bytes_per_line; |
| m_adam7_image_buf[ofs + 0] = (uint8_t)r; |
| m_adam7_image_buf[ofs + 1] = (uint8_t)g; |
| } |
| |
| inline void png_decoder::adam7_write_pixel_24(int x, int y, int r, int g, int b) |
| { |
| uint32_t ofs = x * 3 + y * m_dst_bytes_per_line; |
| m_adam7_image_buf[ofs + 0] = (uint8_t)r; |
| m_adam7_image_buf[ofs + 1] = (uint8_t)g; |
| m_adam7_image_buf[ofs + 2] = (uint8_t)b; |
| } |
| |
| inline void png_decoder::adam7_write_pixel_32(int x, int y, int r, int g, int b, int a) |
| { |
| uint32_t ofs = x * 4 + y * m_dst_bytes_per_line; |
| m_adam7_image_buf[ofs + 0] = (uint8_t)r; |
| m_adam7_image_buf[ofs + 1] = (uint8_t)g; |
| m_adam7_image_buf[ofs + 2] = (uint8_t)b; |
| m_adam7_image_buf[ofs + 3] = (uint8_t)a; |
| } |
| |
| static void PixelDePack2(void* src, void* dst, int numbytes) |
| { |
| uint8_t* src8 = (uint8_t*)src; |
| uint8_t* dst8 = (uint8_t*)dst; |
| |
| while (numbytes) |
| { |
| uint8_t v = *src8++; |
| |
| for (uint32_t i = 0; i < 8; i++) |
| dst8[7 - i] = (v >> i) & 1; |
| |
| dst8 += 8; |
| numbytes--; |
| } |
| } |
| |
| static void PixelDePack16(void* src, void* dst, int numbytes) |
| { |
| uint8_t* src8 = (uint8_t*)src; |
| uint8_t* dst8 = (uint8_t*)dst; |
| |
| while (numbytes) |
| { |
| uint8_t v = *src8++; |
| |
| dst8[0] = (uint8_t)v >> 4; |
| dst8[1] = (uint8_t)v & 0xF; |
| dst8 += 2; |
| |
| numbytes--; |
| } |
| } |
| |
| static int unpack_grey_1(uint8_t* src, uint8_t* dst, int pixels, png_decoder *pwi) |
| { |
| (void)pwi; |
| PixelDePack2(src, dst, pixels >> 3); |
| |
| dst += (pixels & 0xFFF8); |
| |
| if ((pixels & 7) != 0) |
| { |
| uint8_t c = src[pixels >> 3]; |
| |
| pixels &= 7; |
| |
| while (pixels--) |
| { |
| *dst++ = ((c & 128) >> 7); |
| |
| c <<= 1; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_2(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| int i = pixels; |
| uint8_t c; |
| |
| while (i >= 4) |
| { |
| c = *src++; |
| |
| *dst++ = (c >> 6); |
| *dst++ = (c >> 4) & 3; |
| *dst++ = (c >> 2) & 3; |
| *dst++ = (c) & 3; |
| |
| i -= 4; |
| } |
| |
| if (i) |
| { |
| c = *src; |
| |
| while (i--) |
| { |
| *dst++ = (c >> 6); |
| |
| c <<= 2; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_4(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| |
| PixelDePack16(src, dst, pixels >> 1); |
| |
| if (pixels & 1) |
| dst[pixels & 0xFFFE] = (src[pixels >> 1] >> 4); |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_8(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)src; |
| (void)dst; |
| (void)pixels; |
| (void)pwi; |
| return FALSE; |
| } |
| |
| static int unpack_grey_16(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| while (pixels--) |
| { |
| *dst++ = *src++; |
| |
| src++; |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_16_2(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| if (pwi->m_trns_flag) |
| { |
| while (pixels--) |
| { |
| uint32_t v = (src[0] << 8) + src[1]; |
| src += 2; |
| |
| *dst++ = (uint8_t)(v >> 8); |
| *dst++ = (v == pwi->m_trns_value[0]) ? 0 : 255; |
| } |
| } |
| else |
| { |
| while (pixels--) |
| { |
| *dst++ = *src++; |
| *dst++ = 0xFF; |
| |
| src++; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_true_8(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| if (pwi->m_trns_flag) |
| { |
| const uint32_t tr = pwi->m_trns_value[0]; |
| const uint32_t tg = pwi->m_trns_value[1]; |
| const uint32_t tb = pwi->m_trns_value[2]; |
| |
| for (int i = 0; i < pixels; i++) |
| { |
| uint8_t r = src[i * 3 + 0]; |
| uint8_t g = src[i * 3 + 1]; |
| uint8_t b = src[i * 3 + 2]; |
| |
| dst[i * 4 + 0] = r; |
| dst[i * 4 + 1] = g; |
| dst[i * 4 + 2] = b; |
| dst[i * 4 + 3] = ((r == tr) && (g == tg) && (b == tb)) ? 0 : 255; |
| } |
| } |
| else |
| { |
| for (int i = 0; i < pixels; i++) |
| { |
| dst[i * 4 + 0] = src[i * 3 + 0]; |
| dst[i * 4 + 1] = src[i * 3 + 1]; |
| dst[i * 4 + 2] = src[i * 3 + 2]; |
| dst[i * 4 + 3] = 255; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_true_16(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| if (pwi->m_trns_flag) |
| { |
| const uint32_t tr = pwi->m_trns_value[0]; |
| const uint32_t tg = pwi->m_trns_value[1]; |
| const uint32_t tb = pwi->m_trns_value[2]; |
| |
| for (int i = 0; i < pixels; i++) |
| { |
| uint32_t r = (src[i * 6 + 0] << 8) + src[i * 6 + 1]; |
| uint32_t g = (src[i * 6 + 2] << 8) + src[i * 6 + 3]; |
| uint32_t b = (src[i * 6 + 4] << 8) + src[i * 6 + 5]; |
| |
| dst[i * 4 + 0] = (uint8_t)(r >> 8); |
| dst[i * 4 + 1] = (uint8_t)(g >> 8); |
| dst[i * 4 + 2] = (uint8_t)(b >> 8); |
| dst[i * 4 + 3] = ((r == tr) && (g == tg) && (b == tb)) ? 0 : 255; |
| } |
| } |
| else |
| { |
| while (pixels--) |
| { |
| dst[0] = src[0]; |
| dst[1] = src[2]; |
| dst[2] = src[4]; |
| dst[3] = 255; |
| |
| dst += 4; |
| src += 6; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_alpha_8(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| while (pixels--) |
| { |
| dst[0] = src[0]; |
| dst[1] = src[1]; |
| dst += 2; |
| src += 2; |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_grey_alpha_16(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| while (pixels--) |
| { |
| dst[0] = src[0]; |
| dst[1] = src[2]; |
| dst += 2; |
| src += 4; |
| } |
| |
| return TRUE; |
| } |
| |
| static int unpack_true_alpha_8(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)src; |
| (void)dst; |
| (void)pixels; |
| (void)pwi; |
| return FALSE; |
| } |
| |
| static int unpack_true_alpha_16(uint8_t* src, uint8_t* dst, int pixels, png_decoder* pwi) |
| { |
| (void)pwi; |
| while (pixels--) |
| { |
| dst[0] = src[0]; |
| dst[1] = src[2]; |
| dst[2] = src[4]; |
| dst[3] = src[6]; |
| dst += 4; |
| src += 8; |
| } |
| |
| return TRUE; |
| } |
| |
| void png_decoder::unpredict_sub(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp) |
| { |
| (void)lst; |
| if (bytes == (uint32_t)bpp) |
| return; |
| |
| cur += bpp; |
| bytes -= bpp; |
| |
| while (bytes--) |
| { |
| *cur += *(cur - bpp); |
| cur++; |
| } |
| } |
| |
| void png_decoder::unpredict_up(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp) |
| { |
| (void)bpp; |
| while (bytes--) |
| *cur++ += *lst++; |
| } |
| |
| void png_decoder::unpredict_average(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp) |
| { |
| int i; |
| |
| for (i = 0; i < bpp; i++) |
| *cur++ += (*lst++ >> 1); |
| |
| if (bytes == (uint32_t)bpp) |
| return; |
| |
| bytes -= bpp; |
| |
| while (bytes--) |
| { |
| *cur += ((*lst++ + *(cur - bpp)) >> 1); |
| cur++; |
| } |
| } |
| |
| inline uint8_t png_decoder::paeth_predictor(int a, int b, int c) |
| { |
| int p, pa, pb, pc; |
| |
| /* a = left, b = above, c = upper left */ |
| |
| p = a + b - c; |
| |
| pa = abs(p - a); |
| pb = abs(p - b); |
| pc = abs(p - c); |
| |
| if ((pa <= pb) && (pa <= pc)) |
| return (uint8_t)a; |
| else if (pb <= pc) |
| return (uint8_t)b; |
| else |
| return (uint8_t)c; |
| } |
| |
| void png_decoder::unpredict_paeth(uint8_t* lst, uint8_t* cur, uint32_t bytes, int bpp) |
| { |
| int i; |
| |
| for (i = 0; i < bpp; i++) |
| *cur++ += paeth_predictor(0, *lst++, 0); |
| |
| if (bytes == (uint32_t)bpp) |
| return; |
| |
| bytes -= bpp; |
| |
| while (bytes--) |
| { |
| int p, a, b, c, pa, pb, pc; |
| |
| a = *(cur - bpp); |
| b = *lst; |
| c = *(lst - bpp); |
| |
| p = a + b - c; |
| |
| pa = abs(p - a); |
| pb = abs(p - b); |
| pc = abs(p - c); |
| |
| if ((pa <= pb) && (pa <= pc)) |
| *cur++ += (uint8_t)a; |
| else if (pb <= pc) |
| *cur++ += (uint8_t)b; |
| else |
| *cur++ += (uint8_t)c; |
| |
| lst++; |
| } |
| } |
| |
| int png_decoder::adam7_pass_size(int size, int start, int step) |
| { |
| if (size > start) |
| return 1 + ((size - 1) - start) / step; |
| else |
| return 0; |
| } |
| |
| // TRUE if no more data, negative on error, FALSE if OK |
| int png_decoder::decompress_line(uint32_t* bytes_decoded) |
| { |
| int status; |
| uint32_t temp, src_bytes_left, dst_bytes_left; |
| |
| m_inflate_dst_buf_ofs = 0; |
| |
| for (; ; ) |
| { |
| if (m_inflate_src_buf_ofs == PNG_INFLATE_SRC_BUF_SIZE) |
| { |
| int res = unchunk_data(inflate_src_buf, PNG_INFLATE_SRC_BUF_SIZE, &temp); |
| if (res < 0) |
| return res; |
| m_inflate_eof_flag = res; |
| |
| m_inflate_src_buf_size = temp; |
| |
| m_inflate_src_buf_ofs = 0; |
| } |
| |
| for (; ; ) |
| { |
| src_bytes_left = m_inflate_src_buf_size - m_inflate_src_buf_ofs; |
| dst_bytes_left = m_dec_bytes_per_line - m_inflate_dst_buf_ofs; |
| |
| m_inflator.next_in = inflate_src_buf + m_inflate_src_buf_ofs; |
| m_inflator.avail_in = src_bytes_left; |
| |
| m_inflator.next_out = m_pCur_line_buf + m_inflate_dst_buf_ofs; |
| m_inflator.avail_out = dst_bytes_left; |
| |
| status = buminiz::mz_inflate2(&m_inflator, buminiz::MZ_NO_FLUSH, PVPNG_ADLER32_CHECKING); |
| |
| const uint32_t src_bytes_consumed = src_bytes_left - m_inflator.avail_in; |
| const uint32_t dst_bytes_written = dst_bytes_left - m_inflator.avail_out; |
| |
| m_inflate_src_buf_ofs += src_bytes_consumed; |
| m_inflate_dst_buf_ofs += dst_bytes_written; |
| |
| if (status != buminiz::MZ_OK) |
| { |
| if (status != buminiz::MZ_STREAM_END) |
| return terminate(PNG_INVALID_DATA_STREAM); |
| |
| if (bytes_decoded) |
| *bytes_decoded = m_inflate_dst_buf_ofs; |
| |
| return TRUE; |
| } |
| |
| if (m_inflate_dst_buf_ofs == m_dec_bytes_per_line) |
| { |
| if (bytes_decoded) |
| *bytes_decoded = m_inflate_dst_buf_ofs; |
| |
| return FALSE; |
| } |
| |
| if ((m_inflate_src_buf_ofs == m_inflate_src_buf_size) && |
| (m_inflate_eof_flag == FALSE)) |
| break; |
| } |
| } |
| } |
| |
| int png_decoder::find_iend_chunk() |
| { |
| uint32_t dummy; |
| |
| while (!m_end_of_idat_chunks) |
| { |
| int res = unchunk_data(m_temp_buf, TEMP_BUF_SIZE * 4, &dummy); |
| if (res < 0) |
| return res; |
| } |
| |
| for (; ; ) |
| { |
| if ((m_chunk_name[0] == 'I') && |
| (m_chunk_name[1] == 'E') && |
| (m_chunk_name[2] == 'N') && |
| (m_chunk_name[3] == 'D')) |
| break; |
| |
| int res = fetch_next_chunk_init(); |
| if (res < 0) |
| return res; |
| } |
| |
| return 0; |
| } |
| |
| int png_decoder::png_decode(void** ppImg_ptr, uint32_t* pImg_len) |
| { |
| int status; |
| uint8_t* decoded_line; |
| uint32_t bytes_decoded; |
| |
| if (m_adam7_decoded_flag) |
| { |
| if (m_pass_y_left == 0) |
| return PNG_ALLDONE; |
| |
| *ppImg_ptr = &m_adam7_image_buf[(m_ihdr.m_height - m_pass_y_left) * m_dst_bytes_per_line]; |
| *pImg_len = m_dst_bytes_per_line; |
| |
| m_pass_y_left--; |
| |
| return 0; |
| } |
| |
| if (m_pass_y_left == 0) |
| { |
| if (m_ihdr.m_ilace_type == 0) |
| { |
| status = find_iend_chunk(); |
| if (status < 0) |
| return status; |
| |
| return PNG_ALLDONE; |
| } |
| |
| for (; ; ) |
| { |
| if (++m_adam7_pass_num == 7) |
| { |
| status = find_iend_chunk(); |
| if (status < 0) |
| return status; |
| |
| return PNG_ALLDONE; |
| } |
| |
| if (((m_pass_y_left = m_adam7_pass_size_y[m_adam7_pass_num]) != 0) && |
| ((m_pass_x_size = m_adam7_pass_size_x[m_adam7_pass_num]) != 0)) |
| break; |
| } |
| |
| switch (m_adam7_pass_num) |
| { |
| case 0: |
| case 1: |
| case 3: |
| case 5: |
| m_adam7_pass_y = 0; |
| break; |
| case 2: |
| m_adam7_pass_y = 4; |
| break; |
| case 4: |
| m_adam7_pass_y = 2; |
| break; |
| case 6: |
| m_adam7_pass_y = 1; |
| break; |
| } |
| |
| switch (m_ihdr.m_color_type) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| case PNG_COLOR_TYPE_PALETTIZED: |
| { |
| m_src_bytes_per_line = (((uint32_t)m_pass_x_size * m_ihdr.m_bit_depth) + 7) / 8; |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| { |
| m_src_bytes_per_line = ((uint32_t)m_pass_x_size * m_dec_bytes_per_pixel); |
| break; |
| } |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| { |
| m_src_bytes_per_line = ((uint32_t)m_pass_x_size * m_dec_bytes_per_pixel); |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| { |
| m_src_bytes_per_line = ((uint32_t)m_pass_x_size * m_dec_bytes_per_pixel); |
| break; |
| } |
| } |
| |
| m_dec_bytes_per_line = m_src_bytes_per_line + 1; |
| |
| memset(m_pPre_line_buf, 0, m_src_bytes_per_line); |
| } |
| |
| int res = decompress_line(&bytes_decoded); |
| if (res < 0) |
| return terminate(res); |
| |
| if (res) |
| { |
| if (m_ihdr.m_ilace_type == 0) |
| { |
| if (m_pass_y_left != 1) |
| return terminate(PNG_INCOMPLETE_IMAGE); |
| } |
| else |
| { |
| if ((m_pass_y_left != 1) && (m_adam7_pass_num != 6)) |
| return terminate(PNG_INCOMPLETE_IMAGE); |
| } |
| } |
| |
| if (bytes_decoded != m_dec_bytes_per_line) |
| return terminate(PNG_INCOMPLETE_IMAGE); |
| |
| decoded_line = &m_pCur_line_buf[1]; |
| |
| switch (m_pCur_line_buf[0]) |
| { |
| case 0: |
| break; |
| case 1: |
| { |
| unpredict_sub(m_pPre_line_buf, m_pCur_line_buf + 1, m_src_bytes_per_line, m_dec_bytes_per_pixel); |
| break; |
| } |
| case 2: |
| { |
| unpredict_up(m_pPre_line_buf, m_pCur_line_buf + 1, m_src_bytes_per_line, m_dec_bytes_per_pixel); |
| break; |
| } |
| case 3: |
| { |
| unpredict_average(m_pPre_line_buf, m_pCur_line_buf + 1, m_src_bytes_per_line, m_dec_bytes_per_pixel); |
| break; |
| } |
| case 4: |
| { |
| unpredict_paeth(m_pPre_line_buf, m_pCur_line_buf + 1, m_src_bytes_per_line, m_dec_bytes_per_pixel); |
| break; |
| } |
| default: |
| return terminate(PNG_UNS_PREDICTOR); |
| } |
| |
| memmove(m_pPre_line_buf, m_pCur_line_buf + 1, m_src_bytes_per_line); |
| |
| if (m_pProcess_func) |
| { |
| if ((*m_pProcess_func)(m_pCur_line_buf + 1, m_pPro_line_buf, m_pass_x_size, this)) |
| decoded_line = m_pPro_line_buf; |
| } |
| |
| if (m_ihdr.m_ilace_type == 0) |
| { |
| *ppImg_ptr = decoded_line; |
| *pImg_len = m_dst_bytes_per_line; |
| |
| if (--m_pass_y_left == 0) |
| { |
| res = decompress_line(&bytes_decoded); |
| if (res < 0) |
| return terminate(res); |
| |
| if (res == FALSE) |
| return terminate(PNG_TOO_MUCH_DATA); |
| |
| if (bytes_decoded) |
| return terminate(PNG_TOO_MUCH_DATA); |
| } |
| } |
| else |
| { |
| int i, x_ofs = 0, y_ofs = 0, x_stp = 0; |
| uint8_t* p = decoded_line; |
| |
| switch (m_adam7_pass_num) |
| { |
| case 0: { x_ofs = 0; x_stp = 8; break; } |
| case 1: { x_ofs = 4; x_stp = 8; break; } |
| case 2: { x_ofs = 0; x_stp = 4; break; } |
| case 3: { x_ofs = 2; x_stp = 4; break; } |
| case 4: { x_ofs = 0; x_stp = 2; break; } |
| case 5: { x_ofs = 1; x_stp = 2; break; } |
| case 6: { x_ofs = 0; x_stp = 1; break; } |
| } |
| |
| y_ofs = m_adam7_pass_y; |
| |
| assert(x_ofs < (int)m_ihdr.m_width); |
| assert(y_ofs < (int)m_ihdr.m_height); |
| |
| if (m_dst_bytes_per_pixel == 1) |
| { |
| for (i = m_pass_x_size; i > 0; i--, x_ofs += x_stp) |
| adam7_write_pixel_8(x_ofs, y_ofs, *p++); |
| } |
| else if (m_dst_bytes_per_pixel == 2) |
| { |
| for (i = m_pass_x_size; i > 0; i--, x_ofs += x_stp, p += 2) |
| adam7_write_pixel_16(x_ofs, y_ofs, p[0], p[1]); |
| } |
| else if (m_dst_bytes_per_pixel == 3) |
| { |
| for (i = m_pass_x_size; i > 0; i--, x_ofs += x_stp, p += 3) |
| adam7_write_pixel_24(x_ofs, y_ofs, p[0], p[1], p[2]); |
| } |
| else if (m_dst_bytes_per_pixel == 4) |
| { |
| for (i = m_pass_x_size; i > 0; i--, x_ofs += x_stp, p += 4) |
| adam7_write_pixel_32(x_ofs, y_ofs, p[0], p[1], p[2], p[3]); |
| } |
| else |
| { |
| assert(0); |
| } |
| |
| switch (m_adam7_pass_num) |
| { |
| case 0: |
| case 1: |
| case 2: { m_adam7_pass_y += 8; break; } |
| case 3: |
| case 4: { m_adam7_pass_y += 4; break; } |
| case 5: |
| case 6: { m_adam7_pass_y += 2; break; } |
| } |
| |
| if ((--m_pass_y_left == 0) && (m_adam7_pass_num == 6)) |
| { |
| res = decompress_line(&bytes_decoded); |
| if (res < 0) |
| return terminate(res); |
| |
| if (res == FALSE) |
| return terminate(PNG_TOO_MUCH_DATA); |
| |
| if (bytes_decoded) |
| return terminate(PNG_TOO_MUCH_DATA); |
| } |
| } |
| |
| return 0; |
| } |
| |
| void png_decoder::png_decode_end() |
| { |
| uninitialize(); |
| } |
| |
| int png_decoder::png_decode_start() |
| { |
| int status; |
| |
| if (m_img_supported_flag != TRUE) |
| return terminate(m_img_supported_flag); |
| |
| switch (m_ihdr.m_color_type) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| { |
| if (m_ihdr.m_bit_depth == 16) |
| { |
| // This is a special case. We can't pass back 8-bit samples and let the caller decide on transparency because the PNG is 16-bits. |
| // So we expand to 8-bit Gray-Alpha and handle transparency during decoding. |
| // We don't do this with all grayscale cases because that would require more code to deal with 1/2/4bpp expansion. |
| m_dec_bytes_per_pixel = (m_ihdr.m_bit_depth + 7) / 8; |
| m_dst_bytes_per_pixel = 2; |
| |
| m_src_bytes_per_line = (((uint32_t)m_ihdr.m_width * m_ihdr.m_bit_depth) + 7) / 8; |
| m_dst_bytes_per_line = 2 * m_ihdr.m_width; |
| |
| m_pProcess_func = unpack_grey_16_2; |
| } |
| else |
| { |
| m_dec_bytes_per_pixel = (m_ihdr.m_bit_depth + 7) / 8; |
| m_dst_bytes_per_pixel = 1; |
| |
| m_src_bytes_per_line = (((uint32_t)m_ihdr.m_width * m_ihdr.m_bit_depth) + 7) / 8; |
| m_dst_bytes_per_line = m_ihdr.m_width; |
| |
| if (m_ihdr.m_bit_depth == 1) |
| m_pProcess_func = unpack_grey_1; |
| else if (m_ihdr.m_bit_depth == 2) |
| m_pProcess_func = unpack_grey_2; |
| else if (m_ihdr.m_bit_depth == 4) |
| m_pProcess_func = unpack_grey_4; |
| else |
| m_pProcess_func = unpack_grey_8; |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_PALETTIZED: |
| { |
| m_dec_bytes_per_pixel = (m_ihdr.m_bit_depth + 7) / 8; |
| m_dst_bytes_per_pixel = 1; |
| |
| m_src_bytes_per_line = (((uint32_t)m_ihdr.m_width * m_ihdr.m_bit_depth) + 7) / 8; |
| m_dst_bytes_per_line = m_ihdr.m_width; |
| |
| if (m_ihdr.m_bit_depth == 1) |
| m_pProcess_func = unpack_grey_1; |
| else if (m_ihdr.m_bit_depth == 2) |
| m_pProcess_func = unpack_grey_2; |
| else if (m_ihdr.m_bit_depth == 4) |
| m_pProcess_func = unpack_grey_4; |
| else if (m_ihdr.m_bit_depth == 8) |
| m_pProcess_func = unpack_grey_8; |
| else if (m_ihdr.m_bit_depth == 16) |
| m_pProcess_func = unpack_grey_16; |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| { |
| // We always pass back alpha with transparency handling. |
| m_dec_bytes_per_pixel = 3 * (m_ihdr.m_bit_depth / 8); |
| m_dst_bytes_per_pixel = 4; |
| |
| m_src_bytes_per_line = ((uint32_t)m_ihdr.m_width * m_dec_bytes_per_pixel); |
| m_dst_bytes_per_line = 4 * m_ihdr.m_width; |
| |
| if (m_ihdr.m_bit_depth == 8) |
| m_pProcess_func = unpack_true_8; |
| else if (m_ihdr.m_bit_depth == 16) |
| m_pProcess_func = unpack_true_16; |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| { |
| m_dec_bytes_per_pixel = 2 * (m_ihdr.m_bit_depth / 8); |
| m_dst_bytes_per_pixel = 2; |
| |
| m_src_bytes_per_line = ((uint32_t)m_ihdr.m_width * m_dec_bytes_per_pixel); |
| m_dst_bytes_per_line = m_ihdr.m_width * 2; |
| |
| if (m_ihdr.m_bit_depth == 8) |
| m_pProcess_func = unpack_grey_alpha_8; |
| else if (m_ihdr.m_bit_depth == 16) |
| m_pProcess_func = unpack_grey_alpha_16; |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| { |
| m_dec_bytes_per_pixel = 4 * (m_ihdr.m_bit_depth / 8); |
| m_dst_bytes_per_pixel = 4; |
| |
| m_src_bytes_per_line = ((uint32_t)m_ihdr.m_width * m_dec_bytes_per_pixel); |
| m_dst_bytes_per_line = 4 * m_ihdr.m_width; |
| |
| if (m_ihdr.m_bit_depth == 8) |
| m_pProcess_func = unpack_true_alpha_8; |
| else |
| m_pProcess_func = unpack_true_alpha_16; |
| |
| break; |
| } |
| } |
| |
| m_dec_bytes_per_line = m_src_bytes_per_line + 1; |
| |
| m_pPre_line_buf = (uint8_t*)png_calloc(m_src_bytes_per_line); |
| m_pCur_line_buf = (uint8_t*)png_calloc(m_dec_bytes_per_line); |
| m_pPro_line_buf = (uint8_t*)png_calloc(m_dst_bytes_per_line); |
| |
| if (!m_pPre_line_buf || !m_pCur_line_buf || !m_pPro_line_buf) |
| return terminate(PNG_NOTENOUGHMEM); |
| |
| m_inflate_src_buf_ofs = PNG_INFLATE_SRC_BUF_SIZE; |
| |
| int res = mz_inflateInit(&m_inflator); |
| if (res != 0) |
| return terminate(PNG_DECERROR); |
| |
| if (m_ihdr.m_ilace_type == 1) |
| { |
| int i; |
| uint32_t total_lines, lines_processed; |
| |
| m_adam7_pass_size_x[0] = adam7_pass_size(m_ihdr.m_width, 0, 8); |
| m_adam7_pass_size_x[1] = adam7_pass_size(m_ihdr.m_width, 4, 8); |
| m_adam7_pass_size_x[2] = adam7_pass_size(m_ihdr.m_width, 0, 4); |
| m_adam7_pass_size_x[3] = adam7_pass_size(m_ihdr.m_width, 2, 4); |
| m_adam7_pass_size_x[4] = adam7_pass_size(m_ihdr.m_width, 0, 2); |
| m_adam7_pass_size_x[5] = adam7_pass_size(m_ihdr.m_width, 1, 2); |
| m_adam7_pass_size_x[6] = adam7_pass_size(m_ihdr.m_width, 0, 1); |
| |
| m_adam7_pass_size_y[0] = adam7_pass_size(m_ihdr.m_height, 0, 8); |
| m_adam7_pass_size_y[1] = adam7_pass_size(m_ihdr.m_height, 0, 8); |
| m_adam7_pass_size_y[2] = adam7_pass_size(m_ihdr.m_height, 4, 8); |
| m_adam7_pass_size_y[3] = adam7_pass_size(m_ihdr.m_height, 0, 4); |
| m_adam7_pass_size_y[4] = adam7_pass_size(m_ihdr.m_height, 2, 4); |
| m_adam7_pass_size_y[5] = adam7_pass_size(m_ihdr.m_height, 0, 2); |
| m_adam7_pass_size_y[6] = adam7_pass_size(m_ihdr.m_height, 1, 2); |
| |
| m_adam7_image_buf.resize(m_dst_bytes_per_line * m_ihdr.m_height); |
| |
| m_adam7_pass_num = -1; |
| |
| m_pass_y_left = 0; |
| |
| total_lines = lines_processed = 0; |
| |
| for (i = 0; i < 7; i++) |
| total_lines += m_adam7_pass_size_y[i]; |
| |
| for (; ; ) |
| { |
| void* dummy_ptr = nullptr; |
| uint32_t dummy_len = 0; |
| |
| status = png_decode(&dummy_ptr, &dummy_len); |
| |
| if (status) |
| { |
| if (status == PNG_ALLDONE) |
| break; |
| else |
| { |
| uninitialize(); |
| |
| return status; |
| } |
| } |
| |
| lines_processed++; |
| } |
| |
| m_adam7_decoded_flag = TRUE; |
| m_pass_y_left = m_ihdr.m_height; |
| } |
| else |
| { |
| m_pass_x_size = m_ihdr.m_width; |
| m_pass_y_left = m_ihdr.m_height; |
| } |
| |
| return 0; |
| } |
| |
| void png_decoder::calc_gamma_table() |
| { |
| if (m_gama_value == 45000) |
| { |
| for (int i = 0; i < 256; i++) |
| m_gamma_table[i] = (uint8_t)i; |
| return; |
| } |
| |
| float gamma = (float)(m_gama_value) / 100000.0f; |
| |
| gamma = 1.0f / (gamma * 2.2f); |
| |
| for (int i = 0; i < 256; i++) |
| { |
| float temp = powf((float)(i) / 255.0f, gamma) * 255.0f; |
| |
| int j = (int)(temp + .5f); |
| |
| if (j < 0) |
| j = 0; |
| else if (j > 255) |
| j = 255; |
| |
| m_gamma_table[i] = (uint8_t)j; |
| } |
| } |
| |
| void png_decoder::create_grey_palette() |
| { |
| int i, j; |
| uint8_t* p = m_img_pal; |
| |
| const int img_colors = minimum(256, 1 << m_ihdr.m_bit_depth); |
| for (i = 0; i < img_colors; i++) |
| { |
| j = ((uint32_t)255 * (uint32_t)i) / (img_colors - 1); |
| |
| *p++ = (uint8_t)j; |
| *p++ = (uint8_t)j; |
| *p++ = (uint8_t)j; |
| } |
| } |
| |
| int png_decoder::read_signature() |
| { |
| if (m_pFile->read(m_temp_buf, 8) != 8) |
| return terminate(PNG_UNKNOWNTYPE); |
| |
| if ((m_temp_buf[0] != 137) || |
| (m_temp_buf[1] != 80) || |
| (m_temp_buf[2] != 78) || |
| (m_temp_buf[3] != 71) || |
| (m_temp_buf[4] != 13) || |
| (m_temp_buf[5] != 10) || |
| (m_temp_buf[6] != 26) || |
| (m_temp_buf[7] != 10)) |
| { |
| return terminate(PNG_UNKNOWNTYPE); |
| } |
| |
| return 0; |
| } |
| |
| int png_decoder::read_ihdr_chunk() |
| { |
| int res = fetch_next_chunk_init(); |
| if (res < 0) |
| return res; |
| |
| if ((m_chunk_name[0] != 'I') || (m_chunk_name[1] != 'H') || (m_chunk_name[2] != 'D') || (m_chunk_name[3] != 'R') || (m_chunk_size != 13)) |
| return terminate(PNG_NO_IHDR); |
| |
| int64_t v64 = fetch_next_chunk_dword(); |
| if (v64 < 0) |
| return (int)v64; |
| m_ihdr.m_width = (uint32_t)v64; |
| |
| v64 = fetch_next_chunk_dword(); |
| if (v64 < 0) |
| return (int)v64; |
| m_ihdr.m_height = (uint32_t)v64; |
| |
| if ((m_ihdr.m_width == 0) || (m_ihdr.m_width > MAX_SUPPORTED_RES)) |
| return terminate(PNG_BAD_WIDTH); |
| |
| if ((m_ihdr.m_height == 0) || (m_ihdr.m_height > MAX_SUPPORTED_RES)) |
| return terminate(PNG_BAD_HEIGHT); |
| |
| int v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_ihdr.m_bit_depth = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_ihdr.m_color_type = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_ihdr.m_comp_type = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_ihdr.m_filter_type = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_ihdr.m_ilace_type = (uint8_t)v; |
| |
| if (m_ihdr.m_comp_type != 0) |
| m_img_supported_flag = PNG_UNS_COMPRESSION; |
| |
| if (m_ihdr.m_filter_type != 0) |
| m_img_supported_flag = PNG_UNS_FILTER; |
| |
| if (m_ihdr.m_ilace_type > 1) |
| m_img_supported_flag = PNG_UNS_ILACE; |
| |
| switch (m_ihdr.m_color_type) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| { |
| switch (m_ihdr.m_bit_depth) |
| { |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| case 16: |
| { |
| break; |
| } |
| default: |
| return terminate(PNG_BAD_BIT_DEPTH); |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_PALETTIZED: |
| { |
| switch (m_ihdr.m_bit_depth) |
| { |
| case 1: |
| case 2: |
| case 4: |
| case 8: |
| { |
| break; |
| } |
| default: |
| return terminate(PNG_BAD_BIT_DEPTH); |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| { |
| switch (m_ihdr.m_bit_depth) |
| { |
| case 8: |
| case 16: |
| { |
| break; |
| } |
| default: |
| return terminate(PNG_BAD_BIT_DEPTH); |
| } |
| |
| break; |
| } |
| default: |
| return terminate(PNG_UNS_COLOR_TYPE); |
| } |
| |
| return 0; |
| } |
| |
| int png_decoder::read_bkgd_chunk() |
| { |
| m_bkgd_flag = TRUE; |
| |
| if (m_ihdr.m_color_type == PNG_COLOR_TYPE_PALETTIZED) |
| { |
| int v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_bkgd_value[0] = v; |
| } |
| else if ((m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE) || (m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE_ALPHA)) |
| { |
| int v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_bkgd_value[0] = v; |
| } |
| else if ((m_ihdr.m_color_type == PNG_COLOR_TYPE_TRUECOLOR) || (m_ihdr.m_color_type == PNG_COLOR_TYPE_TRUECOLOR_ALPHA)) |
| { |
| int v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_bkgd_value[0] = v; |
| |
| v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_bkgd_value[1] = v; |
| |
| v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_bkgd_value[2] = v; |
| } |
| |
| return 0; |
| } |
| |
| int png_decoder::read_gama_chunk() |
| { |
| m_gama_flag = TRUE; |
| |
| int64_t v = fetch_next_chunk_dword(); |
| if (v < 0) |
| return (int)v; |
| |
| m_gama_value = (uint32_t)v; |
| |
| return 0; |
| } |
| |
| int png_decoder::read_trns_chunk() |
| { |
| int i; |
| |
| m_trns_flag = TRUE; |
| |
| if (m_ihdr.m_color_type == PNG_COLOR_TYPE_PALETTIZED) |
| { |
| for (i = 0; i < 256; i++) |
| m_trns_value[i] = 255; |
| |
| const uint32_t img_colors = 1 << m_ihdr.m_bit_depth; |
| if (m_chunk_size > (uint32_t)img_colors) |
| return terminate(PNG_BAD_TRNS_CHUNK); |
| |
| for (i = 0; i < (int)m_chunk_size; i++) |
| { |
| int v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| m_trns_value[i] = v; |
| } |
| } |
| else if (m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE) |
| { |
| int v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_trns_value[0] = v; |
| } |
| else if (m_ihdr.m_color_type == PNG_COLOR_TYPE_TRUECOLOR) |
| { |
| int v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_trns_value[0] = v; |
| |
| v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_trns_value[1] = v; |
| |
| v = fetch_next_chunk_word(); |
| if (v < 0) |
| return v; |
| m_trns_value[2] = v; |
| } |
| else |
| { |
| return terminate(PNG_BAD_TRNS_CHUNK); |
| } |
| return 0; |
| } |
| |
| int png_decoder::read_plte_chunk() |
| { |
| int i, j; |
| uint8_t* p; |
| |
| if (m_plte_flag) |
| return terminate(PNG_BAD_PLTE_CHUNK); |
| |
| m_plte_flag = TRUE; |
| |
| memset(m_img_pal, 0, 768); |
| |
| if (m_chunk_size % 3) |
| return terminate(PNG_BAD_PLTE_CHUNK); |
| |
| j = m_chunk_size / 3; |
| |
| const int img_colors = minimum(256, 1 << m_ihdr.m_bit_depth); |
| if (j > img_colors) |
| return terminate(PNG_BAD_PLTE_CHUNK); |
| |
| if ((m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE) || |
| (m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE_ALPHA)) |
| return terminate(PNG_BAD_PLTE_CHUNK); |
| |
| p = m_img_pal; |
| |
| for (i = 0; i < j; i++) |
| { |
| int v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| *p++ = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| *p++ = (uint8_t)v; |
| |
| v = fetch_next_chunk_byte(); |
| if (v < 0) |
| return v; |
| *p++ = (uint8_t)v; |
| } |
| |
| return 0; |
| } |
| |
| int png_decoder::find_idat_chunk() |
| { |
| for (; ; ) |
| { |
| int res = fetch_next_chunk_init(); |
| if (res < 0) |
| return res; |
| |
| if (m_chunk_name[0] & 32) /* ancillary? */ |
| { |
| if ((m_chunk_name[0] == 'b') && (m_chunk_name[1] == 'K') && (m_chunk_name[2] == 'G') && (m_chunk_name[3] == 'D')) |
| { |
| res = read_bkgd_chunk(); |
| if (res < 0) |
| return res; |
| } |
| else if ((m_chunk_name[0] == 'g') && (m_chunk_name[1] == 'A') && (m_chunk_name[2] == 'M') && (m_chunk_name[3] == 'A')) |
| { |
| res = read_gama_chunk(); |
| if (res < 0) |
| return res; |
| } |
| else if ((m_chunk_name[0] == 't') && (m_chunk_name[1] == 'R') && (m_chunk_name[2] == 'N') && (m_chunk_name[3] == 'S')) |
| { |
| res = read_trns_chunk(); |
| if (res < 0) |
| return res; |
| } |
| } |
| else |
| { |
| if ((m_chunk_name[0] == 'P') && (m_chunk_name[1] == 'L') && (m_chunk_name[2] == 'T') && (m_chunk_name[3] == 'E')) |
| { |
| res = read_plte_chunk(); |
| if (res < 0) |
| return res; |
| } |
| else if ((m_chunk_name[0] == 'I') && (m_chunk_name[1] == 'D') && (m_chunk_name[2] == 'A') && (m_chunk_name[3] == 'T')) |
| { |
| break; |
| } |
| else |
| { |
| m_img_supported_flag = PNG_UNS_CRITICAL_CHUNK; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| png_decoder::png_decoder() |
| { |
| clear(); |
| } |
| |
| png_decoder::~png_decoder() |
| { |
| uninitialize(); |
| } |
| |
| void png_decoder::clear() |
| { |
| clear_obj(m_pMalloc_blocks); |
| |
| m_pFile = nullptr; |
| |
| clear_obj(m_img_pal); |
| |
| m_img_supported_flag = FALSE; |
| |
| m_adam7_image_buf.clear(); |
| |
| clear_obj(m_ihdr); |
| |
| m_chunk_flag = FALSE; |
| m_chunk_size = 0; |
| m_chunk_left = 0; |
| m_chunk_crc32 = 0; |
| clear_obj(m_chunk_name); |
| |
| m_end_of_idat_chunks = 0; |
| |
| m_dec_bytes_per_pixel = 0; |
| m_dst_bytes_per_pixel = 0; |
| |
| m_dec_bytes_per_line = 0; |
| m_src_bytes_per_line = 0; |
| m_dst_bytes_per_line = 0; |
| |
| m_pProcess_func = nullptr; |
| |
| m_pPre_line_buf = nullptr; |
| m_pCur_line_buf = nullptr; |
| m_pPro_line_buf = nullptr; |
| |
| m_bkgd_flag = FALSE; |
| clear_obj(m_bkgd_value); |
| |
| m_gama_flag = FALSE; |
| m_gama_value = 0; |
| |
| m_plte_flag = FALSE; |
| |
| m_trns_flag = FALSE; |
| clear_obj(m_trns_value); |
| |
| clear_obj(m_inflator); |
| |
| m_inflate_src_buf_ofs = 0; |
| m_inflate_src_buf_size = 0; |
| m_inflate_dst_buf_ofs = 0; |
| |
| m_inflate_eof_flag = FALSE; |
| |
| clear_obj(m_trns_value); |
| |
| m_pass_x_size = 0; |
| m_pass_y_left = 0; |
| |
| m_adam7_pass_num = 0; |
| m_adam7_pass_y = 0; |
| clear_obj(m_adam7_pass_size_x); |
| clear_obj(m_adam7_pass_size_y); |
| |
| m_adam7_decoded_flag = FALSE; |
| |
| m_scanned_flag = false; |
| |
| m_terminate_status = 0; |
| } |
| |
| int png_decoder::png_scan(png_file *pFile) |
| { |
| m_pFile = pFile; |
| |
| m_img_supported_flag = TRUE; |
| m_terminate_status = 0; |
| |
| int res = read_signature(); |
| if (res != 0) |
| return res; |
| |
| res = read_ihdr_chunk(); |
| if (res != 0) |
| return res; |
| |
| res = find_idat_chunk(); |
| if (res != 0) |
| return res; |
| |
| if (m_gama_flag) |
| calc_gamma_table(); |
| |
| if (m_ihdr.m_color_type == PNG_COLOR_TYPE_PALETTIZED) |
| { |
| if (!m_plte_flag) |
| return terminate(PNG_MISSING_PALETTE); |
| } |
| else if ((m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE) || (m_ihdr.m_color_type == PNG_COLOR_TYPE_GREYSCALE_ALPHA)) |
| { |
| create_grey_palette(); |
| } |
| |
| m_scanned_flag = true; |
| |
| return 0; |
| } |
| |
| static inline uint8_t get_709_luma(uint32_t r, uint32_t g, uint32_t b) |
| { |
| return (uint8_t)((13938U * r + 46869U * g + 4729U * b + 32768U) >> 16U); |
| } |
| |
| bool get_png_info(const void* pImage_buf, size_t buf_size, png_info &info) |
| { |
| memset(&info, 0, sizeof(info)); |
| |
| if ((!pImage_buf) || (buf_size < MIN_PNG_SIZE)) |
| return false; |
| |
| png_readonly_memory_file mf; |
| mf.init(pImage_buf, buf_size); |
| |
| png_decoder dec; |
| |
| int status = dec.png_scan(&mf); |
| if ((status != 0) || (dec.m_img_supported_flag != TRUE)) |
| return false; |
| |
| info.m_width = dec.m_ihdr.m_width; |
| info.m_height = dec.m_ihdr.m_height; |
| info.m_bit_depth = dec.m_ihdr.m_bit_depth; |
| info.m_color_type = dec.m_ihdr.m_color_type; |
| info.m_has_gamma = dec.m_gama_flag != 0; |
| info.m_gamma_value = dec.m_gama_value; |
| info.m_has_trns = dec.m_trns_flag != 0; |
| |
| switch (dec.m_ihdr.m_color_type) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| info.m_num_chans = dec.m_trns_flag ? 2 : 1; |
| break; |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| info.m_num_chans = 2; |
| break; |
| case PNG_COLOR_TYPE_PALETTIZED: |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| info.m_num_chans = dec.m_trns_flag ? 4 : 3; |
| break; |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| info.m_num_chans = 4; |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| |
| return true; |
| } |
| |
| void* load_png(const void* pImage_buf, size_t buf_size, uint32_t desired_chans, uint32_t& width, uint32_t& height, uint32_t& num_chans) |
| { |
| width = 0; |
| height = 0; |
| num_chans = 0; |
| |
| if ((!pImage_buf) || (buf_size < MIN_PNG_SIZE)) |
| { |
| assert(0); |
| return nullptr; |
| } |
| |
| if (desired_chans > 4) |
| { |
| assert(0); |
| return nullptr; |
| } |
| |
| png_readonly_memory_file mf; |
| mf.init(pImage_buf, buf_size); |
| |
| png_decoder dec; |
| |
| int status = dec.png_scan(&mf); |
| if ((status != 0) || (dec.m_img_supported_flag != TRUE)) |
| return nullptr; |
| |
| uint32_t colortype = dec.m_ihdr.m_color_type; |
| switch (colortype) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| num_chans = dec.m_trns_flag ? 2 : 1; |
| break; |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| num_chans = 2; |
| break; |
| case PNG_COLOR_TYPE_PALETTIZED: |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| num_chans = dec.m_trns_flag ? 4 : 3; |
| break; |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| num_chans = 4; |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| |
| if (!desired_chans) |
| desired_chans = num_chans; |
| |
| #if 0 |
| printf("lode_png: %ux%u bitdepth: %u colortype: %u trns: %u ilace: %u\n", |
| dec.m_ihdr.m_width, |
| dec.m_ihdr.m_height, |
| dec.m_ihdr.m_bit_depth, |
| dec.m_ihdr.m_color_type, |
| dec.m_trns_flag, |
| dec.m_ihdr.m_ilace_type); |
| #endif |
| |
| width = dec.m_ihdr.m_width; |
| height = dec.m_ihdr.m_height; |
| uint32_t bitdepth = dec.m_ihdr.m_bit_depth; |
| uint32_t pitch = width * desired_chans; |
| |
| uint64_t total_size = (uint64_t)pitch * height; |
| if (total_size > 0x7FFFFFFFULL) |
| return nullptr; |
| |
| uint8_t* pBuf = (uint8_t*)malloc((size_t)total_size); |
| if (!pBuf) |
| return nullptr; |
| |
| if (dec.png_decode_start() != 0) |
| { |
| free(pBuf); |
| return nullptr; |
| } |
| |
| uint8_t* pDst = pBuf; |
| |
| for (uint32_t y = 0; y < height; y++, pDst += pitch) |
| { |
| uint8_t* pLine; |
| uint32_t line_bytes; |
| if (dec.png_decode((void**)&pLine, &line_bytes) != 0) |
| { |
| free(pBuf); |
| return nullptr; |
| } |
| |
| // This conversion matrix handles converting RGB->Luma, converting grayscale samples to 8-bit samples, converting palettized images, and PNG transparency. |
| switch (colortype) |
| { |
| case PNG_COLOR_TYPE_GREYSCALE: |
| { |
| uint32_t trans_value = dec.m_trns_value[0]; |
| |
| switch (desired_chans) |
| { |
| case 1: |
| if (bitdepth == 16) |
| { |
| assert(line_bytes == width * 2); |
| |
| for (uint32_t i = 0; i < width; i++) |
| pDst[i] = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| } |
| else if (bitdepth == 8) |
| { |
| assert(line_bytes == width); |
| memcpy(pDst, pLine, pitch); |
| } |
| else |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| pDst[i] = dec.m_img_pal[pLine[i] * 3]; |
| } |
| break; |
| case 2: |
| if (bitdepth == 16) |
| { |
| assert(line_bytes == width * 2); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| pDst[i * 2 + 0] = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 2 + 1] = pLine[i * 2 + 1]; |
| } |
| } |
| else if (dec.m_trns_flag) |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| pDst[i * 2 + 0] = dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 2 + 1] = (pLine[i] == trans_value) ? 0 : 255; |
| } |
| } |
| else |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| pDst[i * 2 + 0] = dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 2 + 1] = 255; |
| } |
| } |
| break; |
| case 3: |
| if (bitdepth == 16) |
| { |
| assert(line_bytes == width * 2); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 3 + 0] = c; |
| pDst[i * 3 + 1] = c; |
| pDst[i * 3 + 2] = c; |
| } |
| } |
| else |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 3 + 0] = c; |
| pDst[i * 3 + 1] = c; |
| pDst[i * 3 + 2] = c; |
| } |
| } |
| break; |
| case 4: |
| if (bitdepth == 16) |
| { |
| assert(line_bytes == width * 2); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 4 + 0] = c; |
| pDst[i * 4 + 1] = c; |
| pDst[i * 4 + 2] = c; |
| pDst[i * 4 + 3] = pLine[i * 2 + 1]; |
| } |
| } |
| else if (dec.m_trns_flag) |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 4 + 0] = c; |
| pDst[i * 4 + 1] = c; |
| pDst[i * 4 + 2] = c; |
| pDst[i * 4 + 3] = (pLine[i] == trans_value) ? 0 : 255; |
| } |
| } |
| else |
| { |
| assert(line_bytes == width); |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 4 + 0] = c; |
| pDst[i * 4 + 1] = c; |
| pDst[i * 4 + 2] = c; |
| pDst[i * 4 + 3] = 255; |
| } |
| } |
| break; |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_GREYSCALE_ALPHA: |
| { |
| assert(line_bytes == width * 2); |
| |
| switch (desired_chans) |
| { |
| case 1: |
| for (uint32_t i = 0; i < width; i++) |
| pDst[i] = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| break; |
| case 2: |
| assert(line_bytes == pitch); |
| if (bitdepth >= 8) |
| memcpy(pDst, pLine, pitch); |
| else |
| { |
| for (uint32_t i = 0; i < width; i++) |
| { |
| pDst[i * 2 + 0] = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 2 + 1] = pLine[i * 2 + 1]; |
| } |
| } |
| break; |
| case 3: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 3 + 0] = c; |
| pDst[i * 3 + 1] = c; |
| pDst[i * 3 + 2] = c; |
| } |
| break; |
| case 4: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| uint8_t c = dec.m_img_pal[pLine[i * 2 + 0] * 3]; |
| pDst[i * 4 + 0] = c; |
| pDst[i * 4 + 1] = c; |
| pDst[i * 4 + 2] = c; |
| pDst[i * 4 + 3] = pLine[i * 2 + 1]; |
| } |
| break; |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_PALETTIZED: |
| { |
| assert(line_bytes == width); |
| |
| switch (desired_chans) |
| { |
| case 1: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i] = get_709_luma(p[0], p[1], p[2]); |
| } |
| break; |
| case 2: |
| if (dec.m_trns_flag) |
| { |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 2 + 0] = get_709_luma(p[0], p[1], p[2]); |
| pDst[i * 2 + 1] = (uint8_t)dec.m_trns_value[pLine[i]]; |
| } |
| } |
| else |
| { |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 2 + 0] = get_709_luma(p[0], p[1], p[2]); |
| pDst[i * 2 + 1] = 255; |
| } |
| } |
| break; |
| case 3: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 3 + 0] = p[0]; |
| pDst[i * 3 + 1] = p[1]; |
| pDst[i * 3 + 2] = p[2]; |
| } |
| break; |
| case 4: |
| if (dec.m_trns_flag) |
| { |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 4 + 0] = p[0]; |
| pDst[i * 4 + 1] = p[1]; |
| pDst[i * 4 + 2] = p[2]; |
| pDst[i * 4 + 3] = (uint8_t)dec.m_trns_value[pLine[i]]; |
| } |
| } |
| else |
| { |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &dec.m_img_pal[pLine[i] * 3]; |
| pDst[i * 4 + 0] = p[0]; |
| pDst[i * 4 + 1] = p[1]; |
| pDst[i * 4 + 2] = p[2]; |
| pDst[i * 4 + 3] = 255; |
| } |
| } |
| break; |
| } |
| |
| break; |
| } |
| case PNG_COLOR_TYPE_TRUECOLOR: |
| case PNG_COLOR_TYPE_TRUECOLOR_ALPHA: |
| { |
| assert(line_bytes == width * 4); |
| |
| switch (desired_chans) |
| { |
| case 1: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &pLine[i * 4]; |
| pDst[i] = get_709_luma(p[0], p[1], p[2]); |
| } |
| break; |
| case 2: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &pLine[i * 4]; |
| pDst[i * 2 + 0] = get_709_luma(p[0], p[1], p[2]); |
| pDst[i * 2 + 1] = p[3]; |
| } |
| break; |
| case 3: |
| for (uint32_t i = 0; i < width; i++) |
| { |
| const uint8_t* p = &pLine[i * 4]; |
| pDst[i * 3 + 0] = p[0]; |
| pDst[i * 3 + 1] = p[1]; |
| pDst[i * 3 + 2] = p[2]; |
| } |
| break; |
| case 4: |
| memcpy(pDst, pLine, pitch); |
| break; |
| } |
| |
| break; |
| } |
| default: |
| assert(0); |
| break; |
| } |
| |
| } // y |
| |
| return pBuf; |
| } |
| |
| } // namespace pv_png |
| |
| /* |
| This is free and unencumbered software released into the public domain. |
| |
| Anyone is free to copy, modify, publish, use, compile, sell, or |
| distribute this software, either in source code form or as a compiled |
| binary, for any purpose, commercial or non-commercial, and by any |
| means. |
| |
| In jurisdictions that recognize copyright laws, the author or authors |
| of this software dedicate any and all copyright interest in the |
| software to the public domain. We make this dedication for the benefit |
| of the public at large and to the detriment of our heirs and |
| successors. We intend this dedication to be an overt act of |
| relinquishment in perpetuity of all present and future rights to this |
| software under copyright law. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
| OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
| ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| OTHER DEALINGS IN THE SOFTWARE. |
| |
| For more information, please refer to <http://unlicense.org/> |
| |
| Richard Geldreich, Jr. |
| 1/20/2022 |
| */ |