summaryrefslogblamecommitdiffstatshomepage
path: root/src/lib/util/png.cpp
blob: 8023d350cdc63de91a53e8be5acdb96c374ee3cd (plain) (tree)
1
2
3
4
5
6
7
8
9

                                





                                                                      
                                                                            

                 
                   
                   
 


                 

              




                                                                            
                       
 


                                   


  
                  
 




                                       















                                                                            
                                        




                  
                      
                                          


                                                 
      
 
                                          




                                                 
                                                 




                  
                      
                                                   


                                                 
      
 
                                                   




                                                 
                                                      




                                                                     
                                                           
















                                                                                            
                                            


                                                   
                                             



                                                    
                                        
                                       
                                   
 
                                      
                                     
                                 
 
                                      
                                     
                                 












                                                                            
                                                   



                           
                                       













                                                     
                                                                                            




                                            
                                      



                                             
                                      










                                                                    
                        



                                                    
                                     


                                                    
                                                       

                                    
                                        







                                                     
                                      

                            
                                







                                             
                                














































                                                                                                       
                                                                                                 
                                                        


                                                            
                                                           























                                                                                  
                                                                 
                                            




                                                                               
                                             

                                                        

                                                                                                                












































                                                                                                      
                                               






                                                                                                   
                                               



















                                                                                     

                                                                                               









































                                                                
                                           













                                                                           
                                                                   

































                                                                          
                                                                                                            








                                                
                                              








                                                   
                                                               
 
                                    




































                                                                                            
                                     









                                         
                                    

                                                         
                                               



                                              
                                  













                                                                  
          

                                                   
                                                                     






















                                                                                                           
                                               










                                                                                              
                                                                                                                                                                  







                                                              
                                                                                                    






                                                                 
                                                                                                          






                                                                 
                                                                                                              






















                                                            
                                                                   
                              










































                                                                                                                
                                                       
                               



                                                        
                                                                 
                                











                                              
                                

                                                  

                                                                                           












                                                   
                                                                                                









                                                   
                                       




                                         
                                                     





                                                 
                                       










                                                   
                                                                                                   
 
                                     
                             

                           








                                                   
                                       





                                           


                                                           












                                                                        
                                                               


























                                                          
                                       


                                             
                                     
                                         
                                       


                                         
                                                       








                                                   
                                                                                                                                     




                                 

                                          





                                             
                                                    
                                        






                                             


                                                        


                                           
                                                                           
                                      







                                                          
                                                                               
















                                                                 
                                                                                                                                 
 
                                                              



                                 

                                          




                                                    
                                                                           
                                      




                                                          





                                                                 
                                                           
                 
                                                                                         


                                                                


                                                   


                         
                                       
                                                                
                 
                                                                                         

                                                            



                                                     



                                        
                                                                 
                 
                                                                                         

                                                            




                                                     
















                                                         
                                                                                                                                           





                                                                      
                                                                            































                                                                                                                         
                                                                          






                                                                                                                                             
                                                         





                     
                                                                                                                           




                                                      
                            





                                                     
                                            




















                                                                                 
   
                                                                                       
  
                              
  


                                                
  
                        

   
                                                                               



                        
                                            


                                         

                                             


                                                              
                                         






                                                       
   
                                                                                                                                    
  
                              
  




                                                         
  
                        

   
                                                                                                                            



                                                                           
   
                                                       
  
                             
  
                                            
  
                        

   
                                               
 
                                                        
 
// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*********************************************************************

    png.c

    PNG reading functions.

***************************************************************************/

#include <math.h>
#include <stdlib.h>
#include <assert.h>

#include <zlib.h>
#include "png.h"

#include <new>


/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

struct image_data_chunk
{
	image_data_chunk *  next;
	int                 length;
	UINT8 *             data;
};


