grid-apps-cmms/docs/refs/MSLA_FORMAT_REFERENCE.md
2026-07-26 13:52:55 -04:00

30 KiB

mSLA File Format Reference

Generated: 2026-05-16 Total Formats: 31 registered in UVtools AvailableFormats (plus an unregistered CXDLPv1 implementation)


Format Capability Matrix

Format Encode Decode Extensions Versions Profiles RLE Type Notes
Chitubox Family
ChituboxFile photon, cbddlp, ctb, gktwo.ctb 1-5 (def: 5) No RLE125 / CTB variable RLE Most common
CTBEncryptedFile ctb, encrypted.ctb 4-5 (def: 5) No CTB variable RLE + AES Encrypted
PHZFile phz 2 No RLE125 Chitubox PHZ
ChituboxZipFile zip - No PNG G-code based
AnycubicPhotonSFile photons - No RLE128 Legacy Photon S
Anycubic Family - Legacy (v1, v515-516)
AnycubicFile (PWS) pws 1 Yes (22) 1-byte bit-plane RLE125 PhotonS
AnycubicFile (PW0/PWX) pw0, pwx 1 Yes (22) Nibble RLE4 PhotonZero, PhotonX
Anycubic Family - Modern (v515-518)
AnycubicFile (v515-v516) dlp, pwmx, pwmo, pwms, pmsq, pwma, pm3, pm3m varies Yes (22) Nibble RLE4 Ultra, Mono, M3 family
AnycubicFile (v515-v517) dl2p, pwmb, pmx2, pm3r varies Yes (22) Nibble RLE4 D2, X2, M3 Plus/Premium
AnycubicFile (v517) pm3n, pm5, px6s 517 Yes (22) Nibble RLE4 Mono 2, X6Ks, M5
AnycubicFile (v518) pm5s, m5sp 518 Yes (22) Nibble RLE4 Mono M5s/Pro
AnycubicFile (unversioned fallback) pm4n, pwc 1, 515-518 Yes (22) Nibble RLE4 Registered but not explicitly version-pinned
AnycubicZipFile pm4u, pm7, pm7m, pwsz, pp1, pp1m - No PNG ZIP with JSON
Creality Family
CrealityCXDLPv1File v1.cxdlp 1 No RLE Legacy implementation present but not registered in AvailableFormats
CrealityCXDLPFile cxdlp 2-3 (def: 3) Yes RLE HALOT series
CrealityCXDLPv4File cxdlpv4 4 Yes RLE Current
Other Binary Formats
AnetFile n4, n7 3 Yes (N4/N7) RLE Anet printers
FDGFile fdg 2 No RLE Voxelab
GooFile goo, prz - No RLE+delim Elegoo/Phrozen
LGSFile lgs, lgs30, lgs120, lgs4k - Yes Struct Longer Orange
MDLPFile mdlp 1 No Vector Makerbase MKS
GR1File gr1 - No Vector GR Workshop
OSFFile osf 4 (def) No Compressed Vlare
OSLAFile osla - No Varies Open SLA
Archive Formats
SL1File sl1, sl1s - Yes PNG Prusa (INI)
UVJFile uvj - No PNG Vendor-neutral
VDAFile zip - No Images Voxeldance Additive
VDTFile vdt 1 No PNG Voxeldance Tango
NanoDLPFile nanodlp, rgb.nanodlp 1-2 No Images NanoDLP
KlipperFile zip, rgb.zip - No PNG Klipper firmware
ZCodeFile zcode - No Images UnizMaker IBEE
ZCodexFile zcodex - Yes Images Zortrax
CWSFile cws, rgb.cws, xml.cws - No PNG NovaMaker/CW
JXSFile jxs - No Images Uniformation
GenericZIPFile zip - No Varies Fallback
Text/Vector Formats
FlashForgeSVGXFile svgx - No SVG FlashForge
QDTFile qdt - No Text Emake3D Galaxy
Import Only
ImageFile png, jpg, jpeg, jp2, tga, tif, tiff, bmp, pbm - No N/A Image import

Total Extensions: 60+ Total Printer Profiles: 7 formats with profiles (30+ printer models)


Table of Contents - Corrected Binary Formats

1. Chitubox Family

1.1 CBDDLP (Chitubox v1-2)

