Notes on JPEG 2000 for C++ devs

JPEG 2000 (JP2, JP2K, JP2000, with extension .jp2, .j2k), you have several options:

Name Licence Notes
JasPer Open-source See below
OpenJPEG Open-source (BSD) See below
Kakadu Proprietary ($) Industry standard
I have successfully implemented the above libraries in software written in C++ for scanning software. The images produced by the software had to conform to very strict industry standards, and this post is made to provide some insights into that.

JPEG 2000 – ‘Jungle 2000’

The lore is that JP2 was supposed to be ‘the new jpeg (.jpg, .jpeg)’ after the turn of the millenium. Long story short: nope. It’s designed with a brilliant way of compressing, either lossy or lossless, with a really great compression size. The fact that it can be compressed with tiles (precincts for nerds) means that you can easily decode only specific regions, nice for viewing huge files for example.

Verify JP2’s integrity

For this you can use jpylyzer. It’s a great one-of-a-kind free (though funded) initiative. It usually performs well, but I have seen edge cases where it marks viewable files als false and seriously corrupt files as good. Oh boy.

Metadata and Color Profiles

If you want to inject metadata inside a jp2 or j2k file, good luck making this compatible across software packages. The fact that this has not been accurately described with the conception of the standard, it means it’s messy. Including metadata from photoshop or Exiftool or Kakadu is not guaranteed to be readable by software that did not inject it itself. Color-profiles are even more fun, as (I believe until ~2014?) it was not allowed to include a color profile other than just a transformation matrix. Hence, technically, if it has something industry standard like eciRGBv2, the jp2 isn’t to spec.. I have successfully forced in color profiles, but again, not in a way that all packages accept it. Even Kakadu does not allow injection of extended metadata types.


Oh boy did I get frustrated here. I’m a fan of open-source and so my journey started with JasPer and OpenJPEG. They had to do all kinds of things with the same package: (1) lossless and (2)lossy compression, (3) 8-bit and (4) 16-bit and (5) different sizes and (6) allow a custom color profile and (7) allow metadata injection and most importantly the files should be interpreted correctly with different software that customers might use: Microsoft preview, OS-X preview, Adobe Photoshop, Imagemagick, GIMP, ACDSee8, Adobe Lightroom. To make another long story short, I have been unable to get stable results where the files can be opened reliably, repeatably and correctly in the same way across all packages. Kakadu has rarely generated incompatible results. In a professional setting, usually you need to include metadata and custom things. The only package that allows injection in a reliable method that can generally be read by other software is Kakadu.

Speed and Multithreading

Although the compression size is really great (it usually beats TIFF’s ZIP and LZW) it’s really slow. Because OpenJPEG and JasPer don’t really have (great) multithreading, those two fall short for me, because I need speed. Because Kakadu’s multithreading is really professional and great (and uses advanced processor instructions) I have benchmarked different CPU’s and computer configurations, the table is posted somewhere below.

Kakadu Speed Benchmarks

Although it’s commercial, Kakadu is really the only choice for making JP2000’s files in a professional environment. It’s library and code is also one of the cleanest, most well written and best documented libraries I have worked with. It has great multithreaded and advanced processor-instruction support. All that it needs now is some GPU support. (there is a JPEG2000 encoder for GPU’s (CUDA) by the way – use at own risk). Used Kakadu version: 7.6. You can see the performances scales very well with multiple threads, and hyper threading gives a near 20% boost. The command:
time kdu_compress -i iiq.tiff Creversible=yes Clayers=1 Clevels=7 "Cprecincts={256,256},{256,256},{256,256},{128,128},{128,128},{64,64},{64,64},{32,32},{16,16}" Corder=RPCL ORGgen_plt=yes ORGtparts=R "Cblk={64,64}" Cuse_sop=yes -quiet -num_threads $variable
CPU(s) Computer Sockets Physical Cores HT Speed (s)
2xE5 2650 v3 Rack 2 20 OFF 1.04
2xE5 2650 v3 Rack 2 20 ON 0.8
1xE5 1650 v2 Mac Pro 6c 1 6 ON 1.64
1xE5 2697 v2 Mac Pro 12c 1 12 ON 1.05
i5-4258U Macbook Pro 13″ 1 2 ON 6.7
i7-3720QM iMac Mini 1 4 ON 2.6
E3-1275 V2 Rack 1 4 OFF 2.7
E3-1275 V2 Rack 1 4 ON 1.89
*HT=Hyper Threading *Speed (s)= Speed of decoding a 80 megapixel image with said settings. kdu_perf See the full and more elaborate Google Sheet with the benchmarks here: Google Sheet.

OpenCV and Kakadu

In my own library I had efficiently bridged Kakadu’s jpeg 2000 library with OpenCV. That code is a bit long for this post, if you’d like to have it, just drop me an email and i’ll send it over.


Don’t use JP2 unless you totally have to. If you have to, start with Kakadu and test whether the thing you are creating works well on a whole range of different software packages. If that works, the process is probably repeatable. Let’s hope JP2 advanced forward with a very extensive open-source jp2 library.]]>

You may also like...

1 Response

  1. Johan van der Knijff says:

    Hi Tim,
    This is a very useful write-up (and I’m not surprised with the results you ended up with). A couple of minor remarks. First about jpylyzer (I’m the lead author of that tool):

    I have seen edge cases where it marks viewable files als false and seriously corrupt files as good.

    As for the first case: there can be various causes for this. For instance, if a JP2 contains an ICC profile of a type that is not allowed by the spec (e.g. because it is LUT-based), this causes the validation to fail. Nevertheless, most viewers will display such files without problems (even though the color space may not be interpreted correctly). Also, most viewers follow Postel’s law in that they are fairly forgiving for minor deviations from the spec.
    The second case (jpylyzer marks a corrupted images as “valid”) is more troublesome. The most likely cause is that jpylyzer is only capable of limited parsing of the image code stream; it cannot establish if the actual image payload is readable (this would require a full decode of the image, which is beyond jpylyzer’s scope). See also the jpylyzer documentation for more details (as well as a suggestions on how to deal with these limitations:
    Next you mention ICC profiles:

    Hence, technically, if it has something industry standard like eciRGBv2, the jp2 isn’t to spec.

    No, this is actually allowed since the May 2013 revision of the JP2 filespec. I summarises the changes here:
    eciRGBv2 uses a matrix-based display profile, which is now allowed in JP2.