struct png_private
{
	png_info *          pnginfo;
	image_data_chunk *  idata;
	image_data_chunk ** idata_next;
	UINT8               bpp;
	UINT32              rowbytes;
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

static const int samples[] = { 1, 0, 3, 1, 2, 0, 4 };



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

static inline UINT8 fetch_8bit(UINT8 *v)
{
	return *v;
}


#ifdef UNUSED_FUNCTION
static inline UINT16 fetch_16bit(UINT8 *v)
{
	return BIG_ENDIANIZE_INT16(*(UINT16 *)v);
}
#endif

static inline UINT32 fetch_32bit(UINT8 *v)
{
	return BIG_ENDIANIZE_INT32(*(UINT32 *)v);
}


static inline void put_8bit(UINT8 *v, UINT8 data)
{
	*v = data;
}


#ifdef UNUSED_FUNCTION
static inline void put_16bit(UINT8 *v, UINT16 data)
{
	*(UINT16 *)v = BIG_ENDIANIZE_INT16(data);
}
#endif

static inline void put_32bit(UINT8 *v, UINT32 data)
{
	*(UINT32 *)v = BIG_ENDIANIZE_INT32(data);
}


static inline int compute_bpp(const png_info *pnginfo)
{
	return samples[pnginfo->color_type] * pnginfo->bit_depth / 8;
}


static inline int compute_rowbytes(const png_info *pnginfo)
{
	return (pnginfo->width * samples[pnginfo->color_type] * pnginfo->bit_depth + 7) / 8;
}



/***************************************************************************
    GENERAL FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    png_free - free all memory allocated in a
    pnginfo structure
-------------------------------------------------*/

void png_free(png_info *pnginfo)
{
	while (pnginfo->textlist != nullptr)
	{
		png_text *temp = pnginfo->textlist;
		pnginfo->textlist = temp->next;
		if (temp->keyword != nullptr)
			free((void *)temp->keyword);
		free(temp);
	}

	if (pnginfo->palette != nullptr)
		free(pnginfo->palette);
	pnginfo->palette = nullptr;

	if (pnginfo->trans != nullptr)
		free(pnginfo->trans);
	pnginfo->trans = nullptr;

	if (pnginfo->image != nullptr)
		free(pnginfo->image);
	pnginfo->image = nullptr;
}



/***************************************************************************
    PNG READING FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    verify_header - verify the PNG
    header at the current file location
-------------------------------------------------*/

static png_error verify_header(util::core_file &fp)
{
	UINT8 signature[8];

	/* read 8 bytes */
	if (fp.read(signature, 8) != 8)
		return PNGERR_FILE_TRUNCATED;

	/* return an error if we don't match */
	if (memcmp(signature, PNG_Signature, 8) != 0)
		return PNGERR_BAD_SIGNATURE;

	return PNGERR_NONE;
}


/*-------------------------------------------------
    read_chunk - read the next PNG chunk
-------------------------------------------------*/

static png_error read_chunk(util::core_file &fp, UINT8 **data, UINT32 *type, UINT32 *length)
{
	UINT32 crc, chunk_crc;
	UINT8 tempbuff[4];

	/* fetch the length of this chunk */
	if (fp.read(tempbuff, 4) != 4)
		return PNGERR_FILE_TRUNCATED;
	*length = fetch_32bit(tempbuff);

	/* fetch the type of this chunk */
	if (fp.read(tempbuff, 4) != 4)
		return PNGERR_FILE_TRUNCATED;
	*type = fetch_32bit(tempbuff);

	/* stop when we hit an IEND chunk */
	if (*type == PNG_CN_IEND)
		return PNGERR_NONE;

	/* start the CRC with the chunk type (but not the length) */
	crc = crc32(0, tempbuff, 4);

	/* read the chunk itself into an allocated memory buffer */
	*data = nullptr;
	if (*length != 0)
	{
		/* allocate memory for this chunk */
		*data = (UINT8 *)malloc(*length);
		if (*data == nullptr)
			return PNGERR_OUT_OF_MEMORY;

		/* read the data from the file */
		if (fp.read(*data, *length) != *length)
		{
			free(*data);
			*data = nullptr;
			return PNGERR_FILE_TRUNCATED;
		}

		/* update the CRC */
		crc = crc32(crc, *data, *length);
	}

	/* read the CRC */
	if (fp.read(tempbuff, 4) != 4)
	{
		free(*data);
		*data = nullptr;
		return PNGERR_FILE_TRUNCATED;
	}
	chunk_crc = fetch_32bit(tempbuff);

	/* validate the CRC */
	if (crc != chunk_crc)
	{
		free(*data);
		*data = nullptr;
		return PNGERR_FILE_CORRUPT;
	}
	return PNGERR_NONE;
}


/*-------------------------------------------------
    process_chunk - process a PNG chunk
-------------------------------------------------*/

static png_error process_chunk(png_private *png, UINT8 *data, UINT32 type, UINT32 length, int *keepmem)
{
	/* default to not keeping memory */
	*keepmem = FALSE;

	/* switch off of the type */
	switch (type)
	{
		/* image header */
		case PNG_CN_IHDR:
			png->pnginfo->width = fetch_32bit(data);
			png->pnginfo->height = fetch_32bit(data + 4);
			png->pnginfo->bit_depth = fetch_8bit(data + 8);
			png->pnginfo->color_type = fetch_8bit(data + 9);
			png->pnginfo->compression_method = fetch_8bit(data + 10);
			png->pnginfo->filter_method = fetch_8bit(data + 11);
			png->pnginfo->interlace_method = fetch_8bit(data + 12);
			break;

		/* palette */
		case PNG_CN_PLTE:
			png->pnginfo->num_palette = length / 3;
			png->pnginfo->palette = data;
			*keepmem = TRUE;
			break;

		/* transparency information */
		case PNG_CN_tRNS:
			png->pnginfo->num_trans = length;
			png->pnginfo->trans = data;
			*keepmem = TRUE;
			break;

		/* image data */
		case PNG_CN_IDAT:

			/* allocate a new image data descriptor */
			*png->idata_next = (image_data_chunk *)malloc(sizeof(**png->idata_next));
			if (*png->idata_next == nullptr)
				return PNGERR_OUT_OF_MEMORY;

			/* add it to the tail of the list */
			(*png->idata_next)->next = nullptr;
			(*png->idata_next)->length = length;
			(*png->idata_next)->data = data;
			png->idata_next = &(*png->idata_next)->next;
			*keepmem = TRUE;
			break;

		/* gamma */
		case PNG_CN_gAMA:
			png->pnginfo->source_gamma = fetch_32bit(data) / 100000.0;
			break;

		/* physical information */
		case PNG_CN_pHYs:
			png->pnginfo->xres = fetch_32bit(data);
			png->pnginfo->yres = fetch_32bit(data + 4);
			png->pnginfo->resolution_unit = fetch_8bit(data + 8);
			break;

		/* text */
		case PNG_CN_tEXt:
		{
			png_text *text, *pt, *ct;

			/* allocate a new text item */
			text = (png_text *)malloc(sizeof(*text));
			if (text == nullptr)
				return PNGERR_OUT_OF_MEMORY;

			/* set the elements */
			text->keyword = (char *)data;
			text->text = text->keyword + strlen(text->keyword) + 1;
			text->next = nullptr;

			/* add to the end of the list */
			for (pt = nullptr, ct = png->pnginfo->textlist; ct != nullptr; pt = ct, ct = ct->next) ;
			if (pt == nullptr)
				png->pnginfo->textlist = text;
			else
				pt->next = text;

			*keepmem = TRUE;
			break;
		}

		/* anything else */
		default:
			if ((type & 0x20000000) == 0)
				return PNGERR_UNKNOWN_CHUNK;
			break;
	}
	return PNGERR_NONE;
}


/*-------------------------------------------------
    unfilter_row - unfilter a single row of pixels
-------------------------------------------------*/

static png_error unfilter_row(int type, UINT8 *src, UINT8 *dst, UINT8 *dstprev, int bpp, int rowbytes)
{
	int x;

	/* switch off of it */
	switch (type)
	{
		/* no filter, just copy */
		case PNG_PF_None:
			for (x = 0; x < rowbytes; x++)
				*dst++ = *src++;
			break;

		/* SUB = previous pixel */
		case PNG_PF_Sub:
			for (x = 0; x < bpp; x++)
				*dst++ = *src++;
			for (x = bpp; x < rowbytes; x++, dst++)
				*dst = *src++ + dst[-bpp];
			break;

		/* UP = pixel above */
		case PNG_PF_Up:
			if (dstprev == nullptr)
				return unfilter_row(PNG_PF_None, src, dst, dstprev, bpp, rowbytes);
			for (x = 0; x < rowbytes; x++, dst++)
				*dst = *src++ + *dstprev++;
			break;

		/* AVERAGE = average of pixel above and previous pixel */
		case PNG_PF_Average:
			if (dstprev == nullptr)
			{
				for (x = 0; x < bpp; x++)
					*dst++ = *src++;
				for (x = bpp; x < rowbytes; x++, dst++)
					*dst = *src++ + dst[-bpp] / 2;
			}
			else
			{
				for (x = 0; x < bpp; x++, dst++)
					*dst = *src++ + *dstprev++ / 2;
				for (x = bpp; x < rowbytes; x++, dst++)
					*dst = *src++ + (*dstprev++ + dst[-bpp]) / 2;
			}
			break;

		/* PAETH = special filter */
		case PNG_PF_Paeth:
			for (x = 0; x < rowbytes; x++)
			{
				INT32 pa = (x < bpp) ? 0 : dst[-bpp];
				INT32 pc = (x < bpp || dstprev == nullptr) ? 0 : dstprev[-bpp];
				INT32 pb = (dstprev == nullptr) ? 0 : *dstprev++;
				INT32 prediction = pa + pb - pc;
				INT32 da = abs(prediction - pa);
				INT32 db = abs(prediction - pb);
				INT32 dc = abs(prediction - pc);
				if (da <= db && da <= dc)
					*dst++ = *src++ + pa;
				else if (db <= dc)
					*dst++ = *src++ + pb;
				else
					*dst++ = *src++ + pc;
			}
			break;

		/* unknown filter type */
		default:
			return PNGERR_UNKNOWN_FILTER;
	}

	return PNGERR_NONE;
}


/*-------------------------------------------------
    process_image - post-process a loaded iamge
-------------------------------------------------*/

static png_error process_image(png_private *png)
{
	int rowbytes, bpp, imagesize;
	png_error error = PNGERR_NONE;
	image_data_chunk *idat;
	UINT8 *src, *dst;
	z_stream stream;
	int zerr, y;

	/* compute some basic parameters */
	bpp = compute_bpp(png->pnginfo);
	rowbytes = compute_rowbytes(png->pnginfo);
	imagesize = png->pnginfo->height * (rowbytes + 1);

	/* allocate memory for the filtered image */
	png->pnginfo->image = (UINT8 *)malloc(imagesize);
	if (png->pnginfo->image == nullptr)
		return PNGERR_OUT_OF_MEMORY;

	/* initialize the stream */
	memset(&stream, 0, sizeof(stream));
	stream.next_out = png->pnginfo->image;
	stream.avail_out = imagesize;
	zerr = inflateInit(&stream);
	if (zerr != Z_OK)
	{
		error = PNGERR_DECOMPRESS_ERROR;
		goto handle_error;
	}

	/* loop over IDAT and decompress each as part of a larger stream */
	for (idat = png->idata; idat != nullptr; idat = idat->next)
	{
		/* decompress this chunk */
		stream.next_in = idat->data;
		stream.avail_in = idat->length;
		zerr = inflate(&stream, Z_NO_FLUSH);

		/* stop at the end of the stream */
		if (zerr == Z_STREAM_END)
			break;

		/* other errors are fatal */
		if (zerr != Z_OK)
		{
			error = PNGERR_DECOMPRESS_ERROR;
			goto handle_error;
		}
	}

	/* clean up */
	zerr = inflateEnd(&stream);
	if (zerr != Z_OK)
	{
		error = PNGERR_DECOMPRESS_ERROR;
		goto handle_error;
	}

	/* we de-filter in place */
	src = dst = png->pnginfo->image;

	/* iterate over rows */
	for (y = 0; y < png->pnginfo->height && error == PNGERR_NONE; y++)
	{
		/* first byte of each row is the filter type */
		int filter = *src++;
		error = unfilter_row(filter, src, dst, (y == 0) ? nullptr : &dst[-rowbytes], bpp, rowbytes);
		src += rowbytes;
		dst += rowbytes;
	}

handle_error:
	/* if we errored, free the image data */
	if (error != PNGERR_NONE)
	{
		free(png->pnginfo->image);
		png->pnginfo->image = nullptr;
	}
	return error;
}


/*-------------------------------------------------
    png_read_file - read a PNG from a core stream
-------------------------------------------------*/

png_error png_read_file(util::core_file &fp, png_info *pnginfo)
{
	UINT8 *chunk_data = nullptr;
	png_private png;
	png_error error;

	/* initialize the data structures */
	memset(&png, 0, sizeof(png));
	memset(pnginfo, 0, sizeof(*pnginfo));
	png.pnginfo = pnginfo;
	png.idata_next = &png.idata;

	/* verify the signature at the start of the file */
	error = verify_header(fp);
	if (error != PNGERR_NONE)
		goto handle_error;

	/* loop until we hit an IEND chunk */
	for ( ; ; )
	{
		UINT32 chunk_type, chunk_length;
		int keepmem;

		/* read a chunk */
		error = read_chunk(fp, &chunk_data, &chunk_type, &chunk_length);
		if (error != PNGERR_NONE)
			goto handle_error;

		/* stop when we hit an IEND chunk */
		if (chunk_type == PNG_CN_IEND)
			break;

		/* process the chunk */
		error = process_chunk(&png, chunk_data, chunk_type, chunk_length, &keepmem);
		if (error != PNGERR_NONE)
			goto handle_error;

		/* free memory if we didn't want to keep it */
		if (!keepmem)
			free(chunk_data);
		chunk_data = nullptr;
	}

	/* finish processing the image */
	error = process_image(&png);
	if (error != PNGERR_NONE)
		goto handle_error;

handle_error:

	/* free all intermediate data */
	while (png.idata != nullptr)
	{
		image_data_chunk *next = png.idata->next;
		if (png.idata->data != nullptr)
			free(png.idata->data);
		free(png.idata);
		png.idata = next;
	}
	if (chunk_data != nullptr)
		free(chunk_data);

	/* if we have an error, free all the other data as well */
	if (error != PNGERR_NONE)
	{
		png_free(pnginfo);
		memset(pnginfo, 0, sizeof(*pnginfo));
	}
	return error;
}


/*-------------------------------------------------
    png_read_bitmap - load a PNG file into a
    bitmap
-------------------------------------------------*/

png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap)
{
	png_error result;
	png_info png;
	UINT8 *src;
	int x, y;

	/* read the PNG data */
	result = png_read_file(fp, &png);
	if (result != PNGERR_NONE)
		return result;

	/* verify we can handle this PNG */
	if (png.bit_depth > 8 || png.interlace_method != 0 ||
		(png.color_type != 0 && png.color_type != 3 && png.color_type != 2 && png.color_type != 6))
	{
		png_free(&png);
		return PNGERR_UNSUPPORTED_FORMAT;
	}

	/* if less than 8 bits, upsample */
	png_expand_buffer_8bit(&png);

	/* allocate a bitmap of the appropriate size and copy it */
	bitmap.allocate(png.width, png.height);

	/* handle 8bpp palettized case */
	src = png.image;
	if (png.color_type == 3)
	{
		/* loop over width/height */
		for (y = 0; y < png.height; y++)
			for (x = 0; x < png.width; x++, src++)
			{
				/* determine alpha and expand to 32bpp */
				UINT8 alpha = (*src < png.num_trans) ? png.trans[*src] : 0xff;
				bitmap.pix32(y, x) = (alpha << 24) | (png.palette[*src * 3] << 16) | (png.palette[*src * 3 + 1] << 8) | png.palette[*src * 3 + 2];
			}
	}

	/* handle 8bpp grayscale case */
	else if (png.color_type == 0)
	{
		for (y = 0; y < png.height; y++)
			for (x = 0; x < png.width; x++, src++)
				bitmap.pix32(y, x) = 0xff000000 | (*src << 16) | (*src << 8) | *src;
	}

	/* handle 32bpp non-alpha case */
	else if (png.color_type == 2)
	{
		for (y = 0; y < png.height; y++)
			for (x = 0; x < png.width; x++, src += 3)
				bitmap.pix32(y, x) = 0xff000000 | (src[0] << 16) | (src[1] << 8) | src[2];
	}

	/* handle 32bpp alpha case */
	else if (png.color_type == 6)
	{
		for (y = 0; y < png.height; y++)
			for (x = 0; x < png.width; x++, src += 4)
				bitmap.pix32(y, x) = (src[3] << 24) | (src[0] << 16) | (src[1] << 8) | src[2];
	}

	/* free our temporary data and return */
	png_free(&png);
	return PNGERR_NONE;
}


