MozJPEG’s API is built around the concept of destination and source managers. As their respective name implies, destination and source managers tell MozJPEG where to read/write data before/after compression. The default ones are jpeg_stdio_src
/jpeg_stdio_dest
which read/write data from a file stream, but there is also the jpeg_mem_src
/jpeg_mem_dest
that uses memory.
In one of my previous posts (in my personal blog), I presented a full example of how to do image compression using MozJPEG’s API. The example can be found in this gist. The examples uses jpeg_stdio_src
/jpeg_stdio_dest
as destination managers. In this post, I would like to build on that example to demonstrate the usage of jpeg_mem_src
and jpeg_mem_dest
. Some benefits of using a memory buffer over a file stream are reducing CPU cycles, and increasing read/write speed.
Step 1: reading the whole file into a buffer:
There are different ways to read a whole file into memory in C language, but in our case we can get some inspiration from the already existing djpeg
code here:
unsigned char *inbuffer = NULL;
unsigned long insize = 0;
size_t nbytes;
do {
inbuffer = (unsigned char *)realloc(inbuffer, insize + INPUT_BUF_SIZE);
if (inbuffer == NULL) {
fprintf(stderr, "memory allocation failure\n");
exit(EXIT_FAILURE);
}
nbytes = fread(&inbuffer[insize], 1, INPUT_BUF_SIZE, input_file);
if (nbytes < INPUT_BUF_SIZE && ferror(input_file)) {
fprintf(stderr, "can't read from %s\n", filename);
}
insize += (unsigned long)nbytes;
} while (nbytes == INPUT_BUF_SIZE);
//we initialize our source using the decompress struct we already created, the input buffer, and the buffer size
jpeg_mem_src(&dinfo, inbuffer, insize);
The code goes through the input image data and reads chunk by chunk with each chunk having a maximum size of INPUT_BUF_SIZE
(which is a constant that has 4096 as value). With each loop, our input buffer is resized using realloc
to accomodate more bytes. Once our read data size is less than the INPUT_BUF_SIZE
, we know that we have reached the end and we escape the loop. Finally, we call jpeg_mem_src
with our buffer to initialize the source of the image data.
Step 2: preparing our output buffer:
The next step is to configure the output buffer to which data will be written after compression:
unsigned char *outbuffer = NULL;
unsigned long outsize = insize;
jpeg_mem_dest(&cinfo, &outbuffer, &outsize);
As simple as that. jpeg_mem_dest
takes care of doing the memory allocation (as you can see in the source code) for the buffer so we can provide a null buffer without worry.
Full source code:
https://gist.github.com/zak905/ea18633a210cde8adb3cc03e6a8ec92f