Need help in reading JPEG file using libjpeg

33,067

Solution 1

jpeg_read_scanlines function receives an array of pointers (not the direct pointer of pixels as imageData->pixels). So we should create a JSAMPARRAY first:

int buffer_height = 1;
JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW) * buffer_height);
buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);

In your code you've created a "buffer" with "cinfo.mem->alloc_sarray" but you never use it. The final step is to pass the "buffer" as argument of jpeg_read_scanlines:

while (cinfo.output_scanline < cinfo.output_height) {
  jpeg_read_scanlines(&cinfo, buffer, 1);
  memcpy(imageData->pixels+counter, buffer[0], row_stride);
  counter += row_stride;
}

See that we're using "imageData->pixels+counter", not just "imageData->pixels" as in your code. In this way we write each row after another in the whole "imageData->pixels" memory chunk.

Solution 2

Here's a sample for reading a jpeg image:

/***************************************************
    To read a jpg image file and download
    it as a texture map for openGL
    Derived from Tom Lane's example.c
    -- Obtain & install jpeg stuff from web 
    (jpeglib.h, jerror.h jmore.h, jconfig.h,jpeg.lib)
****************************************************/
#include <jpeglib.h>    
#include <jerror.h>
//================================
GLuint LoadJPEG(char* FileName)
//================================
{
  unsigned long x, y;
  unsigned int texture_id;
  unsigned long data_size;     // length of the file
  int channels;               //  3 =>RGB   4 =>RGBA 
  unsigned int type;  
  unsigned char * rowptr[1];    // pointer to an array
  unsigned char * jdata;        // data for the image
  struct jpeg_decompress_struct info; //for our jpeg info
  struct jpeg_error_mgr err;          //the error handler

  FILE* file = fopen(FileName, "rb");  //open the file

  info.err = jpeg_std_error(& err);     
  jpeg_create_decompress(& info);   //fills info structure

  //if the jpeg file doesn't load
  if(!file) {
     fprintf(stderr, "Error reading JPEG file %s!", FileName);
     return 0;
  }

  jpeg_stdio_src(&info, file);    
  jpeg_read_header(&info, TRUE);   // read jpeg file header

  jpeg_start_decompress(&info);    // decompress the file

  //set width and height
  x = info.output_width;
  y = info.output_height;
  channels = info.num_components;
  type = GL_RGB;
  if(channels == 4) type = GL_RGBA;

  data_size = x * y * 3;

  //--------------------------------------------
  // read scanlines one at a time & put bytes 
  //    in jdata[] array. Assumes an RGB image
  //--------------------------------------------
  jdata = (unsigned char *)malloc(data_size);
  while (info.output_scanline < info.output_height) // loop
  {
    // Enable jpeg_read_scanlines() to fill our jdata array
    rowptr[0] = (unsigned char *)jdata +  // secret to method
            3* info.output_width * info.output_scanline; 

    jpeg_read_scanlines(&info, rowptr, 1);
  }
  //---------------------------------------------------

  jpeg_finish_decompress(&info);   //finish decompressing

  //----- create OpenGL tex map (omit if not needed) --------
  glGenTextures(1,&texture_id);
  glBindTexture(GL_TEXTURE_2D, texture_id);
  gluBuild2DMipmaps(GL_TEXTURE_2D,3,x,y,GL_RGB,GL_UNSIGNED_BYTE,jdata);

  jpeg_destroy_decompress(&info);
  fclose(file);                    //close the file
  free(jdata);

  return texture_id;    // for OpenGL tex maps
}

Solution 3

Like dacap said, it is expecting a JSAMPARRAY. That being said, you can write straight to your imageData->pixels array if you would like. You just need to do something like this:

// Allocate imageData->pixels to be the correct size, start decompress and all
// that jazz, like you did in your code. Skip allocating buffer though.
// ...

JSAMPROW output_data;
unsigned int scanline_len = cinfo.output_width * cinfo.output_components;

unsigned int scanline_count = 0;
while (cinfo.output_scanline < cinfo.output_height)
{
    output_data = (imageData->pixels + (scanline_count * scanline_len));
    jpeg_read_scanlines(&cinfo, &output_data, 1);
    scanline_count++;
}

