|
|
@ -26,6 +26,8 @@ GNU General Public License for more details. |
|
|
|
|
|
|
|
|
|
|
|
static const char png_sign[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}; |
|
|
|
static const char png_sign[] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}; |
|
|
|
static const char ihdr_sign[] = {'I', 'H', 'D', 'R'}; |
|
|
|
static const char ihdr_sign[] = {'I', 'H', 'D', 'R'}; |
|
|
|
|
|
|
|
static const char trns_sign[] = {'t', 'R', 'N', 'S'}; |
|
|
|
|
|
|
|
static const char plte_sign[] = {'P', 'L', 'T', 'E'}; |
|
|
|
static const char idat_sign[] = {'I', 'D', 'A', 'T'}; |
|
|
|
static const char idat_sign[] = {'I', 'D', 'A', 'T'}; |
|
|
|
static const char iend_sign[] = {'I', 'E', 'N', 'D'}; |
|
|
|
static const char iend_sign[] = {'I', 'E', 'N', 'D'}; |
|
|
|
static const int iend_crc32 = 0xAE426082; |
|
|
|
static const int iend_crc32 = 0xAE426082; |
|
|
@ -40,8 +42,9 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
int ret; |
|
|
|
int ret; |
|
|
|
short p, a, b, c, pa, pb, pc; |
|
|
|
short p, a, b, c, pa, pb, pc; |
|
|
|
byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL, *rowend; |
|
|
|
byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL, *rowend; |
|
|
|
uint chunk_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; |
|
|
|
byte *pallete = NULL, *trns = NULL; |
|
|
|
uint uncompressed_size, pixel_size, i, y, filter_type, chunk_sign; |
|
|
|
uint chunk_len, trns_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; |
|
|
|
|
|
|
|
uint uncompressed_size, pixel_size, i, y, filter_type, chunk_sign, r_alpha, g_alpha, b_alpha; |
|
|
|
qboolean has_iend_chunk = false; |
|
|
|
qboolean has_iend_chunk = false; |
|
|
|
z_stream stream = {0}; |
|
|
|
z_stream stream = {0}; |
|
|
|
png_t png_hdr; |
|
|
|
png_t png_hdr; |
|
|
@ -67,7 +70,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
// check IHDR chunk length (valid value - 13)
|
|
|
|
// check IHDR chunk length (valid value - 13)
|
|
|
|
if( png_hdr.ihdr_len != sizeof( png_ihdr_t ) ) |
|
|
|
if( png_hdr.ihdr_len != sizeof( png_ihdr_t ) ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid IHDR chunk size (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid IHDR chunk size %u (%s)\n", png_hdr.ihdr_len, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -84,7 +87,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.height == 0 || png_hdr.ihdr_chunk.width == 0 ) |
|
|
|
if( png_hdr.ihdr_chunk.height == 0 || png_hdr.ihdr_chunk.width == 0 ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid image size %dx%d (%s)\n", png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Invalid image size %ux%u (%s)\n", png_hdr.ihdr_chunk.width, png_hdr.ihdr_chunk.height, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -94,21 +97,25 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype != PNG_CT_RGB && png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA ) |
|
|
|
if( !( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB |
|
|
|
|
|
|
|
|| png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA |
|
|
|
|
|
|
|
|| png_hdr.ihdr_chunk.colortype == PNG_CT_GREY |
|
|
|
|
|
|
|
|| png_hdr.ihdr_chunk.colortype == PNG_CT_ALPHA |
|
|
|
|
|
|
|
|| png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE ) ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_WARN "Image_LoadPNG: Only 8-bit RGB and RGBA images is supported (%s)\n", name ); |
|
|
|
Con_DPrintf( S_WARN "Image_LoadPNG: Unknown color type %u (%s)\n", png_hdr.ihdr_chunk.colortype, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.compression > 0 ) |
|
|
|
if( png_hdr.ihdr_chunk.compression > 0 ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown compression method (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown compression method %u (%s)\n", png_hdr.ihdr_chunk.compression, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.filter > 0 ) |
|
|
|
if( png_hdr.ihdr_chunk.filter > 0 ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown filter type (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown filter type %u (%s)\n", png_hdr.ihdr_chunk.filter, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -120,7 +127,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.interlace > 0 ) |
|
|
|
if( png_hdr.ihdr_chunk.interlace > 0 ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown interlacing type (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: Unknown interlacing type %u (%s)\n", png_hdr.ihdr_chunk.interlace, name ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -158,8 +165,19 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
// move pointer
|
|
|
|
// move pointer
|
|
|
|
buf_p += sizeof( chunk_sign ); |
|
|
|
buf_p += sizeof( chunk_sign ); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// find transparency
|
|
|
|
|
|
|
|
if( !memcmp( buf_p, trns_sign, sizeof( trns_sign ) ) ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
trns = buf_p + sizeof( trns_sign ); |
|
|
|
|
|
|
|
trns_len = chunk_len; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// find pallete for indexed image
|
|
|
|
|
|
|
|
else if( !memcmp( buf_p, plte_sign, sizeof( plte_sign ) ) ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
pallete = buf_p + sizeof( plte_sign ); |
|
|
|
|
|
|
|
} |
|
|
|
// get all IDAT chunks data
|
|
|
|
// get all IDAT chunks data
|
|
|
|
if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) |
|
|
|
else if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
newsize = oldsize + chunk_len; |
|
|
|
newsize = oldsize + chunk_len; |
|
|
|
idat_buf = (byte *)Mem_Realloc( host.imagepool, idat_buf, newsize ); |
|
|
|
idat_buf = (byte *)Mem_Realloc( host.imagepool, idat_buf, newsize ); |
|
|
@ -193,6 +211,13 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
buf_p += sizeof( crc32 ); |
|
|
|
buf_p += sizeof( crc32 ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype == PNG_CT_PALLETE && !pallete ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: PLTE chunk not found (%s)\n", name ); |
|
|
|
|
|
|
|
Mem_Free( idat_buf ); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if( !has_iend_chunk ) |
|
|
|
if( !has_iend_chunk ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk not found (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk not found (%s)\n", name ); |
|
|
@ -202,7 +227,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
|
|
|
|
|
|
|
|
if( chunk_len != 0 ) |
|
|
|
if( chunk_len != 0 ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk has wrong size (%s)\n", name ); |
|
|
|
Con_DPrintf( S_ERROR "Image_LoadPNG: IEND chunk has wrong size %u (%s)\n", chunk_len, name ); |
|
|
|
Mem_Free( idat_buf ); |
|
|
|
Mem_Free( idat_buf ); |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
@ -215,6 +240,13 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
|
|
|
|
|
|
|
|
switch( png_hdr.ihdr_chunk.colortype ) |
|
|
|
switch( png_hdr.ihdr_chunk.colortype ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
case PNG_CT_GREY: |
|
|
|
|
|
|
|
case PNG_CT_PALLETE: |
|
|
|
|
|
|
|
pixel_size = 1; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case PNG_CT_ALPHA: |
|
|
|
|
|
|
|
pixel_size = 2; |
|
|
|
|
|
|
|
break; |
|
|
|
case PNG_CT_RGB: |
|
|
|
case PNG_CT_RGB: |
|
|
|
pixel_size = 3; |
|
|
|
pixel_size = 3; |
|
|
|
break; |
|
|
|
break; |
|
|
@ -231,9 +263,11 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
image.width = png_hdr.ihdr_chunk.width; |
|
|
|
image.width = png_hdr.ihdr_chunk.width; |
|
|
|
image.height = png_hdr.ihdr_chunk.height; |
|
|
|
image.height = png_hdr.ihdr_chunk.height; |
|
|
|
image.size = image.height * image.width * 4; |
|
|
|
image.size = image.height * image.width * 4; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype & PNG_CT_RGB ) |
|
|
|
image.flags |= IMAGE_HAS_COLOR; |
|
|
|
image.flags |= IMAGE_HAS_COLOR; |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA ) |
|
|
|
if( trns || ( png_hdr.ihdr_chunk.colortype & PNG_CT_ALPHA ) ) |
|
|
|
image.flags |= IMAGE_HAS_ALPHA; |
|
|
|
image.flags |= IMAGE_HAS_ALPHA; |
|
|
|
|
|
|
|
|
|
|
|
image.depth = 1; |
|
|
|
image.depth = 1; |
|
|
@ -275,7 +309,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
|
|
|
|
|
|
|
|
raw = uncompressed_buffer; |
|
|
|
raw = uncompressed_buffer; |
|
|
|
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) |
|
|
|
if( png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA ) |
|
|
|
prior = pixbuf = raw; |
|
|
|
prior = pixbuf = raw; |
|
|
|
|
|
|
|
|
|
|
|
filter_type = *raw++; |
|
|
|
filter_type = *raw++; |
|
|
@ -377,12 +411,19 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
prior = pixbuf; |
|
|
|
prior = pixbuf; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// convert RGB-to-RGBA
|
|
|
|
|
|
|
|
if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
pixbuf = image.rgba; |
|
|
|
pixbuf = image.rgba; |
|
|
|
raw = uncompressed_buffer; |
|
|
|
raw = uncompressed_buffer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
switch( png_hdr.ihdr_chunk.colortype ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
case PNG_CT_RGB: |
|
|
|
|
|
|
|
if( trns ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
r_alpha = trns[0] << 8 | trns[1]; |
|
|
|
|
|
|
|
g_alpha = trns[2] << 8 | trns[3]; |
|
|
|
|
|
|
|
b_alpha = trns[4] << 8 | trns[5]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for( y = 0; y < image.height; y++ ) |
|
|
|
for( y = 0; y < image.height; y++ ) |
|
|
|
{ |
|
|
|
{ |
|
|
|
rowend = raw + rowsize; |
|
|
|
rowend = raw + rowsize; |
|
|
@ -391,9 +432,68 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi |
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
*pixbuf++ = raw[1]; |
|
|
|
*pixbuf++ = raw[1]; |
|
|
|
*pixbuf++ = raw[2]; |
|
|
|
*pixbuf++ = raw[2]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( trns && r_alpha == raw[0] |
|
|
|
|
|
|
|
&& g_alpha == raw[1] |
|
|
|
|
|
|
|
&& b_alpha == raw[2] ) |
|
|
|
|
|
|
|
*pixbuf++ = 0; |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
*pixbuf++ = 0xFF; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case PNG_CT_GREY: |
|
|
|
|
|
|
|
if( trns ) |
|
|
|
|
|
|
|
r_alpha = trns[0] << 8 | trns[1]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for( y = 0; y < image.height; y++ ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
rowend = raw + rowsize; |
|
|
|
|
|
|
|
for( ; raw < rowend; raw += pixel_size ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( trns && r_alpha == raw[0] ) |
|
|
|
|
|
|
|
*pixbuf++ = 0; |
|
|
|
|
|
|
|
else |
|
|
|
*pixbuf++ = 0xFF; |
|
|
|
*pixbuf++ = 0xFF; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case PNG_CT_ALPHA: |
|
|
|
|
|
|
|
for( y = 0; y < image.height; y++ ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
rowend = raw + rowsize; |
|
|
|
|
|
|
|
for( ; raw < rowend; raw += pixel_size ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
*pixbuf++ = raw[0]; |
|
|
|
|
|
|
|
*pixbuf++ = raw[1]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case PNG_CT_PALLETE: |
|
|
|
|
|
|
|
for( y = 0; y < image.height; y++ ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
rowend = raw + rowsize; |
|
|
|
|
|
|
|
for( ; raw < rowend; raw += pixel_size ) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
*pixbuf++ = pallete[raw[0] + 2]; |
|
|
|
|
|
|
|
*pixbuf++ = pallete[raw[0] + 1]; |
|
|
|
|
|
|
|
*pixbuf++ = pallete[raw[0] + 0]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if( trns && raw[0] < trns_len ) |
|
|
|
|
|
|
|
*pixbuf++ = trns[raw[0]]; |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
*pixbuf++ = 0xFF; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Mem_Free( uncompressed_buffer ); |
|
|
|
Mem_Free( uncompressed_buffer ); |
|
|
|