Skip to content

Instantly share code, notes, and snippets.

@DanielGibson
Last active August 29, 2015 14:17
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save DanielGibson/e0828acfc90f619198cb to your computer and use it in GitHub Desktop.
Crappy benchmark for stb_image, lodepng, libjpeg and libpng
// The Results: http://wp.me/pEPJ4-4S
// you can define USE_LIBPNG to decode with libpng XOR USE_LODEPNG to decode with lodepng
// XOR USE_LIBJPEG to decode with libjpeg or libjpeg-turbo XOR nothing to use stb_image.
// you'll have to provide the corresponding headers and (for libjpeg and libpng) libs to link against
// for lodepng also compile lodepng.c into the binary
// ex: gcc -O4 -DUSE_LODEPNG imgLoadBench.c lodepng.c -o imgLoadBench_lodepng_gcc_O4
// or: gcc -O0 -DUSE_LIBPNG imgLoadBench.c -o imgLoadBench_libpng_gcc_O0 -lpng
// or: gcc -O2 -DUSE_LIBJPEG imgLoadBench.c -o imgLoadBench_libjpeg_gcc_O2 -ljpeg
// or: clang -O3 imgLoadBench.c -o imgLoadBench_stb_clang_O3
/*
===========================================================================
The libpng png decoding and libjpeg jpg decoding code was adapted from RBDoom3BFG:
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.
The stb_image and lodepng jpeg and png decoding code was written by myself
and is so trivial that it doesn't deserve a Copyright.
===========================================================================
*/
#include <stdio.h>
#include <limits.h> // PATH_MAX
#include <time.h>
#include <stdlib.h>
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
const char* progName = "imgLoadBench";
typedef unsigned char byte;
static void printUsage()
{
eprintf("Usage: %s <imgname> [outfilename]\n", progName);
eprintf(" e.g.: %s test.png\n", progName);
eprintf(" %s /path/to/bla.tga /other/path/to/bla.h\n", progName);
}
struct image
{
unsigned char* data;
int w;
int h;
int format; // 3: RGB, 4: RGBA
};
#ifdef USE_LIBPNG
// the libpng png loading code is stolen from rbdoom3bfg's LoadPNG()
static void freeImage(struct image* img)
{
free(img->data);
img->data = NULL;
}
#include <png.h>
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 )
{
memcpy( data, ( byte* )pngPtr->io_ptr, length );
pngPtr->io_ptr = ( ( byte* ) pngPtr->io_ptr ) + length;
}
static struct image loadImage(const char* filename, byte* fbuffer, 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);
}
png_set_read_fn( pngPtr, fbuffer, 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;
}
#elif defined(USE_LODEPNG)
// the lodepng decoding code is written from scratch
#include "lodepng.h"
static void freeImage(struct image* img)
{
free(img->data);
img->data = NULL;
}
static struct image loadImage(const char* filename, byte* fbuffer, int len)
{
struct image ret = {0};
byte* image;
unsigned w, h;
int error = lodepng_decode32(&image, &w, &h, fbuffer, len);
if(error) {
eprintf("ERROR: Couldn't load image file %s: %s!\n", filename, lodepng_error_text(error));
exit(1);
}
ret.data = image;
ret.w = w;
ret.h = h;
ret.format = 4;
return ret;
}
#elif defined(USE_LIBJPEG)
// the libjpeg jpeg loading code is stolen from doom3's LoadJPG()
#include <jpeglib.h>
static void freeImage(struct image* img)
{
free(img->data);
img->data = NULL;
}
static struct image loadImage(const char* filename, byte* fbuffer, int len)
{
struct image ret = {0};
/* This struct contains the JPEG decompression parameters and pointers to
* working space (which is allocated as needed by the JPEG library).
*/
struct jpeg_decompress_struct cinfo;
/* We use our private extension JPEG error handler.
* Note that this struct must live as long as the main JPEG parameter
* struct, to avoid dangling-pointer problems.
*/
/* This struct represents a JPEG error handler. It is declared separately
* because applications often want to supply a specialized error handler
* (see the second half of this file for an example). But here we just
* take the easy way out and use the standard error handler, which will
* print a message on stderr and call exit() if compression fails.
* Note that this struct must live as long as the main JPEG parameter
* struct, to avoid dangling-pointer problems.
*/
struct jpeg_error_mgr jerr;
/* More stuff */
JSAMPARRAY buffer; /* Output row buffer */
int row_stride; /* physical row width in output buffer */
unsigned char* out;
byte* bbuf;
/* Step 1: allocate and initialize JPEG decompression object */
/* We have to set up the error handler first, in case the initialization
* step fails. (Unlikely, but it could happen if you are out of memory.)
* This routine fills in the contents of struct jerr, and returns jerr's
* address which we place into the link field in cinfo.
*/
cinfo.err = jpeg_std_error( &jerr );
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress( &cinfo );
/* Step 2: specify data source (eg, a file) */
jpeg_mem_src( &cinfo, fbuffer, len ); // DG: this looks better.
/* Step 3: read file parameters with jpeg_read_header() */
jpeg_read_header( &cinfo, 1 );
/* We can ignore the return value from jpeg_read_header since
* (a) suspension is not possible with the stdio data source, and
* (b) we passed TRUE to reject a tables-only JPEG file as an error.
* See libjpeg.doc for more info.
*/
/* Step 4: set parameters for decompression */
/* In this example, we don't need to change any of the defaults set by
* jpeg_read_header(), so we do nothing here.
*/
/* Step 5: Start decompressor */
jpeg_start_decompress( &cinfo );
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* We may need to do some setup of our own at this point before reading
* the data. After jpeg_start_decompress() we have the correct scaled
* output image dimensions available, as well as the output colormap
* if we asked for color quantization.
* In this example, we need to make an output work buffer of the right size.
*/
/* JSAMPLEs per row in output buffer */
row_stride = cinfo.output_width * cinfo.output_components;
if( 0 && cinfo.output_components != 4 ) // XXX
{
eprintf( "JPG %s is unsupported color depth (%d)",
filename, cinfo.output_components );
}
out = ( byte* )malloc( cinfo.output_width * cinfo.output_height * 4 );
ret.data = out;
ret.w = cinfo.output_width;
ret.h = cinfo.output_height;
/* Step 6: while (scan lines remain to be read) */
/* jpeg_read_scanlines(...); */
/* Here we use the library's state variable cinfo.output_scanline as the
* loop counter, so that we don't have to keep track ourselves.
*/
while( cinfo.output_scanline < cinfo.output_height )
{
/* jpeg_read_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could ask for
* more than one scanline at a time if that's more convenient.
*/
bbuf = ( ( out + ( row_stride * cinfo.output_scanline ) ) );
buffer = &bbuf;
jpeg_read_scanlines( &cinfo, buffer, 1 );
}
// clear all the alphas to 255
{
int i, j;
byte* buf;
buf = ret.data;
j = cinfo.output_width * cinfo.output_height * 4;
for( i = 3 ; i < j ; i += 4 )
{
buf[i] = 255;
}
}
/* Step 7: Finish decompression */
jpeg_finish_decompress( &cinfo );
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* Step 8: Release JPEG decompression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_decompress( &cinfo );
return ret;
}
#else // STB
// the stb_image decoding code is written from scratch
#define STBI_NO_LINEAR // don't need HDR stuff
#define STBI_NO_HDR
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
static void freeImage(struct image* img)
{
stbi_image_free(img->data);
img->data = NULL;
}
static struct image loadImage(const char* imgFileName, byte* fbuffer, int len)
{
struct image ret = {0};
int format;
ret.data = stbi_load_from_memory(fbuffer, len, &ret.w, &ret.h, &format, 4);
if(ret.data == NULL)
{
eprintf("ERROR: Couldn't load image file %s: %s!\n", imgFileName, stbi_failure_reason());
exit(1);
}
ret.format = 4;
return ret;
}
#endif // USE_STB
int main(int argc, char** argv)
{
progName = argv[0];
if(argc < 2)
{
printUsage();
exit(1);
}
const char* filename = argv[1];
byte* fbuffer;
int len;
// the "load file into buffer code" is stolen from doom3's LoadJPG idFileSystem code (because I was lazy)
{
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("WTF, error while reading file!\n");
exit(1);
}
remaining -= read;
buf += read;
}
}
// I wrote the following code myself
#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, 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment