Task 330: .JPE File Format

Task 330: .JPE File Format

1. List of Properties of the .JPE File Format Intrinsic to Its File Structure

The .JPE file format is an alternative extension for JPEG (Joint Photographic Experts Group) files, specifically conforming to the JPEG File Interchange Format (JFIF) standard (ITU-T T.871 / ISO/IEC 10918-5). It is identical in structure to .JPEG or .JPG files and represents compressed raster images. The "properties intrinsic to its file system" are interpreted here as the structural and metadata properties embedded within the file's binary format (e.g., headers, markers, and fields), rather than external file system attributes like size or timestamps. These can be extracted by parsing the file's segments.

Based on the JFIF and JPEG specifications, the key extractable properties are:

  • File Signature (Magic Bytes): The starting bytes, typically FFD8 (SOI - Start of Image) followed by FFE0 (APP0 marker for JFIF).
  • JFIF Identifier: The string "JFIF" followed by a null byte (4A 46 49 46 00), confirming JFIF compliance.
  • JFIF Version: Major and minor version numbers (e.g., 1.02).
  • Density Units: Units for pixel density (0: no units, aspect ratio only; 1: dots per inch; 2: dots per cm).
  • Horizontal Pixel Density (Xdensity): Horizontal resolution value.
  • Vertical Pixel Density (Ydensity): Vertical resolution value.
  • Thumbnail Width (Xthumbnail): Horizontal pixel count of the embedded thumbnail (0 if none).
  • Thumbnail Height (Ythumbnail): Vertical pixel count of the embedded thumbnail (0 if none).
  • Has Thumbnail: Boolean indicating if thumbnail data is present (based on non-zero thumbnail dimensions).
  • Image Width: The width of the main image in pixels (from the Start of Frame - SOF marker).
  • Image Height: The height of the main image in pixels (from the SOF marker).
  • Bits per Sample: Sample precision, typically 8 bits per component.
  • Number of Color Components: Number of color channels (1 for grayscale, 3 for YCbCr color).
  • Compression Mode: Type of JPEG compression (e.g., Baseline DCT if SOF0 marker, Progressive DCT if SOF2).
  • Has Extension Thumbnail (JFXX): Boolean indicating if an optional JFIF extension (JFXX APP0) is present for advanced thumbnails.
  • Extension Thumbnail Type: If JFXX present, the code (e.g., 10: JPEG-encoded thumbnail, 11: 1-byte/pixel palette, 13: 3-bytes/pixel RGB).

Note: Additional properties like quantization tables, Huffman tables, comments (COM markers), or application-specific data (e.g., EXIF in APP1) may exist but are optional and not core to all .JPE files. The above focuses on intrinsic JFIF/JPEG elements.

Here are two direct download links for sample .JPE files (verified as valid JPEG images with the .JPE extension):

3. Ghost Blog Embedded HTML/JavaScript for Drag-and-Drop .JPE File Property Dump

Assuming "ghost blog embedded" refers to embeddable HTML/JavaScript code suitable for a Ghost blogging platform (or similar), below is a self-contained HTML snippet with JavaScript. It creates a drag-and-drop area where a user can drop a .JPE file. The script reads the file as an ArrayBuffer, parses the JFIF/JPEG structure, extracts the properties listed in #1, and dumps them to the screen in a readable format. Embed this in a Ghost blog post using their HTML card or code injection.

Drag and drop a .JPE file here

4. Python Class for .JPE File Handling

Below is a Python class that can open a .JPE file, decode/read its structure, extract and print the properties to console, and write a modified version (e.g., updating density values as an example of write capability).

import struct
import sys

