Task 338: .JVX File Format

Task 338: .JVX File Format

File Format Specifications for .JXL

The .JXL file format is specified in ISO/IEC 18181-2:2024 for the file container and ISO/IEC 18181-1:2024 for the codestream. The container is based on ISO BMFF (box structure), and the codestream is a bitstream for the compressed image data. The format supports lossy and lossless compression, HDR, wide color gamut, animation, alpha channels, and metadata.

  1. List of all properties of this file format intrinsic to its file structure:
  • Signature: Fixed bytes for identification (container: 00 00 00 0C 4A 58 4C 20 0D 0A 87 0A; codestream: FF 0A).
  • Level: u8 value from 'jxll' box (default 5 if absent).
  • Presence of Exif metadata: Boolean, from 'Exif' box with TIFF header offset (u32) and payload.
  • Presence of XMP metadata: Boolean, from 'xml ' box with XML data.
  • Presence of JUMBF metadata: Boolean, from 'jumb' box (superbox with description and content).
  • Image width: u32 or u64 from codestream image header (SizeHeader bundle).
  • Image height: u32 or u64 from codestream image header (SizeHeader bundle).
  • Bit depth: u8 bits per sample from codestream image header.
  • Floating point samples: Boolean, from codestream image header.
  • Number of color channels: u32 from codestream image header (typically 1 for grayscale, 3 for RGB).
  • Color space: Enum or ICC profile from color encoding bundle (e.g., XYB, sRGB, with primaries, white point, transfer function).
  • Presence of alpha channel: Boolean, from codestream image header (extra channels).
  • Premultiplied alpha: Boolean, from extra channel info.
  • Orientation: u8 from codestream header (1-8, similar to Exif).
  • Animation parameters: Boolean have_animation, number of frames, loop count, tick unit, frame durations (u32 multiples of tick).
  • Intensity target: f32 for HDR tone mapping.
  • Tone mapping parameters: Min/max lift, gain, offset for HDR.
  • Preview presence: Boolean have_preview from header.
  • Compression mode: VarDCT (lossy) or Modular (lossless) inferred from frame headers.
  • Progressive levels: u8 number of LF levels for progressive decoding.
  1. Two direct download links for .JXL files:
  1. Ghost blog embedded HTML JavaScript for drag and drop .JXL file to dump properties:
JXL Property Dumper

Drag and Drop .JXL File Here

Drop file here

  

Note: The codestream parsing is simplified for demonstration; full decoding requires bit-level parsing of bundles as per ISO/IEC 18181-1.

  1. Python class for .JXL:
import struct

class JXLHandler:
    def __init__(self, filename):
        self.filename = filename
        self.data = None
        self.properties = {}

    def open(self):
        with open(self.filename, 'rb') as f:
            self.data = f.read()

    def decode_read(self):
        if not self.data:
            return
        offset = 0
        if self.data[offset:12] == b'\x00\x00\x00\x0cJXL \r\n\x87\n':
            self.properties['Signature'] = 'Container (ISO BMFF)'
            offset = 12
        elif self.data[offset:2] == b'\xff\x0a':
            self.properties['Signature'] = 'Naked Codestream'
            offset = 2
        else:
            raise ValueError('Invalid JXL file')

        while offset < len(self.data):
            box_size, = struct.unpack('>I', self.data[offset:offset+4])
            box_type = self.data[offset+4:offset+8].decode('ascii')
            self.properties[f'Box {box_type} Size'] = box_size
            if box_type == 'jxll':
                level, = struct.unpack('>B', self.data[offset+8:offset+9])
                self.properties['Level'] = level
            elif box_type == 'Exif':
                self.properties['Presence of Exif metadata'] = True
            elif box_type == 'xml ':
                self.properties['Presence of XMP metadata'] = True
            elif box_type == 'jumb':
                self.properties['Presence of JUMBF metadata'] = True
            elif box_type in ('jxlc', 'jxlp'):
                self.properties['Codestream found'] = True
                cs_offset = offset + 8
                cs_props = self.parse_codestream(self.data[cs_offset:])
                self.properties.update(cs_props)
            if box_size == 0:
                break
            offset += box_size

    def parse_codestream(self, data):
        props = {}
        offset = 2  # Skip FF0A
        # Simplified SizeHeader parsing (real is bit-packed)
        # Assume: bool div8, u(3) ratio, if not div8 u(32) height, u(32) width
        byte = data[offset]
        div8 = byte & 1
        ratio = (byte >> 1) & 7
        offset += 1
        height, = struct.unpack('>I', data[offset:offset+4])
        width, = struct.unpack('>I', data[offset+4:offset+8])
        props['Image height'] = height
        props['Image width'] = width
        # Add more parsing for bit depth, etc.
        return props

    def write(self, new_filename):
        # Simplified: write back the same data
        with open(new_filename, 'wb') as f:
            f.write(self.data)

    def print_properties(self):
        for key, value in self.properties.items():
            print(f'{key}: {value}')

