Documentation Buy Contact Blog
Blog

How to use libJpeg in a C++ project

9 October 2013

Altho I do love how simple to integrate and use stb_image is when I can control the input images, I needed something a bit more heavy duty for Number Duck. This is because users will be supplying their own images, and they could submit something a bit more exotic than stb_image is able to handle. Things like interlaced and CMYK JPEGs.

Supposedly you should be using cmake or something to create the project files, but I wanted to quickly and simply get the code building under my own project. All in all it's pretty simple, it mainly involves grabbing the latest source release, configuring and pruning the unneeded files. Read on to for the full instructions, a simple example using libJpeg and the pre pruned library itself if you are feeling lazy.

I'm sure this is not the most optimal setup and that it could be more efficient on each platform with the correct configs, but it works for me and gets the job done.

Firstly, grab the source. Make sure you download from www.ijg.org and not libjpeg.sourceforge.net, even tho it's the first hit on Google, the Source Forge page hasn't been updated since 1998. Amusingly (or not if you downloaded from the wrong one!) both sites look the same even tho they are 15 years apart. Times New Roman on white never goes out of style for the hardcore programming crowd.

On Windows, you'll get some warnings about deprecated functions. Since I have warnings as errors, I disabled these with the compiler flag "/wd4996". Also with Win64, there are some warnings about converting size_t to long and size_t to unsigned long. Altho it might have been ok to just change the type of total_space_allocated and outsize to size_t, I went with manually casting every place where the error came up, as that seemed safer.

The default error handler will call exit(), which isn't so great as loading a borked image will kill your app. So we'll need to override the error handler to be able to gracefully return when things go bad. Check the ErrorExit function below, it uses some wacky jump codes to return control to the main function. Also check the OutputMessage function where I silence any errors that would normally get printed to stderr.

The full sample code using libJpeg is below, and in the download, along with the pruned libJpeg. Version 9 to be exact.

JpegLoader.h
#ifndef JPEG_LOADER_H
#define JPEG_LOADER_H
	#include <stdio.h>
	#include "LibJpeg/jpeglib.h"
	#include <setjmp.h>
	#include <stdint.h>
	class JpegLoader
	{
		public:
			struct ImageInfo
			{
				uint32_t nWidth;
				uint32_t nHeight;
				uint8_t nNumComponent;
				uint8_t* pData;
			};

			JpegLoader();
			~JpegLoader();

			const ImageInfo* Load(const char* szFileName);

		private:
			ImageInfo* m_pImageInfo;
			void Cleanup();

			struct ErrorManager
			{
				jpeg_error_mgr defaultErrorManager;
				jmp_buf jumpBuffer;
			};

			static void ErrorExit(j_common_ptr cinfo);
			static void OutputMessage(j_common_ptr cinfo);
	};
#endif
JpegLoader.cpp
#include "JpegLoader.h"

JpegLoader :: JpegLoader()
{
	m_pImageInfo = NULL;
}

JpegLoader :: ~JpegLoader()
{
	Cleanup();
}

const JpegLoader :: ImageInfo* JpegLoader :: Load(const char* szFileName)
{
	Cleanup();
	
	jpeg_decompress_struct cinfo;
	ErrorManager errorManager;
	
	FILE* pFile = fopen(szFileName, "rb");
	if (!pFile)
		return NULL;

	// set our custom error handler
	cinfo.err = jpeg_std_error(&errorManager.defaultErrorManager);
	errorManager.defaultErrorManager.error_exit = ErrorExit;
	errorManager.defaultErrorManager.output_message = OutputMessage;
	if (setjmp(errorManager.jumpBuffer))
	{
		// We jump here on errorz
		Cleanup();
		jpeg_destroy_decompress(&cinfo);
		fclose(pFile);
		return NULL;
	}

	jpeg_create_decompress(&cinfo);
	jpeg_stdio_src(&cinfo, pFile);
	jpeg_read_header(&cinfo, TRUE);
	jpeg_start_decompress(&cinfo);

	m_pImageInfo = new ImageInfo();
	m_pImageInfo->nWidth = cinfo.image_width;
	m_pImageInfo->nHeight = cinfo.image_height;
	m_pImageInfo->nNumComponent = cinfo.num_components;
	m_pImageInfo->pData = new uint8_t[m_pImageInfo->nWidth*m_pImageInfo->nHeight*m_pImageInfo->nNumComponent];

	while(cinfo.output_scanline < cinfo.image_height)
	{
		uint8_t* p = m_pImageInfo->pData + cinfo.output_scanline*cinfo.image_width*cinfo.num_components;
		jpeg_read_scanlines(&cinfo, &p, 1);
	}
	
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fclose(pFile);

	return m_pImageInfo;
}

void JpegLoader :: Cleanup()
{
	if (m_pImageInfo)
	{
		delete [] m_pImageInfo->pData;
		delete m_pImageInfo;
		m_pImageInfo = NULL;
	}
}

void JpegLoader :: ErrorExit(j_common_ptr cinfo)
{
	// cinfo->err is actually a pointer to my_error_mgr.defaultErrorManager, since pub
	// is the first element of my_error_mgr we can do a sneaky cast
	ErrorManager* pErrorManager = (ErrorManager*) cinfo->err;
	(*cinfo->err->output_message)(cinfo); // print error message (actually disabled below)
	longjmp(pErrorManager->jumpBuffer, 1);
}


void JpegLoader :: OutputMessage(j_common_ptr cinfo)
{
	// disable error messages
	/*char buffer[JMSG_LENGTH_MAX];
	(*cinfo->err->format_message) (cinfo, buffer);
	fprintf(stderr, "%s\n", buffer);*/
}