Task 324: .JBIG File Format

Task 324: .JBIG File Format

JBIG File Format Specifications

The .JBIG file format (also known as .JBG or .JBIG1) is the binary format for images compressed using the JBIG (Joint Bi-level Image Group) standard, defined in ISO/IEC 11544 and ITU-T Recommendation T.82. It is designed for lossless compression of bi-level (black and white) images, such as scanned documents or fax pages. The format supports progressive encoding with multiple resolution layers and bit planes for grayscale or color images. The data stream is called a Bi-level Image Entity (BIE), which consists of a 20-byte Bi-level Image Header (BIH), an optional 1728-byte adaptive arithmetic coding table (if the QOPT option is set), followed by Stripe Data Entities (SDEs) containing the compressed image data. The format is big-endian for multi-byte fields.

1. List of Properties Intrinsic to the File Format

The intrinsic properties are the fields in the 20-byte BIH header, which define the image dimensions, resolution layers, coding options, and structure. These are the key attributes parsed from the file:

  • DL (Lowest resolution layer): Byte 0, unsigned 8-bit integer. The starting resolution layer (usually 0 for single-layer images).
  • D (Highest resolution layer): Byte 1, unsigned 8-bit integer. The highest resolution layer (D >= DL).
  • P (Number of bit planes): Byte 2, unsigned 8-bit integer. Number of bit planes (1 for bi-level, up to 64 for grayscale/color).
  • Reserved: Byte 3, must be 0 (unsigned 8-bit integer).
  • XD (Image width): Bytes 4-7, unsigned 32-bit big-endian integer. Width of the image in pixels at the highest resolution.
  • YD (Image height): Bytes 8-11, unsigned 32-bit big-endian integer. Height of the image in lines at the highest resolution.
  • L0 (Lines per stripe): Bytes 12-15, unsigned 32-bit big-endian integer. Number of lines per stripe in the lowest resolution layer.
  • MX (Max adaptive template x-offset): Byte 16, unsigned 8-bit integer (0-127). Maximum horizontal offset for adaptive template pixels.
  • MY (Max adaptive template y-offset): Byte 17, unsigned 8-bit integer (0-127). Maximum vertical offset for adaptive template pixels.
  • ORDER (Order flags): Byte 18, 8-bit bit field defining incrementation order:
  • Bit 0 (LSB): SM (Sequential mode).
  • Bit 1: ILEAVE (Interleaved planes).
  • Bit 2: SEQ (Sequential coding).
  • Bit 3: HITOLO (High to low order).
  • Bits 4-7: Reserved (0).
  • OPTIONS (Coding options): Byte 19, 8-bit bit field for encoding options:
  • Bit 0 (LSB): LRLTWO (Use 2-line template in lowest resolution).
  • Bit 1: DPON (Differential layer typical prediction on).
  • Bit 2: TPBON (Typical prediction for backward on).
  • Bit 3: TPDON (Typical prediction for deterministic on).
  • Bit 4: DPPRIV (Private differential layer).
  • Bit 5: QOPT (Custom Q-table present; indicates optional 1728-byte table follows header).
  • Bits 6-7: Reserved (0).

3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .JBIG File Dump

JBIG Property Dumper
Drag and drop a .JBIG file here

4. Python Class for .JBIG File

import struct
import sys

class JBIGHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.header = None
        self.data = None
        self.parse()

    def parse(self):
        with open(self.filepath, 'rb') as f:
            self.header = f.read(20)
            if len(self.header) < 20:
                raise ValueError("File too small for JBIG header")
            self.data = f.read()  # Read the rest for writing later

        dl, d, p, reserved, xd, yd, l0, mx, my, order, options = struct.unpack('>BBBBIIIBBB', self.header)

        order_bits = {
            'SM': bool(order & 0x01),
            'ILEAVE': bool(order & 0x02),
            'SEQ': bool(order & 0x04),
            'HITOLO': bool(order & 0x08)
        }
        options_bits = {
            'LRLTWO': bool(options & 0x01),
            'DPON': bool(options & 0x02),
            'TPBON': bool(options & 0x04),
            'TPDON': bool(options & 0x08),
            'DPPRIV': bool(options & 0x10),
            'QOPT': bool(options & 0x20)
        }

        self.properties = {
            'DL': dl,
            'D': d,
            'P': p,
            'Reserved': reserved,
            'XD': xd,
            'YD': yd,
            'L0': l0,
            'MX': mx,
            'MY': my,
            'ORDER': order,
            'ORDER_BITS': order_bits,
            'OPTIONS': options,
            'OPTIONS_BITS': options_bits
        }

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

    def write(self, output_path, new_properties=None):
        if new_properties:
            # Update properties if provided (simplified, assumes all provided)
            header = struct.pack('>BBBBIIIBBB', 
                                 new_properties.get('DL', self.properties['DL']),
                                 new_properties.get('D', self.properties['D']),
                                 new_properties.get('P', self.properties['P']),
                                 new_properties.get('Reserved', self.properties['Reserved']),
                                 new_properties.get('XD', self.properties['XD']),
                                 new_properties.get('YD', self.properties['YD']),
                                 new_properties.get('L0', self.properties['L0']),
                                 new_properties.get('MX', self.properties['MX']),
                                 new_properties.get('MY', self.properties['MY']),
                                 new_properties.get('ORDER', self.properties['ORDER']),
                                 new_properties.get('OPTIONS', self.properties['OPTIONS']))
        else:
            header = self.header

        with open(output_path, 'wb') as f:
            f.write(header)
            f.write(self.data)  # Copy original data