# Example usage:
# handler = JXLHandler('example.jxl')
# handler.open()
# handler.decode_read()
# handler.print_properties()
# handler.write('output.jxl')

Note: Codestream parsing is simplified; full read/write requires complete bundle decoding.

  1. Java class for .JXL:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class JXLHandler {
    private String filename;
    private byte[] data;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public JXLHandler(String filename) {
        this.filename = filename;
    }

    public void open() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename)) {
            data = fis.readAllBytes();
        }
    }

    public void decodeRead() {
        if (data == null) return;
        int offset = 0;
        ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
        if (bb.getInt(0) == 0x0000000C && bb.getInt(4) == 0x4A584C20 && bb.getInt(8) == 0x0D0A870A) {
            properties.put("Signature", "Container (ISO BMFF)");
            offset = 12;
        } else if (bb.getShort(0) == (short)0xFF0A) {
            properties.put("Signature", "Naked Codestream");
            offset = 2;
        } else {
            throw new RuntimeException("Invalid JXL file");
        }

        while (offset < data.length) {
            bb.position(offset);
            int boxSize = bb.getInt();
            String boxType = new String(data, offset + 4, 4);
            properties.put("Box " + boxType + " Size", boxSize);
            if (boxType.equals("jxll")) {
                int level = data[offset + 8] & 0xFF;
                properties.put("Level", level);
            } else if (boxType.equals("Exif")) {
                properties.put("Presence of Exif metadata", true);
            } else if (boxType.equals("xml ")) {
                properties.put("Presence of XMP metadata", true);
            } else if (boxType.equals("jumb")) {
                properties.put("Presence of JUMBF metadata", true);
            } else if (boxType.equals("jxlc") || boxType.equals("jxlp")) {
                properties.put("Codestream found", true);
                java.util.Map<String, Object> csProps = parseCodestream(bb, offset + 8);
                properties.putAll(csProps);
            }
            if (boxSize == 0) break;
            offset += boxSize;
        }
    }

    private java.util.Map<String, Object> parseCodestream(ByteBuffer bb, int start) {
        java.util.Map<String, Object> props = new java.util.HashMap<>();
        bb.position(start + 2); // Skip FF0A
        // Simplified
        int byteVal = bb.get() & 0xFF;
        boolean div8 = (byteVal & 1) == 1;
        int ratio = (byteVal >> 1) & 7;
        int height = bb.getInt();
        int width = bb.getInt();
        props.put("Image height", height);
        props.put("Image width", width);
        return props;
    }

    public void write(String newFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(newFilename)) {
            fos.write(data);
        }
    }

    public void printProperties() {
        properties.forEach((k, v) -> System.out.println(k + ": " + v));
    }

    // Example usage:
    // JXLHandler handler = new JXLHandler("example.jxl");
    // handler.open();
    // handler.decodeRead();
    // handler.printProperties();
    // handler.write("output.jxl");
}

Note: Codestream parsing simplified.

  1. JavaScript class for .JXL:
class JXLHandler {
  constructor(filename) {
    this.filename = filename;
    this.data = null;
    this.properties = {};
  }

  async open() {
    // Assume node.js with fs
    const fs = require('fs');
    this.data = fs.readFileSync(this.filename);
  }

  decodeRead() {
    if (!this.data) return;
    let offset = 0;
    if (this.data[0] === 0x00 && this.data[1] === 0x00 && this.data[2] === 0x00 && this.data[3] === 0x0C &&
        this.data[4] === 0x4A && this.data[5] === 0x58 && this.data[6] === 0x4C && this.data[7] === 0x20) {
      this.properties['Signature'] = 'Container (ISO BMFF)';
      offset = 12;
    } else if (this.data[0] === 0xFF && this.data[1] === 0x0A) {
      this.properties['Signature'] = 'Naked Codestream';
      offset = 2;
    } else {
      throw new Error('Invalid JXL file');
    }

    while (offset < this.data.length) {
      let boxSize = (this.data[offset] << 24) | (this.data[offset+1] << 16) | (this.data[offset+2] << 8) | this.data[offset+3];
      let boxType = String.fromCharCode(this.data[offset+4], this.data[offset+5], this.data[offset+6], this.data[offset+7]);
      this.properties[`Box ${boxType} Size`] = boxSize;
      if (boxType === 'jxll') {
        this.properties['Level'] = this.data[offset+8];
      } else if (boxType === 'Exif') {
        this.properties['Presence of Exif metadata'] = true;
      } else if (boxType === 'xml ') {
        this.properties['Presence of XMP metadata'] = true;
      } else if (boxType === 'jumb') {
        this.properties['Presence of JUMBF metadata'] = true;
      } else if (boxType === 'jxlc' || boxType === 'jxlp') {
        this.properties['Codestream found'] = true;
        let csProps = this.parseCodestream(this.data.slice(offset + 8));
        Object.assign(this.properties, csProps);
      }
      if (boxSize === 0) break;
      offset += boxSize;
    }
  }