Extensions: .cbddlp, .photon Magic: 0x12FD0019 (LE) Versions: 1, 2 RLE: RLE125 (limit: 125 pixels)

Header Structure (CORRECTED - 32-bit floats):

Offset | Size | Type   | Field                    | Notes
-------|------|--------|--------------------------|------------------
0x00   | 4    | uint32 | Magic                    | 0x12FD0019 (LE)
0x04   | 4    | uint32 | Version                  | 1 or 2
0x08   | 4    | float  | BedSizeX                 | mm (32-bit)
0x0C   | 4    | float  | BedSizeY                 | mm (32-bit)
0x10   | 4    | float  | BedSizeZ                 | mm (32-bit)
0x14   | 4    | uint32 | Unknown1                 |
0x18   | 4    | uint32 | Unknown2                 |
0x1C   | 4    | float  | TotalHeightMillimeter    | mm (32-bit)
0x20   | 4    | float  | LayerHeightMillimeter    | mm (32-bit)
0x24   | 4    | float  | LayerExposureSeconds     | seconds (32-bit)
0x28   | 4    | float  | BottomExposureSeconds    | seconds (32-bit)
0x2C   | 4    | float  | LightOffDelay            | seconds (32-bit)
0x30   | 4    | uint32 | BottomLayersCount        |
0x34   | 4    | uint32 | ResolutionX              | pixels
0x38   | 4    | uint32 | ResolutionY              | pixels
0x3C   | 4    | uint32 | PreviewLargeOffsetAddress |
0x40   | 4    | uint32 | LayersDefinitionOffsetAddress |
0x44   | 4    | uint32 | LayerCount               |
0x48   | 4    | uint32 | PreviewSmallOffsetAddress |
0x4C   | 4    | uint32 | PrintTime                | seconds
0x50   | 4    | uint32 | ProjectorType            | 0=Normal
0x54   | 4    | uint32 | PrintParametersOffsetAddress | (v2+)
0x58   | 4    | uint32 | PrintParametersSize      | bytes (v2+)
0x5C   | 4    | uint32 | AntiAliasLevel           | 1 (v2+)
0x60   | 2    | uint16 | LightPWM                 | 0-255 (v2+)
0x62   | 2    | uint16 | BottomLightPWM           | 0-255 (v2+)

Header Size: 0x64 (100 bytes) for v2

1.2 CTB (Chitubox v3)

Extensions: .ctb Magic: 0x12FD0086 (LE) Version: 3 RLE: CTB variable-length 7-bit grayscale RLE, optionally XOR-encrypted with a per-layer seed-derived stream

Header Structure (CORRECTED - matches Chitubox 1.8 fixtures):

Offset | Size | Type   | Field                    | Notes
-------|------|--------|--------------------------|------------------
0x00   | 4    | uint32 | Magic                    | 0x12FD0086 (LE)
0x04   | 4    | uint32 | Version                  | 3
0x08   | 4    | float  | BedSizeX                 | mm (32-bit float)
0x0C   | 4    | float  | BedSizeY                 | mm (32-bit float)
0x10   | 4    | float  | BedSizeZ                 | mm (32-bit float)
0x14   | 4    | uint32 | Unknown1                 |
0x18   | 4    | uint32 | Unknown2                 |
0x1C   | 4    | float  | TotalHeightMillimeter    | mm (32-bit float)
0x20   | 4    | float  | LayerHeightMillimeter    | mm (32-bit float)
0x24   | 4    | float  | LayerExposureSeconds     | seconds (32-bit float)
0x28   | 4    | float  | BottomExposureSeconds    | seconds (32-bit float)
0x2C   | 4    | float  | LightOffDelay            | seconds (32-bit float)
0x30   | 4    | uint32 | BottomLayersCount        |
0x34   | 4    | uint32 | ResolutionX              | pixels
0x38   | 4    | uint32 | ResolutionY              | pixels
0x3C   | 4    | uint32 | PreviewLargeOffsetAddress |
0x40   | 4    | uint32 | LayersDefinitionOffsetAddress |
0x44   | 4    | uint32 | LayerCount               |
0x48   | 4    | uint32 | PreviewSmallOffsetAddress |
0x4C   | 4    | uint32 | PrintTime                | seconds
0x50   | 4    | uint32 | ProjectorType            | 0=Normal
0x54   | 4    | uint32 | PrintParametersOffsetAddress |
0x58   | 4    | uint32 | PrintParametersSize      | bytes
0x5C   | 4    | uint32 | AntiAliasLevel           | 1, 2, 4, 8
0x60   | 2    | uint16 | LightPWM                 | 0-255
0x62   | 2    | uint16 | BottomLightPWM           | 0-255
0x64   | 4    | uint32 | EncryptionSeed           | 0 disables layer RLE XOR
0x68   | 4    | uint32 | SlicerOffsetAddress      |
0x6C   | 4    | uint32 | SlicerDataSize           | bytes