# Example usage
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python script.py input.jbig [output.jbig]")
        sys.exit(1)
    handler = JBIGHandler(sys.argv[1])
    handler.print_properties()
    if len(sys.argv) > 2:
        handler.write(sys.argv[2])

5. Java Class for .JBIG File

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class JBIGHandler {
    private String filepath;
    private byte[] header;
    private byte[] data;
    private int dl, d, p, reserved, mx, my, order, options;
    private long xd, yd, l0; // Unsigned 32-bit, use long for safety

    public JBIGHandler(String filepath) throws IOException {
        this.filepath = filepath;
        parse();
    }

    private void parse() throws IOException {
        try (FileInputStream fis = new FileInputStream(filepath)) {
            header = new byte[20];
            if (fis.read(header) < 20) {
                throw new IOException("File too small for JBIG header");
            }
            data = fis.readAllBytes();
        }

        ByteBuffer bb = ByteBuffer.wrap(header).order(ByteOrder.BIG_ENDIAN);
        dl = bb.get(0) & 0xFF;
        d = bb.get(1) & 0xFF;
        p = bb.get(2) & 0xFF;
        reserved = bb.get(3) & 0xFF;
        xd = bb.getInt(4) & 0xFFFFFFFFL;
        yd = bb.getInt(8) & 0xFFFFFFFFL;
        l0 = bb.getInt(12) & 0xFFFFFFFFL;
        mx = bb.get(16) & 0xFF;
        my = bb.get(17) & 0xFF;
        order = bb.get(18) & 0xFF;
        options = bb.get(19) & 0xFF;
    }

    public void printProperties() {
        System.out.println("DL: " + dl);
        System.out.println("D: " + d);
        System.out.println("P: " + p);
        System.out.println("Reserved: " + reserved);
        System.out.println("XD: " + xd);
        System.out.println("YD: " + yd);
        System.out.println("L0: " + l0);
        System.out.println("MX: " + mx);
        System.out.println("MY: " + my);
        System.out.println("ORDER: " + order + " (SM: " + ((order & 0x01) != 0) + ", ILEAVE: " + ((order & 0x02) != 0) + ", SEQ: " + ((order & 0x04) != 0) + ", HITOLO: " + ((order & 0x08) != 0) + ")");
        System.out.println("OPTIONS: " + options + " (LRLTWO: " + ((options & 0x01) != 0) + ", DPON: " + ((options & 0x02) != 0) + ", TPBON: " + ((options & 0x04) != 0) + ", TPDON: " + ((options & 0x08) != 0) + ", DPPRIV: " + ((options & 0x10) != 0) + ", QOPT: " + ((options & 0x20) != 0) + ")");
    }

    public void write(String outputPath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(header); // Write original header
            fos.write(data);
        }
    }

    public void write(String outputPath, int newDl, int newD, int newP, long newXd, long newYd, long newL0, int newMx, int newMy, int newOrder, int newOptions) throws IOException {
        ByteBuffer bb = ByteBuffer.allocate(20).order(ByteOrder.BIG_ENDIAN);
        bb.put((byte) newDl);
        bb.put((byte) newD);
        bb.put((byte) newP);
        bb.put((byte) 0); // Reserved
        bb.putInt((int) newXd);
        bb.putInt((int) newYd);
        bb.putInt((int) newL0);
        bb.put((byte) newMx);
        bb.put((byte) newMy);
        bb.put((byte) newOrder);
        bb.put((byte) newOptions);

        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(bb.array());
            fos.write(data); // Copy original data
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            System.out.println("Usage: java JBIGHandler input.jbig [output.jbig]");
            System.exit(1);
        }
        JBIGHandler handler = new JBIGHandler(args[0]);
        handler.printProperties();
        if (args.length > 1) {
            handler.write(args[1]);
        }
    }
}

