Very Simple JPEG writer in C/C++
Update: If you want to see another example or would like to know how to write JPEG data straight to a buffer, see this new post.
This little tutorial is for all of those who have searched for how to write jpeg files from C/C++ and have been sadly disapointed by the results. I asume the reader already has some image data in memory that is intended to be compressed into a jpeg file. This article is based on libjpeg by IJG which appears to be the most complete and open jpeg library out there. The primary reference for this tutorial is the libjpeg doc page at http://www.jpegcameras.com/libjpeg/libjpeg.html.
Let’s drive right into the code…
Frist, do some setup and take a screen shot using Xlib. Note that Xlib this has next to nothing to do with the JPEG code and is only included to create a context. In the end, the info and screen_shot object will have some meta-data about the image that we will
be making into a JPEG. The buffer vector will hold raw RGB values for screen shot.
#include <jpeglib.h> int main() { vector<char> buffer; XInfo_t xinfo = getXInfo(":0"); XImage* screen_shot = takeScreenshot(xinfo, buffer);
Now we need to open an output file for the jpeg.
FILE* outfile = fopen("/tmp/test.jpeg", "wb"); if (!outfile) throw FormattedException("%s:%d Failed to open output file", __FILE__, __LINE__);
Now, lets setup the libjpeg objects. Note this is setup for my screen shot so we are using RGB color space.
struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, outfile); cinfo.image_width = xinfo.width; cinfo.image_height = xinfo.height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB;
Call the setup defualts helper function to give us a starting point. Note, don’t call any of the helper functions before you call this, they will no effect if you do. Then call other helper functions, here I call the set quality. Then start the compression.
jpeg_set_defaults(&cinfo); /*set the quality [0..100] */ jpeg_set_quality (&cinfo, 75, true); jpeg_start_compress(&cinfo, true);
Next we setup a pointer and start looping though lines in the image. Notice that the row_pointer is set to the first byte of the row via some pointer math/magic. Once the pointer is calculated, do a write_scanline.
JSAMPROW row_pointer; /* pointer to a single row */ while (cinfo.next_scanline < cinfo.image_height) { row_pointer = (JSAMPROW) &buffer[cinfo.next_scanline*(screen_shot->depth>>3)*screen_shot->width]; jpeg_write_scanlines(&cinfo, &row_pointer, 1); }
Finally, call finish and in my case close the connection to the X server.
jpeg_finish_compress(&cinfo); XCloseDisplay(xinfo.display); }
Compling and liking the code is straight forward. As with any library, get the headers and the shared library and setup the compiler and you are good to go. For reading JPEG files via C/C++ I would recommend using SDL’s Image library. You can use libjpeg but the interface will be more complex than needed for most applications.
Sep 2, 2008aewhiteCategory: General, Programming Read more
Comments (8)
Tags: C++
Facebook comments:


This one is a piece of cake! What about compressinng to memory buffer? You need to replace jpeg_stdio_dest(&cinfo, outfile);
with your own manager!
I remember looking into this at one time and I can’t remember exactly what I came up with. I think there is a easy way to do what you want. If I have time, I’ll see what I can dig up. If you find an answer please let me know.
I have tried various methods ……. I am able to successfully decompress from memory with my own manager………..however compressing and writing to the memory is giving errors and seg faults! tried using Valgrind and ddd debugger. Also had coredump and back traced the problem in jmarker.c. This occurs at the point jpeg_start_compress(&cinfo, true);. I am pretty sure there is a problem with my destination manager……
Here is what I have.
/* Expanded data destination object for stdio output */
typedef struct {
struct jpeg_destination_mgr pub; /* public fields */
// char * destbufferholder; /* target stream */
int destbufferlen;
JOCTET *destbuffer; /* start of buffer */
size_t bufsize;
size_t jpegsize;
} destbuffer_mgr;
typedef destbuffer_mgr * destbuffer_ptr;
#define DESTBUFFER_OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite’able size */
/*
* Initialize destination — called by jpeg_start_compress
* before any data is actually written.
*/
void destbuffer_init_destination (j_compress_ptr cinfo)
{
destbuffer_ptr dest = (destbuffer_ptr) cinfo->dest;
/* Allocate the output buffer — it will be released when done with image */
dest->destbuffer = (JOCTET *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, (dest->bufsize)* sizeof(JOCTET));
dest->pub.next_output_byte = dest->destbuffer;
dest->pub.free_in_buffer = dest->bufsize;//DESTBUFFER_OUTPUT_BUF_SIZE;
dest->jpegsize = 0;
}
/*
* Empty the output buffer — called whenever buffer fills up.
*
* In typical applications, this should write the entire output buffer
* (ignoring the current state of next_output_byte & free_in_buffer),
* reset the pointer & count to the start of the buffer, and return TRUE
* indicating that the buffer has been dumped.
*
* In applications that need to be able to suspend compression due to output
* overrun, a FALSE return indicates that the buffer cannot be emptied now.
* In this situation, the compressor will return to its caller (possibly with
* an indication that it has not accepted all the supplied scanlines). The
* application should resume compression after it has made more room in the
* output buffer. Note that there are substantial restrictions on the use of
* suspension — see the documentation.
*
* When suspending, the compressor will back up to a convenient restart point
* (typically the start of the current MCU). next_output_byte & free_in_buffer
* indicate where the restart point will be if the current call returns FALSE.
* Data beyond this point will be regenerated after resumption, so do not
* write it out when emptying the buffer externally.
*/
boolean destbuffer_empty_output_buffer (j_compress_ptr cinfo)
{
destbuffer_ptr dest = (destbuffer_ptr) cinfo->dest;
dest->pub.next_output_byte = dest->destbuffer;
dest->pub.free_in_buffer = dest->bufsize; //DESTBUFFER_OUTPUT_BUF_SIZE;
/* Treat empty input file as fatal error */
// ERREXIT(cinfo, JERR_INPUT_EMPTY);
return TRUE;
}
/*
* Terminate destination — called by jpeg_finish_compress
* after all data has been written. Usually needs to flush buffer.
*
* NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
* application must deal with any cleanup that should happen even
* for error exit.
*/
void destbuffer_term_destination (j_compress_ptr cinfo)
{
destbuffer_ptr dest = (destbuffer_ptr) cinfo->dest;
dest->jpegsize = dest->bufsize – dest->pub.free_in_buffer;
}
/*
* Prepare for output to a stdio stream.
* The caller must have already opened the stream, and is responsible
* for closing it after finishing compression.
*/
void jpeg_mem_dest (j_compress_ptr cinfo, char * destbufferholder, size_t image_size)
{
destbuffer_ptr dest;
/* The destination object is made permanent so that multiple JPEG images
* can be written to the same file without re-executing jpeg_stdio_dest.
* This makes it dangerous to use this manager and a different destination
* manager serially with the same JPEG object, because their private object
* sizes may be different. Caveat programmer.
*/
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(destbuffer_mgr));
}
dest = (destbuffer_ptr ) cinfo->dest;
dest->pub.init_destination = destbuffer_init_destination;
dest->pub.empty_output_buffer = destbuffer_empty_output_buffer;
dest->pub.term_destination = destbuffer_term_destination;
dest->destbuffer = (JOCTET *)destbufferholder;
dest->bufsize = image_size;
dest->jpegsize = 0;
Could you replace this in your code upon reading any bmp file, jpeg file getting raw_image with this manager and let me know what happens?
The function in the main looks like
char* write_jpeg_file_mem( char *outbuffer, int size )
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
/* this is a pointer to one row of image data */
JSAMPROW row_pointer[1];
//FILE *outfile = fopen( “gyg.jpg”, “wb” );
cinfo.err = jpeg_std_error( &jerr );
jpeg_create_compress(&cinfo);
//jpeg_stdio_dest(&cinfo, outfile);
jpeg_mem_dest(&cinfo, outbuffer, (size_t)size);
/* Setting the parameters of the output file here */
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = color_space;
/* default compression parameters, we shouldn’t be worried about these */
jpeg_set_defaults( &cinfo );
// jpeg_set_quality(&cinfo, 5, TRUE /* limit to baseline-JPEG values */);
/* Now do the compression .. */
jpeg_start_compress( &cinfo, TRUE );
/* like reading a file, this time write one row at a time */
while( cinfo.next_scanline widthStep = (int)cinfo.image_width*3;
// hello->imageData = (char *)raw_image;
// cvSaveImage(“hellower.bmp”,raw_image);
/* similar to read file, clean up after we’re done compressing */
jpeg_finish_compress( &cinfo );
jpeg_destroy_compress( &cinfo );
//fclose( outfile );
/* success code is 1! */
return (char*)raw_image;
}
I changed references of char * to JOCTET * and I at least got to scanline input. I don’t think I’ll be able to go much deeper into this problem right now though, sorry.
I got in-memory compression to work. See the following
#define OUTPUT_BUF_SIZE 4096 /* choose an efficiently fwrite’able size */
/* Expanded data destination object for memory output */
typedef struct {
struct jpeg_destination_mgr pub; /* public fields */
unsigned char ** outbuffer; /* target buffer */
unsigned long * outsize;
unsigned char * newbuffer; /* newly allocated buffer */
JOCTET * buffer; /* start of buffer */
size_t bufsize;
} my_mem_destination_mgr;
typedef my_mem_destination_mgr * my_mem_dest_ptr;
void
init_mem_destination (j_compress_ptr cinfo)
{
/* no work necessary here */
}
boolean
empty_mem_output_buffer (j_compress_ptr cinfo)
{
size_t nextsize;
JOCTET * nextbuffer;
my_mem_dest_ptr dest = (my_mem_dest_ptr) cinfo->dest;
/* Try to allocate new buffer with double size */
nextsize = dest->bufsize * 2;
nextbuffer = (JOCTET *)malloc(nextsize);
if (nextbuffer == NULL)
ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
memcpy(nextbuffer, dest->buffer, dest->bufsize);
if (dest->newbuffer != NULL)
free(dest->newbuffer);
dest->newbuffer = nextbuffer;
dest->pub.next_output_byte = nextbuffer + dest->bufsize;
dest->pub.free_in_buffer = dest->bufsize;
dest->buffer = nextbuffer;
dest->bufsize = nextsize;
return TRUE;
}
void
term_mem_destination (j_compress_ptr cinfo)
{
my_mem_dest_ptr dest = (my_mem_dest_ptr) cinfo->dest;
*dest->outbuffer = dest->buffer;
*dest->outsize = dest->bufsize – dest->pub.free_in_buffer;
}
void
jpeg_mem_dest (j_compress_ptr cinfo,
unsigned char ** outbuffer, unsigned long * outsize)
{
my_mem_dest_ptr dest;
if (outbuffer == NULL || outsize == NULL) /* sanity check */
ERREXIT(cinfo, JERR_BUFFER_SIZE);
/* The destination object is made permanent so that multiple JPEG images
* can be written to the same buffer without re-executing jpeg_mem_dest.
*/
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
cinfo->dest = (struct jpeg_destination_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(my_mem_destination_mgr));
}
dest = (my_mem_dest_ptr) cinfo->dest;
dest->pub.init_destination = init_mem_destination;
dest->pub.empty_output_buffer = empty_mem_output_buffer;
dest->pub.term_destination = term_mem_destination;
dest->outbuffer = outbuffer;
dest->outsize = outsize;
dest->newbuffer = NULL;
if (*outbuffer == NULL || *outsize == 0) {
/* Allocate initial buffer */
dest->newbuffer = *outbuffer = (unsigned char*)malloc(OUTPUT_BUF_SIZE);
if (dest->newbuffer == NULL)
ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 10);
*outsize = OUTPUT_BUF_SIZE;
}
dest->pub.next_output_byte = dest->buffer = *outbuffer;
dest->pub.free_in_buffer = dest->bufsize = *outsize;
}
//*******************************************************************************************
To use this do something like this in the main
/************/
unsigned long outlen;
unsigned char *outbuffer;
jpeg_mem_dest (&cinfo,&outbuffer,&outlen );
printf(“outlen is %lu\n”,(long unsigned int)outlen);
——————hope that helps ———————- if this works for you like me then if you could make tutorial on this that would help many!