Header Size: 0x70 (112 bytes)

CRITICAL NOTE: Previous documentation incorrectly showed 8-byte (double) floats. CTB v3 uses 32-bit floats throughout. This matches Chitubox 1.8 fixtures and actual UVtools implementation.

CTB Variable RLE Encoding (v3+)

CTB v3 does not use fixed 2-byte (color, length) RLE. It uses a 7-bit grayscale, variable-length run format:

Color byte:
  bit 7 clear: single pixel, bits 0-6 are grayscale level
  bit 7 set: run follows, bits 0-6 are grayscale level

Run length bytes:
  0rrrrrrr                         = 7-bit length, 2-127
  10rrrrrr rrrrrrrr                = 14-bit length, 128-16383
  110rrrrr rrrrrrrr rrrrrrrr       = 21-bit length
  1110rrrr rrrrrrrr rrrrrrrr rrrrrrrr = 28-bit length

8-bit pixels are quantized with grey7 = pixel >> 1. When decoding, grey7 == 0 maps to 0; otherwise expand with (grey7 << 1) | 1, so encoded 0x7F returns 0xFF.

If the header encryption seed at 0x64 is non-zero, layer RLE bytes are XORed with a per-layer key stream derived from that seed. The seed is not the direct XOR key.

1.3 CTB v4-5 (Chitubox v4-5)

Extensions: .ctb, .gktwo.ctb Magic: 0x12FD0106 (LE, v4), 0xFF220810 (LE, GKtwo variant) Versions: 4, 5 (default: 5) RLE: CTB variable-length 7-bit grayscale RLE

Same header structure as CTB v3, with additional support for:

  • Per-layer exposure times
  • Per-layer lift heights/speeds
  • PageNumber field in layer table
  • Transition layers

2. Anycubic Family

2.1 Legacy Anycubic Formats (v1)

2.1.1 PWS (Photon / Photon S)

Extensions: .pws Version: 1 RLE: RLE1 (limit: 125) Machines: PhotonS

RLE1 Encoding (PWS):

For each run (1 byte):
  bit 7    = white/on flag
  bits 0-6 = repeat count

Runs are emitted per anti-aliasing bit-plane and then accumulated back to
8-bit grayscale. UVtools' encoder splits runs at 0x7D (125), although the
stored count field is 7 bits.

Constant: RLE1EncodingLimit = 0x7D (125)
2.1.2 PW0/PWX (Photon Zero / Photon X) - CORRECTED

Extensions: .pw0, .pwx Version: 1 RLE: Nibble-coded RLE4 (limit: 4095) Machines: PhotonZero, PhotonX

Nibble-Coded RLE4 Encoding (CORRECTED from UVtools implementation):

The PW0 format uses a nibble-coded variable-length RLE, NOT a simple 3-byte RLE as previously documented.

Encoding Rules:

Each byte is split: [4-bit code] [4-bit repeat]

Code interpretation:
  0x0: Black (0x00) - Extended format (2 bytes total)
       Format: [0x0R] [RR]
       Repeat = (R << 8) | RR (12-bit value, max 4095)

  0xF: White (0xFF) - Extended format (2 bytes total)
       Format: [0xFR] [RR]
       Repeat = (R << 8) | RR (12-bit value, max 4095)

  0x1-0xE: Grayscale - Single byte format
       Color = (code << 4) | code
       Repeat = 4-bit repeat value (1-15)
       Examples:
         0x17 = color 0x11, repeat 7
         0x82 = color 0x88, repeat 2

Decode Algorithm (from UVtools):

