JPEG 2000
editJPEG 2000 compresses images with wavelets, without loss or with loss, up to 16 bits per sample. The library reads JP2 files and bare codestreams, and Jpeg2000Encoder writes lossless JP2 files by default.
What is read and written
| Reading | Writing | |
|---|---|---|
| Files | JP2 files, JPX and JPH files of one codestream, bare codestreams | JP2 files (default), JPH files, or bare codestreams |
| Coding | Lossless (5-3) and lossy (9-7) wavelets, tiles, layers, every progression order, regions of interest, the high-throughput block coder (HTJ2K) | Lossless or lossy; tiles, quality layers with size targets (rate control), the five progression orders, up to 32 decomposition levels; the high-throughput block coder |
| Samples | 1 to 16 bits; gray, RGB, sYCC, palettes, opacity; CMYK with a color conversion | Every pixel format at its own depth (Gray8, Gray16, Rgb24, Rgba32, Bgra32, Rgba64), lossless opacity |
| Metadata | ICC profile, EXIF, XMP, resolution | The same, in a JP2 file only |
Files of at most 8 bits load as Gray8, Rgb24 or Rgba32; deeper files as Gray16, Rgb48 or Rgba64. The EXIF orientation is reported, never applied.
Save a JPEG 2000 file
public static string SaveJpeg2000(string inputPath, string jp2Path, string codestreamPath)
{
using var image = Image.Load(inputPath);
// Lossless by default: every pixel format is stored as it is, 16-bit samples and the colors of fully
// transparent pixels included. A JP2 file stores the ICC profile, the resolution, EXIF and XMP.
image.Save(jp2Path);
// Lossy: the quality sets the quantization, on the scale of the JPEG encoder; the opacity stays exact.
// A codestream alone (.j2k, .j2c, .jpc) stores no metadata: a strict save rejects an image that has some.
image.Save(codestreamPath, new Jpeg2000Encoder
{
Compression = Jpeg2000Compression.Lossy,
Quality = 80,
Container = Jpeg2000Container.Codestream,
MetadataHandling = MetadataHandling.DiscardUnsupported,
});
// Files, codestreams and the high-throughput block coder (HTJ2K) are decoded, whatever wrote them.
var info = Image.Identify(jp2Path);
return $"{info.Format} {info.Size.Width}x{info.Size.Height} {info.PixelFormat}, {info.BitsPerComponent} bits";
}The .jp2 extension writes a JP2 file, and .j2k, .j2c and .jpc a codestream, both lossless; .jph and .jhc write them with the high-throughput block coder (see below).
Options
| Option | What it does | Default |
|---|---|---|
| Compression | Lossless or Lossy | Lossless |
| Quality | 1 (smallest) to 100 (best) for lossy output, on the scale of JpegEncoder.Quality; 100 is still lossy | 90 |
| Container | Jp2 (a file with metadata) or Codestream (no metadata) | Jp2 |
| QualityLayers | The quality layers, each with a target size or number of bits per pixel (Jpeg2000QualityLayer) | One layer with every coding pass |
| ProgressionOrder | The order of the packets: Lrcp, Rlcp, Rpcl, Pcrl or Cprl | Lrcp |
| TileWidth, TileHeight | The size of the tiles, 0 for the size of the image | 0 (one tile) |
| DecompositionLevels | The largest number of wavelet levels, 0 to 32, reduced for small tiles | 5 |
| BlockCoder | Standard (Part 1) or HighThroughput (HTJ2K, Part 15) | Standard |
Quality sets the quantization, not a size: without a target, the size of the file follows from the image. On photos, a quality gives about the visual similarity of a JPEG file of the same quality.
Target sizes, quality layers and tiles
A layer with a target (Jpeg2000QualityLayer.FromSize or FromBitsPerPixel) is rate control: the encoder truncates the coding passes of the code-blocks where they reduce the error most for their bytes (the rate-distortion optimization of the standard), so the file has at most the target size, usually within 1% of it. Several layers make a file a reader can stop early in: the first layers of an Lrcp file are its first bytes. On photographs, the quality at a given size is the one of OpenJPEG's own rate control (opj_compress -r).
public static (long SmallLength, long LayeredLength) SaveJpeg2000Layers(string inputPath, string smallPath, string layeredPath)
{
using var image = Image.Load(inputPath);
// A file of at most 40 KB: the rate control keeps the coding passes that reduce the error most for their bytes.
// Quality only sets the finest quantization, so 100 leaves every choice to the target.
image.Save(smallPath, new Jpeg2000Encoder
{
Compression = Jpeg2000Compression.Lossy,
Quality = 100,
QualityLayers = [Jpeg2000QualityLayer.FromSize(40_000)],
});
// Three quality layers: a reader that stops after the first 0.25 bit per pixel (the first bytes of the file in the
// LRCP order) gets a preview, after 1 bit per pixel a good image, and the last layer makes it lossless. Tiles of
// 256x256 bound the memory of the encoder, and a reader can decode one tile alone.
image.Save(layeredPath, new Jpeg2000Encoder
{
QualityLayers = [Jpeg2000QualityLayer.FromBitsPerPixel(0.25), Jpeg2000QualityLayer.FromBitsPerPixel(1), Jpeg2000QualityLayer.Complete],
ProgressionOrder = Jpeg2000ProgressionOrder.Lrcp,
TileWidth = 256,
TileHeight = 256,
});
return (new FileInfo(smallPath).Length, new FileInfo(layeredPath).Length);
}Tiles are coded one at a time: the encoder holds the wavelet coefficients of one tile instead of the whole image, and a reader can decode a tile alone. Small tiles cost bytes and, for lossy output, quality at their edges.
High-throughput JPEG 2000 (HTJ2K)
BlockCoder = HighThroughput codes each code-block with one pass of the high-throughput block coder of Part 15 of the standard instead of the arithmetic coder of Part 1. The wavelets, the quantization, the tiles and the progression orders are the same, and so are the decoded samples: a lossless file is exact, and a lossy one decodes to the samples of the standard coder at the same quality. On photographs, a lossless file is coded about 4 times and decoded 6 times faster, for about 8% more bytes; a lossy one 1.2 to 3.7 times faster, for 7% to 11% more. A file is a JPH file (.jph), which readers of Part 15 read (OpenJPEG 2.5, FFmpeg, OpenJPH), not every JP2 reader.
public static void SaveHighThroughputJpeg2000(string inputPath, string jphPath, string codestreamPath)
{
using var image = Image.Load(inputPath);
// The .jph extension writes a JPH file whose code-blocks use the high-throughput block coder: lossless by
// default, several times faster to encode and decode than the standard block coder, for a few percent more bytes.
// Its readers must support Part 15 of the standard, as OpenJPEG 2.5, FFmpeg and OpenJPH do.
image.Save(jphPath);
// A codestream (.jhc) of at most 100 KB: the rate control chooses the lowest bit-plane of each code-block, and the
// output has a single quality layer.
image.Save(codestreamPath, new Jpeg2000Encoder
{
BlockCoder = Jpeg2000BlockCoder.HighThroughput,
Compression = Jpeg2000Compression.Lossy,
Quality = 100,
Container = Jpeg2000Container.Codestream,
QualityLayers = [Jpeg2000QualityLayer.FromSize(100_000)],
MetadataHandling = MetadataHandling.DiscardUnsupported,
});
}The pass codes whole bit-planes: an HTJ2K file has one quality layer, and a target is reached by choosing the lowest bit-plane of each code-block. It fits the target as closely, at 0.3 to 0.9 dB of PSNR below the standard coder, and the coding of the candidates makes it barely faster than the standard coder.
Thumbnails and viewports
ImageDecodeOptions.ScaleDenominator (or TargetSize) decodes a lower resolution level: each halving discards one level of the wavelet transformation, up to the number of levels of the file (5 for the files of the library), and the work and the memory shrink with the number of pixels. ImageDecodeOptions.Region decodes an area: the tiles it does not meet are skipped, and in the others only the code-blocks that reach it are decoded, and the wavelet transformation only rebuilds the samples it needs, so the work and the memory follow the area even in an image of one tile. The samples are those of a full decode (OpenJPEG's -r and -d give the same ones). The compressed data of a tile that the area meets is still read whole: tiles make the viewports of very large files cheaper.
public static (Image Thumbnail, Image Preview, Image Viewport) LoadJpeg2000Parts(string path)
{
// A thumbnail at a quarter of the size: the decoder discards the two highest resolution levels of the wavelet
// transformation, for about a sixteenth of the work and of the memory
var thumbnail = Image.Load(path, new ImageDecodeOptions { ScaleDenominator = 4 });
// The smallest image of at least 200x100 pixels the file allows (at most 1/32 with 5 decomposition levels)
var preview = Image.Load(path, new ImageDecodeOptions { TargetSize = new Size(200, 100) });
// A viewport: only the tiles and the code-blocks that reach the area are decoded, to the pixels of a full decode.
// With ScaleDenominator, the area is divided like the image
var viewport = Image.Load(path, new ImageDecodeOptions { Region = new Rectangle(300, 200, 400, 300) });
return (thumbnail, preview, viewport);
}Pitfalls
- A codestream stores no metadata at all: with the default MetadataHandling.Strict, an image with an ICC profile, EXIF, XMP or a resolution is rejected. Use a JP2 file, or set MetadataHandling.
- A JP2 file only stores grayscale and matrix-based RGB ICC profiles; another profile follows MetadataHandling.
Save(path)rejects the.jpxand.jpfextensions with anArgumentException: those files are read, not written.- An HTJ2K output has one quality layer: QualityLayers with more than one layer is an
ArgumentExceptionwhen the image is saved. - A target smaller than the headers and the empty packets cannot be reached: the file then has no coded data. A lossless output whose last layer has a target is not lossless when the target cuts coding passes: end with Jpeg2000QualityLayer.Complete to keep it lossless.
- The decoder decodes a tile as a whole: a very large image of one tile can exceed the default memory budget, unless it is decoded at a reduced scale. See Resource limits.
- A region is in pixels of the stored image at full resolution, before the EXIF orientation; a region narrower than a sample of the reduced resolution keeps the sample that covers it.
- CMYK files load only with ImageDecodeOptions.CmykConversion (see Color management).
- Signed samples, samples of more than 16 bits, components of different depths, premultiplied opacity and the extensions of Part 2 of the standard are rejected with an UnsupportedImageFeatureException.
- A codestream ends with its end marker: a file that is cut is an InvalidImageContentException, never a smaller image.