/*-------------------------------------------------
    png_expand_buffer_8bit - expand a buffer from
    sub 8-bit to 8-bit
-------------------------------------------------*/

png_error png_expand_buffer_8bit(png_info *pnginfo)
{
	int i,j, k;
	UINT8 *inp, *outp, *outbuf;

	/* nothing to do if we're at 8 or greater already */
	if (pnginfo->bit_depth >= 8)
		return PNGERR_NONE;

	/* allocate a new buffer at 8-bit */
	outbuf = (UINT8 *)malloc(pnginfo->width * pnginfo->height);
	if (outbuf == nullptr)
		return PNGERR_OUT_OF_MEMORY;

	inp = pnginfo->image;
	outp = outbuf;

	for (i = 0; i < pnginfo->height; i++)
	{
		for(j = 0; j < pnginfo->width / ( 8 / pnginfo->bit_depth); j++)
		{
			for (k = 8 / pnginfo->bit_depth-1; k >= 0; k--)
				*outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
			inp++;
		}
		if (pnginfo->width % (8 / pnginfo->bit_depth))
		{
			for (k = pnginfo->width % (8 / pnginfo->bit_depth)-1; k >= 0; k--)
				*outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth));
			inp++;
		}
	}
	free (pnginfo->image);
	pnginfo->image = outbuf;

	return PNGERR_NONE;
}



