From fd152e82e837331b2a34c0a077b686a33d5255f3 Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin <15944199+nekonomicon@users.noreply.github.com> Date: Mon, 5 Sep 2022 00:44:35 +0500 Subject: [PATCH] engine: common: imagelib: img_png.c: add support for indexed and grayscale PNG images. --- engine/common/imagelib/img_png.c | 138 ++++++++++++++++++++++++++----- engine/common/imagelib/img_png.h | 2 +- 2 files changed, 120 insertions(+), 20 deletions(-) diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 2c129ba1..25b32544 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -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 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 iend_sign[] = {'I', 'E', 'N', 'D'}; 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; short p, a, b, c, pa, pb, pc; byte *buf_p, *pixbuf, *raw, *prior, *idat_buf = NULL, *uncompressed_buffer = NULL, *rowend; - uint chunk_len, crc32, crc32_check, oldsize = 0, newsize = 0, rowsize; - uint uncompressed_size, pixel_size, i, y, filter_type, chunk_sign; + byte *pallete = NULL, *trns = NULL; + 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; z_stream stream = {0}; 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) 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; } @@ -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 ) { - 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; } @@ -94,21 +97,25 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi 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; } 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; } 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; } @@ -120,7 +127,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi 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; } @@ -158,8 +165,19 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi // move pointer 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 - if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) + else if( !memcmp( buf_p, idat_sign, sizeof( idat_sign ) ) ) { newsize = oldsize + chunk_len; 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 ); } + 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 ) { 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 ) { - 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 ); 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 ) { + case PNG_CT_GREY: + case PNG_CT_PALLETE: + pixel_size = 1; + break; + case PNG_CT_ALPHA: + pixel_size = 2; + break; case PNG_CT_RGB: pixel_size = 3; 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.height = png_hdr.ihdr_chunk.height; image.size = image.height * image.width * 4; - image.flags |= IMAGE_HAS_COLOR; - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGBA ) + if( png_hdr.ihdr_chunk.colortype & PNG_CT_RGB ) + image.flags |= IMAGE_HAS_COLOR; + + if( trns || ( png_hdr.ihdr_chunk.colortype & PNG_CT_ALPHA ) ) image.flags |= IMAGE_HAS_ALPHA; image.depth = 1; @@ -275,7 +309,7 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi raw = uncompressed_buffer; - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) + if( png_hdr.ihdr_chunk.colortype != PNG_CT_RGBA ) prior = pixbuf = raw; filter_type = *raw++; @@ -377,11 +411,18 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi prior = pixbuf; } - // convert RGB-to-RGBA - if( png_hdr.ihdr_chunk.colortype == PNG_CT_RGB ) + pixbuf = image.rgba; + raw = uncompressed_buffer; + + switch( png_hdr.ihdr_chunk.colortype ) { - pixbuf = image.rgba; - raw = uncompressed_buffer; + 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++ ) { @@ -391,9 +432,68 @@ qboolean Image_LoadPNG( const char *name, const byte *buffer, fs_offset_t filesi *pixbuf++ = raw[0]; *pixbuf++ = raw[1]; *pixbuf++ = raw[2]; - *pixbuf++ = 0xFF; + + 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; + } + } + 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 ); diff --git a/engine/common/imagelib/img_png.h b/engine/common/imagelib/img_png.h index 3477805e..ab1255df 100644 --- a/engine/common/imagelib/img_png.h +++ b/engine/common/imagelib/img_png.h @@ -25,8 +25,8 @@ GNU General Public License for more details. enum png_colortype { PNG_CT_GREY, - PNG_CT_PALLETE = BIT(0), PNG_CT_RGB = BIT(1), + PNG_CT_PALLETE = (PNG_CT_RGB|BIT(0)), PNG_CT_ALPHA = BIT(2), PNG_CT_RGBA = (PNG_CT_RGB|PNG_CT_ALPHA) };