for (int i = 0; i < encodedRle.Length; i++)
{
    byte b = encodedRle[i];
    int code = b >> 4;        // Upper nibble
    int repeat = b & 0xf;     // Lower nibble
    byte color;

    switch (code)
    {
        case 0x0:  // Black (extended)
            color = 0;
            i++;
            repeat = (repeat << 8) + encodedRle[i];
            break;

        case 0xf:  // White (extended)
            color = 255;
            i++;
            repeat = (repeat << 8) + encodedRle[i];
            break;

        default:   // Grayscale (single byte)
            color = (byte)((code << 4) | code);
            break;
    }

    // Fill repeat pixels with color
}

Encode Algorithm (from UVtools):

for each pixel:
    int color = pixel >> 4;  // Extract upper nibble

    if (color == 0 or color == 0xf):  // Black or white
        if (repeat > 4095):
            repeat = 4095

        // Encode as 2 bytes: [cR] [RR]
        ushort more = (ushort)(repeat | (color << 12));
        output.Add((byte)(more >> 8));
        output.Add((byte)more);
    else:  // Grayscale
        if (repeat > 15):
            repeat = 15

        // Encode as 1 byte: [cR]
        output.Add((byte)(repeat | color << 4));

Constants:

RLE4EncodingLimit = 0xFFF = 4095

Example Encodings:

White run of 1000 pixels:
  Binary: 1111 0011 1110 1000
  Hex: F3 E8
  Decode: code=0xF, repeat=3, next byte=0xE8
         repeat = (3 << 8) + 232 = 1000
         color = 255

Black run of 500 pixels:
  Binary: 0000 0001 1111 0100
  Hex: 01 F4
  Decode: code=0x0, repeat=1, next byte=0xF4
         repeat = (1 << 8) + 244 = 500
         color = 0

Gray (0x77) run of 10 pixels:
  Binary: 0111 1010
  Hex: 7A
  Decode: code=0x7, repeat=0xA (10)
         color = (7 << 4) | 7 = 0x77 = 119

Gray (0xAA) run of 5 pixels:
  Binary: 1010 0101
  Hex: A5
  Decode: code=0xA, repeat=0x5 (5)
         color = (10 << 4) | 10 = 0xAA = 170

Key Differences from Previous Documentation:

  • NOT a fixed 3-byte format
  • Variable length: 1 byte for grayscale (repeat ≤15), 2 bytes for black/white
  • Nibble-based encoding optimizes for common cases
  • Color is encoded in upper nibble, duplicated to both nibbles for final value
  • Maximum repeat: 4095 for black/white, 15 for grayscale

2.2 Modern Anycubic Formats (v515-518)

All modern Anycubic formats use Nibble-coded RLE4 encoding.

2.2.1 Version-Mapped Formats Before v518

Extension version sets from UVtools GetAvailableVersionsForExtension:

  • .pwmx, .pwmo, .pwms, .pmsq, .dlp: v1, v515, v516
  • .pwma, .pm3, .pm3m: v515, v516
  • .pwmb, .dl2p, .pmx2, .pm3r: v515, v516, v517

Machines:

  • PhotonMono, PhotonMonoX, PhotonMonoX2
  • PhotonMono4K, PhotonMonoSE, PhotonMonoSQ
  • PhotonMonoX6K, PhotonM3, PhotonM3Plus, PhotonM3Max, PhotonM3Premium
  • PhotonUltra, PhotonD2

Features (v515+):

  • Nibble-coded RLE4 encoding
  • Enhanced print parameters
  • Per-layer settings support

Features (v516+):

  • Dual lift/retract speeds
  • Advanced motion profiles
2.2.2 Version 517 Formats

Extensions: .pm3n, .pm5, .px6s (.pm4n is registered by UVtools but not explicitly version-pinned; treat v517 as an assumption until fixture-verified)

Machines:

  • PhotonMono2 (.pm3n)
  • PhotonMonoM5 (.pm5)
  • PhotonMonoX6Ks (.px6s)
  • PhotonMono4 (.pm4n) is mapped as a machine, but its source and PrusaSlicer profile omit an explicit file version

New Features:

  • SOFTWARE and MODEL sections compared with earlier versions
  • MACHINE property field count rises to 7
  • Thumbnail defaults are 224x168 and 330x190; v517 writes the first PREVIEW table
  • HEADER table length: 92 bytes
  • LayerDef entries remain 32 bytes each