/***************************************************************************
    PNG WRITING FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    png_add_text - add a text entry to the png_info
-------------------------------------------------*/

png_error png_add_text(png_info *pnginfo, const char *keyword, const char *text)
{
	png_text *newtext, *pt, *ct;
	char *textdata;
	int keylen;

	/* allocate a new text element */
	newtext = (png_text *)malloc(sizeof(*newtext));
	if (newtext == nullptr)
		return PNGERR_OUT_OF_MEMORY;

	/* allocate a string long enough to hold both */
	keylen = (int)strlen(keyword);
	textdata = (char *)malloc(keylen + 1 + strlen(text) + 1);
	if (textdata == nullptr)
	{
		free(newtext);
		return PNGERR_OUT_OF_MEMORY;
	}

	/* copy in the data */
	strcpy(textdata, keyword);
	strcpy(textdata + keylen + 1, text);

	/* text follows a trailing NULL */
	newtext->keyword = textdata;
	newtext->text = textdata + keylen + 1;
	newtext->next = nullptr;

	/* add us to the end of the linked list */
	for (pt = nullptr, ct = pnginfo->textlist; ct != nullptr; pt = ct, ct = ct->next) ;
	if (pt == nullptr)
		pnginfo->textlist = newtext;
	else
		pt->next = newtext;

	return PNGERR_NONE;
}


