theonlineoasis

This page contains the source code to two C programs for analysing JPEG bitstreams. They rely on the libjpeg library. They were quickly hacked together and are basically untested!

JPEG diff outputs the difference between two JPEG bitstreams based on their quantized DCT coefficients.

Code samples on this website come without any guarantees – use them at your own risk! I am grateful for comments or patches sent to my email address: first name.last name@cl.cam.ac.uk.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <jpeglib.h>
#include <jerror.h>

typedef int boolean;
#define TRUE 1
#define FALSE 0

void compare_coeffs(FILE* infile_one, FILE* infile_two)
{
  struct jpeg_decompress_struct cinfo_one;
  struct jpeg_decompress_struct cinfo_two;
  struct jpeg_error_mgr jerr_one;
  struct jpeg_error_mgr jerr_two;
  //jvirt_barray_ptr* coeff_arrays = (jvirt_barray_ptr*)malloc(sizeof(jvirt_barray_ptr) * 3);
  jvirt_barray_ptr* coeff_arrays_one;
  jvirt_barray_ptr* coeff_arrays_two;
  int ci, by, bx, bi, bi2, i;

  // Create decompression object.
  cinfo_one.err = jpeg_std_error(&jerr_one);
  cinfo_two.err = jpeg_std_error(&jerr_two);
  jpeg_create_decompress(&cinfo_one);
  jpeg_create_decompress(&cinfo_two);

  jpeg_stdio_src(&cinfo_one, infile_one);
  jpeg_stdio_src(&cinfo_two, infile_two);

  // Get all compression parameters.
  jpeg_read_header(&cinfo_one, TRUE);
  jpeg_read_header(&cinfo_two, TRUE);

  // Read coefficients.
  coeff_arrays_one = jpeg_read_coefficients(&cinfo_one);
  coeff_arrays_two = jpeg_read_coefficients(&cinfo_two);

  // Fill virtual arrays.
  printf("Quantized DCT coefficient comparison\n");
  for (ci = 0; ci < 3; ci++)
  {
    JBLOCKARRAY buffer_one;
    JBLOCKARRAY buffer_two;
    JCOEFPTR blockptr_one;
    JCOEFPTR blockptr_two;
    jpeg_component_info* compptr_one;
    jpeg_component_info* compptr_two;
    compptr_one = cinfo_one.comp_info + ci;
    compptr_two = cinfo_two.comp_info + ci;

    if (compptr_one->height_in_blocks != compptr_two->height_in_blocks || compptr_one->width_in_blocks != compptr_two->width_in_blocks)
    {
      printf("Component %d dimensions do not match.\n", ci);
      continue;
    }

    for (bi = 0; bi < 64; bi++)
    {
      if (compptr_one->quant_table->quantval[bi] != compptr_two->quant_table->quantval[bi])
      {
        printf("Quantization tables differ.\n");
        break;
      }
    }

    for (by = 0; by < compptr_one->height_in_blocks; by++)
    {
      buffer_one = (cinfo_one.mem->access_virt_barray)((j_common_ptr)&cinfo_one, coeff_arrays_one[ci], by, (JDIMENSION)1, FALSE);
      buffer_two = (cinfo_two.mem->access_virt_barray)((j_common_ptr)&cinfo_two, coeff_arrays_two[ci], by, (JDIMENSION)1, FALSE);
      for (bx = 0; bx < compptr_one->width_in_blocks; bx++)
      {
        blockptr_one = buffer_one[0][bx];
        blockptr_two = buffer_two[0][bx];
        for (bi = 0; bi < 64; bi++)
        {
          if (blockptr_one[bi] != blockptr_two[bi])
          {
            printf("\n\nImages differ at block (%d, %d). The values were\n", bx, by);
            for (bi2 = 0; bi2 < 64; bi2++)
            {
              if (bi2 % 8 == 0)
                printf("\n");
              printf("%d ", blockptr_one[bi2]);
            }
            printf("\n");
            for (bi2 = 0; bi2 < 64; bi2++)
            {
              if (bi2 % 8 == 0)
                printf("\n");
              printf("%d ", blockptr_two[bi2]);
            }
            printf("\n");

            break;
          }
        }
      }
    }
  }

  // jpeg_finish_decompress
  jpeg_finish_decompress(&cinfo_one);
  jpeg_finish_decompress(&cinfo_two);

  // Destroy decompression object.
  jpeg_destroy_decompress(&cinfo_one);
  jpeg_destroy_decompress(&cinfo_two);
}