2.2.3 Version 518 Formats

Extensions: .pm5s, .m5sp (.pwc is registered as Anycubic Custom Machine but falls through to all available versions)

Machines:

  • PhotonMonoM5s (.pm5s)
  • PhotonMonoM5sPro (.m5sp)

New Features:

  • 11 FileMark table entries
  • second PREVIEW2 table may be present
  • Preview sizes: 224x168, 330x190
  • HEADER table length: 96 bytes
  • LayerDef entries remain 32 bytes each; SUBIMGS sublayer records may also be present
  • MACHINE property field count rises to 15

Anycubic FileMark and Section Table (v515+):

Offset | Size | Type   | Field                    | Notes
-------|------|--------|--------------------------|------------------
0x00   | 12   | char[] | Mark                     | "ANYCUBIC" null-padded
0x0C   | 4    | uint32 | Version                  | 1, 515, 516, 517, 518
0x10   | 4    | uint32 | NumberOfTables           |
0x14   | 4    | uint32 | HeaderAddress            | offset to HEADER table
0x18   | 4    | uint32 | SoftwareAddress          | v517+
0x1C   | 4    | uint32 | PreviewAddress           |
0x20   | 4    | uint32 | LayerImageColorTableAddress |
0x24   | 4    | uint32 | LayerDefinitionAddress   |
0x28   | 4    | uint32 | ExtraAddress             | v516+
0x2C   | 4    | uint32 | MachineAddress           | v516+
0x30   | 4    | uint32 | LayerImageAddress        |
0x34   | 4    | uint32 | ModelAddress             | v517+
0x38   | 4    | uint32 | SubLayerDefinitionAddress| v518+
0x3C   | 4    | uint32 | Preview2Address          | v518+

Each section starts with a 12-byte null-padded table name followed by a 32-bit table length. The HEADER table length is version-dependent: 80 for early versions, 84 for v516, 92 for v517, and 96 for v518.

Machine Detection (Extension → Machine mapping):

.pm3n  → PhotonMono2
.pm4n  → PhotonMono4 (version ambiguous in UVtools; v517 is an implementation assumption pending fixtures)
.pm5   → PhotonMonoM5
.pm5s  → PhotonMonoM5s
.m5sp  → PhotonMonoM5sPro
.px6s  → PhotonMonoX6Ks
.pwms  → PhotonMonoSE
.pwma  → PhotonMono4K
.pwmx  → PhotonMonoX
.pwmo  → PhotonMono
.pwmb  → PhotonMonoX6K / PhotonM3Plus

2.3 Anycubic ZIP Format (PWSZ)

Extensions: .pm4u, .pm7, .pm7m, .pwsz, .pp1, .pp1m Type: ZIP archive with JSON manifest Layer Format: PNG images in staged directories