/*-------------------------------------------------
    write_chunk - write an in-memory chunk to
    the given file
-------------------------------------------------*/

static png_error write_chunk(util::core_file &fp, const UINT8 *data, UINT32 type, UINT32 length)
{
	UINT8 tempbuff[8];
	UINT32 crc;

	/* stuff the length/type into the buffer */
	put_32bit(tempbuff + 0, length);
	put_32bit(tempbuff + 4, type);
	crc = crc32(0, tempbuff + 4, 4);

	/* write that data */
	if (fp.write(tempbuff, 8) != 8)
		return PNGERR_FILE_ERROR;

	/* append the actual data */
	if (length > 0)
	{
		if (fp.write(data, length) != length)
			return PNGERR_FILE_ERROR;
		crc = crc32(crc, data, length);
	}

	/* write the CRC */
	put_32bit(tempbuff, crc);
	if (fp.write(tempbuff, 4) != 4)
		return PNGERR_FILE_ERROR;

	return PNGERR_NONE;
}


/*-------------------------------------------------
    write_deflated_chunk - write an in-memory
    chunk to the given file by deflating it
-------------------------------------------------*/

static png_error write_deflated_chunk(util::core_file &fp, UINT8 *data, UINT32 type, UINT32 length)
{
	UINT64 lengthpos = fp.tell();
	UINT8 tempbuff[8192];
	UINT32 zlength = 0;
	z_stream stream;
	UINT32 crc;
	int zerr;

	/* stuff the length/type into the buffer */
	put_32bit(tempbuff + 0, length);
	put_32bit(tempbuff + 4, type);
	crc = crc32(0, tempbuff + 4, 4);

	/* write that data */
	if (fp.write(tempbuff, 8) != 8)
		return PNGERR_FILE_ERROR;

	/* initialize the stream */
	memset(&stream, 0, sizeof(stream));
	stream.next_in = data;
	stream.avail_in = length;
	zerr = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
	if (zerr != Z_OK)
		return PNGERR_COMPRESS_ERROR;

	/* now loop until we run out of data */
	for ( ; ; )
	{
		/* compress this chunk */
		stream.next_out = tempbuff;
		stream.avail_out = sizeof(tempbuff);
		zerr = deflate(&stream, Z_FINISH);

		/* if there's data to write, do it */
		if (stream.avail_out < sizeof(tempbuff))
		{
			int bytes = sizeof(tempbuff) - stream.avail_out;
			if (fp.write(tempbuff, bytes) != bytes)
			{
				deflateEnd(&stream);
				return PNGERR_FILE_ERROR;
			}
			crc = crc32(crc, tempbuff, bytes);
			zlength += bytes;
		}

		/* stop at the end of the stream */
		if (zerr == Z_STREAM_END)
			break;

		/* other errors are fatal */
		if (zerr != Z_OK)
		{
			deflateEnd(&stream);
			return PNGERR_COMPRESS_ERROR;
		}
	}

	/* clean up deflater(maus) */
	zerr = deflateEnd(&stream);
	if (zerr != Z_OK)
		return PNGERR_COMPRESS_ERROR;

	/* write the CRC */
	put_32bit(tempbuff, crc);
	if (fp.write(tempbuff, 4) != 4)
		return PNGERR_FILE_ERROR;

	/* seek back and update the length */
	fp.seek(lengthpos, SEEK_SET);
	put_32bit(tempbuff + 0, zlength);
	if (fp.write(tempbuff, 4) != 4)
		return PNGERR_FILE_ERROR;

	/* return to the end */
	fp.seek(lengthpos + 8 + zlength + 4, SEEK_SET);
	return PNGERR_NONE;
}


