Skip to content

phrozen/xtx

Repository files navigation

xtx

Go library and CLI for encoding, decoding, and manipulating Xteink e-paper image and comic container formats.

Formats

Format Extension Description
XTG .xtg 1-bit monochrome image (MSB-first, packed)
XTH .xth 2-bit grayscale image (4 levels, column-major dual bit-plane)
XTC .xtc Comic container (multiple XTG pages + metadata)
XTCH .xtch Comic container (multiple XTH pages + metadata)

All formats use little-endian binary encoding. See SPEC.md for the full binary specification.

Install

Library

go get github.com/phrozen/xtx

CLI Tool

Download a pre-built binary from the Releases page (Windows, macOS, Linux), or build from source:

go install github.com/phrozen/xtx/cmd/xtx@latest

CLI Usage

xtx <command> [flags] [arguments]

Commands

encode — Convert image to XTG/XTH

The output format is inferred from the file extension (.xtg = monochrome, .xth = grayscale).

# Monochrome (inferred from .xtg)
xtx encode photo.png page.xtg

# Grayscale with dithering (inferred from .xth)
xtx encode -d photo.png page.xth

# Resize to exact dimensions
xtx encode -resize 480x800 photo.png page.xth

# Resize by width, auto-calculate height (preserves aspect ratio)
xtx encode -resize 480x-1 photo.png page.xth

# Resize by height, auto-calculate width
xtx encode -resize -1x800 photo.png page.xth
Flag Description
-g Force XTH (grayscale) regardless of extension
-dither str Dithering algorithm (none, floyd-steinberg, atkinson, sierra, sierra-lite, stucki)
-resize WxH Resize before encoding; use -1 for auto (e.g. 480x-1)

decode — Convert XTG/XTH to PNG or JPEG

Output format is inferred from extension (.jpg/.jpeg = JPEG, default = PNG).

xtx decode page.xtg output.png
xtx decode page.xth output.jpg

info — Display file header

xtx info page.xtg
# Format:      XTG (Monochrome)
# Size:        800 × 480
# Data size:   48000 bytes

xtx info comic.xtc
# Format:      XTC (Comic Container)
# Pages:       52
# Direction:   Left → Right
# Title:       My Manga
# Author:      Author Name

pack — Build XTC/XTCH from images

xtx pack -g -dither atkinson -title "My Comic" -author "Author" comic.xtc page1.png page2.png
Flag Description
-g Encode pages as XTH instead of XTG (auto-selects XTCH container)
-dither str Dithering algorithm (none, floyd-steinberg, atkinson, sierra, sierra-lite, stucki)
-dir N Reading direction: 0=LTR, 1=RTL, 2=TTB (default: 0)
-title X Set comic title
-author X Set comic author
-publisher X Set publisher name
-lang X Set language code (e.g. en, ja)
-cover N Set cover page index, 0-based (default: 0)

unpack — Extract pages from XTC/XTCH

xtx unpack comic.xtc ./pages/
# Extracts page_000.png, page_001.png, ...

cbz — Convert CBZ comic archive to XTC

Reads a CBZ (ZIP of images) and converts to an XTC container. The -format flag controls how source images are processed into pages:

Format Strategy Direction
comic Fit width, split height per-image (default) LTR
manga Fit width, split height per-image RTL
webtoon Continuous vertical stitching across images TTB

comic/manga: Each source image is resized to fit the page width, then split into multiple pages if it exceeds the page height. Page boundaries respect image boundaries — no content from one source image bleeds into the next.

webtoon: All source images are treated as a continuous vertical strip. Content flows across image boundaries, producing tightly packed pages.

# Western comic (default format, LTR)
xtx cbz -title "Batman" -author "DC Comics" volume1.cbz

# Japanese manga (RTL, per-image split)
xtx cbz -format manga -title "One Piece" -author "Oda" volume1.cbz

# Webtoon (TTB, continuous stitching)
xtx cbz -format webtoon -title "Solo Leveling" -author "Chugong" volume1.cbz

# Landscape reading: pages composed at 800×480 then rotated for e-reader
xtx cbz -format manga -landscape -title "Berserk" volume1.cbz
Flag Description
-o path Output file path (default: input with .xtc/.xtch extension)
-format X Page format: comic, manga, or webtoon (default: comic)
-landscape Landscape reading: swap page dimensions and rotate pages 90°
-mono Encode as monochrome (XTG) instead of grayscale
-dither str Dithering algorithm (none, floyd-steinberg, atkinson, sierra, sierra-lite, stucki)
-width N Page width in pixels (default: 480)
-height N Page height in pixels (default: 800)
-title X Set comic title
-author X Set comic author
-publisher X Set publisher name
-lang X Set language code (e.g. en, ja)
-cover N Set cover page index, 0-based (default: 0)