  parseCodestream(data) {
    let props = {};
    let offset = 2;
    let byte = data[offset];
    let div8 = byte & 1;
    let ratio = (byte >> 1) & 7;
    offset += 1;
    let height = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
    let width = (data[offset+4] << 24) | (data[offset+5] << 16) | (data[offset+6] << 8) | data[offset+7];
    props['Image height'] = height;
    props['Image width'] = width;
    return props;
  }

  write(newFilename) {
    const fs = require('fs');
    fs.writeFileSync(newFilename, this.data);
  }

  printProperties() {
    for (let [key, value] of Object.entries(this.properties)) {
      console.log(`${key}: ${value}`);
    }
  }
}

// Example:
// const handler = new JXLHandler('example.jxl');
// await handler.open();
// handler.decodeRead();
// handler.printProperties();
// handler.write('output.jxl');

Note: For node.js; codestream simplified.

  1. C class (using C++ for class):
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <cstdint>

class JXLHandler {
private:
    std::string filename;
    std::vector<uint8_t> data;
    std::map<std::string, std::string> properties;

public:
    JXLHandler(const std::string& fn) : filename(fn) {}

    void open() {
        std::ifstream file(filename, std::ios::binary);
        if (file) {
            file.seekg(0, std::ios::end);
            size_t size = file.tellg();
            file.seekg(0, std::ios::beg);
            data.resize(size);
            file.read(reinterpret_cast<char*>(data.data()), size);
        }
    }

    void decodeRead() {
        if (data.empty()) return;
        size_t offset = 0;
        if (data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x0C &&
            data[4] == 0x4A && data[5] == 0x58 && data[6] == 0x4C && data[7] == 0x20) {
            properties["Signature"] = "Container (ISO BMFF)";
            offset = 12;
        } else if (data[0] == 0xFF && data[1] == 0x0A) {
            properties["Signature"] = "Naked Codestream";
            offset = 2;
        } else {
            throw std::runtime_error("Invalid JXL file");
        }

        while (offset < data.size()) {
            uint32_t boxSize = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3];
            std::string boxType(1, data[offset+4]);
            boxType += data[offset+5];
            boxType += data[offset+6];
            boxType += data[offset+7];
            properties["Box " + boxType + " Size"] = std::to_string(boxSize);
            if (boxType == "jxll") {
                uint8_t level = data[offset+8];
                properties["Level"] = std::to_string(level);
            } else if (boxType == "Exif") {
                properties["Presence of Exif metadata"] = "Yes";
            } else if (boxType == "xml ") {
                properties["Presence of XMP metadata"] = "Yes";
            } else if (boxType == "jumb") {
                properties["Presence of JUMBF metadata"] = "Yes";
            } else if (boxType == "jxlc" || boxType == "jxlp") {
                properties["Codestream found"] = "Yes";
                auto csProps = parseCodestream(data, offset + 8);
                properties.insert(csProps.begin(), csProps.end());
            }
            if (boxSize == 0) break;
            offset += boxSize;
        }
    }

    std::map<std::string, std::string> parseCodestream(const std::vector<uint8_t>& d, size_t start) {
        std::map<std::string, std::string> props;
        size_t offset = start + 2;
        uint8_t byte = d[offset];
        bool div8 = byte & 1;
        int ratio = (byte >> 1) & 7;
        offset += 1;
        uint32_t height = (d[offset] << 24) | (d[offset+1] << 16) | (d[offset+2] << 8) | d[offset+3];
        uint32_t width = (d[offset+4] << 24) | (d[offset+5] << 16) | (d[offset+6] << 8) | d[offset+7];
        props["Image height"] = std::to_string(height);
        props["Image width"] = std::to_string(width);
        return props;
    }

    void write(const std::string& newFilename) {
        std::ofstream file(newFilename, std::ios::binary);
        file.write(reinterpret_cast<const char*>(data.data()), data.size());
    }

    void printProperties() {
        for (const auto& p : properties) {
            std::cout << p.first << ": " << p.second << std::endl;
        }
    }
};

// Example:
// JXLHandler handler("example.jxl");
// handler.open();
// handler.decodeRead();
// handler.printProperties();
// handler.write("output.jxl");

Note: Codestream parsing is simplified for illustration; full implementation requires handling bitstreams and all bundles per the spec.