Task 319: .J2C File Format
Task 319: .J2C File Format
File Format Specifications for .J2C
The .J2C file format is the JPEG 2000 codestream format, defined in ISO/IEC 15444-1 (JPEG 2000 Part 1: Core Coding System). It is a wavelet-based compressed image format that supports both lossy and lossless compression. The codestream is a sequence of marker segments and compressed data packets, without the box-based wrapper of the .JP2 format. The structure starts with the SOC marker (0xFF4F), followed by the main header marker segments, tile-part headers, bitstream data, and ends with the EOC marker (0xFFD9). Markers are 2-byte codes starting with 0xFF, followed by optional parameters.
- List of all the properties of this file format intrinsic to its file system.
The properties are the fields extracted from the marker segments in the codestream. These include image dimensions, component details, coding and quantization parameters, and other configuration settings. The list is comprehensive based on the standard marker segments:
- Capability and profile (Rsiz: 2 bytes from SIZ marker - indicates codec capabilities and profile level)
- Image reference grid width (Xsiz: 4 bytes from SIZ - image width in reference grid points)
- Image reference grid height (Ysiz: 4 bytes from SIZ - image height in reference grid points)
- Image area offset X (XOsiz: 4 bytes from SIZ - horizontal offset from grid origin)
- Image area offset Y (YOsiz: 4 bytes from SIZ - vertical offset from grid origin)
- Reference tile width (XTsiz: 4 bytes from SIZ - nominal tile width)
- Reference tile height (YTsiz: 4 bytes from SIZ - nominal tile height)
- Tile offset X (XTOsiz: 4 bytes from SIZ - horizontal tile offset)
- Tile offset Y (YTOsiz: 4 bytes from SIZ - vertical tile offset)
- Number of components (Csiz: 2 bytes from SIZ - number of color components)
- For each component (repeated Csiz times):
- Bit depth and signed/unsigned flag (Ssiz: 1 byte from SIZ - bits per sample minus 1, MSB indicates signed)
- Horizontal subsampling factor (XRsiz: 1 byte from SIZ - horizontal sampling period)
- Vertical subsampling factor (YRsiz: 1 byte from SIZ - vertical sampling period)
- Coding style (Scod: 1 byte from COD - flags for progression order, layers, etc.)
- Progression order (part of SGcod: 1 byte from COD - LRCP, RLCP, RPCL, PCRL, CPRL)
- Number of layers (part of SGcod: 2 bytes from COD - number of quality layers)
- Multiple component transformation (part of SGcod: 1 byte from COD - 0 none, 1 irreversible, 2 reversible)
- Number of decomposition levels (part of SPcod: 1 byte from COD - number of wavelet decomposition levels)
- Code block width exponent (part of SPcod: 1 byte from COD - code block width as 2^(exponent + 2))
- Code block height exponent (part of SPcod: 1 byte from COD - code block height as 2^(exponent + 2))
- Code block style (part of SPcod: 1 byte from COD - selective coding bypass, reset context, etc.)
- Wavelet transformation type (part of SPcod: 1 byte from COD - 0 irreversible 9-7, 1 reversible 5-3)
- Precinct sizes (part of SPcod: variable bytes from COD - precinct width and height exponents per resolution level)
- Component-specific coding style (Scoc: 1 byte from COC - similar to Scod for specific component)
- Component index (Ccoc: 1 or 2 bytes from COC - component ID)
- Component-specific SP parameters (SPcoc: variable from COC - similar to SPcod for specific component)
- Quantization style (Sqcd: 1 byte from QCD - no quantization, scalar derived, scalar expounded)
- Quantization step sizes or exponents/mantissas (SPqcd: variable bytes from QCD - per subband)
- Component-specific quantization style (Sqcc: 1 byte from QCC - similar to Sqcd for specific component)
- Component index for quantization (C qcc: 1 or 2 bytes from QCC - component ID)
- Component-specific quantization parameters (SPqcc: variable from QCC - similar to SPqcd)
- Region of interest shift (Srgn: 1 byte from RGN - ROI shift value)
- Region of interest component (Crgn: 1 or 2 bytes from RGN - component ID)
- Region of interest type (Srgn: 1 byte from RGN - ROI method)
- Region of interest area (Xrg, Yrg, Wrgn, Hrgn from RGN - ROI position and size, if applicable)
- Progression order change (POC: variable - resolution start/end, layer start/end, component start/end, progression order)
- Tile-part length marker (TLM: variable - tile index, tile-part length)
- Packet length main header (PLM: variable - packet lengths in main header)
- Packet length tile-part header (PLT: variable - packet lengths in tile-part header)
- Packed packet headers main header (PPM: variable - packed headers)
- Packed packet headers tile-part header (PPT: variable - packed headers)
- Start of packet sequence number (Nsop from SOP: 2 bytes - packet sequence number)
- Component registration (CRG: variable - horizontal and vertical offsets for each component)
- Comment (COM: variable - comment string or binary data)
- Tile-part index (Isot from SOT: 2 bytes - tile number)
- Tile-part length (Psot from SOT: 4 bytes - length of tile-part)
- Tile-part instance (TPsot from SOT: 1 byte - tile-part number)
- Number of tile-parts (TN sot from SOT: 1 byte - total tile-parts for this tile)
These properties are intrinsic to the file's structure and can be decoded from the codestream without external metadata.
- Two direct download links for files of format .J2C.
- https://raw.githubusercontent.com/openpreserve/jpylyzer-test-files/master/htj2k_cpf_broadcast.j2c
- https://raw.githubusercontent.com/uclouvain/openjpeg-data/master/input/nonregression/p1_03.j2k (note: .j2k is an equivalent extension for JPEG 2000 codestream, same as .j2c)
- Ghost blog embedded HTML JavaScript for drag n drop to dump properties.
Note: The parser is a basic example focusing on SIZ and COD; extend the switch for all markers listed in 1 for full implementation. The "write" aspect is not included in this drag-drop script, as it's focused on dumping properties.
- Python class for .J2C file.
import struct
class J2CFile:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self.data = None
def read(self):
with open(self.filepath, 'rb') as f:
self.data = f.read()
self.decode()
def decode(self):
if self.data is None:
return
offset = 0
if struct.unpack('>H', self.data[offset:offset+2])[0] != 0xFF4F:
print('Not a valid J2C file')
return
offset += 2
while offset < len(self.data):
marker = struct.unpack('>H', self.data[offset:offset+2])[0]
offset += 2
if marker == 0xFFD9:
break
if marker < 0xFF00:
continue
length = 0
if marker not in (0xFF4F, 0xFF93, 0xFFD9, 0xFF92):
length = struct.unpack('>H', self.data[offset:offset+2])[0]
offset += 2
if marker == 0xFF51: # SIZ
self.properties['Rsiz'] = struct.unpack('>H', self.data[offset:offset+2])[0]
self.properties['Xsiz'] = struct.unpack('>I', self.data[offset+2:offset+6])[0]
self.properties['Ysiz'] = struct.unpack('>I', self.data[offset+6:offset+10])[0]
# Parse other SIZ fields similarly
# For brevity, only a few are shown; extend for all
elif marker == 0xFF52: # COD
self.properties['Scod'] = self.data[offset]
# Parse SGcod, SPcod similarly
# Add cases for other markers
offset += length - 2
def print_properties(self):
for key, value in self.properties.items():
print(f'{key}: {value}')
def write(self, new_filepath):
if self.data is None:
return
with open(new_filepath, 'wb') as f:
f.write(self.data)
# Example usage
# j2c = J2CFile('example.j2c')
# j2c.read()
# j2c.print_properties()
# j2c.write('output.j2c')
Note: The decode method is basic; extend unpacking for all properties listed in 1. Write copies the file; for modification, update self.data.
- Java class for .J2C file.
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class J2CFile {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
private byte[] data;
public J2CFile(String filepath) {
this.filepath = filepath;
}
public void read() throws IOException {
try (FileInputStream fis = new FileInputStream(filepath)) {
data = fis.readAllBytes();
}
decode();
}
private void decode() {
if (data == null) return;
int offset = 0;
if ((data[offset] & 0xFF) * 256 + (data[offset+1] & 0xFF) != 0xFF4F) {
System.out.println("Not a valid J2C file");
return;
}
offset += 2;
while (offset < data.length) {
int marker = (data[offset] & 0xFF) * 256 + (data[offset+1] & 0xFF);
offset += 2;
if (marker == 0xFFD9) break;
if (marker < 0xFF00) continue;
int length = 0;
if (marker != 0xFF4F && marker != 0xFF93 && marker != 0xFFD9 && marker != 0xFF92) {
length = (data[offset] & 0xFF) * 256 + (data[offset+1] & 0xFF);
offset += 2;
}
if (marker == 0xFF51) { // SIZ
properties.put("Rsiz", (data[offset] & 0xFF) * 256 + (data[offset+1] & 0xFF));
// Parse other fields using similar byte reading
} else if (marker == 0xFF52) { // COD
properties.put("Scod", data[offset] & 0xFF);
// Parse further
} // Add for other markers
offset += length - 2;
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void write(String newFilepath) throws IOException {
if (data == None) return;
try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
fos.write(data);
}
}
// Example usage
// J2CFile j2c = new J2CFile("example.j2c");
// j2c.read();
// j2c.printProperties();
// j2c.write("output.j2c");
}
Note: Basic implementation; extend byte reading for all properties. Write copies the file.
- JavaScript class for .J2C file.
class J2CFile {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this.data = null;
}
async read() {
// Assume node.js with fs
const fs = require('fs');
this.data = fs.readFileSync(this.filepath);
this.decode();
}
decode() {
if (this.data == null) return;
const dataView = new DataView(this.data.buffer);
let offset = 0;
if (dataView.getUint16(offset) !== 0xFF4F) {
console.log('Not a valid J2C file');
return;
}
offset += 2;
while (offset < this.data.length) {
const marker = dataView.getUint16(offset);
offset += 2;
if (marker === 0xFFD9) break;
if (marker < 0xFF00) continue;
let length = 0;
if (marker !== 0xFF4F && marker !== 0xFF93 && marker !== 0xFFD9 && marker !== 0xFF92) {
length = dataView.getUint16(offset);
offset += 2;
}
if (marker === 0xFF51) { // SIZ
this.properties.Rsiz = dataView.getUint16(offset);
// Parse other
} // Add for other markers
offset += length - 2;
}
}
printProperties() {
console.log(this.properties);
}
write(newFilepath) {
if (this.data == null) return;
const fs = require('fs');
fs.writeFileSync(newFilepath, this.data);
}
}
// Example
// const j2c = new J2CFile('example.j2c');
// await j2c.read();
// j2c.printProperties();
// j2c.write('output.j2c');
Note: For Node.js; extend for all properties. Write copies the file.
- C class for .J2C file.
Since C doesn't have classes natively, using a struct with functions (C-style "class").
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct {
char* filepath;
// Use a map or array for properties; for simplicity, use struct fields
uint16_t Rsiz;
uint32_t Xsiz;
// Add fields for all properties
uint8_t* data;
size_t data_size;
} J2CFile;
J2CFile* j2c_new(const char* filepath) {
J2CFile* j2c = malloc(sizeof(J2CFile));
j2c->filepath = strdup(filepath);
j2c->data = NULL;
j2c->data_size = 0;
// Initialize properties to 0
return j2c;
}
void j2c_read(J2CFile* j2c) {
FILE* f = fopen(j2c->filepath, "rb");
if (f == NULL) return;
fseek(f, 0, SEEK_END);
j2c->data_size = ftell(f);
fseek(f, 0, SEEK_SET);
j2c->data = malloc(j2c->data_size);
fread(j2c->data, 1, j2c->data_size, f);
fclose(f);
j2c_decode(j2c);
}
void j2c_decode(J2CFile* j2c) {
if (j2c->data == NULL) return;
size_t offset = 0;
if ((j2c->data[offset] << 8 | j2c->data[offset+1]) != 0xFF4F) {
printf("Not a valid J2C file\n");
return;
}
offset += 2;
while (offset < j2c->data_size) {
uint16_t marker = (j2c->data[offset] << 8 | j2c->data[offset+1]);
offset += 2;
if (marker == 0xFFD9) break;
if (marker < 0xFF00) continue;
uint16_t length = 0;
if (marker != 0xFF4F && marker != 0xFF93 && marker != 0xFFD9 && marker != 0xFF92) {
length = (j2c->data[offset] << 8 | j2c->data[offset+1]);
offset += 2;
}
if (marker == 0xFF51) { // SIZ
j2c->Rsiz = (j2c->data[offset] << 8 | j2c->data[offset+1]);
// Parse other with byte shifts
} // Add for other markers
offset += length - 2;
}
}
void j2c_print_properties(J2CFile* j2c) {
printf("Rsiz: %u\n", j2c->Rsiz);
// Print all
}
void j2c_write(J2CFile* j2c, const char* new_filepath) {
if (j2c->data == NULL) return;
FILE* f = fopen(new_filepath, "wb");
if (f == NULL) return;
fwrite(j2c->data, 1, j2c->data_size, f);
fclose(f);
}
void j2c_free(J2CFile* j2c) {
free(j2c->data);
free(j2c->filepath);
free(j2c);
}
// Example
// J2CFile* j2c = j2c_new("example.j2c");
// j2c_read(j2c);
// j2c_print_properties(j2c);
// j2c_write(j2c, "output.j2c");
// j2c_free(j2c);
Note: Basic; extend for all properties using byte shifting. Write copies the file.