Structure:

  • manifest - JSON with version, machine_type, machine_extern
  • bott_0/*.png, bott_1/*.png - Bottom stage layers
  • normal_0/*.png, normal_1/*.png - Normal stage layers

RLE Encoding Comparison

Format Name Bytes/Run Max Repeat Color Bits Notes
RLE1/RLE125 (PWS/CBDDLP) Bit-plane run-length 1 (fixed) 125 encoded / 127 stored 1 per AA plane bit 7 on/off, low 7 bits repeat
RLE4 (PW0) Nibble-coded 1-2 (variable) 4095 (B/W), 15 (gray) 8 Optimized variable-length
CTB variable RLE 7-bit grayscale variable RLE 1-5 (variable) 268,435,455 7 stored / 8 expanded CTB v3+ layer data
RLE128 (PhotonS) Run-Length 128 2 (fixed) 128 8 Length, Color (BE)
RLE+Delim (GOO) Delimited 5 (+ delim) 65535 8 0x55 + Color + Length16 + 0x0D0A

Performance Comparison:

  • RLE1/RLE125/RLE128: Simple, predictable, easy to implement, but PWS/CBDDLP are bit-plane encodings rather than full grayscale runs
  • CTB variable RLE: Better compression and grayscale AA, plus optional seed-derived XOR
  • Nibble RLE4: Best compression for grayscale AA, complex encoding
  • RLE+Delim: Highest max repeat, overhead from delimiters

Additional Binary Format Details

Photon S (Anycubic Legacy)

Extension: .photons Magic: TAG1=2, TAG2=49 (both big-endian) RLE: RLE128 (limit: 128, big-endian)

Header (Big-Endian):

Offset | Size | Type   | Field                    | Endianness
-------|------|--------|--------------------------|------------
0x00   | 4    | uint32 | TAG1                     | 2 (BE)
0x04   | 4    | uint32 | TAG2                     | 49 (BE)
0x08   | 8    | double | XYPixelSize              | mm (BE)
0x10   | 4    | float  | LayerHeight              | mm (BE)
0x14   | 4    | float  | ExposureTime             | seconds (BE)
0x18   | 4    | float  | ExposureTimeBottom       | seconds (BE)
...

RLE128 Encoding (Big-Endian):

For each run (2 bytes):
  byte[0] = length (1-128, BE)
  byte[1] = color (0x00 or 0xFF)

GOO (Elegoo)

Extensions: .goo, .prz Magic: "V3.0" + 0x07000000 (BE) + "DLP\0" RLE: Delimiter-based

RLE Format:

Each run (5 bytes):
  0x55                    Magic byte
  [color]                 0x00 or 0xFF
  [length_high]           Big-endian uint16
  [length_low]
  0x0D 0x0A               CRLF delimiter

Maximum repeat: 65535 pixels

CXDLP (Creality)

Extension: .cxdlp Magic: "CXSW3DV2" (9 bytes, BE) Versions: 2, 3, 4 RLE: Standard RLE

Header (v3):

Offset | Size | Type   | Field                    | Endianness
-------|------|--------|--------------------------|------------
0x00   | 4    | uint32 | MagicSize                | 9 (BE)
0x04   | 9    | char[] | MagicName                | "CXSW3DV2" (BE)
0x0D   | 4    | uint32 | Version                  | 3 (BE)
0x11   | 4    | uint32 | PrinterModel             | offset (BE)
0x15   | 4    | uint32 | ResolutionX              | pixels (BE)
0x19   | 4    | uint32 | ResolutionY              | pixels (BE)
0x1D   | 256  | uint32[]| LayerOffsets[64]         | offsets (BE)
...

Archive Format Structures

SL1 (Prusa)

Extensions: .sl1, .sl1s Type: ZIP with INI config Layer Format: PNG images

Required Files:

  • config.ini - Print parameters
  • prusaslicer.ini - Slicer settings (optional)
  • 00000.png, 00001.png, ... - Layers (5-digit zero-padded)

config.ini Sample:

[general]
expTime = 8.0
expTimeFirst = 35.0
layerHeight = 0.05
numFade = 10
numSlow = 5
printTime = 3600

[printer]
PrinterModel = SL1

UVJ (Vendor-Neutral)

Extension: .uvj Type: ZIP with JSON config Layer Format: PNG images

Structure:

  • config.json - Print configuration
  • slice/0.png, slice/1.png, ... - Layer images
  • preview/huge.png - Large preview
  • preview/tiny.png - Small preview

config.json Schema:

{
  "size": {
    "x": 2560,
    "y": 1620,
    "layerHeight": 0.05,
    "millimeter": {"x": 192.0, "y": 120.0}
  },
  "exposure": {
    "lightOnTime": 8.0,
    "lightOffTime": 1.0,
    "lightPWM": 255,
    "liftHeight": 5.0,
    "liftSpeed": 100.0,
    "retractSpeed": 150.0
  },
  "bottom": {
    "count": 5,
    "lightOnTime": 35.0,
    "lightPWM": 255,
    ...
  },
  "layers": [
    {"z": 0.05},
    {"z": 0.10},
    ...
  ]
}

Implementation Guidance for Sandboxed Environments

Tier 1 - Simplest (Start Here):

  1. ImageFile - Single image, no container
  2. UVJ - JSON + PNG (if JSON/ZIP available)
  3. SL1 - INI + PNG (if INI/ZIP available)

Tier 2 - Binary Formats:

  1. CBDDLP v1 - Simplest binary RLE
  2. CTB v3 - Modern standard, widely used
  3. PWS - Simple RLE1 encoding

Tier 3 - Advanced Binary:

  1. PW0 - Nibble-coded RLE4 (complex but efficient)
  2. PhotonS - Big-endian RLE128
  3. GOO - Delimiter-based RLE

Tier 4 - Specialized:

  1. CTBEncrypted - Requires AES-256-CBC
  2. AnycubicFile v517+ - Modern features, complex header
  3. CXDLP - Big-endian offsets

Essential Algorithms (Pseudocode)

RLE125 Bit-Plane Decoder (CBDDLP/PWS)

def decode_rle125_bitplanes(data: bytes, width: int, height: int, aa: int = 1) -> bytes:
    levels = bytearray(width * height)
    data_index = 0

    for _plane in range(aa):
        pixel_index = 0
        while pixel_index < len(levels):
            code = data[data_index]
            data_index += 1

            length = code & 0x7F
            if code & 0x80:
                for i in range(length):
                    levels[pixel_index + i] += 1

            pixel_index += length

    pixels = bytearray(len(levels))
    scale = 256 // aa
    for i, level in enumerate(levels):
        value = level * scale
        pixels[i] = value - 1 if value else 0

    return pixels

RLE125 Bit-Plane Encoder (Single Plane)

def encode_rle125_bitplane(bits: list[bool]) -> bytes:
    result = bytearray()
    run_on = False
    run = 0

    def flush():
        nonlocal run
        if run:
            result.append(run | (0x80 if run_on else 0))
            run = 0

    for bit in bits:
        if bit == run_on and run < 0x7D:
            run += 1
            continue

        flush()
        run_on = bit
        run = 1

    flush()

    return result

Nibble-Coded RLE4 Decoder (PW0)

def decode_rle4_nibble(data: bytes, width: int, height: int) -> bytes:
    pixels = bytearray(width * height)
    pixel_pos = 0
    i = 0

    while i < len(data):
        b = data[i]
        code = b >> 4        # Upper nibble
        repeat = b & 0x0F    # Lower nibble

        if code == 0x0:      # Black (extended)
            color = 0
            i += 1
            repeat = (repeat << 8) + data[i]
        elif code == 0xF:    # White (extended)
            color = 255
            i += 1
            repeat = (repeat << 8) + data[i]
        else:                # Grayscale (single byte)
            color = (code << 4) | code

        # Fill pixels
        for _ in range(repeat):
            pixels[pixel_pos] = color
            pixel_pos += 1

        i += 1

    return pixels

Nibble-Coded RLE4 Encoder (PW0)

def encode_rle4_nibble(pixels: bytes) -> bytes:
    result = bytearray()
    i = 0

    while i < len(pixels):
        color_nibble = pixels[i] >> 4
        length = 1

        # Count consecutive pixels
        while (i + length < len(pixels) and
               (pixels[i + length] >> 4) == color_nibble):
            length += 1

        # Encode run
        if color_nibble in (0x0, 0xF):  # Black or white
            while length > 0:
                run = min(length, 4095)
                more = (run | (color_nibble << 12))
                result.append(more >> 8)
                result.append(more & 0xFF)
                length -= run
        else:  # Grayscale
            while length > 0:
                run = min(length, 15)
                result.append((run | (color_nibble << 4)))
                length -= run

        i += length

    return result

CTB Variable RLE Decoder

def decode_ctb_variable_rle(data: bytes, width: int, height: int) -> bytes:
    pixels = bytearray(width * height)
    pixel_pos = 0
    i = 0

    while i < len(data):
        color_byte = data[i]
        i += 1

        grey7 = color_byte & 0x7F
        color = 0 if grey7 == 0 else (grey7 << 1) | 1

        if (color_byte & 0x80) == 0:
            stride = 1
        else:
            length_byte = data[i]
            i += 1

            if (length_byte & 0x80) == 0:
                stride = length_byte
            elif (length_byte & 0x40) == 0:
                stride = ((length_byte & 0x3F) << 8) | data[i]
                i += 1
            elif (length_byte & 0x20) == 0:
                stride = ((length_byte & 0x1F) << 16) | (data[i] << 8) | data[i + 1]
                i += 2
            else:
                stride = ((length_byte & 0x0F) << 24) | (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]
                i += 3

        for _ in range(stride):
            pixels[pixel_pos] = color
            pixel_pos += 1

    return pixels

Dependencies by Format

Minimal (No external libs):

  • Binary I/O, basic data structures
  • Formats: CBDDLP, CTB, PWS, PW0, PhotonS, GOO, CXDLP, FDG

Recommended:

  • ZIP library: SL1, UVJ, VDT, NanoDLP, ZCode, ZCodex, CWS, JXS
  • JSON parser: UVJ, VDT, AnycubicZip, NanoDLP
  • INI parser: SL1, JXS
  • PNG encoder/decoder: All archive formats

Advanced:

  • AES-256-CBC: CTBEncrypted
  • XML parser: CWS, VDA, ZCode, SVGX
  • SVG parser: SVGX

Common Pitfalls

  1. Endianness:

    • Chitubox family: Little-endian (LE)
    • PhotonS, GOO, CXDLP, MDLP: Big-endian (BE)
    • Always verify in format header!
  2. Float Size:

    • CTB v3 uses 32-bit floats, NOT 64-bit doubles
    • Previous docs had this wrong
    • Verified from Chitubox 1.8 fixtures
  3. Nibble RLE Complexity:

    • PW0 nibble-coded RLE is variable-length
    • NOT a fixed 3-byte format
    • Must handle both 1-byte and 2-byte runs
  4. File Offsets:

    • Offsets are absolute from file start (position 0)
    • Always seek before reading data sections
  5. RLE Limits:

    • RLE1/RLE125: max 125 pixels
    • RLE128: max 128 pixels
    • RLE4 nibble: max 4095 (B/W), 15 (grayscale)
    • CTB variable RLE: variable length, up to 28-bit runs
    • Split longer runs
  6. String Encoding:

    • Most: ASCII or UTF-8
    • Anet: UTF-16 Big-Endian
    • Always null-terminate and pad

Format Comparison Quick Reference

By Use Case

Use Case Format Reason
Maximum compatibility CTB v3 Widest printer support
Smallest file size CBDDLP Simple binary RLE
Best compression (AA) PW0 (nibble RLE4) Optimized variable-length
Archival/preservation UVJ or SL1 Open, PNG-based
Editing/manipulation SL1 or UVJ Human-readable config
Fastest decode SL1, UVJ Standard PNG decoders
Embedded systems CBDDLP Minimal complexity
Modern Anycubic .pm5s (v518) Latest features

By Complexity

Level Formats
Low ImageFile, UVJ, SL1, CBDDLP, CTB v3, PWS
Medium PW0, PhotonS, GOO, PHZ, FDG, CXDLP
High AnycubicFile v517+, VDT, NanoDLP, ZCode
Very High CTBEncrypted, SVGX, OSF

Corrections Summary

v2 Changes from v1:

  1. CTB v3 Header: Fixed to 32-bit floats (was incorrectly documented as 64-bit doubles)
  2. Anycubic Split: Separated legacy (PWS/PW0) from modern (.pm3n, .pm4n, .pm5s) formats
  3. RLE4 Encoding: Fully documented nibble-coded variable-length algorithm (was simplified incorrectly)
  4. Version Support: Added v517 (.pm3n, .pm5, .px6s) and v518 (.pm5s, .m5sp) details
  5. Capability Matrix: Added comprehensive format capabilities table
  6. Extension Mapping: Added machine detection by extension
  7. Preview Counts: Documented v517 (7 previews) and v518 (9 previews) differences
  8. Implementation Order: Revised based on actual complexity

Validation Notes

Verified Against:

  • UVtools.Core/FileFormats/ source code
  • Chitubox 1.8 .ctb fixtures
  • AnycubicFile.cs implementation (lines 2270-2409 for RLE4)
  • ChituboxFile.cs Header class (lines 61-159)
  • Format version constants and extension mappings

Status:

  • ✓ CTB v3 header corrected (32-bit floats verified)
  • ✓ Nibble-coded RLE4 algorithm extracted from working implementation
  • ✓ Anycubic v517/v518 extensions documented
  • ✓ All 32 formats have encode/decode capability
  • ✓ 60+ file extensions cataloged

License & Attribution

Derived from UVtools (https://github.com/sn4k3/UVtools) License: AGPL-3.0

Document Version: 2.0 Generated: 2026-05-16