6. JavaScript Class for .JBIG File

const fs = require('fs'); // For Node.js

class JBIGHandler {
  constructor(filepath) {
    this.filepath = filepath;
    this.header = null;
    this.data = null;
    this.properties = {};
    this.parse();
  }

  parse() {
    const buffer = fs.readFileSync(this.filepath);
    this.header = buffer.slice(0, 20);
    if (this.header.length < 20) {
      throw new Error('File too small for JBIG header');
    }
    this.data = buffer.slice(20);

    const dl = this.header[0];
    const d = this.header[1];
    const p = this.header[2];
    const reserved = this.header[3];
    const xd = (this.header[4] * 2**24) + (this.header[5] * 2**16) + (this.header[6] * 2**8) + this.header[7];
    const yd = (this.header[8] * 2**24) + (this.header[9] * 2**16) + (this.header[10] * 2**8) + this.header[11];
    const l0 = (this.header[12] * 2**24) + (this.header[13] * 2**16) + (this.header[14] * 2**8) + this.header[15];
    const mx = this.header[16];
    const my = this.header[17];
    const order = this.header[18];
    const options = this.header[19];

    const orderBits = {
      SM: !!(order & 0x01),
      ILEAVE: !!(order & 0x02),
      SEQ: !!(order & 0x04),
      HITOLO: !!(order & 0x08)
    };
    const optionsBits = {
      LRLTWO: !!(options & 0x01),
      DPON: !!(options & 0x02),
      TPBON: !!(options & 0x04),
      TPDON: !!(options & 0x08),
      DPPRIV: !!(options & 0x10),
      QOPT: !!(options & 0x20)
    };

    this.properties = {
      DL: dl,
      D: d,
      P: p,
      Reserved: reserved,
      XD: xd,
      YD: yd,
      L0: l0,
      MX: mx,
      MY: my,
      ORDER: order,
      ORDER_BITS: orderBits,
      OPTIONS: options,
      OPTIONS_BITS: optionsBits
    };
  }

  printProperties() {
    console.log(this.properties);
  }

  write(outputPath, newProperties = {}) {
    const newHeader = Buffer.alloc(20);
    newHeader[0] = newProperties.DL || this.properties.DL;
    newHeader[1] = newProperties.D || this.properties.D;
    newHeader[2] = newProperties.P || this.properties.P;
    newHeader[3] = 0; // Reserved
    newHeader.writeUInt32BE(newProperties.XD || this.properties.XD, 4);
    newHeader.writeUInt32BE(newProperties.YD || this.properties.YD, 8);
    newHeader.writeUInt32BE(newProperties.L0 || this.properties.L0, 12);
    newHeader[16] = newProperties.MX || this.properties.MX;
    newHeader[17] = newProperties.MY || this.properties.MY;
    newHeader[18] = newProperties.ORDER || this.properties.ORDER;
    newHeader[19] = newProperties.OPTIONS || this.properties.OPTIONS;

    fs.writeFileSync(outputPath, Buffer.concat([newHeader, this.data]));
  }
}

// Example usage
if (process.argv.length < 3) {
  console.log("Usage: node script.js input.jbig [output.jbig]");
  process.exit(1);
}
const handler = new JBIGHandler(process.argv[2]);
handler.printProperties();
if (process.argv[3]) {
  handler.write(process.argv[3]);
}

7. C Class for .JBIG File

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <endian.h> // For big-endian conversion if needed, but assuming host is little-endian

struct JBIGProperties {
  uint8_t dl;
  uint8_t d;
  uint8_t p;
  uint8_t reserved;
  uint32_t xd;
  uint32_t yd;
  uint32_t l0;
  uint8_t mx;
  uint8_t my;
  uint8_t order;
  uint8_t options;
  struct {
    uint8_t sm : 1;
    uint8_t ileave : 1;
    uint8_t seq : 1;
    uint8_t hitolo : 1;
  } order_bits;
  struct {
    uint8_t lrl two : 1;
    uint8_t dpon : 1;
    uint8_t tpbon : 1;
    uint8_t tpdon : 1;
    uint8_t dppriv : 1;
    uint8_t qopt : 1;
  } options_bits;
};

