This document is generated by AI, and there may be issues with the specific details.
- Version: 1.0
- Date: 2025-01-XX
- Purpose: Public technical specification for format conversion
- Target Platform: ESP32 E-Paper Display Devices
Four proprietary file formats designed for ESP32 e-paper displays:
| Format | Extension | Purpose | Description |
|---|---|---|---|
| XTG | .xtg |
Monochrome Image | 1-bit per pixel bitmap |
| XTH | .xth |
4-Level Grayscale Image | 2-bit per pixel bitmap |
| XTC | .xtc |
Comic Container | Container format with multiple XTG pages |
| XTCH | .xtch |
Comic Container Variant | Variant of XTC format |
All multi-byte values are stored in Little-Endian format.
- Little-Endian: Least significant byte at lowest address
- Example:
uint16_t 0x1234→[0x34, 0x12] - Example:
uint32_t 0x12345678→[0x78, 0x56, 0x34, 0x12]
XTG stores 1-bit per pixel monochrome bitmaps optimized for e-paper displays.
| Offset | Size | Type | Field | Description | Value |
|---|---|---|---|---|---|
| 0x00 | 4 | uint32_t | mark | File identifier | 0x00475458 ("XTG\0") |
| 0x04 | 2 | uint16_t | width | Image width (pixels) | 1-65535 |
| 0x06 | 2 | uint16_t | height | Image height (pixels) | 1-65535 |
| 0x08 | 1 | uint8_t | colorMode | Color mode | 0=monochrome |
| 0x09 | 1 | uint8_t | compression | Compression | 0=uncompressed |
| 0x0A | 4 | uint32_t | dataSize | Image data size (bytes) | Calculated |
| 0x0E | 8 | uint64_t | md5 | MD5 checksum (first 8 bytes) | Optional |
- Location: After header (offset 22 bytes)
- Format: Bitmap data, 1 bit per pixel
- Data Size Calculation:
dataSize = ((width + 7) / 8) * height - Pixel Storage:
- Rows stored top to bottom
- Each row stored left to right
- 8 pixels per byte (MSB first)
- Bit order: bit 7 (MSB) = leftmost pixel, bit 0 (LSB) = rightmost pixel
| Bit Value | Display Color |
|---|---|
| 0 | Black |
| 1 | White |
Note: When using inverted bitmap drawing, the display may invert these values (1=black, 0=white).
XTH stores 2-bit per pixel grayscale bitmaps for 4-level grayscale e-paper displays.
Same structure as XTG, but with different file identifier:
| Offset | Size | Type | Field | Description | Value |
|---|---|---|---|---|---|
| 0x00 | 4 | uint32_t | mark | File identifier | 0x00485458 ("XTH\0") |
| 0x04 | 2 | uint16_t | width | Image width (pixels) | 1-65535 |
| 0x06 | 2 | uint16_t | height | Image height (pixels) | 1-65535 |
| 0x08 | 1 | uint8_t | colorMode | Color mode | 0=monochrome |
| 0x09 | 1 | uint8_t | compression | Compression | 0=uncompressed |
| 0x0A | 4 | uint32_t | dataSize | Image data size (bytes) | Calculated |
| 0x0E | 8 | uint64_t | md5 | MD5 checksum (first 8 bytes) | Optional |
- Location: After header (offset 22 bytes)
- Format: Two bit planes, 2 bits per pixel total
- Data Size Calculation:
dataSize = ((width * height + 7) / 8) * 2(rounded up to bytes) - Storage:
- First bit plane: offset 22, size
(width * height + 7) / 8bytes (rounded up) - Second bit plane: immediately after first plane, same size
- Each bit plane stores pixels in vertical scan order (column-major):
- Columns scanned from right to left (x = width-1 to 0)
- 8 vertical pixels packed per byte (MSB = topmost pixel in group)
- See "Data Storage Details" section for complete specification
- First bit plane: offset 22, size
IMPORTANT: The Xteink e-paper LUT has non-linear grayscale mapping with swapped middle values:
| Pixel Value | Binary | Bit1 | Bit2 | LUT Level | Display Color |
|---|---|---|---|---|---|
| 0 | 00 |
0 | 0 | L0 | White |
| 1 | 01 |
0 | 1 | L1 | Dark Grey |
| 2 | 10 |
1 | 0 | L2 | Light Grey |
| 3 | 11 |
1 | 1 | L3 | Black |
Note: Values 1 and 2 are swapped compared to typical linear grayscale ordering. This matches the Xteink device's e-paper LUT behavior.
Bit Plane Usage:
- Bit1 (first plane): Sent via command
0x24 - Bit2 (second plane): Sent via command
0x26
Pixel Value Calculation: pixelValue = (bit1 << 1) | bit2
XTC is a container format storing multiple XTG-format pages for comic/PDF reading.
| Offset | Size | Type | Field | Description | Value |
|---|---|---|---|---|---|
| 0x00 | 4 | uint32_t | mark | File identifier | 0x00435458 ("XTC\0") |
| 0x04 | 2 | uint16_t | version | Version number | 0x0100 (v1.0) |
| 0x06 | 2 | uint16_t | pageCount | Total pages | 1-65535 |
| 0x08 | 1 | uint8_t | readDirection | Reading direction | 0-2 |
| 0x09 | 1 | uint8_t | hasMetadata | Has metadata | 0-1 |
| 0x0A | 1 | uint8_t | hasThumbnails | Has thumbnails | 0-1 |
| 0x0B | 1 | uint8_t | hasChapters | Has chapters | 0-1 |
| 0x0C | 4 | uint32_t | currentPage | Current page (1-based) | 0-65535 |
| 0x10 | 8 | uint64_t | metadataOffset | Metadata offset | Byte offset |
| 0x18 | 8 | uint64_t | indexOffset | Index table offset | Byte offset |
| 0x20 | 8 | uint64_t | dataOffset | Data area offset | Byte offset |
| 0x28 | 8 | uint64_t | thumbOffset | Thumbnail offset | Byte offset |
| Value | Direction | Description |
|---|---|---|
| 0 | L→R | Left to right (normal) |
| 1 | R→L | Right to left (Japanese manga) |
| 2 | Top→Bottom | Top to bottom (vertical) |
If hasMetadata == 1, stored at metadataOffset:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0x00 | 128 | char[] | title | Title (UTF-8, null-terminated) |
| 0x80 | 64 | char[] | author | Author (UTF-8, null-terminated) |
| 0xC0 | 32 | char[] | publisher | Publisher/source (UTF-8, null-terminated) |
| 0xE0 | 16 | char[] | language | Language code (e.g., "zh-CN", "en-US") |
| 0xF0 | 4 | uint32_t | createTime | Creation time (Unix timestamp) |
| 0xF4 | 2 | uint16_t | coverPage | Cover page (0-based, 0xFFFF=none) |
| 0xF6 | 2 | uint16_t | chapterCount | Number of chapters |
| 0xF8 | 8 | uint64_t | reserved | Reserved (zero-filled) |
If hasChapters == 1, stored after metadata:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0x00 | 80 | char[] | chapterName | Chapter name (UTF-8, null-terminated) |
| 0x50 | 2 | uint16_t | startPage | Start page (0-based) |
| 0x52 | 2 | uint16_t | endPage | End page (0-based, inclusive) |
| 0x54 | 4 | uint32_t | reserved1 | Reserved 1 (zero-filled) |
| 0x58 | 4 | uint32_t | reserved2 | Reserved 2 (zero-filled) |
| 0x5C | 4 | uint32_t | reserved3 | Reserved 3 (zero-filled) |
Number of chapters specified by XtcMetadata.chapterCount.
Stored at indexOffset, one entry per page:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0x00 | 8 | uint64_t | offset | XTG/XTH image offset (absolute, from file start) |
| 0x08 | 4 | uint32_t | size | XTG/XTH image size in bytes (including 22-byte header) |
| 0x0C | 2 | uint16_t | width | Image width (pixels) |
| 0x0E | 2 | uint16_t | height | Image height (pixels) |
Total index table size: pageCount * 16 bytes.
All XTG/XTH page images stored starting at dataOffset. Each page's data is stored contiguously, with position specified by the index table's offset field (absolute offset from file start).
If hasThumbnails == 1, thumbnails stored at thumbOffset. Thumbnails are also in XTG format with the same index structure.
[Header: 48 bytes]
[Metadata: 256 bytes] (optional)
[Chapters: N × 96 bytes] (optional)
[Page Index Table: pageCount × 16 bytes]
[Data Area: All XTG page data]
[Thumbnail Area] (optional)
XTCH is identical to XTC in all aspects except the file identifier.
Same structure as XTC, only mark field differs:
| Offset | Size | Type | Field | Description | Value |
|---|---|---|---|---|---|
| 0x00 | 4 | uint32_t | mark | File identifier | 0x48435458 ("XTCH") |
| Format | Identifier (Little-Endian) | ASCII |
|---|---|---|
| XTC | 0x00435458 | "XTC\0" |
| XTCH | 0x48435458 | "XTCH" |
All other structures, fields, and data formats are identical to XTC.
- All string fields use UTF-8 encoding
- Strings are null-terminated (
\0) - Remaining space in fixed-size string fields is zero-filled
- Rows: top to bottom
- Pixels per row: left to right
- 8 pixels per byte
- Bit order: MSB (bit 7) = leftmost pixel, LSB (bit 0) = rightmost pixel
Example: 10-pixel wide image
- 2 bytes per row (rounded up)
- Byte 1: pixels 0-7 (bit 7 = pixel 0, bit 0 = pixel 7)
- Byte 2: pixels 8-9 (bit 7 = pixel 8, bit 6 = pixel 9, unused bits = 0)
- Two bit planes stored sequentially
- First plane: All pixels' Bit1 (pixel value bit 1), packed 8 pixels per byte
- Second plane: All pixels' Bit2 (pixel value bit 0), packed 8 pixels per byte
- IMPORTANT: Uses vertical scan order (column-major), NOT row-major:
- Columns are scanned from right to left (x = width-1 down to 0)
- Within each column, 8 vertical pixels are packed per byte (top to bottom)
- Each byte contains 8 vertically-adjacent pixels, MSB (bit 7) = topmost pixel in group
- This scan order matches the Xteink e-paper display refresh pattern
Pixel Value Calculation: pixelValue = (bit1 << 1) | bit2
Example: 480×800 pixel image
- Total pixels: 384000
- Each column: 800 pixels = 100 bytes (800/8 = 100 vertical groups)
- Total columns: 480 (scanned right to left)
- Each bit plane: 480 × 100 = 48000 bytes
- First plane: offset 22, size 48000 bytes (contains Bit1 for all pixels)
- Second plane: offset 48022, size 48000 bytes (contains Bit2 for all pixels)
- Total data size: 96000 bytes
-
Prepare Source Image:
- Convert to grayscale
- Resize to target resolution
- For XTG: Apply threshold/binarization
- For XTH: Map grayscale to 4 levels (0-3)
-
Generate Bitmap Data:
- XTG: Pack 1 bit per pixel, calculate
dataSize = ((width + 7) / 8) * height - XTH: Generate two bit planes, calculate
dataSize = ((width * height + 7) / 8) * 2
- XTG: Pack 1 bit per pixel, calculate
-
Write File:
- Write header with correct file identifier
- Write bitmap data
- Ensure Little-Endian byte order for all multi-byte values
- Read header to get page count and offsets
- Read page index table to locate each page
- Extract XTG data for each page from data area
- Convert each XTG page using XTG conversion process
| Format | Little-Endian uint32_t | Byte Sequence | ASCII |
|---|---|---|---|
| XTG | 0x00475458 | [0x58, 0x54, 0x47, 0x00] |
"XTG\0" |
| XTH | 0x00485458 | [0x58, 0x54, 0x48, 0x00] |
"XTH\0" |
| XTC | 0x00435458 | [0x58, 0x54, 0x43, 0x00] |
"XTC\0" |
| XTCH | 0x48435458 | [0x58, 0x54, 0x43, 0x48] |
"XTCH" |
- All offsets are byte offsets from the start of the file
- Page numbers in XTC/XTCH are 1-based for display, but 0-based in internal structures
- Reserved fields should be zero-filled
- MD5 checksum field is optional and may be zero
- Compression is currently not implemented (compression field = 0)
FYI: chapters feature is not implemented yet in the Xteink firmware (as of 3.1.0).