/*-------------------------------------------------
    convert_bitmap_to_image_palette - convert a
    bitmap to a palettized image
-------------------------------------------------*/

static png_error convert_bitmap_to_image_palette(png_info *pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
	int rowbytes;
	int x, y;

	/* set the common info */
	pnginfo->width = bitmap.width();
	pnginfo->height = bitmap.height();
	pnginfo->bit_depth = 8;
	pnginfo->color_type = 3;
	pnginfo->num_palette = 256;
	rowbytes = pnginfo->width;

	/* allocate memory for the palette */
	pnginfo->palette = (UINT8 *)malloc(3 * 256);
	if (pnginfo->palette == nullptr)
		return PNGERR_OUT_OF_MEMORY;

	/* build the palette */
	memset(pnginfo->palette, 0, 3 * 256);
	for (x = 0; x < palette_length; x++)
	{
		rgb_t color = palette[x];
		pnginfo->palette[3 * x + 0] = color.r();
		pnginfo->palette[3 * x + 1] = color.g();
		pnginfo->palette[3 * x + 2] = color.b();
	}

	/* allocate memory for the image */
	pnginfo->image = (UINT8 *)malloc(pnginfo->height * (rowbytes + 1));
	if (pnginfo->image == nullptr)
	{
		free(pnginfo->palette);
		return PNGERR_OUT_OF_MEMORY;
	}

	/* copy in the pixels, specifying a NULL filter */
	for (y = 0; y < pnginfo->height; y++)
	{
		UINT16 *src = reinterpret_cast<UINT16 *>(bitmap.raw_pixptr(y));
		UINT8 *dst = pnginfo->image + y * (rowbytes + 1);

		/* store the filter byte, then copy the data */
		*dst++ = 0;
		for (x = 0; x < pnginfo->width; x++)
			*dst++ = *src++;
	}

	return PNGERR_NONE;
}


