mirror of
				https://github.com/pocketpy/pocketpy
				synced 2025-10-31 00:40:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1852 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1852 lines
		
	
	
		
			50 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
| 	------------------------------------------------------------------------------
 | |
| 		Licensing information can be found at the end of the file.
 | |
| 	------------------------------------------------------------------------------
 | |
| 
 | |
| 	cute_png.h - v1.05
 | |
| 
 | |
| 	To create implementation (the function definitions)
 | |
| 		#define CUTE_PNG_IMPLEMENTATION
 | |
| 	in *one* C/CPP file (translation unit) that includes this file
 | |
| 
 | |
| 
 | |
| 	SUMMARY:
 | |
| 
 | |
| 		This header wraps some very nice functions by Richard Mitton from his
 | |
| 		tigr (Tiny Graphics) library, with some additional features and small
 | |
| 		bug-fixes.
 | |
| 
 | |
| 
 | |
| 	Revision history:
 | |
| 		1.00 (12/23/2016) initial release
 | |
| 		1.01 (03/08/2017) tRNS chunk support for paletted images
 | |
| 		1.02 (10/23/2017) support for explicitly loading paletted png images
 | |
| 		1.03 (11/12/2017) construct atlas in memory
 | |
| 		1.04 (08/23/2018) various bug fixes for filter and word decoder
 | |
| 		                  added `cp_load_blank`
 | |
| 		1.05 (11/10/2022) added `cp_save_png_to_memory`
 | |
| 
 | |
| 
 | |
| 	EXAMPLES:
 | |
| 
 | |
| 		Loading a PNG from disk, then freeing it
 | |
| 			cp_image_t img = cp_load_png("images/pic.png");
 | |
| 			...
 | |
| 			free(img.pix);
 | |
| 			CUTE_PNG_MEMSET(&img, 0, sizeof(img));
 | |
| 
 | |
| 		Loading a PNG from memory, then freeing it
 | |
| 			cp_image_t img = cp_load_png_mem(memory, sizeof(memory));
 | |
| 			...
 | |
| 			free(img.pix);
 | |
| 			CUTE_PNG_MEMSET(&img, 0, sizeof(img));
 | |
| 
 | |
| 		Saving a PNG to disk
 | |
| 			cp_save_png("images/example.png", &img);
 | |
| 			// img is just a raw RGBA buffer, and can come from anywhere,
 | |
| 			// not only from cp_load*** functions
 | |
| 
 | |
| 		Creating a texture atlas in memory
 | |
| 			int w = 1024;
 | |
| 			int h = 1024;
 | |
| 			cp_atlas_image_t* imgs_out = (cp_atlas_image_t*)malloc(sizeof(cp_atlas_image_t) * my_png_count);
 | |
| 			cp_image_t atlas_img = cp_make_atlas(w, int h, my_png_array, my_png_count, imgs_out);
 | |
| 			// just pass an array of pointers to images along with the image count. Make sure to also
 | |
| 			// provide an array of `cp_atlas_image_t` for `cp_make_atlas` to output important UV info for the
 | |
| 			// images that fit into the atlas.
 | |
| 
 | |
| 		Using the default atlas saver
 | |
| 			int errors = cp_default_save_atlas("atlas.png", "atlas.txt", atlas_img, atlas_imgs, img_count, names_of_all_images ? names_of_all_images : 0);
 | |
| 			if (errors) { ... }
 | |
| 			// Atlas info (like uv coordinates) are in "atlas.txt", and the image was writen to "atlas.png".
 | |
| 			// atlas_imgs was an array of `cp_atlas_image_t` from the `cp_make_atlas` function.
 | |
| 
 | |
| 		Inflating a DEFLATE block (decompressing memory stored in DEFLATE format)
 | |
| 			cp_inflate(in, in_bytes, out, out_bytes);
 | |
| 			// this function requires knowledge of the un-compressed size
 | |
| 			// does *not* do any internal realloc! Will return errors if an
 | |
| 			// attempt to overwrite the out buffer is made
 | |
| 
 | |
| 	CUSTOMIZATION
 | |
| 
 | |
| 		There are various macros in this header you can customize by defining them before
 | |
| 		including cute_png.h. Simply define one to override the default behavior.
 | |
| 
 | |
| 			CUTE_PNG_ALLOCA
 | |
| 			CUTE_PNG_ALLOC
 | |
| 			CUTE_PNG_FREE
 | |
| 			CUTE_PNG_CALLOC
 | |
| 			CUTE_PNG_REALLOC
 | |
| 			CUTE_PNG_MEMCPY
 | |
| 			CUTE_PNG_MEMCMP
 | |
| 			CUTE_PNG_MEMSET
 | |
| 			CUTE_PNG_ASSERT
 | |
| 			CUTE_PNG_FPRINTF
 | |
| 			CUTE_PNG_SEEK_SET
 | |
| 			CUTE_PNG_SEEK_END
 | |
| 			CUTE_PNG_FILE
 | |
| 			CUTE_PNG_FOPEN
 | |
| 			CUTE_PNG_FSEEK
 | |
| 			CUTE_PNG_FREAD
 | |
| 			CUTE_PNG_FTELL
 | |
| 			CUTE_PNG_FWRITE
 | |
| 			CUTE_PNG_FCLOSE
 | |
| 			CUTE_PNG_FERROR
 | |
| 			CUTE_PNG_ATLAS_MUST_FIT
 | |
| 			CUTE_PNG_ATLAS_FLIP_Y_AXIS_FOR_UV
 | |
| 			CUTE_PNG_ATLAS_EMPTY_COLOR
 | |
| */
 | |
| 
 | |
| /*
 | |
| 	Contributors:
 | |
| 		Zachary Carter    1.01 - bug catch for tRNS chunk in paletted images
 | |
| 		Dennis Korpel     1.03 - fix some pointer/memory related bugs
 | |
| 		Dennis Korpel     1.04 - fix for filter on first row of pixels
 | |
| */
 | |
| 
 | |
| #if !defined(CUTE_PNG_H)
 | |
| 
 | |
| #ifdef _WIN32
 | |
| 	#if !defined(_CRT_SECURE_NO_WARNINGS)
 | |
| 		#define _CRT_SECURE_NO_WARNINGS
 | |
| 	#endif
 | |
| #endif
 | |
| 
 | |
| #ifndef CUTE_PNG_ATLAS_MUST_FIT
 | |
| 	#define CUTE_PNG_ATLAS_MUST_FIT            1 // returns error from cp_make_atlas if *any* input image does not fit
 | |
| #endif // CUTE_PNG_ATLAS_MUST_FIT
 | |
| 
 | |
| #ifndef CUTE_PNG_ATLAS_FLIP_Y_AXIS_FOR_UV
 | |
| 	#define CUTE_PNG_ATLAS_FLIP_Y_AXIS_FOR_UV  1 // flips output uv coordinate's y. Can be useful to "flip image on load"
 | |
| #endif // CUTE_PNG_ATLAS_FLIP_Y_AXIS_FOR_UV
 | |
| 
 | |
| #ifndef CUTE_PNG_ATLAS_EMPTY_COLOR
 | |
| 	#define CUTE_PNG_ATLAS_EMPTY_COLOR         0x000000FF // the fill color for empty areas in a texture atlas (RGBA)
 | |
| #endif // CUTE_PNG_ATLAS_EMPTY_COLOR
 | |
| 
 | |
| #include <stdint.h>
 | |
| #include <limits.h>
 | |
| 
 | |
| typedef struct cp_pixel_t cp_pixel_t;
 | |
| typedef struct cp_image_t cp_image_t;
 | |
| typedef struct cp_indexed_image_t cp_indexed_image_t;
 | |
| typedef struct cp_atlas_image_t cp_atlas_image_t;
 | |
| 
 | |
| // Read this in the event of errors from any function
 | |
| extern const char* cp_error_reason;
 | |
| 
 | |
| // return 1 for success, 0 for failures
 | |
| int cp_inflate(void* in, int in_bytes, void* out, int out_bytes);
 | |
| int cp_save_png(const char* file_name, const cp_image_t* img);
 | |
| 
 | |
| typedef struct cp_saved_png_t
 | |
| {
 | |
| 	int size;   // Size of the `data` buffer.
 | |
| 	void* data; // Pointer to the saved png in memory.
 | |
| 	            // NULL if something went wrong.
 | |
| 	            // Call CUTE_PNG_FREE on `data` when done.
 | |
| } cp_saved_png_t;
 | |
| 
 | |
| // Saves a png file to memory.
 | |
| // Call CUTE_PNG_FREE on .data when done.
 | |
| cp_saved_png_t cp_save_png_to_memory(const cp_image_t* img);
 | |
| 
 | |
| // Constructs an atlas image in-memory. The atlas pixels are stored in the returned image. free the pixels
 | |
| // when done with them. The user must provide an array of cp_atlas_image_t for the `imgs` param. `imgs` holds
 | |
| // information about uv coordinates for an associated image in the `pngs` array. Output image has NULL
 | |
| // pixels buffer in the event of errors.
 | |
| cp_image_t cp_make_atlas(int atlasWidth, int atlasHeight, const cp_image_t* pngs, int png_count, cp_atlas_image_t* imgs_out);
 | |
| 
 | |
| // A decent "default" function, ready to use out-of-the-box. Saves out an easy to parse text formatted info file
 | |
| // along with an atlas image. `names` param can be optionally NULL.
 | |
| int cp_default_save_atlas(const char* out_path_image, const char* out_path_atlas_txt, const cp_image_t* atlas, const cp_atlas_image_t* imgs, int img_count, const char** names);
 | |
| 
 | |
| // these two functions return cp_image_t::pix as 0 in event of errors
 | |
| // call free on cp_image_t::pix when done, or call cp_free_png
 | |
| cp_image_t cp_load_png(const char *file_name);
 | |
| cp_image_t cp_load_png_mem(const void *png_data, int png_length);
 | |
| cp_image_t cp_load_blank(int w, int h); // Alloc's pixels, but `pix` memory is uninitialized.
 | |
| void cp_free_png(cp_image_t* img);
 | |
| void cp_flip_image_horizontal(cp_image_t* img);
 | |
| 
 | |
| // Reads the w/h of the png without doing any other decompression or parsing.
 | |
| void cp_load_png_wh(const void* png_data, int png_length, int* w, int* h);
 | |
| 
 | |
| // loads indexed (paletted) pngs, but does not depalette the image into RGBA pixels
 | |
| // these two functions return cp_indexed_image_t::pix as 0 in event of errors
 | |
| // call free on cp_indexed_image_t::pix when done, or call cp_free_indexed_png
 | |
| cp_indexed_image_t cp_load_indexed_png(const char* file_name);
 | |
| cp_indexed_image_t cp_load_indexed_png_mem(const void *png_data, int png_length);
 | |
| void cp_free_indexed_png(cp_indexed_image_t* img);
 | |
| 
 | |
| // converts paletted image into a standard RGBA image
 | |
| // call free on cp_image_t::pix when done
 | |
| cp_image_t cp_depallete_indexed_image(cp_indexed_image_t* img);
 | |
