37

The instructions for libjpeg-turbo here describes the TurboJPEG API thus: "This API wraps libjpeg-turbo and provides an easy-to-use interface for compressing and decompressing JPEG images in memory". Great, but are there some solid examples of using this API available? Just looking to decompress a fairly vanilla jpeg in memory.

I've found a few bits such as https://github.com/erlyvideo/jpeg/blob/master/c_src/jpeg.c, which appears to be using the TurboJPEG API, but are there any more solid/varied examples?

The source for libjpeg-turbo is well documented, so that does help.

Theolodis
  • 4,977
  • 3
  • 34
  • 53
occulus
  • 16,959
  • 6
  • 53
  • 76

4 Answers4

69

Ok, I know that you did already solve your problem, but as some people, just like me, could be searching some simple example I will share what I created. It is an example, compressing and decompressing an RGB image. Otherwise I think that the API documentation of TurboJPEG is quite easy to understand!

Compression:

#include <turbojpeg.h>

const int JPEG_QUALITY = 75;
const int COLOR_COMPONENTS = 3;
int _width = 1920;
int _height = 1080;
long unsigned int _jpegSize = 0;
unsigned char* _compressedImage = NULL; //!< Memory is allocated by tjCompress2 if _jpegSize == 0
unsigned char buffer[_width*_height*COLOR_COMPONENTS]; //!< Contains the uncompressed image

tjhandle _jpegCompressor = tjInitCompress();

tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
          &_compressedImage, &_jpegSize, TJSAMP_444, JPEG_QUALITY,
          TJFLAG_FASTDCT);

tjDestroy(_jpegCompressor);

//to free the memory allocated by TurboJPEG (either by tjAlloc(), 
//or by the Compress/Decompress) after you are done working on it:
tjFree(&_compressedImage);

After that you have the compressed image in _compressedImage. To decompress you have to do the following:

Decompression:

#include <turbojpeg.h>

long unsigned int _jpegSize; //!< _jpegSize from above
unsigned char* _compressedImage; //!< _compressedImage from above

int jpegSubsamp, width, height;
unsigned char buffer[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image

tjhandle _jpegDecompressor = tjInitDecompress();

tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp);

tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, buffer, width, 0/*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT);

tjDestroy(_jpegDecompressor);

Some random thoughts:

I just came back over this as I am writing my bachelor thesis, and I noticed that if you run the compression in a loop it is preferable to store the biggest size of the JPEG buffer to not have to allocate a new one every turn. Basically, instead of doing:

long unsigned int _jpegSize = 0;

tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
          &_compressedImage, &_jpegSize, TJSAMP_444, JPEG_QUALITY,
          TJFLAG_FASTDCT);

we would add an object variable, holding the size of the allocated memory long unsigned int _jpegBufferSize = 0; and before every compression round we would set the jpegSize back to that value:

long unsigned int jpegSize = _jpegBufferSize;

tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
          &_compressedImage, &jpegSize, TJSAMP_444, JPEG_QUALITY,
          TJFLAG_FASTDCT);

_jpegBufferSize = _jpegBufferSize >= jpegSize? _jpegBufferSize : jpegSize;

after the compression one would compare the memory size with the actual jpegSize and set it to the jpegSize if it is higher than the previous memory size.

Theolodis
  • 4,977
  • 3
  • 34
  • 53
  • I'll try. The turbojpeg documentation says this should be the row length. I don't understand of output or input. anyway, I was mistaken. The sizes are reasonable. By the way - currently I'm loading (decompressing) jpeg, and cropping out regions into files. Is there a turbojpeg 2-liner for cropping an image and sending the output into a file? – mousomer Aug 19 '13 at 08:02
  • I did just that - memcpy the cropped region line-by-line into a buffer (2 line for loop), jpeg compressing (1 line) and writing to fstream (4 lines). I just thought, maybe, there's already a library function doing it already. – mousomer Aug 19 '13 at 11:02
  • 1
    **To those downvoting:** I am still maintaining this answer and I'd be glad to know, what you think is bad. So please, leave me a comment! – Theolodis Mar 10 '15 at 10:29
  • 1
    new tj version (1.4) has decompress to YUV planes: tjDecompressToYUVPlanes(c_jpegDecompressor, c_jpeg_buffer, _jpegSize, m_planesYUV, 0, NULL, 0, m_accuracy); – mousomer Mar 10 '15 at 14:32
  • Thank you, I once read through the original library's documentation and made distilled compress and decompress routines that I used for years. New to Turbo but I gather the difference is mostly using SIMDs. – Alan Corey Mar 04 '18 at 14:56
  • 2
    Turbojpeg's documentation link above is no longer valid, try https://libjpeg-turbo.org/Documentation/Documentation – Alan Corey Mar 04 '18 at 15:23
  • I thought a JPEG header included the number of bytes per pixel (there are grayscale ones, and cameras that can be arty and take black and white pictures). Seems like the header should contain all the info you need to malloc your uncompressed image space. But "the level of chrominance subsampling" (jpegSubsamp), what is that? Fancy name for the same thing? – Alan Corey Mar 04 '18 at 18:57
  • No, it's 1 on a normal color image and 1 on a grayscale, that's not what it is. – Alan Corey Mar 04 '18 at 19:28
  • did the API change ? there is no tj* functions in libjpeg-turbo – jean-loup Jun 15 '18 at 09:02
  • 2
    @jean-loup https://cdn.rawgit.com/libjpeg-turbo/libjpeg-turbo/master/doc/html/group___turbo_j_p_e_g.html were you maybe looking at the faster implementation of the jpeg library? Because libjpeg-turbo != Turbojpeg. – Theolodis Jun 15 '18 at 09:38
  • 1
    @Theolodis there is some confusion because the header of libjpeg-turbo is turbojpeg.h and libjpeg-turbo in ubuntu is not this lib.. but installed from the git repo it worked perfectly. – jean-loup Jun 15 '18 at 15:07
  • How much of a speedup do you see when comparing it to libjpeg? – Rachit Bhargava Aug 16 '18 at 02:32
  • @RachitBhargava "libjpeg-turbo is generally 2-6x as fast as libjpeg, all else being equal", see their documentation. – Theolodis Sep 07 '18 at 19:57
  • Thanks, good answer. But I'm worried about the encoding destination buffer - shouldn't you use `tjAlloc` or at least `TJFLAG_NOREALLOC`? How would the library know otherwise how to realloc your buffer? – Ray Sep 23 '20 at 15:35
  • `tjFree(&_compressedImage);` is wrong: there should be no `&`. – WhatsUp Jun 14 '23 at 15:24
8

I ended up using below code as a working example for both JPEG encoding and decoding. Best example that I can find, it's self-contained that initializes a dummy image and output the encoded image to a local file.

Below code is NOT my own, credit goes to https://sourceforge.net/p/libjpeg-turbo/discussion/1086868/thread/e402d36f/#8722 . Posting it here again to help anyone finds it's difficult to get libjpeg turbo working.

#include "turbojpeg.h"
#include <iostream>
#include <string.h>
#include <errno.h>

using namespace std;

int main(void)
{
    unsigned char *srcBuf; //passed in as a param containing pixel data in RGB pixel interleaved format
    tjhandle handle = tjInitCompress();

    if(handle == NULL)
    {
        const char *err = (const char *) tjGetErrorStr();
        cerr << "TJ Error: " << err << " UNABLE TO INIT TJ Compressor Object\n";
        return -1;
    }
    int jpegQual =92;
    int width = 128;
    int height = 128;
    int nbands = 3;
    int flags = 0;
    unsigned char* jpegBuf = NULL;
    int pitch = width * nbands;
    int pixelFormat = TJPF_GRAY;
    int jpegSubsamp = TJSAMP_GRAY;
    if(nbands == 3)
    {
        pixelFormat = TJPF_RGB;
        jpegSubsamp = TJSAMP_411;
    }
    unsigned long jpegSize = 0;

    srcBuf = new unsigned char[width * height * nbands];
    for(int j = 0; j < height; j++)
    {
        for(int i = 0; i < width; i++)
        {
            srcBuf[(j * width + i) * nbands + 0] = (i) % 256;
            srcBuf[(j * width + i) * nbands + 1] = (j) % 256;
            srcBuf[(j * width + i) * nbands + 2] = (j + i) % 256;
        }
    }

    int tj_stat = tjCompress2( handle, srcBuf, width, pitch, height,
        pixelFormat, &(jpegBuf), &jpegSize, jpegSubsamp, jpegQual, flags);
    if(tj_stat != 0)
    {
        const char *err = (const char *) tjGetErrorStr();
        cerr << "TurboJPEG Error: " << err << " UNABLE TO COMPRESS JPEG IMAGE\n";
        tjDestroy(handle);
        handle = NULL;
        return -1;
    }

    FILE *file = fopen("out.jpg", "wb");
    if (!file) {
        cerr << "Could not open JPEG file: " << strerror(errno);
        return -1;
    }
    if (fwrite(jpegBuf, jpegSize, 1, file) < 1) {
        cerr << "Could not write JPEG file: " << strerror(errno);
        return -1;
    }
    fclose(file);

    //write out the compress date to the image file
    //cleanup
    int tjstat = tjDestroy(handle); //should deallocate data buffer
    handle = 0;
}
Frank Chang
  • 229
  • 3
  • 6
4

In the end I used a combination of random code found on the internet (e.g. https://github.com/erlyvideo/jpeg/blob/master/c_src/jpeg.c) and the .c and header files for libjeg-turbo, which are well documented. This official API is a good information source aswell.

Theolodis
  • 4,977
  • 3
  • 34
  • 53
occulus
  • 16,959
  • 6
  • 53
  • 76
0

Here's a fragment of code what I use to load jpeg's from memory. Maybe it will require a bit of fixing, because I extracted it from different files in my project. It will load both - grayscale and rgb images (bpp will be set either to 1 or to 3).

struct Image
{
    int bpp;
    int width;
    int height;
    unsigned char* data;
};

struct jerror_mgr
{
    jpeg_error_mgr base;
    jmp_buf        jmp;
};

METHODDEF(void) jerror_exit(j_common_ptr jinfo)
{
    jerror_mgr* err = (jerror_mgr*)jinfo->err;
    longjmp(err->jmp, 1);
}

METHODDEF(void) joutput_message(j_common_ptr)
{
}

bool Image_LoadJpeg(Image* image, unsigned char* img_data, unsigned int img_size)
{
    jpeg_decompress_struct jinfo;
    jerror_mgr jerr;

    jinfo.err = jpeg_std_error(&jerr.base);
    jerr.base.error_exit = jerror_exit;
    jerr.base.output_message = joutput_message;
    jpeg_create_decompress(&jinfo);

    image->data = NULL;

    if (setjmp(jerr.jmp)) goto bail;

    jpeg_mem_src(&jinfo, img_data, img_size);

    if (jpeg_read_header(&jinfo, TRUE) != JPEG_HEADER_OK) goto bail;

    jinfo.dct_method = JDCT_FLOAT; // change this to JDCT_ISLOW on Android/iOS

    if (!jpeg_start_decompress(&jinfo)) goto bail;

    if (jinfo.num_components != 1 && jinfo.num_components != 3) goto bail;

    image->data = new (std::nothrow) unsigned char [jinfo.output_width * jinfo.output_height * jinfo.output_components];
    if (!image->data) goto bail;

    {
        JSAMPROW ptr = image->data;
        while (jinfo.output_scanline < jinfo.output_height)
        {
            if (jpeg_read_scanlines(&jinfo, &ptr, 1) != 1) goto bail;

            ptr += jinfo.output_width * jinfo.output_components;
        }
    }

    if (!jpeg_finish_decompress(&jinfo)) goto bail;

    image->bpp = jinfo.output_components;
    image->width = jinfo.output_width;
    image->height = jinfo.output_height;

    jpeg_destroy_decompress(&jinfo);

    return true;

bail:
    jpeg_destroy_decompress(&jinfo);
    if (image->data) delete [] data;

    return false;
}
Mārtiņš Možeiko
  • 12,733
  • 2
  • 45
  • 45
  • 1
    Sorry to unaccept your answer, but on looking closely now I'm actually implementing this I can see you're not even calling tj functions in the code you posted (e.g. tjDecompress). – occulus Feb 15 '12 at 16:15
  • 1
    It is not mandatory to use TurboJpeg API to get speed benefits from libjpeg-turbo library. In my example I'm using just standard libjpeg API. Why you don't want to use libjpeg API? – Mārtiņš Možeiko Feb 15 '12 at 18:17
  • 4
    My question's pretty clearly asking about the TurboJPEG API. I am using it due to its simplicity compared to the standard libjpeg interface. – occulus Feb 18 '12 at 03:06