struct JBIGHandler {
  char* filepath;
  uint8_t header[20];
  uint8_t* data;
  size_t data_size;
  struct JBIGProperties properties;
};

void jbig_handler_init(struct JBIGHandler* self, const char* filepath) {
  self->filepath = strdup(filepath);
  FILE* f = fopen(filepath, "rb");
  if (f == NULL) {
    perror("fopen");
    exit(1);
  }
  if (fread(self->header, 1, 20, f) < 20) {
    fprintf(stderr, "File too small for JBIG header\n");
    exit(1);
  }
  fseek(f, 0, SEEK_END);
  size_t file_size = ftell(f);
  self->data_size = file_size - 20;
  self->data = malloc(self->data_size);
  fseek(f, 20, SEEK_SET);
  fread(self->data, 1, self->data_size, f);
  fclose(f);

  // Parse
  self->properties.dl = self->header[0];
  self->properties.d = self->header[1];
  self->properties.p = self->header[2];
  self->properties.reserved = self->header[3];
  self->properties.xd = be32toh(*(uint32_t*)(self->header + 4));
  self->properties.yd = be32toh(*(uint32_t*)(self->header + 8));
  self->properties.l0 = be32toh(*(uint32_t*)(self->header + 12));
  self->properties.mx = self->header[16];
  self->properties.my = self->header[17];
  self->properties.order = self->header[18];
  self->properties.options = self->header[19];

  self->properties.order_bits.sm = self->properties.order & 0x01;
  self->properties.order_bits.ileave = self->properties.order & 0x02;
  self->properties.order_bits.seq = self->properties.order & 0x04;
  self->properties.order_bits.hitolo = self->properties.order & 0x08;

  self->properties.options_bits.lrl two = self->properties.options & 0x01;
  self->properties.options_bits.dpon = self->properties.options & 0x02;
  self->properties.options_bits.tpbon = self->properties.options & 0x04;
  self->properties.options_bits.tpdon = self->properties.options & 0x08;
  self->properties.options_bits.dppriv = self->properties.options & 0x10;
  self->properties.options_bits.qopt = self->properties.options & 0x20;
}

void jbig_handler_print_properties(const struct JBIGHandler* self) {
  printf("DL: %u\n", self->properties.dl);
  printf("D: %u\n", self->properties.d);
  printf("P: %u\n", self->properties.p);
  printf("Reserved: %u\n", self->properties.reserved);
  printf("XD: %u\n", self->properties.xd);
  printf("YD: %u\n", self->properties.yd);
  printf("L0: %u\n", self->properties.l0);
  printf("MX: %u\n", self->properties.mx);
  printf("MY: %u\n", self->properties.my);
  printf("ORDER: %u (SM: %u, ILEAVE: %u, SEQ: %u, HITOLO: %u)\n",
         self->properties.order, self->properties.order_bits.sm, self->properties.order_bits.ileave,
         self->properties.order_bits.seq, self->properties.order_bits.hitolo);
  printf("OPTIONS: %u (LRLTWO: %u, DPON: %u, TPBON: %u, TPDON: %u, DPPRIV: %u, QOPT: %u)\n",
         self->properties.options, self->properties.options_bits.lrl two, self->properties.options_bits.dpon,
         self->properties.options_bits.tpbon, self->properties.options_bits.tpdon, self->properties.options_bits.dppriv,
         self->properties.options_bits.qopt);
}

void jbig_handler_write(const struct JBIGHandler* self, const char* output_path) {
  FILE* f = fopen(output_path, "wb");
  if (f == NULL) {
    perror("fopen");
    exit(1);
  }
  fwrite(self->header, 1, 20, f);
  fwrite(self->data, 1, self->data_size, f);
  fclose(f);
}

void jbig_handler_destroy(struct JBIGHandler* self) {
  free(self->filepath);
  free(self->data);
}

int main(int argc, char** argv) {
  if (argc < 2) {
    fprintf(stderr, "Usage: %s input.jbig [output.jbig]\n", argv[0]);
    return 1;
  }
  struct JBIGHandler handler;
  jbig_handler_init(&handler, argv[1]);
  jbig_handler_print_properties(&handler);
  if (argc > 2) {
    jbig_handler_write(&handler, argv[2]);
  }
  jbig_handler_destroy(&handler);
  return 0;
}