/*-------------------------------------------------
    convert_bitmap_to_image_rgb - convert a
    bitmap to an RGB image
-------------------------------------------------*/

static png_error convert_bitmap_to_image_rgb(png_info *pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
	int alpha = (bitmap.format() == BITMAP_FORMAT_ARGB32);
	int rowbytes;
	int x, y;

	/* set the common info */
	pnginfo->width = bitmap.width();
	pnginfo->height = bitmap.height();
	pnginfo->bit_depth = 8;
	pnginfo->color_type = alpha ? 6 : 2;
	rowbytes = pnginfo->width * (alpha ? 4 : 3);

	/* allocate memory for the image */
	pnginfo->image = (UINT8 *)malloc(pnginfo->height * (rowbytes + 1));
	if (pnginfo->image == nullptr)
		return PNGERR_OUT_OF_MEMORY;

	/* copy in the pixels, specifying a NULL filter */
	for (y = 0; y < pnginfo->height; y++)
	{
		UINT8 *dst = pnginfo->image + y * (rowbytes + 1);

		/* store the filter byte, then copy the data */
		*dst++ = 0;

		/* 16bpp palettized format */
		if (bitmap.format() == BITMAP_FORMAT_IND16)
		{
			UINT16 *src16 = reinterpret_cast<UINT16 *>(bitmap.raw_pixptr(y));
			for (x = 0; x < pnginfo->width; x++)
			{
				rgb_t color = palette[*src16++];
				*dst++ = color.r();
				*dst++ = color.g();
				*dst++ = color.b();
			}
		}

		/* 32-bit RGB direct */
		else if (bitmap.format() == BITMAP_FORMAT_RGB32)
		{
			UINT32 *src32 = reinterpret_cast<UINT32 *>(bitmap.raw_pixptr(y));
			for (x = 0; x < pnginfo->width; x++)
			{
				rgb_t raw = *src32++;
				*dst++ = raw.r();
				*dst++ = raw.g();
				*dst++ = raw.b();
			}
		}

		/* 32-bit ARGB direct */
		else if (bitmap.format() == BITMAP_FORMAT_ARGB32)
		{
			UINT32 *src32 = reinterpret_cast<UINT32 *>(bitmap.raw_pixptr(y));
			for (x = 0; x < pnginfo->width; x++)
			{
				rgb_t raw = *src32++;
				*dst++ = raw.r();
				*dst++ = raw.g();
				*dst++ = raw.b();
				*dst++ = raw.a();
			}
		}

		/* unsupported format */
		else
			return PNGERR_UNSUPPORTED_FORMAT;
	}

	return PNGERR_NONE;
}


/*-------------------------------------------------
    write_png_stream - stream a series of PNG
    chunks to the given file
-------------------------------------------------*/