You can then skip allocating buffer altogether. Using memcpy works fine, but why do the extra copy if you don't have to?

Solution 4

Here's a cleaned-up version of the code by Jim. It also uses a CImg as the output format, which makes it a bit more explicit how the RGB values are extracted.

CImg<unsigned char> *ReadJpegIntoCImg( const char *path )
{
    FILE *file = fopen( path, "rb" );
    if ( file == NULL )
    {
        return NULL;
    }
    
    struct jpeg_decompress_struct info; //for our jpeg info
    struct jpeg_error_mgr err; //the error handler
    
    info.err = jpeg_std_error( &err );     
    jpeg_create_decompress( &info ); //fills info structure
    
    jpeg_stdio_src( &info, file );    
    jpeg_read_header( &info, true );
    
    jpeg_start_decompress( &info );
    
    unsigned int w = info.output_width;
    unsigned int h = info.output_height;
    unsigned int numChannels = info.num_components; // 3 = RGB, 4 = RGBA
    unsigned long dataSize = w * h * numChannels;
    
    // read RGB(A) scanlines one at a time into jdata[]
    unsigned char *data = (unsigned char *)malloc( dataSize );
    unsigned char* rowptr;
    while ( info.output_scanline < h )
    {
        rowptr = data + info.output_scanline * w * numChannels;
        jpeg_read_scanlines( &info, &rowptr, 1 );
    }
    
    jpeg_finish_decompress( &info );
    jpeg_destroy_decompress( &info );   
    
    fclose( file );
    
    // this code block is only if you happen to want output in CImg format, but is illustrative
    CImg<unsigned char> *image = new CImg<unsigned char>( w, h, 1, numChannels );
    for ( int x = 0 ; x < w ; x++ )
    {
        for ( int y = 0 ; y < h ; y++ )
        {
            for ( int c = 0 ; c < numChannels ; c++ )
            {
                *image->data( x, y, 0, c ) = data[ y * w * numChannels + x * numChannels + c ];
            }
        }
    }
    
    free( data );
    
    return image;
}

Also, a side note for those who may have been stuck on the same issue I was, when using libjpeg from C++, it's important to have a version of jpeglib.h that includes