| 
 | |
| // Pre-process the pixels to transform the image data to a premultiplied alpha format.
 | |
| // Resource: http://www.essentialmath.com/GDC2015/VanVerth_Jim_DoingMathwRGB.pdf
 | |
| void cp_premultiply(cp_image_t* img);
 | |
| 
 | |
| struct cp_pixel_t
 | |
| {
 | |
| 	uint8_t r;
 | |
| 	uint8_t g;
 | |
| 	uint8_t b;
 | |
| 	uint8_t a;
 | |
| };
 | |
| 
 | |
| struct cp_image_t
 | |
| {
 | |
| 	int w;
 | |
| 	int h;
 | |
| 	cp_pixel_t* pix;
 | |
| };
 | |
| 
 | |
| struct cp_indexed_image_t
 | |
| {
 | |
| 	int w;
 | |
| 	int h;
 | |
| 	uint8_t* pix;
 | |
| 	uint8_t palette_len;
 | |
| 	cp_pixel_t palette[256];
 | |
| };
 | |
| 
 | |
| struct cp_atlas_image_t
 | |
| {
 | |
| 	int img_index;    // index into the `imgs` array
 | |
| 	int w, h;         // pixel w/h of original image
 | |
| 	float minx, miny; // u coordinate
 | |
| 	float maxx, maxy; // v coordinate
 | |
| 	int fit;          // non-zero if image fit and was placed into the atlas
 | |
| };
 | |
| 
 | |
| #define CUTE_PNG_H
 | |
| #endif
 | |
| 
 | |
| #ifdef CUTE_PNG_IMPLEMENTATION
 | |
| #ifndef CUTE_PNG_IMPLEMENTATION_ONCE
 | |
| #define CUTE_PNG_IMPLEMENTATION_ONCE
 | |
| 
 | |
| #if !defined(CUTE_PNG_ALLOCA)
 | |
| 	#define CUTE_PNG_ALLOCA alloca
 | |
| 
 | |
| 	#ifdef _WIN32
 | |
| 		#include <malloc.h>
 | |
| 	#elif defined(__linux__)
 | |
| 		#include <alloca.h>
 | |
| 	#endif
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_ALLOC)
 | |
| 	#include <stdlib.h>
 | |
| 	#define CUTE_PNG_ALLOC malloc
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FREE)
 | |
| 	#include <stdlib.h>
 | |
| 	#define CUTE_PNG_FREE free
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_CALLOC)
 | |
| 	#include <stdlib.h>
 | |
| 	#define CUTE_PNG_CALLOC calloc
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_REALLOC)
 | |
| 	#include <stdlib.h>
 | |
| 	#define CUTE_PNG_REALLOC realloc
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_MEMCPY)
 | |
| 	#include <string.h>
 | |
| 	#define CUTE_PNG_MEMCPY memcpy
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_MEMCMP)
 | |
| 	#include <string.h>
 | |
| 	#define CUTE_PNG_MEMCMP memcmp
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_MEMSET)
 | |
| 	#include <string.h>
 | |
| 	#define CUTE_PNG_MEMSET memset
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_ASSERT)
 | |
| 	#include <assert.h>
 | |
| 	#define CUTE_PNG_ASSERT assert
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FPRINTF)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FPRINTF fprintf
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_SEEK_SET)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_SEEK_SET SEEK_SET
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_SEEK_END)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_SEEK_END SEEK_END
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FILE)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FILE FILE
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FOPEN)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FOPEN fopen
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FSEEK)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FSEEK fseek
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FREAD)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FREAD fread
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FTELL)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FTELL ftell
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FWRITE)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FWRITE fwrite
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FCLOSE)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FCLOSE fclose
 | |
| #endif
 | |
| 
 | |
| #if !defined(CUTE_PNG_FERROR)
 | |
| 	#include <stdio.h>
 | |
| 	#define CUTE_PNG_FERROR ferror
 | |
| #endif
 | |
| 
 | |
| static cp_pixel_t cp_make_pixel_a(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
 | |
| {
 | |
| 	cp_pixel_t p;
 | |
| 	p.r = r; p.g = g; p.b = b; p.a = a;
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| static cp_pixel_t cp_make_pixel(uint8_t r, uint8_t g, uint8_t b)
 | |
| {
 | |
| 	cp_pixel_t p;
 | |
| 	p.r = r; p.g = g; p.b = b; p.a = 0xFF;
 | |
| 	return p;
 | |
| }
 | |
| 
 | |
| const char* cp_error_reason;
 | |
| #define CUTE_PNG_FAIL() do { goto cp_err; } while (0)
 | |
| #define CUTE_PNG_CHECK(X, Y) do { if (!(X)) { cp_error_reason = Y; CUTE_PNG_FAIL(); } } while (0)
 | |
| #define CUTE_PNG_CALL(X) do { if (!(X)) goto cp_err; } while (0)
 | |
| #define CUTE_PNG_LOOKUP_BITS 9
 | |
| #define CUTE_PNG_LOOKUP_COUNT (1 << CUTE_PNG_LOOKUP_BITS)
 | |
| #define CUTE_PNG_LOOKUP_MASK (CUTE_PNG_LOOKUP_COUNT - 1)
 | |
| #define CUTE_PNG_DEFLATE_MAX_BITLEN 15
 | |
| 
 | |
| // DEFLATE tables from RFC 1951
 | |
| uint8_t cp_fixed_table[288 + 32] = {
 | |
| 	8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
 | |
| 	8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
 | |
| 	8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
 | |
| 	9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
 | |
| 	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
 | |
| }; // 3.2.6
 | |
| uint8_t cp_permutation_order[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; // 3.2.7
 | |
| uint8_t cp_len_extra_bits[29 + 2] = { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,  0,0 }; // 3.2.5
 | |
| uint32_t cp_len_base[29 + 2] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,  0,0 }; // 3.2.5
 | |
| uint8_t cp_dist_extra_bits[30 + 2] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,  0,0 }; // 3.2.5
 | |
| uint32_t cp_dist_base[30 + 2] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 0,0 }; // 3.2.5
 | |
| 
 | |
| typedef struct cp_state_t
 | |
| {
 | |
| 	uint64_t bits;
 | |
| 	int count;
 | |
| 	uint32_t* words;
 | |
| 	int word_count;
 | |
| 	int word_index;
 | |
| 	int bits_left;
 | |
| 
 | |
| 	int final_word_available;
 | |
| 	uint32_t final_word;
 | |
| 
 | |
| 	char* out;
 | |
| 	char* out_end;
 | |
| 	char* begin;
 | |
| 
 | |
| 	uint16_t lookup[CUTE_PNG_LOOKUP_COUNT];
 | |
| 	uint32_t lit[288];
 | |
| 	uint32_t dst[32];
 | |
| 	uint32_t len[19];
 | |
| 	uint32_t nlit;
 | |
| 	uint32_t ndst;
 | |
| 	uint32_t nlen;
 | |
| } cp_state_t;
 | |
| 
 | |
| static int cp_would_overflow(cp_state_t* s, int num_bits)
 | |
| {
 | |
| 	return (s->bits_left + s->count) - num_bits < 0;
 | |
| }
 | |
| 
 | |
| static char* cp_ptr(cp_state_t* s)
 | |
| {
 | |
| 	CUTE_PNG_ASSERT(!(s->bits_left & 7));
 | |
| 	return (char*)(s->words + s->word_index) - (s->count / 8);
 | |
| }
 | |
| 
 | |
| static uint64_t cp_peak_bits(cp_state_t* s, int num_bits_to_read)
 | |
