OpenJPEG
| Language | C (usable from C++) |
|---|---|
| License | BSD-2-Clause |
| Repository | github.com/uclouvain/openjpeg |
| Documentation | API reference (Doxygen), wiki, and the opj_decompress.c / opj_compress.c sources |
| Depends on | nothing for the library; libpng, libtiff, lcms2 for the command-line tools |
| Read / write | yes / yes; JP2 and J2K codestreams, Part 1 and some Part 2; HTJ2K decoding (since 2.5.0) |
OpenJPEG is the JPEG 2000 implementation recognized by ISO/IEC and ITU-T as reference software. It’s the library behind JPEG 2000 support in Pillow, ImageMagick, libvips, GDAL, Poppler, MuPDF, Ghostscript and many DICOM toolkits. It decodes JP2, J2K/J2C and the codestream inside JPX files, and encodes JP2 and J2K, lossless and lossy.
The repository is marked “unmaintained”: there’s no committed maintainer team, but fixes still get merged and releases still appear. Decoding is slower than Grok or Kakadu, though multi-threaded decoding (opj_codec_set_threads) helps. Errors are reported through callbacks and boolean return values.
Installation#
# Debian / Ubuntu (library + opj_* tools)
sudo apt install libopenjp2-7-dev libopenjp2-tools
# Fedora
sudo dnf install openjpeg2-devel openjpeg2-tools
# macOS
brew install openjpeg
# Windows (vcpkg)
vcpkg install openjpeg
With CMake:
find_package(OpenJPEG REQUIRED)
target_include_directories(myapp PRIVATE ${OPENJPEG_INCLUDE_DIRS})
target_link_libraries(myapp PRIVATE openjp2)
Basic usage: decode a JP2 file#
The decoder gives you an opj_image_t with one OPJ_INT32 array per component. Components may have different sizes (subsampling) and bit depths, so check dx, dy and prec.
#include <openjpeg.h>
#include <stdio.h>
static void on_error(const char *msg, void *user) {
(void)user;
fprintf(stderr, "openjpeg: %s", msg);
}
int main(void) {
opj_dparameters_t params;
opj_set_default_decoder_parameters(¶ms);
opj_stream_t *stream = opj_stream_create_default_file_stream("in.jp2", OPJ_TRUE /*read*/);
if (!stream) return 1;
// OPJ_CODEC_JP2 for .jp2/.jpx files, OPJ_CODEC_J2K for raw .j2k/.j2c codestreams
opj_codec_t *codec = opj_create_decompress(OPJ_CODEC_JP2);
opj_set_error_handler(codec, on_error, NULL);
opj_image_t *image = NULL;
opj_codec_set_threads(codec, 4); // fails harmlessly if built without thread support
int ok = opj_setup_decoder(codec, ¶ms)
&& opj_read_header(stream, codec, &image)
&& opj_decode(codec, stream, image)
&& opj_end_decompress(codec, stream);
if (ok) {
printf("%u x %u, %u components, color space %d\n",
image->x1 - image->x0, image->y1 - image->y0, image->numcomps, image->color_space);
for (OPJ_UINT32 c = 0; c < image->numcomps; c++) {
opj_image_comp_t *comp = &image->comps[c];
printf("component %u: %u x %u, %u bits%s, first sample %d\n", c, comp->w, comp->h,
comp->prec, comp->sgnd ? " signed" : "", comp->data[0]);
}
}
opj_image_destroy(image);
opj_destroy_codec(codec);
opj_stream_destroy(stream);
return ok ? 0 : 1;
}
Link with -lopenjp2.
How to detect JP2 vs J2K#
Pick the codec from the first bytes of the file:
#include <string.h>
OPJ_CODEC_FORMAT detect_format(const unsigned char *buf, size_t len) {
static const unsigned char jp2_sig[12] = {0, 0, 0, 0x0C, 'j', 'P', ' ', ' ', 0x0D, 0x0A, 0x87, 0x0A};
static const unsigned char j2k_sig[4] = {0xFF, 0x4F, 0xFF, 0x51};
if (len >= 12 && memcmp(buf, jp2_sig, 12) == 0) return OPJ_CODEC_JP2;
if (len >= 4 && memcmp(buf, j2k_sig, 4) == 0) return OPJ_CODEC_J2K;
return OPJ_CODEC_UNKNOWN;
}
How to get the image size without decoding#
opj_read_header parses only the headers. The returned image has sizes and bit depths but no pixel data:
opj_image_t *image = NULL;
if (opj_setup_decoder(codec, ¶ms) && opj_read_header(stream, codec, &image)) {
printf("%u x %u, %u components, %u bits\n", image->x1 - image->x0,
image->y1 - image->y0, image->numcomps, image->comps[0].prec);
}
opj_image_destroy(image);
opj_get_cstr_info(codec) returns more codestream details: tile size, number of resolution levels and quality layers.
How to decode at reduced resolution or a region#
Discarding resolution levels is the fastest way to make a thumbnail: each level halves the width and height.
opj_dparameters_t params;
opj_set_default_decoder_parameters(¶ms);
params.cp_reduce = 2; // decode at 1/4 width and height
opj_setup_decoder(codec, ¶ms);
opj_read_header(stream, codec, &image);
// decode only a region, in full-resolution coordinates
opj_set_decode_area(codec, image, 1000, 1000, 3000, 2500);
opj_decode(codec, stream, image);
opj_end_decompress(codec, stream);
params.cp_layer limits the number of quality layers decoded.
How to encode a JP2 file#
Create an opj_image_t, fill the component arrays, then compress. This writes 8-bit RGB with 20:1 lossy compression:
#include <openjpeg.h>
#include <string.h>
int save_rgb_jp2(const char *path, const unsigned char *rgb, int w, int h) {
opj_cparameters_t params;
opj_set_default_encoder_parameters(¶ms);
params.tcp_numlayers = 1;
params.tcp_rates[0] = 20; // compression ratio; 0 = lossless
params.cp_disto_alloc = 1;
params.irreversible = 1; // 9/7 wavelet (lossy); 0 = 5/3 (lossless)
params.tcp_mct = 1; // RGB -> YCbCr
opj_image_cmptparm_t cmpt[3];
memset(cmpt, 0, sizeof(cmpt));
for (int i = 0; i < 3; i++) {
cmpt[i].dx = cmpt[i].dy = 1;
cmpt[i].w = w;
cmpt[i].h = h;
cmpt[i].prec = 8;
cmpt[i].sgnd = 0;
}
opj_image_t *image = opj_image_create(3, cmpt, OPJ_CLRSPC_SRGB);
if (!image) return 0;
image->x0 = 0;
image->y0 = 0;
image->x1 = w;
image->y1 = h;
for (int i = 0; i < w * h; i++) {
image->comps[0].data[i] = rgb[i * 3];
image->comps[1].data[i] = rgb[i * 3 + 1];
image->comps[2].data[i] = rgb[i * 3 + 2];
}
opj_codec_t *codec = opj_create_compress(OPJ_CODEC_JP2); // OPJ_CODEC_J2K for a raw codestream
opj_stream_t *stream = opj_stream_create_default_file_stream(path, OPJ_FALSE /*write*/);
int ok = stream
&& opj_setup_encoder(codec, ¶ms, image)
&& opj_start_compress(codec, image, stream)
&& opj_encode(codec, stream)
&& opj_end_compress(codec, stream);
if (stream) opj_stream_destroy(stream);
opj_destroy_codec(codec);
opj_image_destroy(image);
return ok;
}
For lossless output, set tcp_rates[0] = 0 and irreversible = 0. Several layers (e.g. tcp_numlayers = 3 with rates 40, 20, 10) make a file that can be decoded progressively.
How to convert from the command line#
opj_decompress -i in.jp2 -o out.png # decode
opj_decompress -i in.jp2 -o thumb.png -r 3 # discard 3 resolution levels (1/8 size)
opj_decompress -i in.jp2 -o part.png -d 0,0,1024,1024 # decode a region
opj_compress -i in.png -o out.jp2 # lossless
opj_compress -i in.png -o out.jp2 -r 40,20,10 -I # lossy, 3 quality layers, 9/7 wavelet
opj_dump -i in.jp2 # print boxes and codestream parameters
Tips#
- Pixel data is always
OPJ_INT32, one array per component, withw * hsamples. Scale withprecto get 8-bit values:v >> (prec - 8)whenprec > 8. - For YCbCr JP2 files with subsampled components (
dx,dy> 1), convert to RGB yourself;color_sycc_to_rgbin OpenJPEG’ssrc/bin/common/color.cshows how. - The ICC profile, if present, is in
image->icc_profile_buf/icc_profile_lenafter decoding. - Set a warning handler with
opj_set_warning_handlertoo: many real-world files produce warnings but decode fine.