Challenge: Construct a JPEG 2000 image file that isn’t compressed.
Also, try to do it without spending any money.
The flagship feature of the JPEG 2000 suite of graphics formats is the wavelet-based “JPEG 2000 codestream” compression format. This challenge is not to figure out how to make a degenerate form of that format that isn’t really compressed. The challenge is to not use that format at all.
What most people think of as “JPEG 2000 format” is either the raw codestream format, or the JP2 wrapper format. (Note that JP2 is not an abbreviation of JPEG 2000.) JP2 mandates using the codestream format. So, neither of those formats are going to work. But there are two other file formats that fall under the “JPEG 2000” umbrella, that seem to allow non-compressed images: JPM and JPX.
JPM is a “compound” image format, primarily intended for situations where you want to use different compression schemes for different parts of the same image.
JPX is an “everything and the kitchen sink” extension of JP2.
I decided to try JPX, as it seems like the more correct format for this task.
A problem is that I haven’t found any freely-available JPEG 2000 software that might be able to support such a file. As far as I can tell, OpenJPEG, Grok, and JasPer do not support it. There might be some commercial products that do.
That’s a shame. I’ll have no way to know if I did it right. But I’m going to forge ahead.
Most of the JPEG 2000 specifications documents cost money. But the JPM-specific part of it is available for free, as ITU-T Rec. T.805, and there are two relevant portions of the JP2/JPX specifications that… well, I’m not sure what their status is. I’m referring to:
- ISO/IEC 15444-1 (JPEG 2000: Core coding system) Annex I: JP2 file format syntax
- ISO/IEC 15444-2 (JPEG 2000: Extensions) Annex M: JPX extended file format syntax
They were freely available at the jpeg.org website for a long time, at
I don’t think that could have been an accident, and I can still find copies of them on the web. I’m going to assume it’s okay to use them. I think they have just about all the information I’ll need.
I’ll try to make a JPX file containing a small fully-opaque uncompressed grayscale image, with 8 bits per pixel. That should be a nice easy image type.
JPX files are formatted using a standard hierarchical segmented structure. A segment is called a “box”. Each box starts with 4 bytes giving its total size, then 4 bytes giving its type (as ASCII characters).
There are not many box types that are always required in a JPX file. At the top level, there’s ‘jP’, ‘ftype’, and ‘rreq’. Boxes ‘ihdr’ and ‘colr’ are also mandatory, but they go inside other boxes.
And we’ll have to put the uncompressed image data somewhere. As far as I can tell, the best place for it is a ‘jp2c’ box.
The few JPX files I’ve found on the web are all designed to be compatible with JP2 format, and so they contain a JP2 Header (‘jp2h’) box. This box contains the ‘ihdr’ and ‘colr’ boxes. But our uncompressed file cannot have a ‘jp2h’ box. In this case, the ‘ihdr’ box goes in a Codestream Header (‘jpch’) box. And the ‘colr’ box goes in Colour Group (‘cgrp’) box, which in turn goes in a Compositing Layer Header (‘jplh’).
I don’t see any other boxes I’ll need. I don’t think I need a Component Mapping (‘cmap’) box, or Channel Definition (cdef) box, or anything like that.
So, the outline of our file is:
jP ftyp rreq jpch ihdr jplh cgrp colr jp2c
Now, I’ll go through the individual boxes.
The file starts with a ‘jP’ signature box, which is always the same 12 bytes for all the JPEG 2000 formats.
The JPEG 2000 formats use identifiers called “brands”, which are listed in the ‘ftyp’ box. A brand is a four-character code that identifies a file format, sub-format, or something like that. Each file has a primary brand, plus any number of “compatibility” brands.
An uncompressed image will make our file incompatible with the JP2 brand (‘jp2’), and even with the “Baseline JPX” brand (‘jpxb’). So, we’ll use only the main ‘jpx’ brand. It should appear in the compatibility list as well.
The Reader Requirements box (‘rreq’) is the most complex part of this challenge. The standard defines a numbered list of “features”. Roughly speaking, the ‘rreq’ box tells a decoder which features are (1) used in the file, and (2) required in order to meaningfully decode the file. It’s even possible to express simple conditionals like “either feature-1 or feature-2 is required”.
While I could just make a box that says nothing, I want to do this right. Unfortunately, the specification does not say very much about how to decide which features to declare. The JPX files I’ve collected are of some help, but each one seems to have a slightly different opinion of what should go in the ‘rreq’ box.
Some of the available features are actually the lack of a feature. You can express that your file lacks opacity information. That’s nice for the which-features-are-used list. Some JPX files also list such non-features in their which-features-are-required list, but I don’t think it really makes sense to do that.
I decided on the following feature lists:
- no opacity (8)
- contiguous (12)
- no layers (18)
- one codestream per layer (20)
- scaling not required (31)
- sRGB-gray (46)
- sRGB-gray (46)
The only thing to note about the Image Header (‘ihdr’) box is that the compression method field will be set to 0 for “uncompressed”, instead of the usual 7 for “JPEG 2000 compression”. Otherwise, it’s the same as in an ordinary JP2 file.
In the Colour Specification (‘colr’) box, we use an enumerated color type of 17, meaning grayscale.
The ‘colr’ box has a “gotcha”: The “APPROX” field is usually 0 in JP2, but cannot be 0 in JPX. Not that setting APPROX to 0 would be likely to cause any problems, but I’ll set it to 1, basically meaning “the colors are exactly what we say they are”.
The format for uncompressed image data is described in table M.19 of Annex M. Our image is 8 bits per pixel, and rows are padded to the next byte boundary, so we won’t use any padding.
(Side note: I think there’s an error in the version of Annex M that I’m using. It says samples are packed into bytes, then says each sample shall begin on a byte boundary. That’s contradictory. It would make sense if it said each scan line begins on a byte boundary. This isn’t relevant to our image, though.)
Putting it together
I wrote the following simple Python script to generate the file. It writes a file named unctest.jpf. (Incidentally, I don’t know whether the standard filename extension for JPX format is “.jpx”, or “.jpf”. Annex M says “.jpf”.)
#!/usr/bin/env python3 # mkuncjpx.py: An attempt to generate an example of an # uncompressed JPEG 2000 file. Public domain /js # Write a string (or whatever) def wr(f, s): f.write(s) # Write hex bytes parsed from a string def wh(f, s): f.write(bytes.fromhex(s)) # Write a box header def startbox(f, box_type, payload_len): f.write((8+payload_len).to_bytes(4, byteorder='big')) wr(f, box_type) f = open('unctest.jpf', 'wb') startbox(f, b'jP ', 4) wh(f, '0d 0a 87 0a') # standard contents of signature box startbox(f, b'ftyp', 12) wr(f, b'jpx ') # brand wh(f, '00000000') # version wr(f, b'jpx ') # compatibility brand(s) startbox(f, b'rreq', 25) wh(f, '01') # mask size in bytes wh(f, 'fc 04') # FUAM, DCM masks wh(f, '0006') # number of standard feature items wh(f, '0008 80') # 8: no opacity wh(f, '000c 40') # 12: contiguous wh(f, '0012 20') # 18: no layers needed wh(f, '0014 10') # 20: 1 codestream per layer wh(f, '001f 08') # 31: scaling not req'd wh(f, '002e 04') # 46: sRGB-gray wh(f, '0000') # number of vendor feature items startbox(f, b'jpch', 22) startbox(f, b'ihdr', 14) wh(f, '00000008 0000000c') # height, width wh(f, '0001') # number of components wh(f, '07') # bits/component, minus 1 wh(f, '00') # compression type: 0 = uncompressed wh(f, '00') # flag for unknown colourspace wh(f, '00') # flag for intellectual property startbox(f, b'jplh', 23) startbox(f, b'cgrp', 15) startbox(f, b'colr', 7) wh(f, '01') # 1 = using an enumerated colour type wh(f, '00') # precedence wh(f, '01') # APPROX: 1 = accurate wh(f, '00000011') # 17 = sRGB-gray startbox(f, b'jp2c', 96) wh(f, 'ff ff ff ff ff ff ff ff ff ff ff ff') wh(f, 'ff ff 00 ff 00 00 00 ff 00 ff 00 ff') wh(f, 'ff ff 00 ff 00 ff 00 ff 00 ff 00 ff') wh(f, 'ff ff 00 ff 00 ff 00 ff ff 00 ff ff') wh(f, 'ff ff 00 ff 00 00 00 ff 00 ff 00 ff') wh(f, 'ff ff 00 ff 00 ff ff ff 00 ff 00 ff') wh(f, '00 00 00 ff 00 ff ff ff 00 ff 00 ff') wh(f, 'ff ff ff ff ff ff ff ff ff ff ff ff') f.close()
You can download the program and the file here: mkuncjpx.zip
Note that the box sizes are hardcoded in the script, so if you change the size of a box’s payload, you’ll have to adjust its size manually. Feel free to improve the program so it calculates the sizes automatically. Better yet, write a program to convert a common image format to and from uncompressed JPX.
The file size is 230 bytes, and the raw image size is 96 bytes. The overhead of 134 bytes is close to the minimal amount for an uncompressed JPX file.
Again, I don’t know if this file is correct. There could easily be something in the specification that I overlooked, misunderstood, or didn’t see because it’s in a part of the specification that I don’t have a copy of.