#ifdef __cplusplus
extern "C" {
#endif

I was using a version of the file that didn't have that, and was getting link errors.

Share:
33,067

Related videos on Youtube

all_by_grace
Author by

all_by_grace

Software engineering technologist with 8+ years of software development experience (both at commercial industries and academics/research institutions), a proven record of team collaboration, agile development practices, excellent analytical skills, and solid expertise in distributed computing, data structure &amp; algorithm, and object oriented software development &amp; design. Have worked with top-notch supercomputers and cloud computing infrastructure during previous and current development roles at IBM Research, BGL Corporate Solutions, and other top industries. Inspired by the continuous innovations in distributed systems, cloud, and big data technology. Excellent analytical skills, self motivated, dedicated, adapt well to diverse teams and projects. M.S. in Computer Science &amp; professionally certified in Linux &amp; Scrum.

Updated on July 09, 2022

Comments

  • all_by_grace
    all_by_grace almost 2 years

    I followed the example code in the libjpeg example file, however I was not able to read the image data.

    I have the following struct, and I created an instance of this struct.

     struct ImageData {
            unsigned char *pixels;
            long  width;
            long height;
        };
    
        ImageData *imageData;
    

    Below is my read_JPEG_file function:

    int read_JPEG_file (char * filename)
    {
        struct jpeg_decompress_struct cinfo;
        struct my_error_mgr jerr;
    
        /* More stuff */
        FILE * infile;      /* source file */
        JSAMPARRAY buffer;      /* Output row buffer */
        int row_stride;     /* physical row width in output buffer */
    
        if ((infile = fopen(filename, "rb")) == NULL) {
            fprintf(stderr, "can't open %s\n", filename);
            return 0;
        }
    
        /* Step 1: allocate and initialize JPEG decompression object */
    
        /* We set up the normal JPEG error routines, then override error_exit. */
        cinfo.err = jpeg_std_error(&jerr.pub);
        jerr.pub.error_exit = my_error_exit;
        /* Establish the setjmp return context for my_error_exit to use. */
        if (setjmp(jerr.setjmp_buffer)) {
    
            jpeg_destroy_decompress(&cinfo);
            fclose(infile);
            return 0;
        }
        /* Now we can initialize the JPEG decompression object. */
        jpeg_create_decompress(&cinfo);
    
        /* Step 2: specify data source (eg, a file) */
    
        jpeg_stdio_src(&cinfo, infile);
    
        /* Step 3: read file parameters with jpeg_read_header() */
    
        (void) jpeg_read_header(&cinfo, TRUE);
        /* Step 4: set parameters for decompression */
    
        /* In this example, we don't need to change any of the defaults set by
         * jpeg_read_header(), so we do nothing here.
         */
    
        /* Step 5: Start decompressor */
    
        (void) jpeg_start_decompress(&cinfo);
    
    
        row_stride = cinfo.output_width * cinfo.output_components;
        /* Make a one-row-high sample array that will go away when done with image */
        buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
    
        imageData = new ImageData;
        imageData->width = cinfo.output_width;
        imageData->height = cinfo.output_height;
    
        imageData->pixels = new unsigned char [cinfo.output_width * cinfo.output_height * cinfo.output_components];
        long counter = 0;
    
       //step 6, read the image line by line
        while (cinfo.output_scanline < cinfo.output_height) {
            //IT ALWAYS crash ON THIS JPEG_READ_SCANLINES FUNCTION CALL BELOW
            (void) jpeg_read_scanlines(&cinfo, (JSAMPARRAY)(imageData->pixels), 1);
            counter +=row_stride;
    
        }
           /* Step 7: Finish decompression */
    
        (void) jpeg_finish_decompress(&cinfo);
        /* Step 8: Release JPEG decompression object */
    
        /* This is an important step since it will release a good deal of memory. */
        jpeg_destroy_decompress(&cinfo);
    
        fclose(infile);
        /* And we're done! */
        return 1;
    }
    

    It always fails on this JPEG_READ_SCANLINES function, in the step 6 above. I got an "EXC_BAD_ACCESS" signal on that line.

    Does anyone have any idea, or have some working examples on reading .jpg file with libjpeg that you can share here? I have checked the size of my imageData->pixels, and compared it with the size of the jpeg file itself, and it has the same size. The memory for this variable has also dynamically allocated, so I know that it was not a memory problem.

    Any ideas?

  • all_by_grace
    all_by_grace about 13 years
    Yeah, I forgot to do the memcpy. I thought it is possible to use jpeg_read_scanlines to scan directly to imageData->pixels. I also tried something like: jpeg_read_scanlines(&cinfo, imageData->pixels+counter, 1), and increment the counter, but it doesn't work.
  • dacap
    dacap about 13 years
    Yes, you must use "buffer" in jpeg_read_scanlines(). Anyway I was thinking and you could put "buffer[0] = imageData->pixels" to avoid creating two rows of row_stride size and to avoid memcpy().
  • Camille Goudeseune
    Camille Goudeseune almost 9 years
    FYI, the extern "C" is already in Ubuntu's current /usr/include/jpeglib.h, from package libjpeg62-dev.
  • Aubin
    Aubin almost 7 years
    if type = GL_RGBA, may data_size be x * y * 4 ?
  • Ruslan
    Ruslan about 3 years
    if(channels == 4) type = GL_RGBA;, and then you don't use type anymore. Also, unsigned char * rowptr[1]; // pointer to an array — the way it's declared, it's an array of pointers.
  • Ruslan
    Ruslan about 3 years
    Can't CImg read JPEG itself (via corresponding library bindings)?
  • Cris Luengo
    Cris Luengo almost 3 years
    This code misses a call to jpeg_destroy_decompress.
  • M Katz
    M Katz almost 3 years
    @Chris Luengo Fixed.

Related