All formats

libjpeg-turbo

LanguageC (usable from C++)
LicenseIJG License, BSD-3-Clause and zlib (all permissive)
Repositorygithub.com/libjpeg-turbo/libjpeg-turbo
DocumentationTurboJPEG API reference, libjpeg.txt (classic API)
Depends onnothing (NASM/YASM to build the x86 SIMD code)
Read / writeyes / yes; baseline, progressive, arithmetic coding, 12-bit and lossless JPEG; lossless transforms

libjpeg-turbo is a fork of the Independent JPEG Group’s libjpeg 6b with SIMD code for x86, ARM, PowerPC and others. It decodes and encodes 2-6 times faster than plain libjpeg and is the system JPEG library on most Linux distributions, Android, Chrome and Firefox. It comes with the cjpeg, djpeg and jpegtran command-line tools.

It has two APIs:

The tj3* API needs libjpeg-turbo 3.0 or later. Ubuntu 24.04 and Debian 13 still ship 2.1, which has only the older tjDecompress2/tjCompress2 functions; use vcpkg, Homebrew or a source build to get 3.x.

Installation#

Shell
# Debian / Ubuntu (TurboJPEG and libjpeg headers)
sudo apt install libturbojpeg0-dev libjpeg-turbo8-dev
# Fedora
sudo dnf install turbojpeg-devel libjpeg-turbo-devel
# macOS
brew install jpeg-turbo
# Windows (vcpkg)
vcpkg install libjpeg-turbo

With CMake, the libjpeg API is found with find_package(JPEG); libjpeg-turbo 3 also installs a CMake package for both APIs:

cmake
find_package(libjpeg-turbo CONFIG REQUIRED)
target_link_libraries(myapp PRIVATE libjpeg-turbo::turbojpeg)  # or libjpeg-turbo::jpeg

Basic usage: decode and encode a JPEG#

C
#include <turbojpeg.h>
#include <stdio.h>
#include <stdlib.h>

static unsigned char *read_file(const char *path, size_t *size) {
    FILE *f = fopen(path, "rb");
    if (!f) return NULL;
    fseek(f, 0, SEEK_END);
    long n = ftell(f);
    fseek(f, 0, SEEK_SET);
    unsigned char *buf = malloc(n);
    if (buf && fread(buf, 1, n, f) != (size_t)n) { free(buf); buf = NULL; }
    fclose(f);
    *size = (size_t)n;
    return buf;
}

int main(void) {
    size_t jpeg_size;
    unsigned char *jpeg = read_file("in.jpg", &jpeg_size);
    if (!jpeg) return 1;

    tjhandle dec = tj3Init(TJINIT_DECOMPRESS);
    if (tj3DecompressHeader(dec, jpeg, jpeg_size) < 0) {
        fprintf(stderr, "header: %s\n", tj3GetErrorStr(dec));
        return 1;
    }
    int width = tj3Get(dec, TJPARAM_JPEGWIDTH);
    int height = tj3Get(dec, TJPARAM_JPEGHEIGHT);
    printf("%d x %d, subsampling %d\n", width, height, tj3Get(dec, TJPARAM_SUBSAMP));

    unsigned char *pixels = tj3Alloc((size_t)width * height * tjPixelSize[TJPF_RGB]);
    if (tj3Decompress8(dec, jpeg, jpeg_size, pixels, 0 /*pitch*/, TJPF_RGB) < 0) {
        fprintf(stderr, "decode: %s\n", tj3GetErrorStr(dec));
        return 1;
    }

    // ... modify pixels (RGB, 3 bytes per pixel, top row first) ...

    tjhandle enc = tj3Init(TJINIT_COMPRESS);
    tj3Set(enc, TJPARAM_QUALITY, 85);
    tj3Set(enc, TJPARAM_SUBSAMP, TJSAMP_420);
    unsigned char *out = NULL;  // allocated by TurboJPEG
    size_t out_size = 0;
    if (tj3Compress8(enc, pixels, width, 0, height, TJPF_RGB, &out, &out_size) < 0) {
        fprintf(stderr, "encode: %s\n", tj3GetErrorStr(enc));
        return 1;
    }
    FILE *f = fopen("out.jpg", "wb");
    fwrite(out, 1, out_size, f);
    fclose(f);

    tj3Free(out);
    tj3Free(pixels);
    tj3Destroy(enc);
    tj3Destroy(dec);
    free(jpeg);
    return 0;
}

