Files
panopainter/libs/jpeg/tga2jpg.cpp

534 lines
18 KiB
C++

// tga2jpg.cpp - jpge/jpgd example command line app.
// Public domain, Rich Geldreich <richgel99@gmail.com>
// Last updated May. 19, 2012
// Note: jpge.cpp/h and jpgd.cpp/h are completely standalone, i.e. they do not have any dependencies to each other.
#include "jpge.h"
#include "jpgd.h"
#include "stb_image.c"
#include "timer.h"
#include <ctype.h>
#if defined(_MSC_VER)
#define strcasecmp _stricmp
#else
#define strcpy_s(d, c, s) strcpy(d, s)
#endif
static int print_usage()
{
printf("Usage: jpge [options] <source_file> <dest_file> <quality_factor>\n");
printf("\nRequired parameters (must follow options):\n");
printf("source_file: Source image file, in any format stb_image.c supports.\n");
printf("dest_file: Destination JPEG file.\n");
printf("quality_factor: 1-100, higher=better (only needed in compression mode)\n");
printf("\nDefault mode compresses source_file to dest_file. Alternate modes:\n");
printf("-x: Exhaustive compression test (only needs source_file)\n");
printf("-d: Test jpgd.h. source_file must be JPEG, and dest_file must be .TGA\n");
printf("\nOptions supported in all modes:\n");
printf("-glogfilename.txt: Append output to log file\n");
printf("\nOptions supported in compression mode (the default):\n");
printf("-o: Enable optimized Huffman tables (slower, but smaller files)\n");
printf("-luma: Output Y-only image\n");
printf("-h1v1, -h2v1, -h2v2: Chroma subsampling (default is either Y-only or H2V2)\n");
printf("-m: Test mem to mem compression (instead of mem to file)\n");
printf("-wfilename.tga: Write decompressed image to filename.tga\n");
printf("-s: Use stb_image.c to decompress JPEG image, instead of jpgd.cpp\n");
printf("\nExample usages:\n");
printf("Test compression: jpge orig.png comp.jpg 90\n");
printf("Test decompression: jpge -d comp.jpg uncomp.tga\n");
printf("Exhaustively test compressor: jpge -x orig.png\n");
return EXIT_FAILURE;
}
static char s_log_filename[256];
static void log_printf(const char *pMsg, ...)
{
va_list args;
va_start(args, pMsg);
char buf[2048];
vsnprintf(buf, sizeof(buf) - 1, pMsg, args);
buf[sizeof(buf) - 1] = '\0';
va_end(args);
printf("%s", buf);
if (s_log_filename[0])
{
FILE *pFile = fopen(s_log_filename, "a+");
if (pFile)
{
fprintf(pFile, "%s", buf);
fclose(pFile);
}
}
}
static uint get_file_size(const char *pFilename)
{
FILE *pFile = fopen(pFilename, "rb");
if (!pFile) return 0;
fseek(pFile, 0, SEEK_END);
uint file_size = ftell(pFile);
fclose(pFile);
return file_size;
}
struct image_compare_results
{
image_compare_results() { memset(this, 0, sizeof(*this)); }
double max_err;
double mean;
double mean_squared;
double root_mean_squared;
double peak_snr;
};
static void get_pixel(int* pDst, const uint8 *pSrc, bool luma_only, int num_comps)
{
int r, g, b;
if (num_comps == 1)
{
r = g = b = pSrc[0];
}
else if (luma_only)
{
const int YR = 19595, YG = 38470, YB = 7471;
r = g = b = (pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) / 65536;
}
else
{
r = pSrc[0]; g = pSrc[1]; b = pSrc[2];
}
pDst[0] = r; pDst[1] = g; pDst[2] = b;
}
// Compute image error metrics.
static void image_compare(image_compare_results &results, int width, int height, const uint8 *pComp_image, int comp_image_comps, const uint8 *pUncomp_image_data, int uncomp_comps, bool luma_only)
{
double hist[256];
memset(hist, 0, sizeof(hist));
const uint first_channel = 0, num_channels = 3;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int a[3]; get_pixel(a, pComp_image + (y * width + x) * comp_image_comps, luma_only, comp_image_comps);
int b[3]; get_pixel(b, pUncomp_image_data + (y * width + x) * uncomp_comps, luma_only, uncomp_comps);
for (uint c = 0; c < num_channels; c++)
hist[labs(a[first_channel + c] - b[first_channel + c])]++;
}
}
results.max_err = 0;
double sum = 0.0f, sum2 = 0.0f;
for (uint i = 0; i < 256; i++)
{
if (!hist[i])
continue;
if (i > results.max_err)
results.max_err = i;
double x = i * hist[i];
sum += x;
sum2 += i * x;
}
// See http://bmrc.berkeley.edu/courseware/cs294/fall97/assignment/psnr.html
double total_values = width * height;
results.mean = sum / total_values;
results.mean_squared = sum2 / total_values;
results.root_mean_squared = sqrt(results.mean_squared);
if (!results.root_mean_squared)
results.peak_snr = 1e+10f;
else
results.peak_snr = log10(255.0f / results.root_mean_squared) * 20.0f;
}
// Simple exhaustive test. Tries compressing/decompressing image using all supported quality, subsampling, and Huffman optimization settings.
static int exhausive_compression_test(const char *pSrc_filename, bool use_jpgd)
{
int status = EXIT_SUCCESS;
// Load the source image.
const int req_comps = 3; // request RGB image
int width = 0, height = 0, actual_comps = 0;
uint8 *pImage_data = stbi_load(pSrc_filename, &width, &height, &actual_comps, req_comps);
if (!pImage_data)
{
log_printf("Failed loading file \"%s\"!\n", pSrc_filename);
return EXIT_FAILURE;
}
log_printf("Source file: \"%s\" Image resolution: %ix%i Actual comps: %i\n", pSrc_filename, width, height, actual_comps);
int orig_buf_size = width * height * 3; // allocate a buffer that's hopefully big enough (this is way overkill for jpeg)
if (orig_buf_size < 1024) orig_buf_size = 1024;
void *pBuf = malloc(orig_buf_size);
uint8 *pUncomp_image_data = NULL;
double max_err = 0;
double lowest_psnr = 9e+9;
double threshold_psnr = 9e+9;
double threshold_max_err = 0.0f;
image_compare_results prev_results;
for (uint quality_factor = 1; quality_factor <= 100; quality_factor++)
{
for (uint subsampling = 0; subsampling <= jpge::H2V2; subsampling++)
{
for (uint optimize_huffman_tables = 0; optimize_huffman_tables <= 1; optimize_huffman_tables++)
{
// Fill in the compression parameter structure.
jpge::params params;
params.m_quality = quality_factor;
params.m_subsampling = static_cast<jpge::subsampling_t>(subsampling);
params.m_two_pass_flag = (optimize_huffman_tables != 0);
int comp_size = orig_buf_size;
if (!jpge::compress_image_to_jpeg_file_in_memory(pBuf, comp_size, width, height, req_comps, pImage_data, params))
{
status = EXIT_FAILURE;
goto failure;
}
int uncomp_width = 0, uncomp_height = 0, uncomp_actual_comps = 0, uncomp_req_comps = 3;
free(pUncomp_image_data);
if (use_jpgd)
pUncomp_image_data = jpgd::decompress_jpeg_image_from_memory((const stbi_uc*)pBuf, comp_size, &uncomp_width, &uncomp_height, &uncomp_actual_comps, uncomp_req_comps);
else
pUncomp_image_data = stbi_load_from_memory((const stbi_uc*)pBuf, comp_size, &uncomp_width, &uncomp_height, &uncomp_actual_comps, uncomp_req_comps);
if (!pUncomp_image_data)
{
status = EXIT_FAILURE;
goto failure;
}
if ((uncomp_width != width) || (uncomp_height != height))
{
status = EXIT_FAILURE;
goto failure;
}
image_compare_results results;
image_compare(results, width, height, pImage_data, req_comps, pUncomp_image_data, uncomp_req_comps, (params.m_subsampling == jpge::Y_ONLY) || (actual_comps == 1) || (uncomp_actual_comps == 1));
//log_printf("Q: %3u, S: %u, O: %u, CompSize: %7u, Error Max: %3.3f, Mean: %3.3f, Mean^2: %5.3f, RMSE: %3.3f, PSNR: %3.3f\n", quality_factor, subsampling, optimize_huffman_tables, comp_size, results.max_err, results.mean, results.mean_squared, results.root_mean_squared, results.peak_snr);
log_printf("%3u, %u, %u, %7u, %3.3f, %3.3f, %5.3f, %3.3f, %3.3f\n", quality_factor, subsampling, optimize_huffman_tables, comp_size, results.max_err, results.mean, results.mean_squared, results.root_mean_squared, results.peak_snr);
if (results.max_err > max_err) max_err = results.max_err;
if (results.peak_snr < lowest_psnr) lowest_psnr = results.peak_snr;
if (quality_factor == 1)
{
if (results.peak_snr < threshold_psnr)
threshold_psnr = results.peak_snr;
if (results.max_err > threshold_max_err)
threshold_max_err = results.max_err;
}
else
{
// Couple empirically determined tests - worked OK on my test data set.
if ((results.peak_snr < (threshold_psnr - 3.0f)) || (results.peak_snr < 6.0f))
{
status = EXIT_FAILURE;
goto failure;
}
if (optimize_huffman_tables)
{
if ((prev_results.max_err != results.max_err) || (prev_results.peak_snr != results.peak_snr))
{
status = EXIT_FAILURE;
goto failure;
}
}
}
prev_results = results;
}
}
}
log_printf("Max error: %f Lowest PSNR: %f\n", max_err, lowest_psnr);
failure:
free(pImage_data);
free(pBuf);
free(pUncomp_image_data);
log_printf((status == EXIT_SUCCESS) ? "Success.\n" : "Exhaustive test failed!\n");
return status;
}
// Test JPEG file decompression using jpgd.h
static int test_jpgd(const char *pSrc_filename, const char *pDst_filename)
{
// Load the source JPEG image.
const int req_comps = 3; // request RGB image
int width = 0, height = 0, actual_comps = 0;
timer tm;
tm.start();
uint8 *pImage_data = jpgd::decompress_jpeg_image_from_file(pSrc_filename, &width, &height, &actual_comps, req_comps);
tm.stop();
if (!pImage_data)
{
log_printf("Failed loading JPEG file \"%s\"!\n", pSrc_filename);
return EXIT_FAILURE;
}
log_printf("Source JPEG file: \"%s\", image resolution: %ix%i, actual comps: %i\n", pSrc_filename, width, height, actual_comps);
log_printf("Decompression time: %3.3fms\n", tm.get_elapsed_ms());
if (!stbi_write_tga(pDst_filename, width, height, req_comps, pImage_data))
{
log_printf("Failed writing image to file \"%s\"!\n", pDst_filename);
free(pImage_data);
return EXIT_FAILURE;
}
log_printf("Wrote decompressed image to TGA file \"%s\"\n", pDst_filename);
log_printf("Success.\n");
free(pImage_data);
return EXIT_SUCCESS;
}
int main(int arg_c, char* ppArgs[])
{
printf("jpge/jpgd example app\n");
// Parse command line.
bool run_exhausive_test = false;
bool test_memory_compression = false;
bool optimize_huffman_tables = false;
int subsampling = -1;
char output_filename[256] = "";
bool use_jpgd = true;
bool test_jpgd_decompression = false;
int arg_index = 1;
while ((arg_index < arg_c) && (ppArgs[arg_index][0] == '-'))
{
switch (tolower(ppArgs[arg_index][1]))
{
case 'd':
test_jpgd_decompression = true;
break;
case 'g':
strcpy_s(s_log_filename, sizeof(s_log_filename), &ppArgs[arg_index][2]);
break;
case 'x':
run_exhausive_test = true;
break;
case 'm':
test_memory_compression = true;
break;
case 'o':
optimize_huffman_tables = true;
break;
case 'l':
if (strcasecmp(&ppArgs[arg_index][1], "luma") == 0)
subsampling = jpge::Y_ONLY;
else
{
log_printf("Unrecognized option: %s\n", ppArgs[arg_index]);
return EXIT_FAILURE;
}
break;
case 'h':
if (strcasecmp(&ppArgs[arg_index][1], "h1v1") == 0)
subsampling = jpge::H1V1;
else if (strcasecmp(&ppArgs[arg_index][1], "h2v1") == 0)
subsampling = jpge::H2V1;
else if (strcasecmp(&ppArgs[arg_index][1], "h2v2") == 0)
subsampling = jpge::H2V2;
else
{
log_printf("Unrecognized subsampling: %s\n", ppArgs[arg_index]);
return EXIT_FAILURE;
}
break;
case 'w':
{
strcpy_s(output_filename, sizeof(output_filename), &ppArgs[arg_index][2]);
break;
}
case 's':
{
use_jpgd = false;
break;
}
default:
log_printf("Unrecognized option: %s\n", ppArgs[arg_index]);
return EXIT_FAILURE;
}
arg_index++;
}
if (run_exhausive_test)
{
if ((arg_c - arg_index) < 1)
{
log_printf("Not enough parameters (expected source file)\n");
return print_usage();
}
const char* pSrc_filename = ppArgs[arg_index++];
return exhausive_compression_test(pSrc_filename, use_jpgd);
}
else if (test_jpgd_decompression)
{
if ((arg_c - arg_index) < 2)
{
log_printf("Not enough parameters (expected source and destination files)\n");
return print_usage();
}
const char* pSrc_filename = ppArgs[arg_index++];
const char* pDst_filename = ppArgs[arg_index++];
return test_jpgd(pSrc_filename, pDst_filename);
}
// Test jpge
if ((arg_c - arg_index) < 3)
{
log_printf("Not enough parameters (expected source file, dest file, quality factor to follow options)\n");
return print_usage();
}
const char* pSrc_filename = ppArgs[arg_index++];
const char* pDst_filename = ppArgs[arg_index++];
int quality_factor = atoi(ppArgs[arg_index++]);
if ((quality_factor < 1) || (quality_factor > 100))
{
log_printf("Quality factor must range from 1-100!\n");
return EXIT_FAILURE;
}
// Load the source image.
const int req_comps = 3; // request RGB image
int width = 0, height = 0, actual_comps = 0;
uint8 *pImage_data = stbi_load(pSrc_filename, &width, &height, &actual_comps, req_comps);
if (!pImage_data)
{
log_printf("Failed loading file \"%s\"!\n", pSrc_filename);
return EXIT_FAILURE;
}
log_printf("Source file: \"%s\", image resolution: %ix%i, actual comps: %i\n", pSrc_filename, width, height, actual_comps);
// Fill in the compression parameter structure.
jpge::params params;
params.m_quality = quality_factor;
params.m_subsampling = (subsampling < 0) ? ((actual_comps == 1) ? jpge::Y_ONLY : jpge::H2V2) : static_cast<jpge::subsampling_t>(subsampling);
params.m_two_pass_flag = optimize_huffman_tables;
log_printf("Writing JPEG image to file: %s\n", pDst_filename);
timer tm;
// Now create the JPEG file.
if (test_memory_compression)
{
int buf_size = width * height * 3; // allocate a buffer that's hopefully big enough (this is way overkill for jpeg)
if (buf_size < 1024) buf_size = 1024;
void *pBuf = malloc(buf_size);
tm.start();
if (!jpge::compress_image_to_jpeg_file_in_memory(pBuf, buf_size, width, height, req_comps, pImage_data, params))
{
log_printf("Failed creating JPEG data!\n");
return EXIT_FAILURE;
}
tm.stop();
FILE *pFile = fopen(pDst_filename, "wb");
if (!pFile)
{
log_printf("Failed creating file \"%s\"!\n", pDst_filename);
return EXIT_FAILURE;
}
if (fwrite(pBuf, buf_size, 1, pFile) != 1)
{
log_printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
if (fclose(pFile) == EOF)
{
log_printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
}
else
{
tm.start();
if (!jpge::compress_image_to_jpeg_file(pDst_filename, width, height, req_comps, pImage_data, params))
{
log_printf("Failed writing to output file!\n");
return EXIT_FAILURE;
}
tm.stop();
}
double total_comp_time = tm.get_elapsed_ms();
const uint comp_file_size = get_file_size(pDst_filename);
const uint total_pixels = width * height;
log_printf("Compressed file size: %u, bits/pixel: %3.3f\n", comp_file_size, (comp_file_size * 8.0f) / total_pixels);
// Now try loading the JPEG file using jpgd or stbi_image's JPEG decompressor.
int uncomp_width = 0, uncomp_height = 0, uncomp_actual_comps = 0, uncomp_req_comps = 3;
tm.start();
uint8 *pUncomp_image_data;
if (use_jpgd)
pUncomp_image_data = jpgd::decompress_jpeg_image_from_file(pDst_filename, &uncomp_width, &uncomp_height, &uncomp_actual_comps, uncomp_req_comps);
else
pUncomp_image_data = stbi_load(pDst_filename, &uncomp_width, &uncomp_height, &uncomp_actual_comps, uncomp_req_comps);
double total_uncomp_time = tm.get_elapsed_ms();
if (!pUncomp_image_data)
{
log_printf("Failed loading compressed image file \"%s\"!\n", pDst_filename);
return EXIT_FAILURE;
}
log_printf("Compression time: %3.3fms, Decompression time: %3.3fms\n", total_comp_time, total_uncomp_time);
// Write uncompressed image.
if (output_filename[0])
stbi_write_tga(output_filename, uncomp_width, uncomp_height, uncomp_req_comps, pUncomp_image_data);
if ((uncomp_width != width) || (uncomp_height != height))
{
log_printf("Loaded JPEG file has a different resolution than the original file!\n");
return EXIT_FAILURE;
}
// Diff the original and compressed images.
image_compare_results results;
image_compare(results, width, height, pImage_data, req_comps, pUncomp_image_data, uncomp_req_comps, (params.m_subsampling == jpge::Y_ONLY) || (actual_comps == 1) || (uncomp_actual_comps == 1));
log_printf("Error Max: %f, Mean: %f, Mean^2: %f, RMSE: %f, PSNR: %f\n", results.max_err, results.mean, results.mean_squared, results.root_mean_squared, results.peak_snr);
log_printf("Success.\n");
return EXIT_SUCCESS;
}