Search code examples
clibjpeg-turbo

Why read_JPEG_file() fails to write image (libjpeg/jpeg-turbo) in C?


It is slightly modified code from example.c from jpeg-trubo (that code contained errors).

When I try to write image data to file it fails when cinfo.next_scanline==9 it crashes on line with jpeg_write_scanlines. Error: SigSegv error. cinfo.image_height is set to 404. The reading loop looks fine. Can you help to solve the problem?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libjpeg-turbo\libjpeg-turbo-gcc\include\jerror.h"

#include "libjpeg-turbo\libjpeg-turbo-gcc\include\jpeglib.h"
#include <setjmp.h> // optional error recovery mechanism
//# include "conversions.h" // color conversion functions

extern JSAMPLE * image_buffer;  // Points to large array of R,G,B-order data
int image_height;     // Number of rows
int image_width;      // Number of columns

int read_JPEG_file(char * filename, unsigned char * image_buffer);
void write_JPEG_file(char * filename, unsigned char * image_buffer, int quality);

int main()
{
    char * filename1 = "source.jpg";
    char * filename2 = "target.jpg";
    unsigned char * image_buffer; // Final image
    read_JPEG_file(filename1, image_buffer);
    write_JPEG_file(filename2, image_buffer, 95);
    return 0;
}

struct my_error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */

  jmp_buf setjmp_buffer;        /* for return to caller */
};
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;
  (*cinfo->err->output_message) (cinfo);
  longjmp(myerr->setjmp_buffer, 1);
}

GLOBAL(int)
read_JPEG_file (char * filename, unsigned char * image_buffer)
{
  struct jpeg_decompress_struct cinfo;
  struct my_error_mgr jerr;
  FILE * infile;                // source file
  JSAMPARRAY rows_buffer;  // Output row rows_buffer
  int row_stride_len;  // physical row width in output rows_buffer

  if ((infile = fopen(filename, "rb")) == NULL) {
    fprintf(stderr, "can't open %s\n", filename);
    return 0;
  }

  cinfo.err = jpeg_std_error(&jerr.pub);
  jerr.pub.error_exit = my_error_exit;
  if (setjmp(jerr.setjmp_buffer)) {
    jpeg_destroy_decompress(&cinfo);
    fclose(infile); // close the input file and return
    return 0;
  }

  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, infile);

  (void) jpeg_read_header(&cinfo, TRUE);

  (void) jpeg_start_decompress(&cinfo); // no errors possible
  row_stride_len = cinfo.output_width * cinfo.output_components;
  rows_buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride_len, 1);

  size_t counter=0;
  size_t raw_size = row_stride_len;
  image_buffer = (unsigned char*) malloc( row_stride_len*cinfo.output_height );
  while (cinfo.output_scanline < cinfo.output_height) {
    (void) jpeg_read_scanlines(&cinfo, rows_buffer, 1);
    memcpy(image_buffer+counter, rows_buffer[0], raw_size);
    counter += row_stride_len;
  }

  // Save global values for later use
  image_width=cinfo.image_width;
  image_height=cinfo.image_height;

  (void) jpeg_finish_decompress(&cinfo); // Finish decompression, no errors possible
  jpeg_destroy_decompress(&cinfo); // 8) Release JPEG decompression object
  fclose(infile);
  return 1;
}

GLOBAL(void)
write_JPEG_file(char * filename, unsigned char * image_buffer, int quality)
{
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  FILE * outfile;  // target file
  JSAMPROW rows_buffer[1];  // pointer to JSAMPLE row[s]
  int row_stride_len;  // physical row width in image rows_buffer
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo); // initialize the JPEG compression object
  if ((outfile = fopen(filename, "wb")) == NULL) {
    fprintf(stderr, "can't open %s\n", filename);
    exit(1);
  }
  jpeg_stdio_dest(&cinfo, outfile);
  cinfo.image_width  = image_width;
  cinfo.image_height = image_height;
  cinfo.input_components = 3;
  cinfo.in_color_space = JCS_RGB;
  jpeg_set_defaults(&cinfo); // set default compression parameters
  jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
  jpeg_start_compress(&cinfo, TRUE); // TRUE - write a complete interchange-JPEG file.
  row_stride_len = cinfo.image_width * 3; /* JSAMPLEs per row in image_buffer */

  // pass array of pointers to scan lines
  while (cinfo.next_scanline < cinfo.image_height) {
    rows_buffer[0] = & image_buffer[cinfo.next_scanline * row_stride_len];
    (void) jpeg_write_scanlines(&cinfo, rows_buffer, 1);
  }

  jpeg_finish_compress(&cinfo);
  fclose(outfile);
  jpeg_destroy_compress(&cinfo);
}

Solution

  • You allocate space for the image buffer in read_JPEG_file and store a pointer to it in that function's image_buffer argument, but that value is lost when the function returns. The image_buffer variable in main is a different variable and isn't changed by the call to read_JPEG_file. Since the image_buffer variable in main is never initialized or assigned a value, you pass an uninitialized value to write_JPEG_file rather than a valid pointer.

    Also the global variable image_buffer, declared as a pointer to JSAMPLE near the start of your example, is also different variable. This variable is completely unused by your example program.

    Since you're already using global variables to communicate the image's width an height between the functions read_JPEG_file and write_JPEG_file the simple fix (but not necessarily the best) to your problem is to also use a global variable for the pointer to the image data. Change the type of the global variable image_buffer from JSAMPLE * to unsigned char *, remove the definition of image_buffer from main, and the arguments image_buffer from the functions read_JPEG_file and write_JPEG_file. This will result in the global variable image_buffer being used to store the pointer to the image data.

    For example:

    ...
    
    unsigned char * image_buffer;  // Points to large array of R,G,B-order data
    int image_height;     // Number of rows
    int image_width;      // Number of columns
    
    int read_JPEG_file(char * filename);
    void write_JPEG_file(char * filename, int quality);
    
    int main()
    {
        char * filename1 = "source.jpg";
        char * filename2 = "target.jpg";
        read_JPEG_file(filename1);
        write_JPEG_file(filename2, 95);
        return 0;
    }
    
    ...
    
    GLOBAL(int)
    read_JPEG_file (char * filename)
    {
        ...
    }
    
    GLOBAL(void)
    write_JPEG_file(char * filename, int quality)
    {
        ...
    }