class JPEParser:
    def __init__(self, filepath):
        self.filepath = filepath
        self.data = None
        self.properties = {}

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

    def decode(self):
        if len(self.data) < 4 or self.data[:2] != b'\xFF\xD8':
            print("Not a valid .JPE file")
            return
        offset = 2
        while offset < len(self.data):
            marker = struct.unpack('>H', self.data[offset:offset+2])[0]
            offset += 2
            if marker == 0xFFE0:  # APP0 JFIF
                length = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                identifier = self.data[offset:offset+4].decode('ascii', errors='ignore')
                if identifier != 'JFIF':
                    continue
                self.properties['JFIF Identifier'] = identifier
                offset += 5  # JFIF\0
                major, minor = self.data[offset], self.data[offset+1]
                self.properties['JFIF Version'] = f"{major}.{minor:02d}"
                offset += 2
                self.properties['Density Units'] = self.data[offset]
                offset += 1
                self.properties['Horizontal Pixel Density'] = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                self.properties['Vertical Pixel Density'] = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                thumb_w = self.data[offset]
                offset += 1
                thumb_h = self.data[offset]
                offset += 1
                self.properties['Thumbnail Width'] = thumb_w
                self.properties['Thumbnail Height'] = thumb_h
                self.properties['Has Thumbnail'] = 'Yes' if thumb_w > 0 and thumb_h > 0 else 'No'
                offset += 3 * thumb_w * thumb_h  # Skip thumbnail
            elif marker & 0xFFF0 == 0xFFC0 or marker & 0xFFF0 == 0xFFC8:  # SOF
                self.properties['Compression Mode'] = 'Baseline DCT' if marker == 0xFFC0 else 'Progressive DCT'
                length = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                self.properties['Bits per Sample'] = self.data[offset]
                offset += 1
                self.properties['Image Height'] = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                self.properties['Image Width'] = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += 2
                self.properties['Number of Color Components'] = self.data[offset]
                offset += 1
                offset += (length - 8)  # Skip rest
            elif marker == 0xFFD9:  # EOI
                break
            else:
                if (marker & 0xFF00) != 0xFF00:
                    break
                length = struct.unpack('>H', self.data[offset:offset+2])[0]
                offset += length
        self.properties['File Signature'] = 'FFD8FFE0 (JFIF)'

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

    def write(self, output_path, modify_example=False):
        if not self.data:
            print("No data to write")
            return
        data = bytearray(self.data)
        if modify_example:
            # Example: Modify horizontal density to 300 (offset hardcoded for simplicity; in real use, track positions)
            app0_start = data.find(b'\xFF\xE0')
            if app0_start != -1:
                density_offset = app0_start + 2 + 2 + 5 + 2 + 1  # Marker + length + JFIF\0 + version + units
                struct.pack_into('>H', data, density_offset, 300)  # Set Xdensity to 300
        with open(output_path, 'wb') as f:
            f.write(data)

# Usage example:
# parser = JPEParser('sample.jpe')
# parser.read()
# parser.print_properties()
# parser.write('modified.jpe', modify_example=True)

5. Java Class for .JPE File Handling

Below is a Java class that opens a .JPE file, decodes/reads its structure, extracts and prints the properties to console, and writes a modified version (e.g., updating density).

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;

public class JPEParser {
    private String filepath;
    private byte[] data;
    private Map<String, Object> properties = new HashMap<>();

    public JPEParser(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        FileInputStream fis = new FileInputStream(filepath);
        data = fis.readAllBytes();
        fis.close();
        decode();
    }

    private void decode() {
        if (data.length < 4 || (data[0] & 0xFF) != 0xFF || (data[1] & 0xFF) != 0xD8) {
            System.out.println("Not a valid .JPE file");
            return;
        }
        int offset = 2;
        while (offset < data.length) {
            int marker = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
            offset += 2;
            if (marker == 0xFFE0) { // APP0 JFIF
                int length = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                offset += 2;
                String id = new String(data, offset, 4);
                if (!id.equals("JFIF")) continue;
                properties.put("JFIF Identifier", id);
                offset += 5; // JFIF\0
                int major = data[offset] & 0xFF;
                int minor = data[offset + 1] & 0xFF;
                properties.put("JFIF Version", major + "." + String.format("%02d", minor));
                offset += 2;
                properties.put("Density Units", data[offset] & 0xFF);
                offset += 1;
                int xDensity = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                properties.put("Horizontal Pixel Density", xDensity);
                offset += 2;
                int yDensity = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                properties.put("Vertical Pixel Density", yDensity);
                offset += 2;
                int thumbW = data[offset] & 0xFF;
                offset += 1;
                int thumbH = data[offset] & 0xFF;
                offset += 1;
                properties.put("Thumbnail Width", thumbW);
                properties.put("Thumbnail Height", thumbH);
                properties.put("Has Thumbnail", (thumbW > 0 && thumbH > 0) ? "Yes" : "No");
                offset += 3 * thumbW * thumbH;
            } else if ((marker & 0xFFF0) == 0xFFC0 || (marker & 0xFFF0) == 0xFFC8) { // SOF
                properties.put("Compression Mode", (marker == 0xFFC0) ? "Baseline DCT" : "Progressive DCT");
                int length = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                offset += 2;
                properties.put("Bits per Sample", data[offset] & 0xFF);
                offset += 1;
                int height = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                properties.put("Image Height", height);
                offset += 2;
                int width = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                properties.put("Image Width", width);
                offset += 2;
                properties.put("Number of Color Components", data[offset] & 0xFF);
                offset += 1;
                offset += (length - 8);
            } else if (marker == 0xFFD9) {
                break;
            } else {
                if ((marker & 0xFF00) != 0xFF00) break;
                int length = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
                offset += length;
            }
        }
        properties.put("File Signature", "FFD8FFE0 (JFIF)");
    }

