2014-01-22

Detecting errors in jpeg file using libjpeg-turbo

Recently I have the need to detect the integrity of a jpeg file. First I try to see the famous libjpeg (wiki) library can do the job. I am using Visual Studio (i.e. windows) to write my codes and I download the win32 version of libjpeg from internet. However, I keep on failure because the win32 libjpeg library keeps on emitting "Access Violation" at runtime. I try to solve the problem by googling solution but in vain.

Later I turn to use libjpeg-turbo although I really do not need to performance of SIMD instructions. Luckily libjpeg-turbo has a Visual Studio version of static library and I do to encounter the runtime error in libjpeg any more.

Basically my codes should be simply, as illustrated by the following pseudo-codes:

jpeg_create_decompress();
fopen(jpeg_file);
jpeg_stdio_src();
jpeg_read_header();
jpeg_start_decompress();
while loop {
  jpeg_read_scanlines();
  }
jpeg_finish_decompress();
jpeg_destroy_decompress();
fclose();

However, to my surprise, libjpeg use a setjmp/longjmp approach to detect and handle the errors. Frankly speaking, although I have been using C languages for years, I really do not like the GOTO style of handling errors. The libjpeg is emulating an Object-oriented approach by allowing programmer to build an error hander to intercept the errors.

I am interested in two kinds of errors:
  • Fatal errors: the library will quit in this case
  • Warnings: the library can continue with data corruption
The former method is called "error_exit()" and hte latter will invoke "output_message()". So, my job is to build my version of error_exit() and output_message(), with pseudo codes as follows:

my_error_exit ()
{

  /* store the error code in the global variable */
  /* store the error message in the global variable */
  longjmp (myerr->setjmp_buffer, 1); /* Return control to the setjmp point */
}

my_output_message ()
{
  /* store the error code in the global variable */
  /* store the error message in the global variable */
}

The full source listing is as follows:

#include 
#include "stdafx.h"
#include "jpeglib.h"
#include 
#include 

int error_message_code;
char error_message_buffer[JMSG_LENGTH_MAX];

struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  error_message_code = cinfo->err->msg_code; /* store the code in the global variable */
  (*cinfo->err->format_message) (cinfo, error_message_buffer);  /* store the message in the global variable */
  printf ("Error intercepted in my_error_exit(): error_code = %d message = \'%s\'\n",
   error_message_code, error_message_buffer);
  longjmp(myerr->setjmp_buffer, 1); /* Return control to the setjmp point */
}

METHODDEF(void)
my_output_message (j_common_ptr cinfo)
{
  error_message_code = cinfo->err->msg_code;
  (*cinfo->err->format_message) (cinfo, error_message_buffer);
}

int _tmain(int argc, _TCHAR* argv[])
{
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  FILE * infile;
  JSAMPARRAY ptr;  /* Output row buffer */
  int row_stride;  /* physical row width in output buffer */

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  jerr.pub.output_message = my_output_message;
  /* Establish the setjmp return context for my_error_exit to use. */
  if (setjmp(jerr.setjmp_buffer)) {
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return 0;
  }

  jpeg_create_decompress(&cinfo);

  if ((infile = fopen("C:\\bin\\libjpeg-turbo\\b.jpg", "rb")) == NULL) {
 printf("cannot open jpeg file");
 return 1;
 }

  jpeg_stdio_src(&cinfo, infile);

  (void) jpeg_read_header(&cinfo, TRUE);

  jpeg_start_decompress(&cinfo);

  row_stride = cinfo.output_width * cinfo.output_components;
  ptr = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo,JPOOL_IMAGE,row_stride,1);
 
  while (cinfo.output_scanline < cinfo.output_height) {
    error_message_code = 0;
    jpeg_read_scanlines(&cinfo, (JSAMPARRAY) ptr, 1);
    if (error_message_code) 
       printf("Error occured at scanline %d: Error code = %d message= \'%s\'\n",
         cinfo.output_scanline, error_message_code, error_message_buffer);
    }  // while

  (void) jpeg_finish_decompress(&cinfo);

  jpeg_destroy_decompress(&cinfo);

  fclose (infile);

  return 0;
}

Postscript

I prepare two corrupted jpeg files. The first is truncated file that the exact image height is smaller than that is specified in the jpeg header. In this case, I can successfully intercept the error at scanline 313 with Error code = 120 and error message = "Premature end of JPEG file". The second is truncated that even the jpeg header cannot be successfully read. In this case, my my_error_exit() terminating route can capture the error_code = 51 and error message = "JPEG datastream contains no image".