tj3LoadImage8 and tj3SaveImage8 read and write BMP/PPM files, handy for tests.

How to get JPEG width and height without decoding#

tj3DecompressHeader parses only the markers up to the first scan:

C
tjhandle h = tj3Init(TJINIT_DECOMPRESS);
if (tj3DecompressHeader(h, jpeg, jpeg_size) == 0) {
    printf("%d x %d, %s\n", tj3Get(h, TJPARAM_JPEGWIDTH), tj3Get(h, TJPARAM_JPEGHEIGHT),
           tj3Get(h, TJPARAM_PROGRESSIVE) ? "progressive" : "baseline");
}
tj3Destroy(h);

How to write a progressive JPEG with 4:4:4 sampling#

C
tjhandle enc = tj3Init(TJINIT_COMPRESS);
tj3Set(enc, TJPARAM_QUALITY, 92);
tj3Set(enc, TJPARAM_SUBSAMP, TJSAMP_444);   // full color resolution
tj3Set(enc, TJPARAM_PROGRESSIVE, 1);
tj3Set(enc, TJPARAM_OPTIMIZE, 1);           // optimal Huffman tables (baseline only)
tj3Compress8(enc, pixels, width, 0, height, TJPF_RGB, &out, &out_size);

How to rotate a JPEG losslessly#

tj3Transform works on DCT coefficients, like jpegtran, so the image isn’t re-compressed. TJXOPT_TRIM drops partial MCU blocks at the edge that can’t be rotated:

C
tjhandle tr = tj3Init(TJINIT_TRANSFORM);
tjtransform xform = {0};
xform.op = TJXOP_ROT90;
xform.options = TJXOPT_TRIM;  // add TJXOPT_PROGRESSIVE, TJXOPT_COPYNONE as needed
unsigned char *dst = NULL;
size_t dst_size = 0;
if (tj3Transform(tr, jpeg, jpeg_size, 1, &dst, &dst_size, &xform) < 0)
    fprintf(stderr, "%s\n", tj3GetErrorStr(tr));
// ... write dst, then tj3Free(dst) ...
tj3Destroy(tr);

Markers (Exif, ICC) are copied unless you pass TJXOPT_COPYNONE. The Exif orientation tag is copied unchanged, so set it to 1 yourself if you rotated to match it.

How to decode a scaled-down image (fast thumbnails)#

The decoder can skip DCT coefficients and produce ½, ¼ or 1/8 size output much faster than a full decode plus resize:

C
tjscalingfactor sf = {1, 4};  // 1/4 size
tj3SetScalingFactor(dec, sf);
int w = TJSCALED(width, sf), h = TJSCALED(height, sf);
unsigned char *small = tj3Alloc((size_t)w * h * 3);
tj3Decompress8(dec, jpeg, jpeg_size, small, 0, TJPF_RGB);

How to read Exif data with the libjpeg API#

Tell the decoder to keep APP1 segments, then look for the Exif\0\0 identifier. The rest of the segment is a TIFF structure to pass to an Exif parser:

C
#include <stdio.h>
#include <string.h>
#include <jpeglib.h>

int main(void) {
    FILE *fp = fopen("in.jpg", "rb");
    if (!fp) return 1;
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);  // default handler prints and exits
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, fp);
    jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xFFFF);
    jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
    jpeg_read_header(&cinfo, TRUE);

    printf("%u x %u, %d components\n", cinfo.image_width, cinfo.image_height, cinfo.num_components);
    for (jpeg_saved_marker_ptr m = cinfo.marker_list; m; m = m->next) {
        if (m->marker == JPEG_APP0 + 1 && m->data_length > 6 && memcmp(m->data, "Exif\0\0", 6) == 0)
            printf("Exif block: %u bytes\n", m->data_length - 6);
        else if (m->marker == JPEG_COM)
            printf("comment: %.*s\n", (int)m->data_length, m->data);
    }
    jpeg_destroy_decompress(&cinfo);
    fclose(fp);
    return 0;
}

jpeg_read_icc_profile(&cinfo, &icc, &icc_len) reassembles an ICC profile split across APP2 segments (call jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF) first).

Tips#