libjpeg example – encode JPEG to memory buffer instead of file

Q: How can I use libjpeg to encode directly to memory on my embedded system without using a file?

A: If you’re encoding images into JPEG on your ARM/Linux embedded platform then you’re most likely using libjpeg, and if you’re encoding quickly then you’re most likely using its drop-in replacement libjpeg-turbo!

Most of the examples of how to use the libjpeg API show how to encode directly to a file, but it’s also possible to use the API to encode to a memory buffer instead. This is handy if you want to transmit the JPEG via MQTT or something and don’t actually need a JPEG file – you can avoid the overhead of writing to your flash filesystem and having to read the data from it before sending.

Of course, one easy way of achieving this if your embedded system has a RAM disk (as a lot of ARM/Linux based systems will) is to just get libjpeg to encode to file on the RAM disk, in this way you can avoid writing to flash, however you will still have to open the file and read in all of the data into a buffer afterwards.

The trick to getting libjpeg to encode directly to  memory is make a call to jpeg_mem_dest() (instead of to jpeg_stdio_dest() for a file), this allows the caller to specify an output buffer for the jpeg data (as well as its size).

You can either supply a pointer to a buffer, or if you pass NULL, the library will allocate a buffer for you – either way, you must free() the output buffer once you are finished with it!

Some things to note are:

  • You can pass in a pointer to a buffer that you have already malloc()’ed
  • If you pass in NULL, the lib will allocate a buffer for you.
  • If the buffer that you specify (or that the lib automatically malloced()’ed) turns out to be too small to hold the JPEG data then the lib will free() the buffer and malloc() a new one (this will probably also involve a memory copy).
  • After encoding the size variable will contain the number of JPEG bytes in the output buffer (i.e. it no longer contains the size of the buffer used!)
  • If you want to re-use the same buffer for each encode, then just pass in the same buffer pointer each time with it’s original size and don’t free() it after encoding.

Here is an example of a c++ function that encodes to memory, it uses a buffer that’s allocated by the library:

 

//
// Encodes a 256 Greyscale image to JPEG directly to a memory buffer
// libJEPG will malloc() the buffer so the caller must free() it when
// they are finished with it.
//
// image    - the input greyscale image, 1 byte is 1 pixel.
// width    - the width of the input image
// height   - the height of the input image
// quality  - target JPEG 'quality' factor (max 100)
// comment  - optional JPEG NULL-termoinated comment, pass NULL for no comment.
// jpegSize - output, the number of bytes in the output JPEG buffer
// jpegBuf  - output, a pointer to the output JPEG buffer, must call free() when finished with it.
//
void encode_jpeg_to_memory(unsigned char* image, int width, int height, int quality,
                            const char* comment, unsigned long* jpegSize, unsigned char** jpegBuf) {
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    JSAMPROW row_pointer[1];
    int row_stride;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    cinfo.image_width = width;
    cinfo.image_height = height;
    // Input is greyscale, 1 byte per pixel
    cinfo.input_components = 1;
    cinfo.in_color_space = JCS_GRAYSCALE;
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE);
    //
    //
    // Tell libJpeg to encode to memory, this is the bit that's different!
    // Lib will alloc buffer.
    //
    jpeg_mem_dest(&cinfo, jpegBuf, jpegSize);
    jpeg_start_compress(&cinfo, TRUE);
    // Add comment section if any..
    if (comment) {
        jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET*)comment, strlen(comment));
    }
    // 1 BPP
    row_stride = width;
    // Encode
    while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = &image[cinfo.next_scanline * row_stride];
        jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
}

The we can use this function to encode an 8bit image like this:

 

void test_encode_jpeg_to_memory() {
    int width = 1920;
    int height = 1080;
    // Create an 8bit greyscale image
    unsigned char* image = (unsigned char*)malloc(width * height);
    // With a pattern
    for (int j = 0; j != height; j++) {
        for (int i = 0; i != width; i++)
            image[i + j * width] = i + j;
    }
    // Will hold encoded size
    unsigned long jSize = 0;
    // Will point to JPEG buffer
    unsigned char* jBuf = NULL;
    // Encode image
    encode_jpeg_to_memory(image, width, height, 85, "A Comment!", &jSize, &jBuf);
    printf("JPEG size (bytes): %ld", jSize);
    //
    // Now, do something with the JPEG image in jBuf like stream it over UDP or sommit!
    //
    // Free jpeg memory
    free(jBuf);
    // Free original image
    free(image);
}