// Compilation: gcc -O2 pngbench.c -o pngbench -lpng
/*
===========================================================================

The libpng png decoding code was adapted from RBDoom3BFG by Daniel Gibson and patched by Tobias Frost.

Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
Copyright (C) 2012-2014 Robert Beckebans

This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").

Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/

#include <stdio.h>
#include <string.h>
#include <limits.h> // PATH_MAX
#include <png.h>
#include <time.h>
#include <stdlib.h>

#define eprintf(...) fprintf(stderr, __VA_ARGS__)

const char* progName = "pngbench";

typedef unsigned char byte;

static void printUsage()
{
	eprintf("Usage: %s <imgname>\n", progName);
	eprintf(" e.g.: %s test.png\n", progName);
	eprintf("       %s /path/to/file.png\n", progName);
}

struct image
{
	unsigned char* data;
	int w;
	int h;
	int format; // 3: RGB, 4: RGBA
};

// the libpng png loading code is stolen from rbdoom3bfg's LoadPNG()

static void freeImage(struct image* img)
{
	free(img->data);
	img->data = NULL;
}

static void png_Error( png_structp pngPtr, png_const_charp msg )
{
	eprintf( "%s\n", msg );
	exit(1);
}

static void png_Warning( png_structp pngPtr, png_const_charp msg )
{
	eprintf( "%s\n", msg );
}

static void	png_ReadData( png_structp pngPtr, png_bytep data, png_size_t length )
{
	// There is a get_io_ptr but not a set_io_ptr.. Therefore we need some tmp storage here.
	byte **ioptr = (byte **)png_get_io_ptr(pngPtr);

	memcpy( data, *ioptr, length );
	*ioptr += length;
}

static struct image loadImage(const char* filename, byte* fbuffer, byte* readptr, int len)
{
	struct image ret = {0};
	
		// create png_struct with the custom error handlers
	png_structp pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING, ( png_voidp ) NULL, png_Error, png_Warning );
	if( !pngPtr )
	{
		eprintf( "LoadPNG( %s ): png_create_read_struct failed\n", filename );
		exit(1);
	}
	
	// allocate the memory for image information
	png_infop infoPtr = png_create_info_struct( pngPtr );
	if( !infoPtr )
	{
		eprintf( "LoadPNG( %s ): png_create_info_struct failed\n", filename );
		exit(1);
	}
	
	readptr = fbuffer;
	png_set_read_fn( pngPtr, &readptr, png_ReadData );
	
	png_set_sig_bytes( pngPtr, 0 );
	
	png_read_info( pngPtr, infoPtr );
	
	png_uint_32 pngWidth, pngHeight;
	int bitDepth, colorType, interlaceType;
	png_get_IHDR( pngPtr, infoPtr, &pngWidth, &pngHeight, &bitDepth, &colorType, &interlaceType, NULL, NULL );
	
	// 16 bit -> 8 bit
	png_set_strip_16( pngPtr );
	
	// 1, 2, 4 bit -> 8 bit
	if( bitDepth < 8 )
	{
		png_set_packing( pngPtr );
	}

	if( colorType & PNG_COLOR_MASK_PALETTE )
	{
		png_set_expand( pngPtr );
	}

	if( !( colorType & PNG_COLOR_MASK_COLOR ) )
	{
		png_set_gray_to_rgb( pngPtr );
	}

	// set paletted or RGB images with transparency to full alpha so we get RGBA
	if( png_get_valid( pngPtr, infoPtr, PNG_INFO_tRNS ) )
	{
		png_set_tRNS_to_alpha( pngPtr );
	}

	// make sure every pixel has an alpha value
	if( !( colorType & PNG_COLOR_MASK_ALPHA ) )
	{
		png_set_filler( pngPtr, 255, PNG_FILLER_AFTER );
	}

	png_read_update_info( pngPtr, infoPtr );

	byte* out = ( byte* )malloc( pngWidth * pngHeight * 4 );

	ret.data = out;
	ret.w = pngWidth;
	ret.h = pngHeight;
	ret.format = 4;

	png_uint_32 rowBytes = png_get_rowbytes( pngPtr, infoPtr );

	png_bytep* rowPointers = ( png_bytep* ) malloc( sizeof( png_bytep ) * pngHeight );
	for( png_uint_32 row = 0; row < pngHeight; row++ )
	{
		rowPointers[row] = ( png_bytep )( out + ( row * pngWidth * 4 ) );
	}

	png_read_image( pngPtr, rowPointers );

	png_read_end( pngPtr, infoPtr );

	png_destroy_read_struct( &pngPtr, &infoPtr, NULL );

	free( rowPointers );

	return ret;
}

int main(int argc, char** argv)
{
	progName = argv[0];
	if(argc < 2)
	{
		printUsage();
		exit(1);
	}

	const char* filename = argv[1];
	
	byte*	fbuffer;
	byte*   readptr;
	int     len;
	{
		FILE* f = fopen(filename, "r");
		if(f == NULL)
		{
			eprintf("ERROR: Couldn't open %s!\n", filename);
			exit(1);
		}
		
		fseek(f, 0, SEEK_END);
		len = ftell(f);
		fseek(f, 0, SEEK_SET); // go back to start
		
		fbuffer = ( byte* )malloc( len + 4096 );
		byte* buf = fbuffer;
		int remaining = len;
		while(remaining)
		{
			int block = remaining;
			int read = fread( buf, 1, block, f );
			if(read < 0)
			{
				eprintf("Error while reading file!\n");
				exit(1);
			}
			remaining -= read;
			buf += read;
		}
		
	}
	
#define numIterations 100
	
	struct timespec before = {0};
	struct timespec after = {0};
	
	clock_gettime(CLOCK_MONOTONIC_RAW, &before);
	
	int i;
	for(i=0; i<numIterations; ++i)
	{
		struct image img = loadImage(filename, fbuffer, readptr, len);
		freeImage(&img);
	}
	
	clock_gettime(CLOCK_MONOTONIC_RAW, &after);
	
	int secs = after.tv_sec - before.tv_sec;
	int nsecs = after.tv_nsec - before.tv_nsec;
	
	if(nsecs < 0)
	{
		--secs;
		nsecs += 1000000000;
	}
	
	double ms = 1000.0*secs;
	ms += nsecs * 0.000001;
	printf("Decoding %s %d times took %fms => %fms avg\n", filename, numIterations, ms, ms/((float)numIterations));
	
	return 0;
}