Task 332: .JPG File Format
Task 332: .JPG File Format
File Format Specifications for .JPG (JPEG)
The .JPG file format, commonly known as JPEG (Joint Photographic Experts Group), is defined by the ISO/IEC 10918-1 standard (also known as ITU-T.81). It specifies a lossy (and optionally lossless) compression method for digital images using discrete cosine transform (DCT) or predictive coding. The file structure is a sequence of binary segments, each starting with a marker (0xFF followed by a type byte). Common variants include JFIF (JPEG File Interchange Format) for basic interchange and Exif for metadata-rich files (e.g., from cameras). JPEG supports baseline sequential, progressive, hierarchical, and lossless modes, with color spaces like YCbCr, grayscale, or CMYK. Files typically start with SOI (0xFFD8) and end with EOI (0xFFD9), containing headers for dimensions, tables for quantization and entropy coding, and compressed data.
List of All Properties Intrinsic to the .JPG File Format
Based on the JPEG standard, the following are the key extractable properties from the file's binary structure (segments and markers). These are intrinsic to the format itself, not external file system metadata like timestamps or permissions. Properties are grouped by segment/marker for clarity:
- Start of Image (SOI, 0xFFD8): Presence (boolean, always true for valid files).
- Frame Header (SOF_n markers, e.g., 0xFFC0 for baseline):
- Frame type (e.g., baseline DCT, progressive, lossless).
- Sample precision (bits per sample, typically 8, range 2-16).
- Image height (lines, 16-bit unsigned, 1-65535).
- Image width (samples per line, 16-bit unsigned, 1-65535).
- Number of color components (1-255, typically 1 for grayscale, 3 for YCbCr).
- For each component: Component ID (unique 8-bit), Horizontal sampling factor (1-4), Vertical sampling factor (1-4), Quantization table ID (0-3).
- Quantization Tables (DQT, 0xFFDB):
- For each table (up to 4): Table ID (0-3), Precision (0 for 8-bit, 1 for 16-bit), 64 quantization values (8 or 16-bit each, zigzag order).
- Huffman Tables (DHT, 0xFFC4):
- For each table (up to 4 DC and 4 AC): Table class (0=DC, 1=AC), Table ID (0-3), 16 code lengths (counts of codes per bit length 1-16), Variable-length symbols (Huffman codes' values).
- Arithmetic Conditioning Tables (DAC, 0xFFCC): For each table: Conditioning table ID, Lower/Upper bound values (rarely used in common files).
- Restart Interval (DRI, 0xFFDD): Interval value (16-bit, number of MCUs between restarts, 0 if no restarts).
- Scan Header (SOS, 0xFFDA):
- Number of components in scan (1-4).
- For each scan component: Selector (matches component ID), DC entropy table ID (0-3), AC entropy table ID (0-3).
- Spectral selection start (0-63 for DCT).
- Spectral selection end (0-63 for DCT).
- Successive approximation bit high (0-13).
- Successive approximation bit low (0-13).
- Restart Markers (RST_n, 0xFFD0-0xFFD7): Cycle count (0-7, for error resilience).
- Comment (COM, 0xFFFE): Arbitrary text string (variable length).
- Application Segments (APP_n, 0xFFE0-0xFFEF):
- JFIF (APP0 "JFIF"): Version (major/minor, e.g., 1.02), Units (0=no units/aspect ratio, 1=dpi, 2=dpcm), X density (horizontal, 16-bit), Y density (vertical, 16-bit), Thumbnail width (8-bit), Thumbnail height (8-bit), Thumbnail RGB data (3 bytes per pixel if present).
- JFXX Extension (APP0 "JFXX"): Extension code (0x10=JPEG thumbnail, 0x11=1-byte/pixel palette thumbnail, 0x13=3-byte/pixel RGB thumbnail), Extension data (variable, e.g., compressed thumbnail or palette).
- Exif (APP1 "Exif"): TIFF-based metadata including camera model, exposure, orientation, GPS (structure too complex for full list here, but extractable as key-value pairs).
- Other APP_n: Vendor-specific data (e.g., Adobe APP14 for color space), ICC profiles, or thumbnails.
- Hierarchical Markers (e.g., DHT for hierarchical, EXP 0xFFDF): Expansion factors (rare in standard files).
- End of Image (EOI, 0xFFD9): Presence (boolean).
- General Properties: Compression mode (inferred from markers, e.g., progressive if multiple scans), Color space (inferred from components and APP segments, e.g., YCbCr), Entropy coding type (Huffman or arithmetic), Byte stuffing presence in data.
These properties define the image's decoding parameters, metadata, and compressed data layout.
Two Direct Download Links for .JPG Files
- https://filesamples.com/samples/image/jpeg/sample_640×426.jpeg (Sample landscape image, ~50KB)
- https://yavuzceliker.github.io/sample-images/image-1.jpg (Sample abstract image from GitHub repo, ~100KB)
Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .JPG Property Dump
Here's a self-contained HTML snippet with JavaScript that can be embedded in a Ghost blog post (or any HTML-enabled blog). It creates a drag-and-drop area. When a .JPG file is dropped, it reads the file as binary, parses the JPEG markers, extracts the properties from the list above, and dumps them to the screen in a readable format. It handles basic validation and displays errors if the file isn't a valid JPEG.
Python Class for .JPG Handling
Here's a Python class JPEGHandler
that can open a .JPG file, decode (parse) its structure, read and print all properties to console, and write (re-encode) the file (basic copy for now, as full encoding from scratch is complex; it parses and rewrites the segments).
import struct
import sys
class JPEGHandler:
def __init__(self, filepath):
self.filepath = filepath
self.data = None
self.properties = {}
def open(self):
with open(self.filepath, 'rb') as f:
self.data = f.read()
def decode(self):
if self.data is None:
raise ValueError("File not opened")
offset = 0
if self.data[offset:offset+2] != b'\xFF\xD8':
raise ValueError("Invalid JPEG: Missing SOI")
self.properties['SOI'] = 'Present'
offset += 2
while offset < len(self.data):
if self.data[offset] != 0xFF:
break
marker = self.data[offset + 1]
offset += 2
if marker == 0xD9:
self.properties['EOI'] = 'Present'
break
if marker in [0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7]: # RST
self.properties.setdefault('Restart Markers', []).append(marker - 0xD0)
continue
length = struct.unpack('>H', self.data[offset:offset+2])[0]
offset += 2
if marker in range(0xC0, 0xD0) and marker not in [0xC4, 0xC8, 0xCC]: # SOF
frame_type = self.get_frame_type(marker)
self.properties['Frame Type'] = frame_type
precision, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
height, = struct.unpack('>H', self.data[offset:offset+2])
offset += 2
width, = struct.unpack('>H', self.data[offset:offset+2])
offset += 2
components, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
self.properties['Sample Precision'] = precision
self.properties['Height'] = height
self.properties['Width'] = width
self.properties['Components'] = components
comp_list = []
for i in range(components):
id_, sampling, qtable = struct.unpack('>BBB', self.data[offset:offset+3])
h = sampling >> 4
v = sampling & 0x0F
comp_list.append({'ID': id_, 'H': h, 'V': v, 'Quant Table': qtable})
offset += 3
self.properties['Component Details'] = comp_list
elif marker == 0xDB: # DQT
qt_list = []
end = offset + length - 2
while offset < end:
info, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
prec = 16 if info >> 4 else 8
id_ = info & 0x0F
values = []
for _ in range(64):
if prec == 16:
val, = struct.unpack('>H', self.data[offset:offset+2])
offset += 2
else:
val, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
values.append(val)
qt_list.append({'ID': id_, 'Precision': prec, 'Values': values})
self.properties['Quantization Tables'] = qt_list
elif marker == 0xC4: # DHT
ht_list = []
end = offset + length - 2
while offset < end:
info, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
class_ = 'AC' if info >> 4 else 'DC'
id_ = info & 0x0F
lengths = list(struct.unpack('>16B', self.data[offset:offset+16]))
offset += 16
total_sym = sum(lengths)
symbols = list(struct.unpack(f'>{total_sym}B', self.data[offset:offset+total_sym]))
offset += total_sym
ht_list.append({'Class': class_, 'ID': id_, 'Code Lengths': lengths, 'Symbols': symbols})
self.properties['Huffman Tables'] = ht_list
elif marker == 0xDD: # DRI
interval, = struct.unpack('>H', self.data[offset:offset+2])
self.properties['Restart Interval'] = interval
offset += 2
elif marker == 0xDA: # SOS
comps, = struct.unpack('>B', self.data[offset:offset+1])
offset += 1
scan_comps = []
for _ in range(comps):
selector, tables = struct.unpack('>BB', self.data[offset:offset+2])
dc = tables >> 4
ac = tables & 0x0F
scan_comps.append({'Selector': selector, 'DC Table': dc, 'AC Table': ac})
offset += 2
ss, se, approx = struct.unpack('>BBB', self.data[offset:offset+3])
ah = approx >> 4
al = approx & 0x0F
self.properties['Scan Header'] = {
'Components in Scan': comps,
'Component Details': scan_comps,
'Spectral Start': ss,
'Spectral End': se,
'Approx High': ah,
'Approx Low': al
}
offset += 3
# Skip entropy data until next marker
while offset < len(self.data) and (self.data[offset] != 0xFF or self.data[offset+1] == 0x00):
offset += 1
if self.data[offset] == 0xFF and self.data[offset+1] != 0x00:
offset -= 2 # Backtrack for next marker loop
elif marker == 0xFE: # COM
comment = self.data[offset:offset + length - 2].decode('utf-8', errors='ignore')
self.properties['Comment'] = comment
elif marker in range(0xE0, 0xF0): # APPn
app_data = self.data[offset:offset+5].decode('ascii', errors='ignore')
if app_data == 'JFIF\x00':
major, minor, units = struct.unpack('>BBB', self.data[offset+5:offset+8])
xdens, = struct.unpack('>H', self.data[offset+8:offset+10])
ydens, = struct.unpack('>H', self.data[offset+10:offset+12])
thumb_w, thumb_h = struct.unpack('>BB', self.data[offset+12:offset+14])
self.properties['JFIF'] = {
'Version': f'{major}.{minor}',
'Units': units,
'X Density': xdens,
'Y Density': ydens,
'Thumbnail Width': thumb_w,
'Thumbnail Height': thumb_h
}
elif app_data == 'JFXX\x00':
ext_code, = struct.unpack('>B', self.data[offset+5:offset+6])
self.properties['JFXX'] = {'Extension Code': hex(ext_code)}
else:
self.properties[f'APP{marker - 0xE0}'] = app_data
offset += length - 2
def print_properties(self):
for key, value in self.properties.items():
print(f'{key}: {value}')
def write(self, output_path):
if self.data is None:
raise ValueError("No data to write")
with open(output_path, 'wb') as f:
f.write(self.data) # Basic write; extend for modifications
@staticmethod
def get_frame_type(marker):
types = {0xC0: 'Baseline DCT', 0xC1: 'Extended Sequential DCT', 0xC2: 'Progressive DCT', 0xC3: 'Lossless', 0xC5: 'Diff Sequential DCT', 0xC6: 'Diff Progressive DCT', 0xC7: 'Diff Lossless'}
return types.get(marker, 'Unknown')
# Example usage:
# handler = JPEGHandler('example.jpg')
# handler.open()
# handler.decode()
# handler.print_properties()
# handler.write('output.jpg')
Java Class for .JPG Handling
Here's a Java class JPEGHandler
that opens a .JPG file, decodes its structure, reads and prints properties to console, and writes the file (basic copy).
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class JPEGHandler {
private String filepath;
private byte[] data;
private java.util.Map<String, Object> properties = new java.util.HashMap<>();
public JPEGHandler(String filepath) {
this.filepath = filepath;
}
public void open() throws IOException {
try (FileInputStream fis = new FileInputStream(filepath)) {
data = fis.readAllBytes();
}
}
public void decode() {
if (data == null) throw new IllegalStateException("File not opened");
int offset = 0;
if (data[offset] != (byte)0xFF || data[offset+1] != (byte)0xD8) {
throw new IllegalStateException("Invalid JPEG: Missing SOI");
}
properties.put("SOI", "Present");
offset += 2;
while (offset < data.length) {
if ((data[offset] & 0xFF) != 0xFF) break;
int marker = data[offset + 1] & 0xFF;
offset += 2;
if (marker == 0xD9) {
properties.put("EOI", "Present");
break;
}
if (marker >= 0xD0 && marker <= 0xD7) { // RST
java.util.List<Integer> rsts = (java.util.List<Integer>) properties.getOrDefault("Restart Markers", new java.util.ArrayList<>());
rsts.add(marker - 0xD0);
properties.put("Restart Markers", rsts);
continue;
}
ByteBuffer bb = ByteBuffer.wrap(data, offset, data.length - offset).order(ByteOrder.BIG_ENDIAN);
int length = bb.getShort() & 0xFFFF;
offset += 2;
if (marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC) { // SOF
properties.put("Frame Type", getFrameType(marker));
int precision = bb.get() & 0xFF;
int height = bb.getShort() & 0xFFFF;
int width = bb.getShort() & 0xFFFF;
int components = bb.get() & 0xFF;
properties.put("Sample Precision", precision);
properties.put("Height", height);
properties.put("Width", width);
properties.put("Components", components);
java.util.List<java.util.Map<String, Integer>> compList = new java.util.ArrayList<>();
for (int i = 0; i < components; i++) {
int id = bb.get() & 0xFF;
int sampling = bb.get() & 0xFF;
int h = sampling >> 4;
int v = sampling & 0x0F;
int qTable = bb.get() & 0xFF;
java.util.Map<String, Integer> comp = new java.util.HashMap<>();
comp.put("ID", id);
comp.put("H", h);
comp.put("V", v);
comp.put("Quant Table", qTable);
compList.add(comp);
}
properties.put("Component Details", compList);
offset += length - 2;
continue;
} else if (marker == 0xDB) { // DQT
java.util.List<java.util.Map<String, Object>> qtList = new java.util.ArrayList<>();
int end = offset + length - 2;
while (offset < end) {
int info = bb.get() & 0xFF;
int prec = (info >> 4) == 1 ? 16 : 8;
int id = info & 0x0F;
int[] values = new int[64];
for (int i = 0; i < 64; i++) {
values[i] = prec == 16 ? bb.getShort() & 0xFFFF : bb.get() & 0xFF;
}
java.util.Map<String, Object> qt = new java.util.HashMap<>();
qt.put("ID", id);
qt.put("Precision", prec);
qt.put("Values", values);
qtList.add(qt);
offset = bb.position();
}
properties.put("Quantization Tables", qtList);
} else if (marker == 0xC4) { // DHT
java.util.List<java.util.Map<String, Object>> htList = new java.util.ArrayList<>();
int end = offset + length - 2;
while (offset < end) {
int info = bb.get() & 0xFF;
String classType = (info >> 4) == 1 ? "AC" : "DC";
int id = info & 0x0F;
int[] lengths = new int[16];
int totalSym = 0;
for (int i = 0; i < 16; i++) {
lengths[i] = bb.get() & 0xFF;
totalSym += lengths[i];
}
int[] symbols = new int[totalSym];
for (int i = 0; i < totalSym; i++) {
symbols[i] = bb.get() & 0xFF;
}
java.util.Map<String, Object> ht = new java.util.HashMap<>();
ht.put("Class", classType);
ht.put("ID", id);
ht.put("Code Lengths", lengths);
ht.put("Symbols", symbols);
htList.add(ht);
offset = bb.position();
}
properties.put("Huffman Tables", htList);
} else if (marker == 0xDD) { // DRI
int interval = bb.getShort() & 0xFFFF;
properties.put("Restart Interval", interval);
} else if (marker == 0xDA) { // SOS
int comps = bb.get() & 0xFF;
java.util.List<java.util.Map<String, Integer>> scanComps = new java.util.ArrayList<>();
for (int i = 0; i < comps; i++) {
int selector = bb.get() & 0xFF;
int tables = bb.get() & 0xFF;
int dc = tables >> 4;
int ac = tables & 0x0F;
java.util.Map<String, Integer> sc = new java.util.HashMap<>();
sc.put("Selector", selector);
sc.put("DC Table", dc);
sc.put("AC Table", ac);
scanComps.add(sc);
}
int ss = bb.get() & 0xFF;
int se = bb.get() & 0xFF;
int approx = bb.get() & 0xFF;
int ah = approx >> 4;
int al = approx & 0x0F;
java.util.Map<String, Object> scanHeader = new java.util.HashMap<>();
scanHeader.put("Components in Scan", comps);
scanHeader.put("Component Details", scanComps);
scanHeader.put("Spectral Start", ss);
scanHeader.put("Spectral End", se);
scanHeader.put("Approx High", ah);
scanHeader.put("Approx Low", al);
properties.put("Scan Header", scanHeader);
offset = bb.position();
// Skip entropy data
while (offset < data.length && ( (data[offset] & 0xFF) != 0xFF || (data[offset+1] & 0xFF) == 0x00 )) {
offset++;
}
if (offset < data.length && (data[offset] & 0xFF) == 0xFF) offset -= 2; // Backtrack
continue;
} else if (marker == 0xFE) { // COM
String comment = new String(data, offset, length - 2);
properties.put("Comment", comment);
} else if (marker >= 0xE0 && marker <= 0xEF) { // APPn
String appData = new String(data, offset, 5);
if (appData.equals("JFIF\0")) {
int major = data[offset+5] & 0xFF;
int minor = data[offset+6] & 0xFF;
int units = data[offset+7] & 0xFF;
int xDensity = ((data[offset+8] & 0xFF) << 8) | (data[offset+9] & 0xFF);
int yDensity = ((data[offset+10] & 0xFF) << 8) | (data[offset+11] & 0xFF);
int thumbW = data[offset+12] & 0xFF;
int thumbH = data[offset+13] & 0xFF;
java.util.Map<String, Object> jfif = new java.util.HashMap<>();
jfif.put("Version", major + "." + minor);
jfif.put("Units", units);
jfif.put("X Density", xDensity);
jfif.put("Y Density", yDensity);
jfif.put("Thumbnail Width", thumbW);
jfif.put("Thumbnail Height", thumbH);
properties.put("JFIF", jfif);
} else if (appData.equals("JFXX\0")) {
int extCode = data[offset+5] & 0xFF;
java.util.Map<String, Integer> jfxx = new java.util.HashMap<>();
jfxx.put("Extension Code", extCode);
properties.put("JFXX", jfxx);
} else {
properties.put("APP" + (marker - 0xE0), appData);
}
}
offset += length - 2;
}
}
public void printProperties() {
properties.forEach((key, value) -> System.out.println(key + ": " + value));
}
public void write(String outputPath) throws IOException {
if (data == null) throw new IllegalStateException("No data to write");
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
fos.write(data);
}
}
private String getFrameType(int marker) {
switch (marker) {
case 0xC0: return "Baseline DCT";
case 0xC1: return "Extended Sequential DCT";
case 0xC2: return "Progressive DCT";
case 0xC3: return "Lossless";
case 0xC5: return "Diff Sequential DCT";
case 0xC6: return "Diff Progressive DCT";
case 0xC7: return "Diff Lossless";
default: return "Unknown";
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// JPEGHandler handler = new JPEGHandler("example.jpg");
// handler.open();
// handler.decode();
// handler.printProperties();
// handler.write("output.jpg");
// }
}
JavaScript Class for .JPG Handling
Here's a JavaScript class JPEGHandler
for Node.js (using fs for file I/O). It opens, decodes, reads/prints properties to console, and writes the file.
const fs = require('fs');
class JPEGHandler {
constructor(filepath) {
this.filepath = filepath;
this.data = null;
this.properties = {};
}
open() {
this.data = fs.readFileSync(this.filepath);
}
decode() {
if (!this.data) throw new Error('File not opened');
let offset = 0;
if (this.data[offset] !== 0xFF || this.data[offset + 1] !== 0xD8) {
throw new Error('Invalid JPEG: Missing SOI');
}
this.properties.SOI = 'Present';
offset += 2;
while (offset < this.data.length) {
if (this.data[offset] !== 0xFF) break;
const marker = this.data[offset + 1];
offset += 2;
if (marker === 0xD9) {
this.properties.EOI = 'Present';
break;
}
if (marker >= 0xD0 && marker <= 0xD7) { // RST
this.properties.RestartMarkers = this.properties.RestartMarkers || [];
this.properties.RestartMarkers.push(marker - 0xD0);
continue;
}
let length = (this.data[offset] << 8) | this.data[offset + 1];
offset += 2;
if (marker >= 0xC0 && marker <= 0xCF && marker !== 0xC4 && marker !== 0xC8 && marker !== 0xCC) { // SOF
this.properties['Frame Type'] = this.getFrameType(marker);
const precision = this.data[offset++];
const height = (this.data[offset] << 8) | this.data[offset + 1]; offset += 2;
const width = (this.data[offset] << 8) | this.data[offset + 1]; offset += 2;
const components = this.data[offset++];
this.properties['Sample Precision'] = precision;
this.properties.Height = height;
this.properties.Width = width;
this.properties.Components = components;
this.properties['Component Details'] = [];
for (let i = 0; i < components; i++) {
const id = this.data[offset++];
const sampling = this.data[offset++];
const h = sampling >> 4, v = sampling & 0x0F;
const qTable = this.data[offset++];
this.properties['Component Details'].push({ID: id, H: h, V: v, 'Quant Table': qTable});
}
} else if (marker === 0xDB) { // DQT
this.properties['Quantization Tables'] = [];
const end = offset + length - 2;
while (offset < end) {
const info = this.data[offset++];
const prec = info >> 4 ? 16 : 8;
const id = info & 0x0F;
const values = [];
for (let i = 0; i < 64; i++) {
let val;
if (prec === 16) {
val = (this.data[offset] << 8) | this.data[offset + 1];
offset += 2;
} else {
val = this.data[offset++];
}
values.push(val);
}
this.properties['Quantization Tables'].push({ID: id, Precision: prec, Values: values});
}
} else if (marker === 0xC4) { // DHT
this.properties['Huffman Tables'] = [];
const end = offset + length - 2;
while (offset < end) {
const info = this.data[offset++];
const classType = info >> 4 ? 'AC' : 'DC';
const id = info & 0x0F;
const lengths = [];
let totalSym = 0;
for (let i = 0; i < 16; i++) {
const count = this.data[offset++];
lengths.push(count);
totalSym += count;
}
const symbols = [];
for (let i = 0; i < totalSym; i++) {
symbols.push(this.data[offset++]);
}
this.properties['Huffman Tables'].push({Class: classType, ID: id, 'Code Lengths': lengths, Symbols: symbols});
}
} else if (marker === 0xDD) { // DRI
const interval = (this.data[offset] << 8) | this.data[offset + 1];
this.properties['Restart Interval'] = interval;
offset += 2;
} else if (marker === 0xDA) { // SOS
const comps = this.data[offset++];
this.properties['Scan Header'] = { 'Components in Scan': comps, 'Component Details': [] };
for (let i = 0; i < comps; i++) {
const selector = this.data[offset++];
const tables = this.data[offset++];
const dc = tables >> 4, ac = tables & 0x0F;
this.properties['Scan Header']['Component Details'].push({Selector: selector, 'DC Table': dc, 'AC Table': ac});
}
const ss = this.data[offset++], se = this.data[offset++];
const approx = this.data[offset++];
const ah = approx >> 4, al = approx & 0x0F;
this.properties['Scan Header']['Spectral Start'] = ss;
this.properties['Scan Header']['Spectral End'] = se;
this.properties['Scan Header']['Approx High'] = ah;
this.properties['Scan Header']['Approx Low'] = al;
// Skip entropy
while (offset < this.data.length && (this.data[offset] !== 0xFF || this.data[offset + 1] === 0x00)) offset++;
if (this.data[offset] === 0xFF && this.data[offset + 1] !== 0x00) offset -= 2;
} else if (marker === 0xFE) { // COM
const comment = this.data.slice(offset, offset + length - 2).toString('utf-8');
this.properties.Comment = comment;
} else if (marker >= 0xE0 && marker <= 0xEF) { // APPn
const appData = this.data.slice(offset, offset + 5).toString('ascii');
if (appData === 'JFIF\0') {
const major = this.data[offset + 5];
const minor = this.data[offset + 6];
const units = this.data[offset + 7];
const xDensity = (this.data[offset + 8] << 8) | this.data[offset + 9];
const yDensity = (this.data[offset + 10] << 8) | this.data[offset + 11];
const thumbW = this.data[offset + 12];
const thumbH = this.data[offset + 13];
this.properties.JFIF = {
Version: `${major}.${minor}`,
Units: units,
'X Density': xDensity,
'Y Density': yDensity,
'Thumbnail Width': thumbW,
'Thumbnail Height': thumbH
};
} else if (appData === 'JFXX\0') {
const extCode = this.data[offset + 5];
this.properties.JFXX = { 'Extension Code': extCode.toString(16) };
} else {
this.properties[`APP${marker - 0xE0}`] = appData;
}
}
offset += length - 2;
}
}
printProperties() {
console.log(this.properties);
}
write(outputPath) {
if (!this.data) throw new Error('No data to write');
fs.writeFileSync(outputPath, this.data);
}
getFrameType(marker) {
const types = {
0xC0: 'Baseline DCT',
0xC1: 'Extended Sequential DCT',
0xC2: 'Progressive DCT',
0xC3: 'Lossless',
0xC5: 'Diff Sequential DCT',
0xC6: 'Diff Progressive DCT',
0xC7: 'Diff Lossless'
};
return types[marker] || 'Unknown';
}
}
// Example usage:
// const handler = new JPEGHandler('example.jpg');
// handler.open();
// handler.decode();
// handler.printProperties();
// handler.write('output.jpg');
C "Class" (Struct with Functions) for .JPG Handling
In C, classes aren't native, so here's a struct JPEGHandler
with associated functions. It opens, decodes, reads/prints properties to stdout, and writes the file. Compile with gcc -o jpeg_handler jpeg_handler.c
.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char* filepath;
uint8_t* data;
size_t size;
// For properties, use a simple array of strings for demo; in real, use dict-like
char* properties[100]; // Limited, for simplicity
int prop_count;
} JPEGHandler;
void init_jpeg_handler(JPEGHandler* handler, const char* filepath) {
handler->filepath = strdup(filepath);
handler->data = NULL;
handler->size = 0;
handler->prop_count = 0;
}
void open_file(JPEGHandler* handler) {
FILE* f = fopen(handler->filepath, "rb");
if (!f) {
perror("open_file");
exit(1);
}
fseek(f, 0, SEEK_END);
handler->size = ftell(f);
fseek(f, 0, SEEK_SET);
handler->data = malloc(handler->size);
fread(handler->data, 1, handler->size, f);
fclose(f);
}
void add_property(JPEGHandler* handler, const char* prop) {
if (handler->prop_count < 100) {
handler->properties[handler->prop_count++] = strdup(prop);
}
}
void decode(JPEGHandler* handler) {
if (!handler->data) {
fprintf(stderr, "File not opened\n");
return;
}
size_t offset = 0;
if (handler->data[offset] != 0xFF || handler->data[offset + 1] != 0xD8) {
fprintf(stderr, "Invalid JPEG: Missing SOI\n");
return;
}
add_property(handler, "SOI: Present");
offset += 2;
char buf[256];
while (offset < handler->size) {
if (handler->data[offset] != 0xFF) break;
uint8_t marker = handler->data[offset + 1];
offset += 2;
if (marker == 0xD9) {
add_property(handler, "EOI: Present");
break;
}
if (marker >= 0xD0 && marker <= 0xD7) { // RST
snprintf(buf, sizeof(buf), "Restart Marker: %d", marker - 0xD0);
add_property(handler, buf);
continue;
}
uint16_t length = (handler->data[offset] << 8) | handler->data[offset + 1];
offset += 2;
if (marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC) { // SOF
const char* type = get_frame_type(marker);
snprintf(buf, sizeof(buf), "Frame Type: %s", type);
add_property(handler, buf);
uint8_t precision = handler->data[offset++];
uint16_t height = (handler->data[offset] << 8) | handler->data[offset + 1]; offset += 2;
uint16_t width = (handler->data[offset] << 8) | handler->data[offset + 1]; offset += 2;
uint8_t components = handler->data[offset++];
snprintf(buf, sizeof(buf), "Sample Precision: %u", precision);
add_property(handler, buf);
snprintf(buf, sizeof(buf), "Height: %u", height);
add_property(handler, buf);
snprintf(buf, sizeof(buf), "Width: %u", width);
add_property(handler, buf);
snprintf(buf, sizeof(buf), "Components: %u", components);
add_property(handler, buf);
add_property(handler, "Component Details:");
for (uint8_t i = 0; i < components; i++) {
uint8_t id = handler->data[offset++];
uint8_t sampling = handler->data[offset++];
uint8_t h = sampling >> 4, v = sampling & 0x0F;
uint8_t qtable = handler->data[offset++];
snprintf(buf, sizeof(buf), " Component %u: ID=%u, H=%u, V=%u, Quant Table=%u", i+1, id, h, v, qtable);
add_property(handler, buf);
}
} else if (marker == 0xDB) { // DQT
add_property(handler, "Quantization Tables:");
size_t end = offset + length - 2;
while (offset < end) {
uint8_t info = handler->data[offset++];
uint8_t prec = info >> 4 ? 16 : 8;
uint8_t id = info & 0x0F;
snprintf(buf, sizeof(buf), " Table ID: %u, Precision: %u-bit", id, prec);
add_property(handler, buf);
add_property(handler, " Values:");
char val_buf[1024] = "";
for (int i = 0; i < 64; i++) {
uint16_t val;
if (prec == 16) {
val = (handler->data[offset] << 8) | handler->data[offset + 1];
offset += 2;
} else {
val = handler->data[offset++];
}
char temp[10];
snprintf(temp, sizeof(temp), "%u ", val);
strcat(val_buf, temp);
if (strlen(val_buf) > 900) { // Prevent overflow
add_property(handler, val_buf);
val_buf[0] = '\0';
}
}
add_property(handler, val_buf);
}
} else if (marker == 0xC4) { // DHT
add_property(handler, "Huffman Tables:");
size_t end = offset + length - 2;
while (offset < end) {
uint8_t info = handler->data[offset++];
const char* class_type = (info >> 4) ? "AC" : "DC";
uint8_t id = info & 0x0F;
snprintf(buf, sizeof(buf), " Table: %s ID=%u", class_type, id);
add_property(handler, buf);
add_property(handler, " Code Lengths:");
char len_buf[256] = "";
int total_sym = 0;
for (int i = 0; i < 16; i++) {
uint8_t count = handler->data[offset++];
char temp[10];
snprintf(temp, sizeof(temp), "%u ", count);
strcat(len_buf, temp);
total_sym += count;
}
add_property(handler, len_buf);
add_property(handler, " Symbols:");
char sym_buf[1024] = "";
for (int i = 0; i < total_sym; i++) {
uint8_t sym = handler->data[offset++];
char temp[10];
snprintf(temp, sizeof(temp), "%u ", sym);
strcat(sym_buf, temp);
if (strlen(sym_buf) > 900) {
add_property(handler, sym_buf);
sym_buf[0] = '\0';
}
}
add_property(handler, sym_buf);
}
} else if (marker == 0xDD) { // DRI
uint16_t interval = (handler->data[offset] << 8) | handler->data[offset + 1];
snprintf(buf, sizeof(buf), "Restart Interval: %u", interval);
add_property(handler, buf);
offset += 2;
} else if (marker == 0xDA) { // SOS
add_property(handler, "Scan Header:");
uint8_t comps = handler->data[offset++];
snprintf(buf, sizeof(buf), " Components in Scan: %u", comps);
add_property(handler, buf);
for (uint8_t i = 0; i < comps; i++) {
uint8_t selector = handler->data[offset++];
uint8_t tables = handler->data[offset++];
uint8_t dc = tables >> 4, ac = tables & 0x0F;
snprintf(buf, sizeof(buf), " Component %u: Selector=%u, DC Table=%u, AC Table=%u", i+1, selector, dc, ac);
add_property(handler, buf);
}
uint8_t ss = handler->data[offset++], se = handler->data[offset++];
uint8_t approx = handler->data[offset++];
uint8_t ah = approx >> 4, al = approx & 0x0F;
snprintf(buf, sizeof(buf), " Spectral Start: %u, End: %u", ss, se);
add_property(handler, buf);
snprintf(buf, sizeof(buf), " Approx High: %u, Low: %u", ah, al);
add_property(handler, buf);
// Skip entropy
while (offset < handler->size && (handler->data[offset] != 0xFF || handler->data[offset + 1] == 0x00)) offset++;
if (handler->data[offset] == 0xFF && handler->data[offset + 1] != 0x00) offset -= 2;
} else if (marker == 0xFE) { // COM
char* comment = malloc(length - 1);
strncpy(comment, (const char*)&handler->data[offset], length - 2);
comment[length - 2] = '\0';
snprintf(buf, sizeof(buf), "Comment: %s", comment);
add_property(handler, buf);
free(comment);
} else if (marker >= 0xE0 && marker <= 0xEF) { // APPn
char app_data[6];
strncpy(app_data, (const char*)&handler->data[offset], 5);
app_data[5] = '\0';
if (strcmp(app_data, "JFIF") == 0) { // Note: \0 is next byte
uint8_t major = handler->data[offset + 5];
uint8_t minor = handler->data[offset + 6];
uint8_t units = handler->data[offset + 7];
uint16_t xdens = (handler->data[offset + 8] << 8) | handler->data[offset + 9];
uint16_t ydens = (handler->data[offset + 10] << 8) | handler->data[offset + 11];
uint8_t thumb_w = handler->data[offset + 12];
uint8_t thumb_h = handler->data[offset + 13];
snprintf(buf, sizeof(buf), "JFIF: Version %u.%u, Units=%u, XDensity=%u, YDensity=%u, Thumbnail %ux%u", major, minor, units, xdens, ydens, thumb_w, thumb_h);
add_property(handler, buf);
} else if (strcmp(app_data, "JFXX") == 0) {
uint8_t ext_code = handler->data[offset + 5];
snprintf(buf, sizeof(buf), "JFXX Extension: Code 0x%02X", ext_code);
add_property(handler, buf);
} else {
snprintf(buf, sizeof(buf), "APP%u: %s", marker - 0xE0, app_data);
add_property(handler, buf);
}
}
offset += length - 2;
}
}
void print_properties(const JPEGHandler* handler) {
for (int i = 0; i < handler->prop_count; i++) {
printf("%s\n", handler->properties[i]);
}
}
void write_file(const JPEGHandler* handler, const char* output_path) {
if (!handler->data) {
fprintf(stderr, "No data to write\n");
return;
}
FILE* f = fopen(output_path, "wb");
if (!f) {
perror("write_file");
return;
}
fwrite(handler->data, 1, handler->size, f);
fclose(f);
}
void free_handler(JPEGHandler* handler) {
free(handler->data);
free(handler->filepath);
for (int i = 0; i < handler->prop_count; i++) {
free(handler->properties[i]);
}
}
const char* get_frame_type(uint8_t marker) {
switch (marker) {
case 0xC0: return "Baseline DCT";
case 0xC1: return "Extended Sequential DCT";
case 0xC2: return "Progressive DCT";
case 0xC3: return "Lossless";
case 0xC5: return "Diff Sequential DCT";
case 0xC6: return "Diff Progressive DCT";
case 0xC7: return "Diff Lossless";
default: return "Unknown";
}
}
// Example usage:
// int main() {
// JPEGHandler handler;
// init_jpeg_handler(&handler, "example.jpg");
// open_file(&handler);
// decode(&handler);
// print_properties(&handler);
// write_file(&handler, "output.jpg");
// free_handler(&handler);
// return 0;
// }