| {
 | |
| 	if (s->count < num_bits_to_read)
 | |
| 	{
 | |
| 		if (s->word_index < s->word_count)
 | |
| 		{
 | |
| 			uint32_t word = s->words[s->word_index++];
 | |
| 			s->bits |= (uint64_t)word << s->count;
 | |
| 			s->count += 32;
 | |
| 			CUTE_PNG_ASSERT(s->word_index <= s->word_count);
 | |
| 		}
 | |
| 
 | |
| 		else if (s->final_word_available)
 | |
| 		{
 | |
| 			uint32_t word = s->final_word;
 | |
| 			s->bits |= (uint64_t)word << s->count;
 | |
| 			s->count += s->bits_left;
 | |
| 			s->final_word_available = 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return s->bits;
 | |
| }
 | |
| 
 | |
| static uint32_t cp_consume_bits(cp_state_t* s, int num_bits_to_read)
 | |
| {
 | |
| 	CUTE_PNG_ASSERT(s->count >= num_bits_to_read);
 | |
| 	uint32_t bits = s->bits & (((uint64_t)1 << num_bits_to_read) - 1);
 | |
| 	s->bits >>= num_bits_to_read;
 | |
| 	s->count -= num_bits_to_read;
 | |
| 	s->bits_left -= num_bits_to_read;
 | |
| 	return bits;
 | |
| }
 | |
| 
 | |
| static uint32_t cp_read_bits(cp_state_t* s, int num_bits_to_read)
 | |
| {
 | |
| 	CUTE_PNG_ASSERT(num_bits_to_read <= 32);
 | |
| 	CUTE_PNG_ASSERT(num_bits_to_read >= 0);
 | |
| 	CUTE_PNG_ASSERT(s->bits_left > 0);
 | |
| 	CUTE_PNG_ASSERT(s->count <= 64);
 | |
| 	CUTE_PNG_ASSERT(!cp_would_overflow(s, num_bits_to_read));
 | |
| 	cp_peak_bits(s, num_bits_to_read);
 | |
| 	uint32_t bits = cp_consume_bits(s, num_bits_to_read);
 | |
| 	return bits;
 | |
| }
 | |
| 
 | |
| static char* cp_read_file_to_memory(const char* path, int* size)
 | |
| {
 | |
| 	char* data = 0;
 | |
| 	CUTE_PNG_FILE* fp = CUTE_PNG_FOPEN(path, "rb");
 | |
| 	int sizeNum = 0;
 | |
| 
 | |
| 	if (fp)
 | |
| 	{
 | |
| 		CUTE_PNG_FSEEK(fp, 0, CUTE_PNG_SEEK_END);
 | |
| 		sizeNum = CUTE_PNG_FTELL(fp);
 | |
| 		CUTE_PNG_FSEEK(fp, 0, CUTE_PNG_SEEK_SET);
 | |
| 		data = (char*)CUTE_PNG_ALLOC(sizeNum + 1);
 | |
| 		CUTE_PNG_FREAD(data, sizeNum, 1, fp);
 | |
| 		data[sizeNum] = 0;
 | |
| 		CUTE_PNG_FCLOSE(fp);
 | |
| 	}
 | |
| 
 | |
| 	if (size) *size = sizeNum;
 | |
| 	return data;
 | |
| }
 | |
| 
 | |
| static uint32_t cp_rev16(uint32_t a)
 | |
| {
 | |
| 	a = ((a & 0xAAAA) >>  1) | ((a & 0x5555) << 1);
 | |
| 	a = ((a & 0xCCCC) >>  2) | ((a & 0x3333) << 2);
 | |
| 	a = ((a & 0xF0F0) >>  4) | ((a & 0x0F0F) << 4);
 | |
| 	a = ((a & 0xFF00) >>  8) | ((a & 0x00FF) << 8);
 | |
| 	return a;
 | |
| }
 | |
| 
 | |
| // RFC 1951 section 3.2.2
 | |
| static int cp_build(cp_state_t* s, uint32_t* tree, uint8_t* lens, int sym_count)
 | |
| {
 | |
| 	int n, codes[16], first[16], counts[16] = { 0 };
 | |
| 
 | |
| 	// Frequency count
 | |
| 	for (n = 0; n < sym_count; n++) counts[lens[n]]++;
 | |
| 
 | |
| 	// Distribute codes
 | |
| 	counts[0] = codes[0] = first[0] = 0;
 | |
| 	for (n = 1; n <= 15; ++n)
 | |
| 	{
 | |
| 		codes[n] = (codes[n - 1] + counts[n - 1]) << 1;
 | |
| 		first[n] = first[n - 1] + counts[n - 1];
 | |
| 	}
 | |
| 
 | |
| 	if (s) CUTE_PNG_MEMSET(s->lookup, 0, sizeof(s->lookup));
 | |
| 	for (int i = 0; i < sym_count; ++i)
 | |
| 	{
 | |
| 		int len = lens[i];
 | |
| 
 | |
| 		if (len != 0)
 | |
| 		{
 | |
| 			CUTE_PNG_ASSERT(len < 16);
 | |
| 			uint32_t code = codes[len]++;
 | |
| 			uint32_t slot = first[len]++;
 | |
| 			tree[slot] = (code << (32 - len)) | (i << 4) | len;
 | |
| 
 | |
| 			if (s && len <= CUTE_PNG_LOOKUP_BITS)
 | |
| 			{
 | |
| 				int j = cp_rev16(code) >> (16 - len);
 | |
| 				while (j < (1 << CUTE_PNG_LOOKUP_BITS))
 | |
| 				{
 | |
| 					s->lookup[j] = (uint16_t)((len << CUTE_PNG_LOOKUP_BITS) | i);
 | |
| 					j += (1 << len);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	int max_index = first[15];
 | |
| 	return max_index;
 | |
| }
 | |
| 
 | |
| static int cp_stored(cp_state_t* s)
 | |
| {
 | |
| 	char* p;
 | |
| 
 | |
| 	// 3.2.3
 | |
| 	// skip any remaining bits in current partially processed byte
 | |
| 	cp_read_bits(s, s->count & 7);
 | |
| 
 | |
| 	// 3.2.4
 | |
| 	// read LEN and NLEN, should complement each other
 | |
| 	uint16_t LEN = (uint16_t)cp_read_bits(s, 16);
 | |
| 	uint16_t NLEN = (uint16_t)cp_read_bits(s, 16);
 | |
| 	CUTE_PNG_CHECK(LEN == (uint16_t)(~NLEN), "Failed to find LEN and NLEN as complements within stored (uncompressed) stream.");
 | |
| 	CUTE_PNG_CHECK(s->bits_left / 8 <= (int)LEN, "Stored block extends beyond end of input stream.");
 | |
| 	p = cp_ptr(s);
 | |
| 	CUTE_PNG_MEMCPY(s->out, p, LEN);
 | |
| 	s->out += LEN;
 | |
| 	return 1;
 | |
| 
 | |
| cp_err:
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| // 3.2.6
 | |
| static int cp_fixed(cp_state_t* s)
 | |
| {
 | |
| 	s->nlit = cp_build(s, s->lit, cp_fixed_table, 288);
 | |
| 	s->ndst = cp_build(0, s->dst, cp_fixed_table + 288, 32);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static int cp_decode(cp_state_t* s, uint32_t* tree, int hi)
 | |
| {
 | |
| 	uint64_t bits = cp_peak_bits(s, 16);
 | |
| 	uint32_t search = (cp_rev16((uint32_t)bits) << 16) | 0xFFFF;
 | |
| 	int lo = 0;
 | |
| 	while (lo < hi)
 | |
| 	{
 | |
| 		int guess = (lo + hi) >> 1;
 | |
| 		if (search < tree[guess]) hi = guess;
 | |
| 		else lo = guess + 1;
 | |
| 	}
 | |
| 
 | |
| 	uint32_t key = tree[lo - 1];
 | |
| 	uint32_t len = (32 - (key & 0xF));
 | |
| 	CUTE_PNG_ASSERT((search >> len) == (key >> len));
 | |
| 
 | |
| 	int code = cp_consume_bits(s, key & 0xF);
 | |
| 	(void)code;
 | |
| 	return (key >> 4) & 0xFFF;
 | |
| }
 | |
| 
 | |
| // 3.2.7
 | |
| static int cp_dynamic(cp_state_t* s)
 | |
| {
 | |
| 	uint8_t lenlens[19] = { 0 };
 | |
| 
 | |
| 	int nlit = 257 + cp_read_bits(s, 5);
 | |
| 	int ndst = 1 + cp_read_bits(s, 5);
 | |
| 	int nlen = 4 + cp_read_bits(s, 4);
 | |
| 
 | |
| 	for (int i = 0 ; i < nlen; ++i)
 | |
| 		lenlens[cp_permutation_order[i]] = (uint8_t)cp_read_bits(s, 3);
 | |
| 
 | |
| 	// Build the tree for decoding code lengths
 | |
| 	s->nlen = cp_build(0, s->len, lenlens, 19);
 | |
| 	uint8_t lens[288 + 32];
 | |
| 
 | |
| 	for (int n = 0; n < nlit + ndst;)
 | |
| 	{
 | |
| 		int sym = cp_decode(s, s->len, s->nlen);
 | |
| 		switch (sym)
 | |
| 		{
 | |
| 		case 16: for (int i =  3 + cp_read_bits(s, 2); i; --i, ++n) lens[n] = lens[n - 1]; break;
 | |
| 		case 17: for (int i =  3 + cp_read_bits(s, 3); i; --i, ++n) lens[n] = 0; break;
 | |
| 		case 18: for (int i = 11 + cp_read_bits(s, 7); i; --i, ++n) lens[n] = 0; break;
 | |
| 		default: lens[n++] = (uint8_t)sym; break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	s->nlit = cp_build(s, s->lit, lens, nlit);
 | |
| 	s->ndst = cp_build(0, s->dst, lens + nlit, ndst);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| // 3.2.3
 | |
| static int cp_block(cp_state_t* s)
 | |
| {
 | |
| 	while (1)
 | |
| 	{
 | |
| 		int symbol = cp_decode(s, s->lit, s->nlit);
 | |
| 
 | |
| 		if (symbol < 256)
 | |
| 		{
 | |
| 			CUTE_PNG_CHECK(s->out + 1 <= s->out_end, "Attempted to overwrite out buffer while outputting a symbol.");
 | |
| 			*s->out = (char)symbol;
 | |
| 			s->out += 1;
 | |
| 		}
 | |
| 
 | |
| 		else if (symbol > 256)
 | |
| 		{
 | |
| 			symbol -= 257;
 | |
| 			int length = cp_read_bits(s, cp_len_extra_bits[symbol]) + cp_len_base[symbol];
 | |
| 			int distance_symbol = cp_decode(s, s->dst, s->ndst);
 | |
| 			int backwards_distance = cp_read_bits(s, cp_dist_extra_bits[distance_symbol]) + cp_dist_base[distance_symbol];
 | |
| 			CUTE_PNG_CHECK(s->out - backwards_distance >= s->begin, "Attempted to write before out buffer (invalid backwards distance).");
 | |
| 			CUTE_PNG_CHECK(s->out + length <= s->out_end, "Attempted to overwrite out buffer while outputting a string.");
 | |
| 			char* src = s->out - backwards_distance;
 | |
| 			char* dst = s->out;
 | |
| 			s->out += length;
 | |
| 
 | |
| 			switch (backwards_distance)
 | |
| 			{
 | |
| 			case 1: // very common in images
 | |
| 				CUTE_PNG_MEMSET(dst, *src, length);
 | |
| 				break;
 | |
| 			default: while (length--) *dst++ = *src++;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		else break;
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| 
 | |
| cp_err:
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| // 3.2.3
 | |
| int cp_inflate(void* in, int in_bytes, void* out, int out_bytes)
 | |
| {
 | |
| 	cp_state_t* s = (cp_state_t*)CUTE_PNG_CALLOC(1, sizeof(cp_state_t));
 | |
| 	s->bits = 0;
 | |
| 	s->count = 0;
 | |
| 	s->word_index = 0;
 | |
| 	s->bits_left = in_bytes * 8;
 | |
| 
 | |
| 	// s->words is the in-pointer rounded up to a multiple of 4
 | |
| 	int first_bytes = (int) ((( (size_t) in + 3) & ~3) - (size_t) in);
 | |
| 	s->words = (uint32_t*)((char*)in + first_bytes);
 | |
| 	s->word_count = (in_bytes - first_bytes) / 4;
 | |
| 	int last_bytes = ((in_bytes - first_bytes) & 3);
 | |
| 
 | |
| 	for (int i = 0; i < first_bytes; ++i)
 | |
| 		s->bits |= (uint64_t)(((uint8_t*)in)[i]) << (i * 8);
 | |
| 
 | |
| 	s->final_word_available = last_bytes ? 1 : 0;
 | |
| 	s->final_word = 0;
 | |
| 	for(int i = 0; i < last_bytes; i++) 
 | |
| 		s->final_word |= ((uint8_t*)in)[in_bytes - last_bytes+i] << (i * 8);
 | |
| 
 | |
| 	s->count = first_bytes * 8;
 | |
| 
 | |
| 	s->out = (char*)out;
 | |
| 	s->out_end = s->out + out_bytes;
 | |
| 	s->begin = (char*)out;
 | |
| 
 | |
| 	int count = 0;
 | |
| 	int bfinal;
 | |
| 	do
 | |
| 	{
 | |
| 		bfinal = cp_read_bits(s, 1);
 | |
| 		int btype = cp_read_bits(s, 2);
 | |
| 
 | |
| 		switch (btype)
 | |
| 		{
 | |
| 		case 0: CUTE_PNG_CALL(cp_stored(s)); break;
 | |
| 		case 1: cp_fixed(s); CUTE_PNG_CALL(cp_block(s)); break;
 | |
| 		case 2: cp_dynamic(s); CUTE_PNG_CALL(cp_block(s)); break;
 | |
| 		case 3: CUTE_PNG_CHECK(0, "Detected unknown block type within input stream.");
 | |
| 		}
 | |
| 
 | |
| 		++count;
 | |
| 	}
 | |
| 	while (!bfinal);
 | |
| 
 | |
| 	CUTE_PNG_FREE(s);
 | |
| 	return 1;
 | |
| 
 | |
| cp_err:
 | |
| 	CUTE_PNG_FREE(s);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static uint8_t cp_paeth(uint8_t a, uint8_t b, uint8_t c)
 | |
| {
 | |
| 	int p = a + b - c;
 | |
| 	int pa = abs(p - a);
 | |
| 	int pb = abs(p - b);
 | |
| 	int pc = abs(p - c);
 | |
| 	return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
 | |
| }
 | |
| 
 | |
| typedef struct cp_save_png_data_t
 | |
| {
 | |
| 	uint32_t crc;
 | |
| 	uint32_t adler;
 | |
| 	uint32_t bits;
 | |
| 	uint32_t prev;
 | |
| 	uint32_t runlen;
 | |
| 	int buflen;
 | |
| 	int bufcap;
 | |
| 	char* buffer;
 | |
| } cp_save_png_data_t;
 | |
| 
 | |
| uint32_t CP_CRC_TABLE[] = {
 | |
| 	0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
 | |
| 	0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
 | |
| };
 | |
| 
 | |
| static void cp_put8(cp_save_png_data_t* s, uint32_t a)
 | |
| {
 | |
| 	if (s->buflen >= s->bufcap)
 | |
| 	{
 | |
| 		s->bufcap *= 2;
 | |
| 		s->buffer = (char*)CUTE_PNG_REALLOC(s->buffer, s->bufcap);
 | |
| 	}
 | |
| 	s->buffer[s->buflen++] = a;
 | |
| 	s->crc = (s->crc >> 4) ^ CP_CRC_TABLE[(s->crc & 15) ^ (a & 15)];
 | |
| 	s->crc = (s->crc >> 4) ^ CP_CRC_TABLE[(s->crc & 15) ^ (a >> 4)];
 | |
| }
 | |
| 
 | |
| static void cp_update_adler(cp_save_png_data_t* s, uint32_t v)
 | |
| {
 | |
| 	uint32_t s1 = s->adler & 0xFFFF;
 | |
| 	uint32_t s2 = (s->adler >> 16) & 0xFFFF;
 | |
| 	s1 = (s1 + v) % 65521;
 | |
| 	s2 = (s2 + s1) % 65521;
 | |
| 	s->adler = (s2 << 16) + s1;
 | |
| }
 | |
| 
 | |
| static void cp_put32(cp_save_png_data_t* s, uint32_t v)
 | |
| {
 | |
| 	cp_put8(s, (v >> 24) & 0xFF);
 | |
| 	cp_put8(s, (v >> 16) & 0xFF);
 | |
| 	cp_put8(s, (v >> 8) & 0xFF);
 | |
| 	cp_put8(s, v & 0xFF);
 | |
| }
 | |
| 
 | |
| static void cp_put_bits(cp_save_png_data_t* s, uint32_t data, uint32_t bitcount)
 | |
| {
 | |
| 	while (bitcount--)
 | |
| 	{
 | |
| 		uint32_t prev = s->bits;
 | |
| 		s->bits = (s->bits >> 1) | ((data & 1) << 7);
 | |
| 		data >>= 1;
 | |
| 
 | |
| 		if (prev & 1)
 | |
| 		{
 | |
| 			cp_put8(s, s->bits);
 | |
| 			s->bits = 0x80;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void cp_put_bitsr(cp_save_png_data_t* s, uint32_t data, uint32_t bitcount)
 | |
| {
 | |
| 	while (bitcount--)
 | |
| 		cp_put_bits(s, data >> bitcount, 1);
 | |
| }
 | |
| 
 | |
| static void cp_begin_chunk(cp_save_png_data_t* s, const char* id, uint32_t len)
 | |
| {
 | |
| 	cp_put32(s, len);
 | |
| 	s->crc = 0xFFFFFFFF;
 | |
| 	cp_put8(s, (unsigned char)id[0]);
 | |
| 	cp_put8(s, (unsigned char)id[1]);
 | |
| 	cp_put8(s, (unsigned char)id[2]);
 | |
| 	cp_put8(s, (unsigned char)id[3]);
 | |
| }
 | |
| 
 | |
| static void cp_encode_literal(cp_save_png_data_t* s, uint32_t v)
 | |
| {
 | |
| 	// Encode a literal/length using the built-in tables.
 | |
| 	// Could do better with a custom table but whatever.
 | |
| 	     if (v < 144) cp_put_bitsr(s, 0x030 + v -   0, 8);
 | |
| 	else if (v < 256) cp_put_bitsr(s, 0x190 + v - 144, 9);
 | |
| 	else if (v < 280) cp_put_bitsr(s, 0x000 + v - 256, 7);
 | |
| 	else              cp_put_bitsr(s, 0x0c0 + v - 280, 8);
 | |
| }
 | |
| 
 | |
| static void cp_encode_len(cp_save_png_data_t* s, uint32_t code, uint32_t bits, uint32_t len)
 | |
| {
 | |
| 	cp_encode_literal(s, code + (len >> bits));
 | |
| 	cp_put_bits(s, len, bits);
 | |
| 	cp_put_bits(s, 0, 5);
 | |
| }
 | |
| 
 | |
| static void cp_end_run(cp_save_png_data_t* s)
 | |
| {
 | |
| 	s->runlen--;
 | |
| 	cp_encode_literal(s, s->prev);
 | |
| 
 | |
| 	if (s->runlen >= 67) cp_encode_len(s, 277, 4, s->runlen - 67);
 | |
| 	else if (s->runlen >= 35) cp_encode_len(s, 273, 3, s->runlen - 35);
 | |
| 	else if (s->runlen >= 19) cp_encode_len(s, 269, 2, s->runlen - 19);
 | |
| 	else if (s->runlen >= 11) cp_encode_len(s, 265, 1, s->runlen - 11);
 | |
| 	else if (s->runlen >= 3) cp_encode_len(s, 257, 0, s->runlen - 3);
 | |
| 	else while (s->runlen--) cp_encode_literal(s, s->prev);
 | |
| }
 | |
| 
 | |
| static void cp_encode_byte(cp_save_png_data_t *s, uint8_t v)
 | |
| {
 | |
| 	cp_update_adler(s, v);
 | |
| 
 | |
| 	// Simple RLE compression. We could do better by doing a search
 | |
| 	// to find matches, but this works pretty well TBH.
 | |
| 	if (s->prev == v && s->runlen < 115) s->runlen++;
 | |
| 
 | |
| 	else
 | |
| 	{
 | |
| 		if (s->runlen) cp_end_run(s);
 | |
| 
 | |
| 		s->prev = v;
 | |
| 		s->runlen = 1;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void cp_save_header(cp_save_png_data_t* s, cp_image_t* img)
 | |
| {
 | |
| 	const unsigned char* hdr = (const unsigned char*)"\211PNG\r\n\032\n";
 | |
| 	for (int i = 0; i < 8; ++i) {
 | |
| 		cp_put8(s, *hdr++);
 | |
| 	}
 | |
| 	cp_begin_chunk(s, "IHDR", 13);
 | |
| 	cp_put32(s, img->w);
 | |
| 	cp_put32(s, img->h);
 | |
| 	cp_put8(s, 8); // bit depth
 | |
| 	cp_put8(s, 6); // RGBA
 | |
| 	cp_put8(s, 0); // compression (deflate)
 | |
| 	cp_put8(s, 0); // filter (standard)
 | |
| 	cp_put8(s, 0); // interlace off
 | |
| 	cp_put32(s, ~s->crc);
 | |
| }
 | |
| 
 | |
| static void cp_save_data(cp_save_png_data_t* s, cp_image_t* img, long dataPos, long* dataSize)
 | |
| {
 | |
| 	cp_begin_chunk(s, "IDAT", 0);
 | |
| 	cp_put8(s, 0x08); // zlib compression method
 | |
| 	cp_put8(s, 0x1D); // zlib compression flags
 | |
| 	cp_put_bits(s, 3, 3); // zlib last block + fixed dictionary
 | |
| 
 | |
| 	for (int y = 0; y < img->h; ++y)
 | |
| 	{
 | |
| 		cp_pixel_t *row = &img->pix[y * img->w];
 | |
| 		cp_pixel_t prev = cp_make_pixel_a(0, 0, 0, 0);
 | |
| 
 | |
| 		cp_encode_byte(s, 1); // sub filter
 | |
| 		for (int x = 0; x < img->w; ++x)
 | |
| 		{
 | |
| 			cp_encode_byte(s, row[x].r - prev.r);
 | |
| 			cp_encode_byte(s, row[x].g - prev.g);
 | |
| 			cp_encode_byte(s, row[x].b - prev.b);
 | |
| 			cp_encode_byte(s, row[x].a - prev.a);
 | |
| 			prev = row[x];
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	cp_end_run(s);
 | |
| 	cp_encode_literal(s, 256); // terminator
 | |
| 	while (s->bits != 0x80) cp_put_bits(s, 0, 1);
 | |
| 	cp_put32(s, s->adler);
 | |
| 	*dataSize = (s->buflen - dataPos) - 8;
 | |
| 	cp_put32(s, ~s->crc);
 | |
| }
 | |
| 
 | |
| cp_saved_png_t cp_save_png_to_memory(const cp_image_t* img)
 | |
| {
 | |
| 	cp_saved_png_t result = { 0 };
 | |
| 	cp_save_png_data_t s = { 0 };
 | |
| 	long dataPos, dataSize, fileSize;
 | |
| 	if (!img) return result;
 | |
| 
 | |
| 	s.adler = 1;
 | |
| 	s.bits = 0x80;
 | |
| 	s.prev = 0xFFFF;
 | |
| 	s.bufcap = 1024;
 | |
| 	s.buffer = (char*)CUTE_PNG_ALLOC(1024);
 | |
| 
 | |
| 	cp_save_header(&s, (cp_image_t*)img);
 | |
| 	dataPos = s.buflen;
 | |
| 	cp_save_data(&s, (cp_image_t*)img, dataPos, &dataSize);
 | |
| 
 | |
| 	// End chunk.
 | |
| 	cp_begin_chunk(&s, "IEND", 0);
 | |
| 	cp_put32(&s, ~s.crc);
 | |
| 
 | |
| 	// Write back payload size.
 | |
| 	fileSize = s.buflen;
 | |
| 	s.buflen = dataPos;
 | |
| 	cp_put32(&s, dataSize);
 | |
| 
 | |
| 	result.size = fileSize;
 | |
| 	result.data = s.buffer;
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| int cp_save_png(const char* file_name, const cp_image_t* img)
 | |
| {
 | |
| 	cp_saved_png_t s;
 | |
| 	long err;
 | |
| 	CUTE_PNG_FILE* fp = CUTE_PNG_FOPEN(file_name, "wb");
 | |
| 	if (!fp) return 1;
 | |
| 	s = cp_save_png_to_memory(img);
 | |
| 	CUTE_PNG_FWRITE(s.data, s.size, 1, fp);
 | |
| 	err = CUTE_PNG_FERROR(fp);
 | |
| 	CUTE_PNG_FCLOSE(fp);
 | |
| 	CUTE_PNG_FREE(s.data);
 | |
| 	return !err;
 | |
| }
 | |
| 
 | |
| typedef struct cp_raw_png_t
 | |
| {
 | |
| 	const uint8_t* p;
 | |
| 	const uint8_t* end;
 | |
| } cp_raw_png_t;
 | |
| 
 | |
| static uint32_t cp_make32(const uint8_t* s)
 | |
| {
 | |
| 	return (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
 | |
| }
 | |
| 
 | |
| static const uint8_t* cp_chunk(cp_raw_png_t* png, const char* chunk, uint32_t minlen)
 | |
| {
 | |
| 	uint32_t len = cp_make32(png->p);
 | |
| 	const uint8_t* start = png->p;
 | |
| 
 | |
| 	if (!CUTE_PNG_MEMCMP(start + 4, chunk, 4) && len >= minlen)
 | |
| 	{
 | |
| 		int offset = len + 12;
 | |
| 
 | |
| 		if (png->p + offset <= png->end)
 | |
| 		{
 | |
| 			png->p += offset;
 | |
| 			return start + 8;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const uint8_t* cp_find(cp_raw_png_t* png, const char* chunk, uint32_t minlen)
 | |
| {
 | |
| 	const uint8_t *start;
 | |
| 	while (png->p < png->end)
 | |
| 	{
 | |
| 		uint32_t len = cp_make32(png->p);
 | |
| 		start = png->p;
 | |
| 		png->p += len + 12;
 | |
| 
 | |
| 		if (!CUTE_PNG_MEMCMP(start+4, chunk, 4) && len >= minlen && png->p <= png->end)
 | |
| 			return start + 8;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int cp_unfilter(int w, int h, int bpp, uint8_t* raw)
 | |
| {
 | |
| 	int len = w * bpp;
 | |
| 	uint8_t *prev;
 | |
| 	int x;
 | |
| 
 | |
| 	if (h > 0)
 | |
| 	{
 | |
| #define FILTER_LOOP_FIRST(A) for (x = bpp; x < len; x++) raw[x] += A; break
 | |
| 		switch (*raw++)
 | |
| 		{
 | |
| 		case 0: break;
 | |
| 		case 1: FILTER_LOOP_FIRST(raw[x - bpp]);
 | |
| 		case 2: break;
 | |
| 		case 3: FILTER_LOOP_FIRST(raw[x - bpp] / 2);
 | |
| 		case 4: FILTER_LOOP_FIRST(cp_paeth(raw[x - bpp], 0, 0));
 | |
| 		default: return 0;
 | |
| 		}
 | |
| #undef FILTER_LOOP_FIRST
 | |
| 	}
 | |
| 
 | |
| 	prev = raw;
 | |
| 	raw += len;
 | |
| 
 | |
| 	for (int y = 1; y < h; y++, prev = raw, raw += len)
 | |
| 	{
 | |
| #define FILTER_LOOP(A, B) for (x = 0 ; x < bpp; x++) raw[x] += A; for (; x < len; x++) raw[x] += B; break
 | |
| 		switch (*raw++)
 | |
| 		{
 | |
| 		case 0: break;
 | |
| 		case 1: FILTER_LOOP(0          , raw[x - bpp] );
 | |
| 		case 2: FILTER_LOOP(prev[x]    , prev[x]);
 | |
| 		case 3: FILTER_LOOP(prev[x] / 2, (raw[x - bpp] + prev[x]) / 2);
 | |
| 		case 4: FILTER_LOOP(prev[x]    , cp_paeth(raw[x - bpp], prev[x], prev[x -bpp]));
 | |
| 		default: return 0;
 | |
| 		}
 | |
| #undef FILTER_LOOP
 | |
| 	}
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static void cp_convert(int bpp, int w, int h, uint8_t* src, cp_pixel_t* dst)
 | |
| {
 | |
| 	for (int y = 0; y < h; y++)
 | |
| 	{
 | |
| 		// skip filter byte
 | |
| 		src++;
 | |
| 
 | |
| 		for (int x = 0; x < w; x++, src += bpp)
 | |
| 		{
 | |
| 			switch (bpp)
 | |
| 			{
 | |
| 				case 1: *dst++ = cp_make_pixel(src[0], src[0], src[0]); break;
 | |
| 				case 2: *dst++ = cp_make_pixel_a(src[0], src[0], src[0], src[1]); break;
 | |
| 				case 3: *dst++ = cp_make_pixel(src[0], src[1], src[2]); break;
 | |
| 				case 4: *dst++ = cp_make_pixel_a(src[0], src[1], src[2], src[3]); break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.tRNS
 | |
| static uint8_t cp_get_alpha_for_indexed_image(int index, const uint8_t* trns, uint32_t trns_len)
 | |
| {
 | |
| 	if (!trns) return 255;
 | |
| 	else if ((uint32_t)index >= trns_len) return 255;
 | |
| 	else return trns[index];
 | |
| }
 | |
| 
 | |
| static void cp_depalette(int w, int h, uint8_t* src, cp_pixel_t* dst, const uint8_t* plte, const uint8_t* trns, uint32_t trns_len)
 | |
| {
 | |
| 	for (int y = 0; y < h; ++y)
 | |
| 	{
 | |
| 		// skip filter byte
 | |
| 		++src;
 | |
| 
 | |
| 		for (int x = 0; x < w; ++x, ++src)
 | |
| 		{
 | |
| 			int c = *src;
 | |
| 			uint8_t r = plte[c * 3];
 | |
| 			uint8_t g = plte[c * 3 + 1];
 | |
| 			uint8_t b = plte[c * 3 + 2];
 | |
| 			uint8_t a = cp_get_alpha_for_indexed_image(c, trns, trns_len);
 | |
| 			*dst++ = cp_make_pixel_a(r, g, b, a);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static uint32_t cp_get_chunk_byte_length(const uint8_t* chunk)
 | |
| {
 | |
| 	return cp_make32(chunk - 8);
 | |
| }
 | |
| 
 | |
| static int cp_out_size(cp_image_t* img, int bpp)
 | |
| {
 | |
| 	return (img->w + 1) * img->h * bpp;
 | |
| }
 | |
| 
 | |
| cp_image_t cp_load_png_mem(const void* png_data, int png_length)
 | |
| {
 | |
| 	const char* sig = "\211PNG\r\n\032\n";
 | |
| 	const uint8_t* ihdr, *first, *plte, *trns;
 | |
| 	int bit_depth, color_type, bpp, w, h, pix_bytes;
 | |
| 	int compression, filter, interlace;
 | |
| 	int datalen, offset;
 | |
| 	uint8_t* out;
 | |
| 	cp_image_t img = { 0 };
 | |
| 	uint8_t* data = 0;
 | |
| 	cp_raw_png_t png;
 | |
| 	png.p = (uint8_t*)png_data;
 | |
| 	png.end = (uint8_t*)png_data + png_length;
 | |
| 
 | |
| 	CUTE_PNG_CHECK(!CUTE_PNG_MEMCMP(png.p, sig, 8), "incorrect file signature (is this a png file?)");
 | |
| 	png.p += 8;
 | |
| 
 | |
| 	ihdr = cp_chunk(&png, "IHDR", 13);
 | |
| 	CUTE_PNG_CHECK(ihdr, "unable to find IHDR chunk");
 | |
| 	bit_depth = ihdr[8];
 | |
| 	color_type = ihdr[9];
 | |
| 	CUTE_PNG_CHECK(bit_depth == 8, "only bit-depth of 8 is supported");
 | |
| 
 | |
| 	switch (color_type)
 | |
| 	{
 | |
| 		case 0: bpp = 1; break; // greyscale
 | |
| 		case 2: bpp = 3; break; // RGB
 | |
| 		case 3: bpp = 1; break; // paletted
 | |
| 		case 4: bpp = 2; break; // grey+alpha
 | |
| 		case 6: bpp = 4; break; // RGBA
 | |
| 		default: CUTE_PNG_CHECK(0, "unknown color type");
 | |
| 	}
 | |
| 
 | |
| 	// +1 for filter byte (which is dumb! just stick this at file header...)
 | |
| 	w = cp_make32(ihdr) + 1;
 | |
| 	h = cp_make32(ihdr + 4);
 | |
| 	CUTE_PNG_CHECK(w >= 1, "invalid IHDR chunk found, image width was less than 1");
 | |
| 	CUTE_PNG_CHECK(h >= 1, "invalid IHDR chunk found, image height was less than 1");
 | |
|         CUTE_PNG_CHECK((int64_t) w * h * sizeof(cp_pixel_t) < INT_MAX, "image too large");
 | |
| 	pix_bytes = w * h * sizeof(cp_pixel_t);
 | |
| 	img.w = w - 1;
 | |
| 	img.h = h;
 | |
| 	img.pix = (cp_pixel_t*)CUTE_PNG_ALLOC(pix_bytes);
 | |
| 	CUTE_PNG_CHECK(img.pix, "unable to allocate raw image space");
 | |
| 
 | |
| 	compression = ihdr[10];
 | |
| 	filter = ihdr[11];
 | |
| 	interlace = ihdr[12];
 | |
| 	CUTE_PNG_CHECK(!compression, "only standard compression DEFLATE is supported");
 | |
| 	CUTE_PNG_CHECK(!filter, "only standard adaptive filtering is supported");
 | |
| 	CUTE_PNG_CHECK(!interlace, "interlacing is not supported");
 | |
| 
 | |
| 	// PLTE must come before any IDAT chunk
 | |
| 	first = png.p;
 | |
| 	plte = cp_find(&png, "PLTE", 0);
 | |
| 	if (!plte) png.p = first;
 | |
| 	else first = png.p;
 | |
| 
 | |
| 	// tRNS can come after PLTE
 | |
| 	trns = cp_find(&png, "tRNS", 0);
 | |
| 	if (!trns) png.p = first;
 | |
| 	else first = png.p;
 | |
| 
 | |
| 	// Compute length of the DEFLATE stream through IDAT chunk data sizes
 | |
| 	datalen = 0;
 | |
| 	for (const uint8_t* idat = cp_find(&png, "IDAT", 0); idat; idat = cp_chunk(&png, "IDAT", 0))
 | |
| 	{
 | |
| 		uint32_t len = cp_get_chunk_byte_length(idat);
 | |
| 		datalen += len;
 | |
| 	}
 | |
| 
 | |
| 	// Copy in IDAT chunk data sections to form the compressed DEFLATE stream
 | |
| 	png.p = first;
 | |
| 	data = (uint8_t*)CUTE_PNG_ALLOC(datalen);
 | |
| 	offset = 0;
 | |
| 	for (const uint8_t* idat = cp_find(&png, "IDAT", 0); idat; idat = cp_chunk(&png, "IDAT", 0))
 | |
| 	{
 | |
| 		uint32_t len = cp_get_chunk_byte_length(idat);
 | |
| 		CUTE_PNG_MEMCPY(data + offset, idat, len);
 | |
| 		offset += len;
 | |
| 	}
 | |
| 
 | |
| 	// check for proper zlib structure in DEFLATE stream
 | |
| 	CUTE_PNG_CHECK(data && datalen >= 6, "corrupt zlib structure in DEFLATE stream");
 | |
| 	CUTE_PNG_CHECK((data[0] & 0x0f) == 0x08, "only zlib compression method (RFC 1950) is supported");
 | |
| 	CUTE_PNG_CHECK((data[0] & 0xf0) <= 0x70, "innapropriate window size detected");
 | |
| 	CUTE_PNG_CHECK(!(data[1] & 0x20), "preset dictionary is present and not supported");
 | |
| 
 | |
| 	// check for integer overflow
 | |
| 	CUTE_PNG_CHECK(cp_out_size(&img, 4) >= 1, "invalid image size found");
 | |
| 	CUTE_PNG_CHECK(cp_out_size(&img, bpp) >= 1, "invalid image size found");
 | |
| 
 | |
| 	out = (uint8_t*)img.pix + cp_out_size(&img, 4) - cp_out_size(&img, bpp);
 | |
| 	CUTE_PNG_CHECK(cp_inflate(data + 2, datalen - 6, out, pix_bytes), "DEFLATE algorithm failed");
 | |
| 	CUTE_PNG_CHECK(cp_unfilter(img.w, img.h, bpp, out), "invalid filter byte found");
 | |
| 
 | |
| 	if (color_type == 3)
 | |
| 	{
 | |
| 		CUTE_PNG_CHECK(plte, "color type of indexed requires a PLTE chunk");
 | |
| 		uint32_t trns_len = trns ? cp_get_chunk_byte_length(trns) : 0;
 | |
| 		cp_depalette(img.w, img.h, out, img.pix, plte, trns, trns_len);
 | |
| 	}
 | |
| 	else cp_convert(bpp, img.w, img.h, out, img.pix);
 | |
| 
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	return img;
 | |
| 
 | |
| cp_err:
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	CUTE_PNG_FREE(img.pix);
 | |
| 	img.pix = 0;
 | |
| 
 | |
| 	return img;
 | |
| }
 | |
| 
 | |
| cp_image_t cp_load_blank(int w, int h)
 | |
| {
 | |
| 	cp_image_t img;
 | |
| 	img.w = w;
 | |
| 	img.h = h;
 | |
| 	img.pix = (cp_pixel_t*)CUTE_PNG_ALLOC(w * h * sizeof(cp_pixel_t));
 | |
| 	return img;
 | |
| }
 | |
| 
 | |
| cp_image_t cp_load_png(const char *file_name)
 | |
| {
 | |
| 	cp_image_t img = { 0 };
 | |
| 	int len;
 | |
| 	void* data = cp_read_file_to_memory(file_name, &len);
 | |
| 	if (!data) return img;
 | |
| 	img = cp_load_png_mem(data, len);
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	return img;
 | |
| }
 | |
| 
 | |
| void cp_free_png(cp_image_t* img)
 | |
| {
 | |
| 	CUTE_PNG_FREE(img->pix);
 | |
| 	img->pix = 0;
 | |
| 	img->w = img->h = 0;
 | |
| }
 | |
| 
 | |
| void cp_flip_image_horizontal(cp_image_t* img)
 | |
| {
 | |
| 	cp_pixel_t* pix = img->pix;
 | |
| 	int w = img->w;
 | |
| 	int h = img->h;
 | |
| 	int flips = h / 2;
 | |
| 	for (int i = 0; i < flips; ++i)
 | |
| 	{
 | |
| 		cp_pixel_t* a = pix + w * i;
 | |
| 		cp_pixel_t* b = pix + w * (h - i - 1);
 | |
| 		for (int j = 0; j < w; ++j)
 | |
| 		{
 | |
| 			cp_pixel_t t = *a;
 | |
| 			*a = *b;
 | |
| 			*b = t;
 | |
| 			++a;
 | |
| 			++b;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void cp_load_png_wh(const void* png_data, int png_length, int* w_out, int* h_out)
 | |
| {
 | |
| 	const char* sig = "\211PNG\r\n\032\n";
 | |
| 	const uint8_t* ihdr;
 | |
| 	cp_raw_png_t png;
 | |
| 	int w, h;
 | |
| 	png.p = (uint8_t*)png_data;
 | |
| 	png.end = (uint8_t*)png_data + png_length;
 | |
| 
 | |
| 	if (w_out) *w_out = 0;
 | |
| 	if (h_out) *h_out = 0;
 | |
| 
 | |
| 	CUTE_PNG_CHECK(!CUTE_PNG_MEMCMP(png.p, sig, 8), "incorrect file signature (is this a png file?)");
 | |
| 	png.p += 8;
 | |
| 
 | |
| 	ihdr = cp_chunk(&png, "IHDR", 13);
 | |
| 	CUTE_PNG_CHECK(ihdr, "unable to find IHDR chunk");
 | |
| 
 | |
| 	// +1 for filter byte (which is dumb! just stick this at file header...)
 | |
| 	w = cp_make32(ihdr) + 1;
 | |
| 	h = cp_make32(ihdr + 4);
 | |
| 	if (w_out) *w_out = w - 1;
 | |
| 	if (h_out) *h_out = h;
 | |
| 
 | |
| 	cp_err:;
 | |
| }
 | |
| 
 | |
| cp_indexed_image_t cp_load_indexed_png(const char* file_name)
 | |
| {
 | |
| 	cp_indexed_image_t img = { 0 };
 | |
| 	int len;
 | |
| 	void* data = cp_read_file_to_memory(file_name, &len);
 | |
| 	if (!data) return img;
 | |
| 	img = cp_load_indexed_png_mem(data, len);
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	return img;
 | |
| }
 | |
| 
 | |
| static void cp_unpack_indexed_rows(int w, int h, uint8_t* src, uint8_t* dst)
 | |
| {
 | |
| 	for (int y = 0; y < h; ++y)
 | |
| 	{
 | |
| 		// skip filter byte
 | |
| 		++src;
 | |
| 
 | |
| 		for (int x = 0; x < w; ++x, ++src)
 | |
| 		{
 | |
| 			*dst++ = *src;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void cp_unpack_palette(cp_pixel_t* dst, const uint8_t* plte, int plte_len, const uint8_t* trns, int trns_len)
 | |
| {
 | |
| 	for (int i = 0; i < plte_len * 3; i += 3)
 | |
| 	{
 | |
| 		unsigned char r = plte[i];
 | |
| 		unsigned char g = plte[i + 1];
 | |
| 		unsigned char b = plte[i + 2];
 | |
| 		unsigned char a = cp_get_alpha_for_indexed_image(i / 3, trns, trns_len);
 | |
| 		cp_pixel_t p = cp_make_pixel_a(r, g, b, a);
 | |
| 		*dst++ = p;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| cp_indexed_image_t cp_load_indexed_png_mem(const void *png_data, int png_length)
 | |
| {
 | |
| 	const char* sig = "\211PNG\r\n\032\n";
 | |
| 	const uint8_t* ihdr, *first, *plte, *trns;
 | |
| 	int bit_depth, color_type, bpp, w, h, pix_bytes;
 | |
| 	int compression, filter, interlace;
 | |
| 	int datalen, offset;
 | |
| 	int plte_len;
 | |
| 	uint8_t* out;
 | |
| 	cp_indexed_image_t img = { 0 };
 | |
| 	uint8_t* data = 0;
 | |
| 	cp_raw_png_t png;
 | |
| 	png.p = (uint8_t*)png_data;
 | |
| 	png.end = (uint8_t*)png_data + png_length;
 | |
| 
 | |
| 	CUTE_PNG_CHECK(!CUTE_PNG_MEMCMP(png.p, sig, 8), "incorrect file signature (is this a png file?)");
 | |
| 	png.p += 8;
 | |
| 
 | |
| 	ihdr = cp_chunk(&png, "IHDR", 13);
 | |
| 	CUTE_PNG_CHECK(ihdr, "unable to find IHDR chunk");
 | |
| 	bit_depth = ihdr[8];
 | |
| 	color_type = ihdr[9];
 | |
| 	bpp = 1; // bytes per pixel
 | |
| 	CUTE_PNG_CHECK(bit_depth == 8, "only bit-depth of 8 is supported");
 | |
| 	CUTE_PNG_CHECK(color_type == 3, "only indexed png images (images with a palette) are valid for cp_load_indexed_png_mem");
 | |
| 
 | |
| 	// +1 for filter byte (which is dumb! just stick this at file header...)
 | |
| 	w = cp_make32(ihdr) + 1;
 | |
| 	h = cp_make32(ihdr + 4);
 | |
|         CUTE_PNG_CHECK((int64_t) w * h * sizeof(uint8_t) < INT_MAX, "image too large");
 | |
| 	pix_bytes = w * h * sizeof(uint8_t);
 | |
| 	img.w = w - 1;
 | |
| 	img.h = h;
 | |
| 	img.pix = (uint8_t*)CUTE_PNG_ALLOC(pix_bytes);
 | |
| 	CUTE_PNG_CHECK(img.pix, "unable to allocate raw image space");
 | |
| 
 | |
| 	compression = ihdr[10];
 | |
| 	filter = ihdr[11];
 | |
| 	interlace = ihdr[12];
 | |
| 	CUTE_PNG_CHECK(!compression, "only standard compression DEFLATE is supported");
 | |
| 	CUTE_PNG_CHECK(!filter, "only standard adaptive filtering is supported");
 | |
| 	CUTE_PNG_CHECK(!interlace, "interlacing is not supported");
 | |
| 
 | |
| 	// PLTE must come before any IDAT chunk
 | |
| 	first = png.p;
 | |
| 	plte = cp_find(&png, "PLTE", 0);
 | |
| 	if (!plte) png.p = first;
 | |
| 	else first = png.p;
 | |
| 
 | |
| 	// tRNS can come after PLTE
 | |
| 	trns = cp_find(&png, "tRNS", 0);
 | |
| 	if (!trns) png.p = first;
 | |
| 	else first = png.p;
 | |
| 
 | |
| 	// Compute length of the DEFLATE stream through IDAT chunk data sizes
 | |
| 	datalen = 0;
 | |
| 	for (const uint8_t* idat = cp_find(&png, "IDAT", 0); idat; idat = cp_chunk(&png, "IDAT", 0))
 | |
| 	{
 | |
| 		uint32_t len = cp_get_chunk_byte_length(idat);
 | |
| 		datalen += len;
 | |
| 	}
 | |
| 
 | |
| 	// Copy in IDAT chunk data sections to form the compressed DEFLATE stream
 | |
| 	png.p = first;
 | |
| 	data = (uint8_t*)CUTE_PNG_ALLOC(datalen);
 | |
| 	offset = 0;
 | |
| 	for (const uint8_t* idat = cp_find(&png, "IDAT", 0); idat; idat = cp_chunk(&png, "IDAT", 0))
 | |
| 	{
 | |
| 		uint32_t len = cp_get_chunk_byte_length(idat);
 | |
| 		CUTE_PNG_MEMCPY(data + offset, idat, len);
 | |
| 		offset += len;
 | |
| 	}
 | |
| 
 | |
| 	// check for proper zlib structure in DEFLATE stream
 | |
| 	CUTE_PNG_CHECK(data && datalen >= 6, "corrupt zlib structure in DEFLATE stream");
 | |
| 	CUTE_PNG_CHECK((data[0] & 0x0f) == 0x08, "only zlib compression method (RFC 1950) is supported");
 | |
| 	CUTE_PNG_CHECK((data[0] & 0xf0) <= 0x70, "innapropriate window size detected");
 | |
| 	CUTE_PNG_CHECK(!(data[1] & 0x20), "preset dictionary is present and not supported");
 | |
| 
 | |
| 	out = img.pix;
 | |
| 	CUTE_PNG_CHECK(cp_inflate(data + 2, datalen - 6, out, pix_bytes), "DEFLATE algorithm failed");
 | |
| 	CUTE_PNG_CHECK(cp_unfilter(img.w, img.h, bpp, out), "invalid filter byte found");
 | |
| 	cp_unpack_indexed_rows(img.w, img.h, out, img.pix);
 | |
| 
 | |
| 	plte_len = cp_get_chunk_byte_length(plte) / 3;
 | |
| 	cp_unpack_palette(img.palette, plte, plte_len, trns, cp_get_chunk_byte_length(trns));
 | |
| 	img.palette_len = (uint8_t)plte_len;
 | |
| 
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	return img;
 | |
| 
 | |
| cp_err:
 | |
| 	CUTE_PNG_FREE(data);
 | |
| 	CUTE_PNG_FREE(img.pix);
 | |
| 	img.pix = 0;
 | |
| 
 | |
| 	return img;
 | |
| }
 | |
| 
 | |
| void cp_free_indexed_png(cp_indexed_image_t* img)
 | |
| {
 | |
| 	CUTE_PNG_FREE(img->pix);
 | |
| 	img->pix = 0;
 | |
| 	img->w = img->h = 0;
 | |
| }
 | |
| 
 | |
| cp_image_t cp_depallete_indexed_image(cp_indexed_image_t* img)
 | |
| {
 | |
| 	cp_image_t out = { 0 };
 | |
| 	out.w = img->w;
 | |
| 	out.h = img->h;
 | |
| 	out.pix = (cp_pixel_t*)CUTE_PNG_ALLOC(sizeof(cp_pixel_t) * out.w * out.h);
 | |
| 
 | |
| 	cp_pixel_t* dst = out.pix;
 | |
| 	uint8_t* src = img->pix;
 | |
| 
 | |
| 	for (int y = 0; y < out.h; ++y)
 | |
| 	{
 | |
| 		for (int x = 0; x < out.w; ++x)
 | |
| 		{
 | |
| 			int index = *src++;
 | |
| 			cp_pixel_t p = img->palette[index];
 | |
| 			*dst++ = p;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return out;
 | |
| }
 | |
| 
 | |
| typedef struct cp_v2i_t
 | |
| {
 | |
| 	int x;
 | |
| 	int y;
 | |
| } cp_v2i_t;
 | |
| 
 | |
| typedef struct cp_integer_image_t
 | |
| {
 | |
| 	int img_index;
 | |
| 	cp_v2i_t size;
 | |
| 	cp_v2i_t min;
 | |
| 	cp_v2i_t max;
 | |
| 	int fit;
 | |
| } cp_integer_image_t;
 | |
| 
 | |
| static cp_v2i_t cp_v2i(int x, int y)
 | |
| {
 | |
| 	cp_v2i_t v;
 | |
| 	v.x = x;
 | |
| 	v.y = y;
 | |
| 	return v;
 | |
| }
 | |
| 
 | |
| static cp_v2i_t cp_sub(cp_v2i_t a, cp_v2i_t b)
 | |
| {
 | |
| 	cp_v2i_t v;
 | |
| 	v.x = a.x - b.x;
 | |
| 	v.y = a.y - b.y;
 | |
| 	return v;
 | |
| }
 | |
| 
 | |
| static cp_v2i_t cp_add(cp_v2i_t a, cp_v2i_t b)
 | |
| {
 | |
| 	cp_v2i_t v;
 | |
| 	v.x = a.x + b.x;
 | |
| 	v.y = a.y + b.y;
 | |
| 	return v;
 | |
| }
 | |
| 
 | |
| typedef struct cp_atlas_node_t
 | |
| {
 | |
| 	cp_v2i_t size;
 | |
| 	cp_v2i_t min;
 | |
| 	cp_v2i_t max;
 | |
| } cp_atlas_node_t;
 | |
| 
 | |
| static cp_atlas_node_t* cp_best_fit(int sp, const cp_image_t* png, cp_atlas_node_t* nodes)
 | |
| {
 | |
| 	int bestVolume = INT_MAX;
 | |
| 	cp_atlas_node_t *best_node = 0;
 | |
| 	int width = png->w;
 | |
| 	int height = png->h;
 | |
| 	int png_volume = width * height;
 | |
| 
 | |
| 	for (int i = 0; i < sp; ++i)
 | |
| 	{
 | |
| 		cp_atlas_node_t *node = nodes + i;
 | |
| 		int can_contain = node->size.x >= width && node->size.y >= height;
 | |
| 		if (can_contain)
 | |
| 		{
 | |
| 			int node_volume = node->size.x * node->size.y;
 | |
| 			if (node_volume == png_volume) return node;
 | |
| 			if (node_volume < bestVolume)
 | |
| 			{
 | |
| 				bestVolume = node_volume;
 | |
| 				best_node = node;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return best_node;
 | |
| }
 | |
| 
 | |
| static int cp_perimeter_pred(cp_integer_image_t* a, cp_integer_image_t* b)
 | |
| {
 | |
| 	int perimeterA = 2 * (a->size.x + a->size.y);
 | |
| 	int perimeterB = 2 * (b->size.x + b->size.y);
 | |
| 	return perimeterB < perimeterA;
 | |
| }
 | |
| 
 | |
| void cp_premultiply(cp_image_t* img)
 | |
| {
 | |
| 	int w = img->w;
 | |
| 	int h = img->h;
 | |
| 	int stride = w * sizeof(cp_pixel_t);
 | |
| 	uint8_t* data = (uint8_t*)img->pix;
 | |
| 
 | |
| 	for(int i = 0; i < (int)stride * h; i += sizeof(cp_pixel_t))
 | |
| 	{
 | |
| 		float a = (float)data[i + 3] / 255.0f;
 | |
| 		float r = (float)data[i + 0] / 255.0f;
 | |
| 		float g = (float)data[i + 1] / 255.0f;
 | |
| 		float b = (float)data[i + 2] / 255.0f;
 | |
| 		r *= a;
 | |
| 		g *= a;
 | |
| 		b *= a;
 | |
| 		data[i + 0] = (uint8_t)(r * 255.0f);
 | |
| 		data[i + 1] = (uint8_t)(g * 255.0f);
 | |
| 		data[i + 2] = (uint8_t)(b * 255.0f);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void cp_qsort(cp_integer_image_t* items, int count)
 | |
| {
 | |
| 	if (count <= 1) return;
 | |
| 
 | |
| 	cp_integer_image_t pivot = items[count - 1];
 | |
| 	int low = 0;
 | |
| 	for (int i = 0; i < count - 1; ++i)
 | |
| 	{
 | |
| 		if (cp_perimeter_pred(items + i, &pivot))
 | |
| 		{
 | |
| 			cp_integer_image_t tmp = items[i];
 | |
| 			items[i] = items[low];
 | |
| 			items[low] = tmp;
 | |
| 			low++;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	items[count - 1] = items[low];
 | |
| 	items[low] = pivot;
 | |
| 	cp_qsort(items, low);
 | |
| 	cp_qsort(items + low + 1, count - 1 - low);
 | |
| }
 | |
| 
 | |
| static void cp_write_pixel(char* mem, long color) {
 | |
| 	mem[0] = (color >> 24) & 0xFF;
 | |
| 	mem[1] = (color >> 16) & 0xFF;
 | |
| 	mem[2] = (color >>  8) & 0xFF;
 | |
| 	mem[3] = (color >>  0) & 0xFF;
 | |
| }
 | |
| 
 | |
| cp_image_t cp_make_atlas(int atlas_width, int atlas_height, const cp_image_t* pngs, int png_count, cp_atlas_image_t* imgs_out)
 | |
| {
 | |
| 	float w0, h0, div, wTol, hTol;
 | |
| 	int atlas_image_size, atlas_stride, sp;
 | |
| 	void* atlas_pixels = 0;
 | |
| 	int atlas_node_capacity = png_count * 2;
 | |
| 	cp_image_t atlas_image;
 | |
| 	cp_integer_image_t* images = 0;
 | |
| 	cp_atlas_node_t* nodes = 0;
 | |
| 
 | |
| 	atlas_image.w = atlas_width;
 | |
| 	atlas_image.h = atlas_height;
 | |
| 	atlas_image.pix = 0;
 | |
| 
 | |
| 	CUTE_PNG_CHECK(pngs, "pngs array was NULL");
 | |
| 	CUTE_PNG_CHECK(imgs_out, "imgs_out array was NULL");
 | |
| 
 | |
| 	images = (cp_integer_image_t*)CUTE_PNG_ALLOCA(sizeof(cp_integer_image_t) * png_count);
 | |
| 	nodes = (cp_atlas_node_t*)CUTE_PNG_ALLOC(sizeof(cp_atlas_node_t) * atlas_node_capacity);
 | |
| 	CUTE_PNG_CHECK(images, "out of mem");
 | |
| 	CUTE_PNG_CHECK(nodes, "out of mem");
 | |
| 
 | |
| 	for (int i = 0; i < png_count; ++i)
 | |
| 	{
 | |
| 		const cp_image_t* png = pngs + i;
 | |
| 		cp_integer_image_t* image = images + i;
 | |
| 		image->fit = 0;
 | |
| 		image->size = cp_v2i(png->w, png->h);
 | |
| 		image->img_index = i;
 | |
| 	}
 | |
| 
 | |
| 	// Sort PNGs from largest to smallest
 | |
| 	cp_qsort(images, png_count);
 | |
| 
 | |
| 	// stack pointer, the stack is the nodes array which we will
 | |
| 	// allocate nodes from as necessary.
 | |
| 	sp = 1;
 | |
| 
 | |
| 	nodes[0].min = cp_v2i(0, 0);
 | |
| 	nodes[0].max = cp_v2i(atlas_width, atlas_height);
 | |
| 	nodes[0].size = cp_v2i(atlas_width, atlas_height);
 | |
| 
 | |
| 	// Nodes represent empty space in the atlas. Placing a texture into the
 | |
| 	// atlas involves splitting a node into two smaller pieces (or, if a
 | |
| 	// perfect fit is found, deleting the node).
 | |
| 	for (int i = 0; i < png_count; ++i)
 | |
| 	{
 | |
| 		cp_integer_image_t* image = images + i;
 | |
| 		const cp_image_t* png = pngs + image->img_index;
 | |
| 		int width = png->w;
 | |
| 		int height = png->h;
 | |
| 		cp_atlas_node_t *best_fit = cp_best_fit(sp, png, nodes);
 | |
| 		if (CUTE_PNG_ATLAS_MUST_FIT) CUTE_PNG_CHECK(best_fit, "Not enough room to place image in atlas.");
 | |
| 		else if (!best_fit) 
 | |
| 		{
 | |
| 			image->fit = 0;
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		image->min = best_fit->min;
 | |
| 		image->max = cp_add(image->min, image->size);
 | |
| 
 | |
| 		if (best_fit->size.x == width && best_fit->size.y == height)
 | |
| 		{
 | |
| 			cp_atlas_node_t* last_node = nodes + --sp;
 | |
| 			*best_fit = *last_node;
 | |
| 			image->fit = 1;
 | |
| 
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		image->fit = 1;
 | |
| 
 | |
| 		if (sp == atlas_node_capacity)
 | |
| 		{
 | |
| 			int new_capacity = atlas_node_capacity * 2;
 | |
| 			cp_atlas_node_t* new_nodes = (cp_atlas_node_t*)CUTE_PNG_ALLOC(sizeof(cp_atlas_node_t) * new_capacity);
 | |
| 			CUTE_PNG_CHECK(new_nodes, "out of mem");
 | |
| 			CUTE_PNG_MEMCPY(new_nodes, nodes, sizeof(cp_atlas_node_t) * sp);
 | |
| 
 | |
| 			// best_fit became a dangling pointer, so relocate it
 | |
| 			best_fit = new_nodes + (best_fit - nodes);
 | |
| 			CUTE_PNG_FREE(nodes);
 | |
| 
 | |
| 			nodes = new_nodes;
 | |
| 			atlas_node_capacity = new_capacity;
 | |
| 		}
 | |
| 
 | |
| 		cp_atlas_node_t* new_node = nodes + sp++;
 | |
| 		new_node->min = best_fit->min;
 | |
| 
 | |
| 		// Split bestFit along x or y, whichever minimizes
 | |
| 		// fragmentation of empty space
 | |
| 		cp_v2i_t d = cp_sub(best_fit->size, cp_v2i(width, height));
 | |
| 		if (d.x < d.y)
 | |
| 		{
 | |
| 			new_node->size.x = d.x;
 | |
| 			new_node->size.y = height;
 | |
| 			new_node->min.x += width;
 | |
| 
 | |
| 			best_fit->size.y = d.y;
 | |
| 			best_fit->min.y += height;
 | |
| 		}
 | |
| 
 | |
| 		else
 | |
| 		{
 | |
| 			new_node->size.x = width;
 | |
| 			new_node->size.y = d.y;
 | |
| 			new_node->min.y += height;
 | |
| 
 | |
| 			best_fit->size.x = d.x;
 | |
| 			best_fit->min.x += width;
 | |
| 		}
 | |
| 
 | |
| 		new_node->max = cp_add(new_node->min, new_node->size);
 | |
| 	}
 | |
| 
 | |
| 	// Write the final atlas image, use CUTE_PNG_ATLAS_EMPTY_COLOR as base color
 | |
| 	atlas_stride = atlas_width * sizeof(cp_pixel_t);
 | |
| 	atlas_image_size = atlas_width * atlas_height * sizeof(cp_pixel_t);
 | |
| 	atlas_pixels = CUTE_PNG_ALLOC(atlas_image_size);
 | |
| 	CUTE_PNG_CHECK(atlas_pixels, "out of mem");
 | |
| 	
 | |
| 	for(int i = 0; i < atlas_image_size; i += sizeof(cp_pixel_t)) {
 | |
| 		cp_write_pixel((char*)atlas_pixels + i, CUTE_PNG_ATLAS_EMPTY_COLOR);
 | |
| 	}
 | |
| 
 | |
| 	for (int i = 0; i < png_count; ++i)
 | |
| 	{
 | |
| 		cp_integer_image_t* image = images + i;
 | |
| 
 | |
| 		if (image->fit)
 | |
| 		{
 | |
| 			const cp_image_t* png = pngs + image->img_index;
 | |
| 			char* pixels = (char*)png->pix;
 | |
| 			cp_v2i_t min = image->min;
 | |
| 			cp_v2i_t max = image->max;
 | |
| 			int atlas_offset = min.x * sizeof(cp_pixel_t);
 | |
| 			int tex_stride = png->w * sizeof(cp_pixel_t);
 | |
| 
 | |
| 			for (int row = min.y, y = 0; row < max.y; ++row, ++y)
 | |
| 			{
 | |
| 				void* row_ptr = (char*)atlas_pixels + (row * atlas_stride + atlas_offset);
 | |
| 				CUTE_PNG_MEMCPY(row_ptr, pixels + y * tex_stride, tex_stride);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	atlas_image.pix = (cp_pixel_t*)atlas_pixels;
 | |
| 
 | |
| 	// squeeze UVs inward by 128th of a pixel
 | |
| 	// this prevents atlas bleeding. tune as necessary for good results.
 | |
| 	w0 = 1.0f / (float)(atlas_width);
 | |
| 	h0 = 1.0f / (float)(atlas_height);
 | |
| 	div = 1.0f / 128.0f;
 | |
| 	wTol = w0 * div;
 | |
| 	hTol = h0 * div;
 | |
| 
 | |
| 	for (int i = 0; i < png_count; ++i)
 | |
| 	{
 | |
| 		cp_integer_image_t* image = images + i;
 | |
| 		cp_atlas_image_t* img_out = imgs_out + i;
 | |
| 
 | |
| 		img_out->img_index = image->img_index;
 | |
| 		img_out->w = image->size.x;
 | |
| 		img_out->h = image->size.y;
 | |
| 		img_out->fit = image->fit;
 | |
| 
 | |
| 		if (image->fit)
 | |
| 		{
 | |
| 			cp_v2i_t min = image->min;
 | |
| 			cp_v2i_t max = image->max;
 | |
| 
 | |
| 			float min_x = (float)min.x * w0 + wTol;
 | |
| 			float min_y = (float)min.y * h0 + hTol;
 | |
| 			float max_x = (float)max.x * w0 - wTol;
 | |
| 			float max_y = (float)max.y * h0 - hTol;
 | |
| 
 | |
| 			// flip image on y axis
 | |
| 			if (CUTE_PNG_ATLAS_FLIP_Y_AXIS_FOR_UV)
 | |
| 			{
 | |
| 				float tmp = min_y;
 | |
| 				min_y = max_y;
 | |
| 				max_y = tmp;
 | |
| 			}
 | |
| 
 | |
| 			img_out->minx = min_x;
 | |
| 			img_out->miny = min_y;
 | |
| 			img_out->maxx = max_x;
 | |
| 			img_out->maxy = max_y;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	CUTE_PNG_FREE(nodes);
 | |
| 	return atlas_image;
 | |
| 
 | |
| cp_err:
 | |
| 	CUTE_PNG_FREE(atlas_pixels);
 | |
| 	CUTE_PNG_FREE(nodes);
 | |
| 	atlas_image.pix = 0;
 | |
| 	return atlas_image;
 | |
| }
 | |
| 
 | |
| int cp_default_save_atlas(const char* out_path_image, const char* out_path_atlas_txt, const cp_image_t* atlas, const cp_atlas_image_t* imgs, int img_count, const char** names)
 | |
| {
 | |
| 	CUTE_PNG_FILE* fp = CUTE_PNG_FOPEN(out_path_atlas_txt, "wt");
 | |
| 	CUTE_PNG_CHECK(fp, "unable to open out_path_atlas_txt in cp_default_save_atlas");
 | |
| 
 | |
| 	CUTE_PNG_FPRINTF(fp, "%s\n%d\n\n", out_path_image, img_count);
 | |
| 
 | |
| 	for (int i = 0; i < img_count; ++i)
 | |
| 	{
 | |
| 		const cp_atlas_image_t* image = imgs + i;
 | |
| 		const char* name = names ? names[image->img_index] : 0;
 | |
| 
 | |
| 		if (image->fit)
 | |
| 		{
 | |
| 			int width = image->w;
 | |
| 			int height = image->h;
 | |
| 			float min_x = image->minx;
 | |
| 			float min_y = image->miny;
 | |
| 			float max_x = image->maxx;
 | |
| 			float max_y = image->maxy;
 | |
| 
 | |
| 			if (name) CUTE_PNG_FPRINTF(fp, "{ \"%s\", w = %d, h = %d, u = { %.10f, %.10f }, v = { %.10f, %.10f } }\n", name, width, height, min_x, min_y, max_x, max_y);
 | |
| 			else CUTE_PNG_FPRINTF(fp, "{ w = %d, h = %d, u = { %.10f, %.10f }, v = { %.10f, %.10f } }\n", width, height, min_x, min_y, max_x, max_y);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Save atlas image PNG to disk
 | |
| 	CUTE_PNG_CHECK(cp_save_png(out_path_image, atlas), "failed to save atlas image to disk");
 | |
| 
 | |
| cp_err:
 | |
| 	CUTE_PNG_FCLOSE(fp);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #endif // CUTE_PNG_IMPLEMENTATION_ONCE
 | |
| #endif // CUTE_PNG_IMPLEMENTATION
 | |
| 
 | |
| /*
 | |
| 	------------------------------------------------------------------------------
 | |
| 	This software is available under 2 licenses - you may choose the one you like.
 | |
| 	------------------------------------------------------------------------------
 | |
| 	ALTERNATIVE A - zlib license
 | |
| 	Copyright (c) 2019 Randy Gaul http://www.randygaul.net
 | |
| 	This software is provided 'as-is', without any express or implied warranty.
 | |
| 	In no event will the authors be held liable for any damages arising from
 | |
| 	the use of this software.
 | |
| 	Permission is granted to anyone to use this software for any purpose,
 | |
| 	including commercial applications, and to alter it and redistribute it
 | |
| 	freely, subject to the following restrictions:
 | |
| 	  1. The origin of this software must not be misrepresented; you must not
 | |
| 	     claim that you wrote the original software. If you use this software
 | |
| 	     in a product, an acknowledgment in the product documentation would be
 | |
| 	     appreciated but is not required.
 | |
| 	  2. Altered source versions must be plainly marked as such, and must not
 | |
| 	     be misrepresented as being the original software.
 | |
| 	  3. This notice may not be removed or altered from any source distribution.
 | |
| 	------------------------------------------------------------------------------
 | |
| 	ALTERNATIVE B - Public Domain (www.unlicense.org)
 | |
| 	This is free and unencumbered software released into the public domain.
 | |
| 	Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 
 | |
| 	software, either in source code form or as a compiled binary, for any purpose, 
 | |
| 	commercial or non-commercial, and by any means.
 | |
| 	In jurisdictions that recognize copyright laws, the author or authors of this 
 | |
| 	software dedicate any and all copyright interest in the software to the public 
 | |
| 	domain. We make this dedication for the benefit of the public at large and to 
 | |
| 	the detriment of our heirs and successors. We intend this dedication to be an 
 | |
| 	overt act of relinquishment in perpetuity of all present and future rights to 
 | |
| 	this software under copyright law.
 | |
| 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 | |
| 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 | |
| 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 | |
| 	AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 | |
| 	ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 | |
| 	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | |
| 	------------------------------------------------------------------------------
 | |
| */ |