Library Usage

The library follows standard Go image idioms. Encode writes to an io.Writer, Decode reads from an io.Reader, and all image types implement image.Image.

Encoding

import "github.com/phrozen/xtx"

// func Encode(w io.Writer, img image.Image, opts *Options) error

// Encode any image.Image as monochrome XTG (nil opts = defaults)
f, _ := os.Create("page.xtg")
err := xtx.Encode(f, img, nil)

// Encode as grayscale XTH with Floyd-Steinberg dithering
f, _ := os.Create("page.xth")
err := xtx.Encode(f, img, &xtx.Options{
    Format: xtx.FormatGrayscale,
    Dither: true,
})

Decoding

// func Decode(r io.Reader) (image.Image, error)

// Decode XTG or XTH (format auto-detected from magic bytes)
f, _ := os.Open("page.xtg")
img, err := xtx.Decode(f)

// XTG/XTH are also registered with image.RegisterFormat,
// so importing the package enables standard library decoding:
import _ "github.com/phrozen/xtx"
img, format, err := image.Decode(f) // format = "xtg" or "xth"

Image Types

Both Monochrome and Grayscale implement image.Image, draw.Image, and work with the standard library's image/draw package.

// Create a 1-bit monochrome image
mono := xtx.NewMonochrome(image.Rect(0, 0, 800, 480))
mono.SetMono(x, y, xtx.MonoBlack)

// Create a 2-bit grayscale image (4 levels)
gray := xtx.NewGrayscale(image.Rect(0, 0, 480, 800))
gray.SetGray4(x, y, xtx.Gray4{V: 2}) // light grey

// Floyd-Steinberg dithering via stdlib
draw.FloydSteinberg.Draw(mono, mono.Bounds(), src, src.Bounds().Min)

Color Models

Type Bits Levels Bit Values
Mono 1 2 0=black, 1=white
Gray4 2 4 0=white, 1=dark grey, 2=light grey, 3=black

The Gray4 level mapping follows the non-linear Xteink e-paper LUT (levels 1 and 2 are swapped relative to a linear ramp).

Note: The zero-value convention is inconsistent between formats — 0 means black in Mono but white in Gray4. This matches the Xteink hardware LUT and cannot be changed. The library handles this internally, so users working with image.Image don't need to worry about it.

Comic Containers

comic := xtx.NewXTC(false) // true for XTCH variant
comic.SetDirection(xtx.RightToLeft)
comic.SetMetadata(xtx.Metadata{
    Title:  "My Manga",
    Author: "Author Name",
})

// Add pages (accepts any image.Image, encodes internally)
comic.AddPage(page1)     // as XTG (monochrome)
comic.AddPageXTH(page2)  // as XTH (grayscale)

// Write container
err := comic.Encode(w)

Image Transforms

// Resize (uses approximate bilinear interpolation)
resized := xtx.Resize(img, 480, 800)

// Aspect-ratio preserving resize
resized := xtx.ResizeToWidth(img, 480)
resized := xtx.ResizeToHeight(img, 800)

// Rotation (90° clockwise / counter-clockwise)
rotated := xtx.RotateCW(img)
rotated := xtx.RotateCCW(img)

// Create a blank white page
page := xtx.NewWhitePage(480, 800)

Reading CBZ Archives

// Read and decode all images from a CBZ file
// (filters hidden files, sorts alphabetically)
images, err := xtx.ReadCBZ("volume1.cbz")

Reading Direction

Comic containers support three reading directions, matching the XTC specification:

Value Constant Description
0 LeftToRight Western comics (default)
1 RightToLeft Japanese manga
2 TopToBottom Webtoons / vertical scroll

Project Structure

cmd/
└── xtx/
    ├── main.go        CLI entry point and usage text
    ├── encode.go      encode command
    ├── decode.go      decode command
    ├── info.go        info command
    ├── pack.go        pack command
    ├── unpack.go      unpack command
    ├── cbz.go         cbz command
    └── util.go        shared helpers (image loading)
xtx.go               Core types, constants, errors
color.go             Mono and Gray4 color types and models
monochrome.go        1-bit image type (image.Image)
grayscale.go         2-bit image type (image.Image)
header.go            22-byte XTG/XTH header read/write/validate
encode.go            Encode image.Image → XTG/XTH
decode.go            Decode XTG/XTH → image.Image
xtc.go               XTC/XTCH container builder and encoder
transform.go         Resize, rotate, white page utilities
cbz.go               CBZ archive reader
SPEC.md              Full binary format specification

License

MIT — see LICENSE.

About

Go library and CLI for encoding, decoding, and manipulating Xteink e-paper image and comic container formats.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages