Skip to content

Instantly share code, notes, and snippets.

@DanielGibson
Created July 18, 2015 02:30
Show Gist options
  • Save DanielGibson/eb322f8054c2dfef06a9 to your computer and use it in GitHub Desktop.
Save DanielGibson/eb322f8054c2dfef06a9 to your computer and use it in GitHub Desktop.
Hacky cmdline app that converts to png with several encoders for comparison
/*
* Encodes a given file to png using stb_image_write, miniz, lodepng and libpng
*
* Useful to compare the compression ratios achievable with the different encoders.
* Blog post with results: http://wp.me/pEPJ4-5U
*
* Needs:
* - stb_image.h and stb_image_write.h from https://github.com/nothings/stb
* - lodepng.c and lodepng.h from http://lodev.org/lodepng/
* - miniz.c from https://github.com/richgel999/miniz
* - libpng from http://libpng.org/ or your Linux distro or whatever (tested v1.2.50)
*
* Can be built with "gcc -o encPng encPng.c lodepng.c -lpng" (or use clang instead of gcc).
* Building with MSVC (as console application) is probably possible, but untested.
*
* writePngLibPng() is based on writeImage() from Dr. Andrew Greensted's makePNG.c
* (C) Dr. Andrew Greensted
* see http://www.labbookpages.co.uk/software/imgProc/libPNG.html#write
* http://www.labbookpages.co.uk/home/licences.html says I can use it as long as
* I mention him and his homepage, which I've hereby done :-)
*
* Rest of the code:
* (C) 2015 Daniel Gibson
*
* License (for everything except for writePngLibPng()):
* This software is in the public domain. Where that dedication is not
* recognized, you are granted a perpetual, irrevocable license to copy
* and modify this file however you want.
* No warranty implied; use at your own risk.
*/
#include <stdio.h>
#include <limits.h> // PATH_MAX
#ifndef PATH_MAX
// should be defined by limits.h, but maybe it's not on windows, not sure...
#define PATH_MAX 4096
#endif
#define STBI_NO_LINEAR // don't need HDR stuff
#define STBI_NO_HDR
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
const char* progName = "encPng";
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/converted/conv_bla\n", progName);
}
struct image
{
unsigned char* data;
int w;
int h;
int format; // 3: RGB, 4: RGBA
};
static struct image loadImage(const char* imgFileName)
{
struct image ret = {0};
int inforet = stbi_info(imgFileName, &ret.w, &ret.h, &ret.format);
if(!inforet)
{
eprintf("ERROR: Couldn't load image file %s: %s!\n", imgFileName, stbi_failure_reason());
exit(1);
}
int bppToUse = 4;
// no alpha => use RGB, else use RGBA
if(ret.format == 1 || ret.format == 3) bppToUse = 3;
ret.data = stbi_load(imgFileName, &ret.w, &ret.h, &ret.format, bppToUse);
if(ret.data == NULL)
{
eprintf("ERROR: Couldn't load image file %s: %s!\n", imgFileName, stbi_failure_reason());
exit(1);
}
ret.format = bppToUse;
return ret;
}
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
static void writePngStb(struct image img, const char* outFilePrefix)
{
char outFileBuf[PATH_MAX];
strcpy(outFileBuf, outFilePrefix);
strcat(outFileBuf, "_stb.png");
stbi_write_png(outFileBuf, img.w, img.h, img.format, img.data, 0);
}
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES // prevent conflict with libpng/zlib
#include "miniz.c"
static void writePngMiniz(struct image img, const char* outFilePrefix)
{
char outFileBuf[PATH_MAX];
strcpy(outFileBuf, outFilePrefix);
strcat(outFileBuf, "_miniz.png");
size_t outLen = 0;
int comprLevel = MZ_BEST_COMPRESSION; // MZ_DEFAULT_LEVEL
void* data = tdefl_write_image_to_png_file_in_memory_ex(img.data, img.w, img.h, img.format, &outLen, comprLevel, 0);
if(data == NULL)
{
eprintf("WTF, miniz's png compression failed!\n");
return;
}
FILE* f = fopen(outFileBuf, "wb");
if(f == NULL)
{
eprintf("WTF, couldn't open outfile: %s\n", outFileBuf);
return;
}
fwrite(data, outLen, 1, f);
fclose(f);
}
#include "lodepng.h"
static void writePngLode(struct image img, const char* outFilePrefix)
{
char outFileBuf[PATH_MAX];
strcpy(outFileBuf, outFilePrefix);
strcat(outFileBuf, "_lode.png");
LodePNGColorType colortype = LCT_RGB;
if(img.format == STBI_rgb_alpha) colortype = LCT_RGBA;
lodepng_encode_file(outFileBuf, img.data, img.w, img.h, colortype, 8);
}
#include <libpng/png.h>
// the following function is based on writeImage() from A. Greensted's makePNG.c
// see http://www.labbookpages.co.uk/software/imgProc/libPNG.html#write
// !! this function is *not* in the public domain, but can still be freely used !!
// !! see http://www.labbookpages.co.uk/home/licences.html !!
static void writePngLibPng(struct image img, const char* outFilePrefix)
{
char outFileBuf[PATH_MAX];
strcpy(outFileBuf, outFilePrefix);
strcat(outFileBuf, "_libPng.png");
if(img.format != 3) {
// support for RGBA shouldn't be hard, but it's an additional case I don't care about right now
eprintf("the libpng code only supports RGB, not RGBA!\n");
return;
}
int width = img.w, height = img.h;
int code = 0;
// DG: with all the gotos, we should initialize things!
FILE *fp = NULL;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
png_bytep row = NULL;
// Open file for writing (binary mode)
fp = fopen(outFileBuf, "wb");
if (fp == NULL) {
fprintf(stderr, "Could not open file %s for writing\n", outFileBuf);
code = 1;
goto finalise;
}
// Initialize write structure
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (png_ptr == NULL) {
fprintf(stderr, "Could not allocate write struct\n");
code = 1;
goto finalise;
}
// DG: set highest compression level
png_set_compression_level(png_ptr, 9);
// Initialize info structure
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
fprintf(stderr, "Could not allocate info struct\n");
code = 1;
goto finalise;
}
// Setup Exception handling
if (setjmp(png_jmpbuf(png_ptr))) {
fprintf(stderr, "Error during png creation\n");
code = 1;
goto finalise;
}
png_init_io(png_ptr, fp);
// Write header (8 bit colour depth)
png_set_IHDR(png_ptr, info_ptr, width, height,
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
// Set title
/*
if (title != NULL) {
png_text title_text;
title_text.compression = PNG_TEXT_COMPRESSION_NONE;
title_text.key = "Title";
title_text.text = title;
png_set_text(png_ptr, info_ptr, &title_text, 1);
}
*/
png_write_info(png_ptr, info_ptr);
// Write image data
int y;
for (y=0 ; y<height ; y++) {
row = img.data + y*img.w*3;
png_write_row(png_ptr, row);
}
// End write
png_write_end(png_ptr, NULL);
finalise:
if (fp != NULL) fclose(fp);
if (info_ptr != NULL) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
}
int main(int argc, char** argv)
{
progName = argv[0];
if(argc < 2)
{
printUsage();
exit(1);
}
const char* fileName = argv[1];
struct image img = loadImage(fileName);
char outFileBuf[PATH_MAX];
const char* outFilePrefix = outFileBuf;
if(argc > 2)
{
outFilePrefix = argv[2];
}
else
{
strncpy(outFileBuf, fileName, PATH_MAX);
outFileBuf[PATH_MAX-1] = '\0';
char* ext = strrchr(outFileBuf, '.');
if(ext == NULL)
{
eprintf("ERROR: Image file %s has no file extension that could be replaced with .c!\n", fileName);
exit(1);
}
*ext = '\0';
}
writePngStb(img, outFilePrefix);
writePngMiniz(img, outFilePrefix);
writePngLode(img, outFilePrefix);
writePngLibPng(img, outFilePrefix);
stbi_image_free(img.data);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment