#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);
}
};
class png_decoder
{
public:
png_decoder();
~png_decoder();
int png_scan(png_file *pFile);
int png_decode(void** ppImg_ptr, uint32_t* pImg_len);
int png_decode_start();
void png_decode_end();
png_file* m_pFile;
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; uint32_t m_dst_bytes_per_pixel;
uint32_t m_dec_bytes_per_line; uint32_t m_src_bytes_per_line; uint32_t m_dst_bytes_per_line;
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;
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;
}
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)
{
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:
{
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)
{
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#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;
}
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;
}
}
return pBuf;
}
}