void show_psnr(FILE* infile_one, FILE* infile_two)
{
  struct jpeg_decompress_struct cinfo_one;
  struct jpeg_decompress_struct cinfo_two;
  struct jpeg_error_mgr jerr_one;
  struct jpeg_error_mgr jerr_two;
  jvirt_barray_ptr* coeff_arrays_one;
  jvirt_barray_ptr* coeff_arrays_two;
  int ci, by, bx, bi, bi2, i;

  // Create decompression object.
  cinfo_one.err = jpeg_std_error(&jerr_one);
  cinfo_two.err = jpeg_std_error(&jerr_two);
  jpeg_create_decompress(&cinfo_one);
  jpeg_create_decompress(&cinfo_two);

  jpeg_stdio_src(&cinfo_one, infile_one);
  jpeg_stdio_src(&cinfo_two, infile_two);

  // Get all compression parameters.
  jpeg_read_header(&cinfo_one, TRUE);
  jpeg_read_header(&cinfo_two, TRUE);

  // Configure the decompressors for raw data output.
  cinfo_one.raw_data_out = TRUE;
  cinfo_two.raw_data_out = TRUE;

  jpeg_start_decompress(&cinfo_one);
  jpeg_start_decompress(&cinfo_two);

  JSAMPIMAGE image_one = (JSAMPIMAGE)malloc(3 * sizeof(JSAMPARRAY));
  JSAMPIMAGE image_two = (JSAMPIMAGE)malloc(3 * sizeof(JSAMPARRAY));
  for (ci = 0; ci < 3; ci++)
  {
    image_one[ci] = (JSAMPARRAY)malloc(16 * sizeof(JSAMPROW));
    image_two[ci] = (JSAMPARRAY)malloc(16 * sizeof(JSAMPROW));
    for (bi = 0; bi < 16; bi++)
    {
      image_one[ci][bi] = (JSAMPROW)malloc(cinfo_one.output_width * sizeof(JSAMPLE));
      image_two[ci][bi] = (JSAMPROW)malloc(cinfo_two.output_width * sizeof(JSAMPLE));
    }
  }

  int yx, yr;
  int yrowmcu = 0;
  double luma_se, chroma_se, luma_mse, chroma_mse, luma_psnr, chroma_psnr;
  luma_se = 0.0;
  chroma_se = 0.0;
  while (cinfo_one.output_scanline < cinfo_one.output_height)
  {
    jpeg_read_raw_data(&cinfo_one, image_one, (JDIMENSION)16);
    jpeg_read_raw_data(&cinfo_two, image_two, (JDIMENSION)16);

    for (yr = 0; yr < 16; yr++)
    {
      for (yx = 0; yx < cinfo_one.output_width; yx++)
      {
        luma_se += (image_one[0][yr][yx] - image_two[0][yr][yx]) * (image_one[0][yr][yx] - image_two[0][yr][yx]);
      }
    }
/*
    // DEBUG!!!!!!!!!!!!!
    if (yrowmcu == 8)
    {
      printf("DEBUG\n");
      for (yr = 8; yr < 16; yr ++)
      {
        for (yx = 24; yx < 32; yx++)
        {
          printf("%d ", image_one[0][yr][yx]);
        }
        printf("\n");
      }
    }
*/

    yrowmcu += 2;

    for (yr = 0; yr < 8; yr++)
    {
      for (yx = 0; yx < cinfo_one.output_width / 2; yx++)
      {
        chroma_se += (image_one[1][yr][yx] - image_two[1][yr][yx]) * (image_one[1][yr][yx] - image_two[1][yr][yx]);
        chroma_se += (image_one[2][yr][yx] - image_two[2][yr][yx]) * (image_one[2][yr][yx] - image_two[2][yr][yx]);
      }
    }
  }

  for (ci = 0; ci < 3; ci++)
  {
    for (bi = 0; bi < 16; bi++)
    {
      free(image_one[ci][bi]);
      free(image_two[ci][bi]);
    }

    free(image_one[ci]);
    free(image_two[ci]);
  }
  free(image_one);
  free(image_two);

  luma_mse = luma_se / (double)(cinfo_one.output_width * cinfo_one.output_height);
  chroma_mse = chroma_se / (double)(2.0 * (cinfo_one.output_width / 2) * (cinfo_one.output_height / 2));
  luma_psnr = 10.0 * log10((255.0 * 255.0) / (luma_mse));
  chroma_psnr = 10.0 * log10((255.0 * 255.0) / (chroma_mse));

  printf("Luma PSNR: %f dB, chroma PSNR: %f dB\n", luma_psnr, chroma_psnr);

  // jpeg_finish_decompress
  jpeg_finish_decompress(&cinfo_one);
  jpeg_finish_decompress(&cinfo_two);

  // Destroy decompression object.
  jpeg_destroy_decompress(&cinfo_one);
  jpeg_destroy_decompress(&cinfo_two);
}

int main(int argc, const char** argv)
{
  int arg_index = 0;
  boolean calculate_psnr = FALSE;
  FILE* infile_one;
  FILE* infile_two;

  if (argc != 3 && argc != 4)
  {
    printf("Usage:\n\t%s [-psnr] one.jpg two.jpg\n", argv[0]);
    return 0;
  }

  if (!strncmp(argv[1], "-psnr", 5))
  {
    arg_index = 1;
    calculate_psnr = TRUE;
  }

  // Diff of quantized coefficient values
  if ((infile_one = fopen(argv[arg_index + 1], "rb")) == NULL)
  {
    fprintf(stderr, "Can't open %s\n", argv[arg_index + 1]);
    exit(1);
  }
  if ((infile_two = fopen(argv[arg_index + 2], "rb")) == NULL)
  {
    fprintf(stderr, "Can't open %s\n", argv[arg_index + 2]);
    exit(1);
  }
  compare_coeffs(infile_one, infile_two);
  fclose(infile_one);
  fclose(infile_two);

  // PSNR
  if (calculate_psnr)
  {
    if ((infile_one = fopen(argv[arg_index + 1], "rb")) == NULL)
    {
      fprintf(stderr, "Can't open %s\n", argv[arg_index + 1]);
      exit(1);
    }
    if ((infile_two = fopen(argv[arg_index + 2], "rb")) == NULL)
    {
      fprintf(stderr, "Can't open %s\n", argv[arg_index + 2]);
      exit(1);
    }
    show_psnr(infile_one, infile_two);
    fclose(infile_one);
    fclose(infile_two);
  }

  return 0;
}

JPEG dump outputs to standard output the quantization matrices and quantized coefficients stored in a JPEG bitstream.

#include <stdio.h>
#include <stdlib.h>
#include <jpeglib.h>
#include <jerror.h>

#include "ijgdct.h"

typedef int boolean;
#define TRUE 1
#define FALSE 0

int main(int argc, char** argv)
{
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;
  //jvirt_barray_ptr* coeff_arrays = (jvirt_barray_ptr*)malloc(sizeof(jvirt_barray_ptr) * 3);
  jvirt_barray_ptr* coeff_arrays;
  int ci, by, bx, bi;
  int arg_index = 0;
  boolean quantization_matrix = TRUE;
  boolean dequantize = FALSE;
  FILE* infile;

  if (argc != 2 && argc != 3)
  {
    printf("Usage:\n\t%s [-dequantize] file.jpg\n", argv[0]);
    return 0;
  }

  if (!strncmp(argv[1], "-dequantize", 11))
  {
    arg_index = 1;
    dequantize = TRUE;
  }

  // Create decompression object.
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);

  if ((infile = fopen(argv[arg_index + 1], "rb")) == NULL)
  {
    fprintf(stderr, "Can't open %s\n", argv[1]);
    exit(1);
  }
  jpeg_stdio_src(&cinfo, infile);

  // Get all compression parameters.
  jpeg_read_header(&cinfo, TRUE);

  // Read coefficients.
  coeff_arrays = jpeg_read_coefficients(&cinfo);

  // Fill virtual arrays.
  printf("Fill virtual\n");
  for (ci = 0; ci < 3; ci++)
  {
    JBLOCKARRAY buffer;
    JCOEFPTR blockptr;
    jpeg_component_info* compptr;
    compptr = cinfo.comp_info + ci;

    if (quantization_matrix)
    {
      printf("Quantization matrix %d", ci);
      for (bi = 0; bi < 64; bi++)
      {
        if (bi % 8 == 0)
          printf("\n");
        printf("%d ", compptr->quant_table->quantval[bi]);
      }
      printf("\n");
    }

    int* block = (int*)malloc(sizeof(int) * 64);
    for (by = 0; by < compptr->height_in_blocks; by++)
    {
      buffer = (cinfo.mem->access_virt_barray)((j_common_ptr)&cinfo, coeff_arrays[ci], by, (JDIMENSION)1, FALSE);
      for (bx = 0; bx < compptr->width_in_blocks; bx++)
      {
	printf("(%d, %d)\n", bx, by);
        blockptr = buffer[0][bx];
        printf("%d, %d\n", bx, by);
        for (bi = 0; bi < 64; bi++)
        {
          if (bi % 8 == 0)
            printf("\n");
          printf("%d ", dequantize ? ((int)blockptr[bi] * (int)compptr->quant_table->quantval[bi]) : blockptr[bi]);
	  block[bi] = (int)blockptr[bi] * (int)compptr->quant_table->quantval[bi];
        }
        printf("\n");

	int clips = block_idct(block);
        if (clips & 1)
          printf("Clips at 255\n");
        if (clips & 2)
          printf("Clips at 0\n");

        for (bi = 0; bi < 64; bi++)
        {
          if (bi % 8 == 0)
            printf("\n");
          printf("%3d ", block[bi]);
        }

        printf("\n\n");
      }
    }
  }

  // jpeg_finish_decompress
  jpeg_finish_decompress(&cinfo);

  // Destroy decompression object.
  jpeg_destroy_decompress(&cinfo);

  return 0;
}