libjpeg-turbo
| Language | C (usable from C++) |
|---|---|
| License | IJG License, BSD-3-Clause and zlib (all permissive) |
| Repository | github.com/libjpeg-turbo/libjpeg-turbo |
| Documentation | TurboJPEG API reference, libjpeg.txt (classic API) |
| Depends on | nothing (NASM/YASM to build the x86 SIMD code) |
| Read / write | yes / 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:
- TurboJPEG (
turbojpeg.h, linkturbojpeg): decode or encode a whole image in one call, lossless transforms, scaled decoding. Version 3 (tj3*functions) uses a single handle with parameters set bytj3Set. Use it unless you need streaming. - libjpeg (
jpeglib.h, linkjpeg): the classic IJG API, compatible with code written for libjpeg 6b/8. Row-by-row I/O, full control, access to every marker. Errors are reported through an error manager (setjmp/longjmp).
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#
# 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:
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#
#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:
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#
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:
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:
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:
#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#
- Command-line tools:
cjpeg -quality 85 -progressive in.ppm > out.jpg,djpeg -scale 1/4 in.jpg > small.ppm,jpegtran -rotate 90 -copy all in.jpg > out.jpg. - For untrusted input set
TJPARAM_MAXPIXELS(reject huge images) andTJPARAM_SCANLIMIT(stop progressive files with thousands of scans). TJPARAM_STOPONWARNINGturns corrupt-data warnings into errors; by default the decoder returns a partly gray image.- libjpeg-turbo can decode and encode arithmetic-coded and lossless JPEG, but browsers can’t display them.