    public void printProperties() {
        for (Map.Entry<String, Object> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputPath, boolean modifyExample) throws IOException {
        if (data == null) {
            System.out.println("No data to write");
            return;
        }
        byte[] modifiedData = data.clone();
        if (modifyExample) {
            // Example: Set Xdensity to 300
            ByteBuffer buffer = ByteBuffer.wrap(modifiedData).order(ByteOrder.BIG_ENDIAN);
            int app0Start = -1;
            for (int i = 0; i < modifiedData.length - 1; i++) {
                if ((modifiedData[i] & 0xFF) == 0xFF && (modifiedData[i+1] & 0xFF) == 0xE0) {
                    app0Start = i;
                    break;
                }
            }
            if (app0Start != -1) {
                int densityOffset = app0Start + 2 + 2 + 5 + 2 + 1; // Marker + length + JFIF\0 + version + units
                buffer.position(densityOffset);
                buffer.putShort((short) 300);
            }
        }
        FileOutputStream fos = new FileOutputStream(outputPath);
        fos.write(modifiedData);
        fos.close();
    }

    // Usage example:
    // public static void main(String[] args) throws IOException {
    //     JPEParser parser = new JPEParser("sample.jpe");
    //     parser.read();
    //     parser.printProperties();
    //     parser.write("modified.jpe", true);
    // }
}

6. JavaScript Class for .JPE File Handling

Below is a JavaScript class (for Node.js) that opens a .JPE file, decodes/reads its structure, extracts and prints the properties to console, and writes a modified version (using Node's fs module).

const fs = require('fs');

class JPEParser {
  constructor(filepath) {
    this.filepath = filepath;
    this.data = null;
    this.properties = {};
  }

  read() {
    this.data = fs.readFileSync(this.filepath);
    this.decode();
  }

  decode() {
    if (this.data.length < 4 || this.data[0] !== 0xFF || this.data[1] !== 0xD8) {
      console.log('Not a valid .JPE file');
      return;
    }
    let offset = 2;
    while (offset < this.data.length) {
      const marker = (this.data[offset] << 8) | this.data[offset + 1];
      offset += 2;
      if (marker === 0xFFE0) { // APP0 JFIF
        const length = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        const id = String.fromCharCode(this.data[offset], this.data[offset+1], this.data[offset+2], this.data[offset+3]);
        if (id !== 'JFIF') continue;
        this.properties['JFIF Identifier'] = id;
        offset += 5; // JFIF\0
        const major = this.data[offset];
        const minor = this.data[offset + 1];
        this.properties['JFIF Version'] = `${major}.${minor.toString().padStart(2, '0')}`;
        offset += 2;
        this.properties['Density Units'] = this.data[offset];
        offset += 1;
        this.properties['Horizontal Pixel Density'] = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        this.properties['Vertical Pixel Density'] = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        const thumbW = this.data[offset];
        offset += 1;
        const thumbH = this.data[offset];
        offset += 1;
        this.properties['Thumbnail Width'] = thumbW;
        this.properties['Thumbnail Height'] = thumbH;
        this.properties['Has Thumbnail'] = (thumbW > 0 && thumbH > 0) ? 'Yes' : 'No';
        offset += 3 * thumbW * thumbH;
      } else if ((marker & 0xFFF0) === 0xFFC0 || (marker & 0xFFF0) === 0xFFC8) { // SOF
        this.properties['Compression Mode'] = (marker === 0xFFC0) ? 'Baseline DCT' : 'Progressive DCT';
        const length = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        this.properties['Bits per Sample'] = this.data[offset];
        offset += 1;
        this.properties['Image Height'] = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        this.properties['Image Width'] = (this.data[offset] << 8) | this.data[offset + 1];
        offset += 2;
        this.properties['Number of Color Components'] = this.data[offset];
        offset += 1;
        offset += (length - 8);
      } else if (marker === 0xFFD9) {
        break;
      } else {
        if ((marker & 0xFF00) !== 0xFF00) break;
        const length = (this.data[offset] << 8) | this.data[offset + 1];
        offset += length;
      }
    }
    this.properties['File Signature'] = 'FFD8FFE0 (JFIF)';
  }

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

  write(outputPath, modifyExample = false) {
    if (!this.data) {
      console.log('No data to write');
      return;
    }
    const modifiedData = Buffer.from(this.data);
    if (modifyExample) {
      // Example: Set Xdensity to 300
      const app0Start = modifiedData.indexOf(Buffer.from([0xFF, 0xE0]));
      if (app0Start !== -1) {
        const densityOffset = app0Start + 2 + 2 + 5 + 2 + 1; // Marker + length + JFIF\0 + version + units
        modifiedData.writeUInt16BE(300, densityOffset);
      }
    }
    fs.writeFileSync(outputPath, modifiedData);
  }
}

// Usage example:
// const parser = new JPEParser('sample.jpe');
// parser.read();
// parser.printProperties();
// parser.write('modified.jpe', true);

7. C++ Class for .JPE File Handling

Below is a C++ class that opens a .JPE file, decodes/reads its structure, extracts and prints the properties to console, and writes a modified version.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>
#include <map>

class JPEParser {
private:
    std::string filepath;
    std::vector<unsigned char> data;
    std::map<std::string, std::string> properties;

public:
    JPEParser(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::binary);
        if (!file) {
            std::cout << "Cannot open file" << std::endl;
            return;
        }
        data = std::vector<unsigned char>(std::istreambuf_iterator<char>(file), {});
        file.close();
        decode();
    }

    void decode() {
        if (data.size() < 4 || data[0] != 0xFF || data[1] != 0xD8) {
            std::cout << "Not a valid .JPE file" << std::endl;
            return;
        }
        size_t offset = 2;
        while (offset < data.size()) {
            unsigned short marker = (data[offset] << 8) | data[offset + 1];
            offset += 2;
            if (marker == 0xFFE0) { // APP0 JFIF
                unsigned short length = (data[offset] << 8) | data[offset + 1];
                offset += 2;
                std::string id = "";
                for (int i = 0; i < 4; ++i) id += data[offset + i];
                if (id != "JFIF") continue;
                properties["JFIF Identifier"] = id;
                offset += 5; // JFIF\0
                unsigned char major = data[offset];
                unsigned char minor = data[offset + 1];
                properties["JFIF Version"] = std::to_string(major) + "." + std::to_string(minor);
                offset += 2;
                properties["Density Units"] = std::to_string(data[offset]);
                offset += 1;
                unsigned short xDensity = (data[offset] << 8) | data[offset + 1];
                properties["Horizontal Pixel Density"] = std::to_string(xDensity);
                offset += 2;
                unsigned short yDensity = (data[offset] << 8) | data[offset + 1];
                properties["Vertical Pixel Density"] = std::to_string(yDensity);
                offset += 2;
                unsigned char thumbW = data[offset];
                offset += 1;
                unsigned char thumbH = data[offset];
                offset += 1;
                properties["Thumbnail Width"] = std::to_string(thumbW);
                properties["Thumbnail Height"] = std::to_string(thumbH);
                properties["Has Thumbnail"] = (thumbW > 0 && thumbH > 0) ? "Yes" : "No";
                offset += 3 * thumbW * thumbH;
            } else if ((marker & 0xFFF0) == 0xFFC0 || (marker & 0xFFF0) == 0xFFC8) { // SOF
                properties["Compression Mode"] = (marker == 0xFFC0) ? "Baseline DCT" : "Progressive DCT";
                unsigned short length = (data[offset] << 8) | data[offset + 1];
                offset += 2;
                properties["Bits per Sample"] = std::to_string(data[offset]);
                offset += 1;
                unsigned short height = (data[offset] << 8) | data[offset + 1];
                properties["Image Height"] = std::to_string(height);
                offset += 2;
                unsigned short width = (data[offset] << 8) | data[offset + 1];
                properties["Image Width"] = std::to_string(width);
                offset += 2;
                properties["Number of Color Components"] = std::to_string(data[offset]);
                offset += 1;
                offset += (length - 8);
            } else if (marker == 0xFFD9) {
                break;
            } else {
                if ((marker & 0xFF00) != 0xFF00) break;
                unsigned short length = (data[offset] << 8) | data[offset + 1];
                offset += length;
            }
        }
        properties["File Signature"] = "FFD8FFE0 (JFIF)";
    }

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

    void write(const std::string& outputPath, bool modifyExample = false) {
        if (data.empty()) {
            std::cout << "No data to write" << std::endl;
            return;
        }
        std::vector<unsigned char> modifiedData = data;
        if (modifyExample) {
            // Example: Set Xdensity to 300
            size_t app0Start = 0;
            for (size_t i = 0; i < modifiedData.size() - 1; ++i) {
                if (modifiedData[i] == 0xFF && modifiedData[i + 1] == 0xE0) {
                    app0Start = i;
                    break;
                }
            }
            if (app0Start != 0) {
                size_t densityOffset = app0Start + 2 + 2 + 5 + 2 + 1; // Marker + length + JFIF\0 + version + units
                modifiedData[densityOffset] = 300 >> 8;
                modifiedData[densityOffset + 1] = 300 & 0xFF;
            }
        }
        std::ofstream outFile(outputPath, std::ios::binary);
        outFile.write(reinterpret_cast<const char*>(modifiedData.data()), modifiedData.size());
        outFile.close();
    }
};

// Usage example:
// int main() {
//     JPEParser parser("sample.jpe");
//     parser.read();
//     parser.printProperties();
//     parser.write("modified.jpe", true);
//     return 0;
// }