static png_error write_png_stream(util::core_file &fp, png_info *pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
	UINT8 tempbuff[16];
	png_text *text;
	png_error error;

	/* create an unfiltered image in either palette or RGB form */
	if (bitmap.format() == BITMAP_FORMAT_IND16 && palette_length <= 256)
		error = convert_bitmap_to_image_palette(pnginfo, bitmap, palette_length, palette);
	else
		error = convert_bitmap_to_image_rgb(pnginfo, bitmap, palette_length, palette);
	if (error != PNGERR_NONE)
		goto handle_error;

	/* if we wanted to get clever and do filtering, we would do it here */

	/* write the IHDR chunk */
	put_32bit(tempbuff + 0, pnginfo->width);
	put_32bit(tempbuff + 4, pnginfo->height);
	put_8bit(tempbuff + 8, pnginfo->bit_depth);
	put_8bit(tempbuff + 9, pnginfo->color_type);
	put_8bit(tempbuff + 10, pnginfo->compression_method);
	put_8bit(tempbuff + 11, pnginfo->filter_method);
	put_8bit(tempbuff + 12, pnginfo->interlace_method);
	error = write_chunk(fp, tempbuff, PNG_CN_IHDR, 13);
	if (error != PNGERR_NONE)
		goto handle_error;

	/* write the PLTE chunk */
	if (pnginfo->num_palette > 0)
		error = write_chunk(fp, pnginfo->palette, PNG_CN_PLTE, pnginfo->num_palette * 3);
	if (error != PNGERR_NONE)
		goto handle_error;

	/* write a single IDAT chunk */
	error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(pnginfo) + 1));
	if (error != PNGERR_NONE)
		goto handle_error;

	/* write TEXT chunks */
	for (text = pnginfo->textlist; text != nullptr; text = text->next)
	{
		error = write_chunk(fp, (UINT8 *)text->keyword, PNG_CN_tEXt, (UINT32)strlen(text->keyword) + 1 + (UINT32)strlen(text->text));
		if (error != PNGERR_NONE)
			goto handle_error;
	}

	/* write an IEND chunk */
	error = write_chunk(fp, nullptr, PNG_CN_IEND, 0);

handle_error:
	return error;
}


png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
	png_info pnginfo;
	png_error error;

	/* use a dummy pnginfo if none passed to us */
	if (info == nullptr)
	{
		info = &pnginfo;
		memset(&pnginfo, 0, sizeof(pnginfo));
	}

	/* write the PNG signature */
	if (fp.write(PNG_Signature, 8) != 8)
	{
		if (info == &pnginfo)
			png_free(&pnginfo);
		return PNGERR_FILE_ERROR;
	}

	/* write the rest of the PNG data */
	error = write_png_stream(fp, info, bitmap, palette_length, palette);
	if (info == &pnginfo)
		png_free(&pnginfo);
	return error;
}



/********************************************************************************

  MNG write functions

********************************************************************************/

/**
 * @fn  png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, double rate)
 *
 * @brief   Mng capture start.
 *
 * @param [in,out]  fp      If non-null, the fp.
 * @param [in,out]  bitmap  The bitmap.
 * @param   rate            The rate.
 *
 * @return  A png_error.
 */

png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, double rate)
{
	UINT8 mhdr[28];
	png_error error;

	if (fp.write(MNG_Signature, 8) != 8)
		return PNGERR_FILE_ERROR;

	memset(mhdr, 0, 28);
	put_32bit(mhdr + 0, bitmap.width());
	put_32bit(mhdr + 4, bitmap.height());
	put_32bit(mhdr + 8, rate);
	put_32bit(mhdr + 24, 0x0041); /* Simplicity profile */
	/* frame count and play time unspecified because
	   we don't know at this stage */
	error = write_chunk(fp, mhdr, MNG_CN_MHDR, 28);
	if (error != PNGERR_NONE)
		return error;

	return PNGERR_NONE;
}

/**
 * @fn  png_error mng_capture_frame(util::core_file &fp, png_info *info, bitmap_t &bitmap, int palette_length, const rgb_t *palette)
 *
 * @brief   Mng capture frame.
 *
 * @param [in,out]  fp      If non-null, the fp.
 * @param [in,out]  info    If non-null, the information.
 * @param [in,out]  bitmap  The bitmap.
 * @param   palette_length  Length of the palette.
 * @param   palette         The palette.
 *
 * @return  A png_error.
 */

png_error mng_capture_frame(util::core_file &fp, png_info *info, bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
	return write_png_stream(fp, info, bitmap, palette_length, palette);
}

/**
 * @fn  png_error mng_capture_stop(util::core_file &fp)
 *
 * @brief   Mng capture stop.
 *
 * @param [in,out]  fp  If non-null, the fp.
 *
 * @return  A png_error.
 */

png_error mng_capture_stop(util::core_file &fp)
{
	return write_chunk(fp, nullptr, MNG_CN_MEND, 0);
}