Making an uncompressed JPEG 2000 file

Challenge: Construct a JPEG 2000 image file that isn’t compressed.

Also, try to do it without spending any money.

Overview

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.

Application support?

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.

Specifications

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

http://www.jpeg.org/public/15444-1annexi.pdf
http://www.jpeg.org/public/15444-2annexm.pdf

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.

Planning

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.

jP box

The file starts with a ‘jP’ signature box, which is always the same 12 bytes for all the JPEG 2000 formats.

ftyp box

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.

rreq box

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:

Features-that-are-used:

  • no opacity (8)
  • contiguous (12)
  • no layers (18)
  • one codestream per layer (20)
  • scaling not required (31)
  • sRGB-gray (46)

Features-that-are-required:

  • sRGB-gray (46)

ihdr box

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.

colr box

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”.

jp2c box

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s