more msla corrections

This commit is contained in:
Stewart Allen 2026-05-16 11:50:26 -04:00
commit 50e0758f99
2 changed files with 89 additions and 54 deletions

View file

@ -11,6 +11,10 @@
### Previous (Incorrect) Description ### Previous (Incorrect) Description
"RLE125: Simple 2-byte fixed format (color, length), max 125 pixels" "RLE125: Simple 2-byte fixed format (color, length), max 125 pixels"
This was doubly misleading: CTB does not use RLE125, and UVtools' CBDDLP/PWS
RLE125 implementation is a 1-byte bit-plane run format, not a two-byte
`(color, length)` pair.
### Actual Implementation (from ChituboxFile.cs:825-908) ### Actual Implementation (from ChituboxFile.cs:825-908)
CTB uses a **7-bit grayscale variable-length RLE** with optional encryption, NOT simple RLE125. CTB uses a **7-bit grayscale variable-length RLE** with optional encryption, NOT simple RLE125.
@ -236,9 +240,9 @@ Encoded: E4 C0 4E 20
- Output: [0xE4] [0xC0] [0x4E] [0x20] (4 bytes) - Output: [0xE4] [0xC0] [0x4E] [0x20] (4 bytes)
``` ```
#### Key Differences from Simple RLE125 #### Key Differences from the Previous Two-Byte Description
| Feature | Simple RLE125 | CTB Variable RLE | | Feature | Previous two-byte description | CTB Variable RLE |
|---------|---------------|------------------| |---------|---------------|------------------|
| **Color depth** | 8-bit (0-255) | 7-bit (0-127) | | **Color depth** | 8-bit (0-255) | 7-bit (0-127) |
| **Max run (1-byte)** | 125 | 127 | | **Max run (1-byte)** | 125 | 127 |
@ -316,12 +320,20 @@ public override uint[] GetAvailableVersionsForExtension(string? extension)
// AvailableVersions = [VERSION_1, VERSION_515, VERSION_516, VERSION_517, VERSION_518] // AvailableVersions = [VERSION_1, VERSION_515, VERSION_516, VERSION_517, VERSION_518]
``` ```
**PrusaSlicer Profile:**
```ini
# PrusaSlicer/printer/Anycubic Photon Mono 4.ini
FILEFORMAT_PM4N
# No FILEVERSION_517 entry is present.
```
### The Ambiguity ### The Ambiguity
**`.pm4n` extension:** **`.pm4n` extension:**
- ✓ Registered as valid extension - ✓ Registered as valid extension
- ✓ Maps to PhotonMono4 machine - ✓ Maps to PhotonMono4 machine
- ✗ **NOT** explicitly assigned to any version in `GetAvailableVersionsForExtension` - ✗ **NOT** explicitly assigned to any version in `GetAvailableVersionsForExtension`
- ✗ PrusaSlicer profile declares `FILEFORMAT_PM4N` but does not declare `FILEVERSION_517`
- Returns all versions: `[1, 515, 516, 517, 518]` - Returns all versions: `[1, 515, 516, 517, 518]`
### Possible Interpretations ### Possible Interpretations
@ -335,7 +347,7 @@ public override uint[] GetAvailableVersionsForExtension(string? extension)
**User's current implementation treats `.pm4n` as v517**, which is: **User's current implementation treats `.pm4n` as v517**, which is:
- ✓ Consistent with similar naming (.pm3n, .pm5 are v517) - ✓ Consistent with similar naming (.pm3n, .pm5 are v517)
- ✓ Logical based on product generation - ✓ Logical based on product generation
- ⚠️ **UNVERIFIED** by UVtools code (could be any version) - ⚠️ **UNVERIFIED** by UVtools code/profile metadata (could be any version)
### Recommendations ### Recommendations
@ -414,13 +426,14 @@ if (extension == "pm4n") {
- Extension mapping: `AnycubicFile.cs:1155-1191` (GetAvailableVersionsForExtension) - Extension mapping: `AnycubicFile.cs:1155-1191` (GetAvailableVersionsForExtension)
- Machine detection: `AnycubicFile.cs:1731-1733` (FileEndsWith check) - Machine detection: `AnycubicFile.cs:1731-1733` (FileEndsWith check)
- Version constants: `AnycubicFile.cs:30-33` (VERSION_515-518) - Version constants: `AnycubicFile.cs:30-33` (VERSION_515-518)
- Photon Mono 4 PrusaSlicer profile: `PrusaSlicer/printer/Anycubic Photon Mono 4.ini` (`FILEFORMAT_PM4N`, no `FILEVERSION_517`)
--- ---
## Summary ## Summary
**CTB RLE:** **CTB RLE:**
- ❌ Previous: Simple RLE125 (2 bytes/run, max 125) - ❌ Previous: Incorrectly described as simple two-byte RLE125
- ✅ Correct: Variable-length 7-bit grayscale RLE (1-5 bytes/run, max 268M, optional encryption) - ✅ Correct: Variable-length 7-bit grayscale RLE (1-5 bytes/run, max 268M, optional encryption)
**.pm4n Version:** **.pm4n Version:**

View file

@ -1,7 +1,7 @@
# mSLA File Format Reference # mSLA File Format Reference
**Generated:** 2026-05-16 **Generated:** 2026-05-16
**Total Formats:** 32 **Total Formats:** 31 registered in UVtools `AvailableFormats` (plus an unregistered CXDLPv1 implementation)
--- ---
@ -16,16 +16,17 @@
| ChituboxZipFile | ✓ | ✓ | zip | - | No | PNG | G-code based | | ChituboxZipFile | ✓ | ✓ | zip | - | No | PNG | G-code based |
| AnycubicPhotonSFile | ✓ | ✓ | photons | - | No | RLE128 | Legacy Photon S | | AnycubicPhotonSFile | ✓ | ✓ | photons | - | No | RLE128 | Legacy Photon S |
| **Anycubic Family - Legacy (v1, v515-516)** | | **Anycubic Family - Legacy (v1, v515-516)** |
| AnycubicFile (PWS) | ✓ | ✓ | pws, pwx | 1 | Yes (22) | RLE1 (125) | PhotonS, PhotonX | | AnycubicFile (PWS) | ✓ | ✓ | pws | 1 | Yes (22) | 1-byte bit-plane RLE125 | PhotonS |
| AnycubicFile (PW0) | ✓ | ✓ | pw0 | 1 | Yes (22) | Nibble RLE4 | PhotonZero | | AnycubicFile (PW0/PWX) | ✓ | ✓ | pw0, pwx | 1 | Yes (22) | Nibble RLE4 | PhotonZero, PhotonX |
| **Anycubic Family - Modern (v515-518)** | | **Anycubic Family - Modern (v515-518)** |
| AnycubicFile (v515) | ✓ | ✓ | pwmx, pwmo, dl2p, pwmb, pmx2 | 515-517 | Yes (22) | Nibble RLE4 | Mono series | | 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 (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 (v518) | ✓ | ✓ | pm5s, m5sp | 518 | Yes (22) | Nibble RLE4 | Mono M5s/Pro |
| AnycubicFile (all) | ✓ | ✓ | dlp, pwms, pwma, pmsq, pm3, pm3m, pm3r, pwc | varies | Yes (22) | Nibble RLE4 | Additional models | | 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 | | AnycubicZipFile | ✓ | ✓ | pm4u, pm7, pm7m, pwsz, pp1, pp1m | - | No | PNG | ZIP with JSON |
| **Creality Family** | | **Creality Family** |
| CrealityCXDLPv1File | ✓ | ✓ | v1.cxdlp | 1 | No | RLE | Legacy | | CrealityCXDLPv1File | ✓ | ✓ | v1.cxdlp | 1 | No | RLE | Legacy implementation present but not registered in `AvailableFormats` |
| CrealityCXDLPFile | ✓ | ✓ | cxdlp | 2-3 (def: 3) | Yes | RLE | HALOT series | | CrealityCXDLPFile | ✓ | ✓ | cxdlp | 2-3 (def: 3) | Yes | RLE | HALOT series |
| CrealityCXDLPv4File | ✓ | ✓ | cxdlpv4 | 4 | Yes | RLE | Current | | CrealityCXDLPv4File | ✓ | ✓ | cxdlpv4 | 4 | Yes | RLE | Current |
| **Other Binary Formats** | | **Other Binary Formats** |
@ -188,26 +189,30 @@ Same header structure as CTB v3, with additional support for:
##### 2.1.1 PWS (Photon / Photon S) ##### 2.1.1 PWS (Photon / Photon S)
**Extensions:** `.pws`, `.pwx` **Extensions:** `.pws`
**Version:** 1 **Version:** 1
**RLE:** RLE1 (limit: 125) **RLE:** RLE1 (limit: 125)
**Machines:** PhotonS, PhotonX **Machines:** PhotonS
**RLE1 Encoding (PWS):** **RLE1 Encoding (PWS):**
``` ```
For each run (2 bytes): For each run (1 byte):
byte[0] = color (0x00 = black, 0xFF = white) bit 7 = white/on flag
byte[1] = length (1-125) 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) Constant: RLE1EncodingLimit = 0x7D (125)
``` ```
##### 2.1.2 PW0 (Photon Zero) - CORRECTED ##### 2.1.2 PW0/PWX (Photon Zero / Photon X) - CORRECTED
**Extensions:** `.pw0` **Extensions:** `.pw0`, `.pwx`
**Version:** 1 **Version:** 1
**RLE:** Nibble-coded RLE4 (limit: 4095) **RLE:** Nibble-coded RLE4 (limit: 4095)
**Machine:** PhotonZero **Machines:** PhotonZero, PhotonX
**Nibble-Coded RLE4 Encoding (CORRECTED from UVtools implementation):** **Nibble-Coded RLE4 Encoding (CORRECTED from UVtools implementation):**
@ -332,9 +337,12 @@ Gray (0xAA) run of 5 pixels:
All modern Anycubic formats use **Nibble-coded RLE4** encoding. All modern Anycubic formats use **Nibble-coded RLE4** encoding.
##### 2.2.1 Version 515-516 Formats ##### 2.2.1 Version-Mapped Formats Before v518
**Extensions:** `.pwmx`, `.pwmo`, `.dl2p`, `.pwmb`, `.pmx2`, `.dlp`, `.pwms`, `.pwma`, `.pmsq`, `.pm3`, `.pm3m`, `.pm3r` **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:** **Machines:**
- PhotonMono, PhotonMonoX, PhotonMonoX2 - PhotonMono, PhotonMonoX, PhotonMonoX2
@ -359,29 +367,30 @@ All modern Anycubic formats use **Nibble-coded RLE4** encoding.
- PhotonMono2 (.pm3n) - PhotonMono2 (.pm3n)
- PhotonMonoM5 (.pm5) - PhotonMonoM5 (.pm5)
- PhotonMonoX6Ks (.px6s) - PhotonMonoX6Ks (.px6s)
- PhotonMono4 (.pm4n) is mapped as a machine, but its source and PrusaSlicer profile omit an explicit file version
**New Features:** **New Features:**
- Additional preview/model/software metadata compared with earlier versions - `SOFTWARE` and `MODEL` sections compared with earlier versions
- Preview sizes: 224x168, 330x190 - `MACHINE` property field count rises to 7
- Thumbnail defaults are 224x168 and 330x190; v517 writes the first `PREVIEW` table
- `HEADER` table length: 92 bytes - `HEADER` table length: 92 bytes
- `LayerDef` entries remain 32 bytes each - `LayerDef` entries remain 32 bytes each
##### 2.2.3 Version 518 Formats ##### 2.2.3 Version 518 Formats
**Extensions:** `.pm5s`, `.m5sp`, `.pwc` **Extensions:** `.pm5s`, `.m5sp` (`.pwc` is registered as Anycubic Custom Machine but falls through to all available versions)
**Machines:** **Machines:**
- PhotonMonoM5s (.pm5s) - PhotonMonoM5s (.pm5s)
- PhotonMonoM5sPro (.m5sp) - PhotonMonoM5sPro (.m5sp)
- AnycubicCustomMachine (.pwc)
**New Features:** **New Features:**
- 9 preview images - 11 FileMark table entries
- 11 distinct printer profiles - second `PREVIEW2` table may be present
- Preview sizes: 224x168, 330x190 - Preview sizes: 224x168, 330x190
- `HEADER` table length: 96 bytes - `HEADER` table length: 96 bytes
- `LayerDef` entries remain 32 bytes each; `SUBIMGS` sublayer records may also be present - `LayerDef` entries remain 32 bytes each; `SUBIMGS` sublayer records may also be present
- 15 slicer metadata fields - `MACHINE` property field count rises to 15
**Anycubic FileMark and Section Table (v515+):** **Anycubic FileMark and Section Table (v515+):**
``` ```
@ -437,14 +446,14 @@ Each section starts with a 12-byte null-padded table name followed by a 32-bit t
| Format | Name | Bytes/Run | Max Repeat | Color Bits | Notes | | Format | Name | Bytes/Run | Max Repeat | Color Bits | Notes |
|--------|------|-----------|------------|------------|-------| |--------|------|-----------|------------|------------|-------|
| RLE1 (PWS) | Run-Length 1 | 2 (fixed) | 125 | 8 | Color, Length | | 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 | | 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 | | 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) | | RLE128 (PhotonS) | Run-Length 128 | 2 (fixed) | 128 | 8 | Length, Color (BE) |
| RLE+Delim (GOO) | Delimited | 5 (+ delim) | 65535 | 8 | 0x55 + Color + Length16 + 0x0D0A | | RLE+Delim (GOO) | Delimited | 5 (+ delim) | 65535 | 8 | 0x55 + Color + Length16 + 0x0D0A |
**Performance Comparison:** **Performance Comparison:**
- **RLE1/RLE125/RLE128:** Simple, predictable, easy to implement - **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 - **CTB variable RLE:** Better compression and grayscale AA, plus optional seed-derived XOR
- **Nibble RLE4:** Best compression for grayscale AA, complex encoding - **Nibble RLE4:** Best compression for grayscale AA, complex encoding
- **RLE+Delim:** Highest max repeat, overhead from delimiters - **RLE+Delim:** Highest max repeat, overhead from delimiters
@ -618,43 +627,57 @@ PrinterModel = SL1
### Essential Algorithms (Pseudocode) ### Essential Algorithms (Pseudocode)
#### RLE125 Decoder (CBDDLP) #### RLE125 Bit-Plane Decoder (CBDDLP/PWS)
```python ```python
def decode_rle125(data: bytes, width: int, height: int) -> bytes: def decode_rle125_bitplanes(data: bytes, width: int, height: int, aa: int = 1) -> bytes:
pixels = bytearray(width * height) levels = bytearray(width * height)
pixel_index = 0
data_index = 0 data_index = 0
while data_index < len(data): for _plane in range(aa):
color = data[data_index] pixel_index = 0
length = data[data_index + 1] while pixel_index < len(levels):
data_index += 2 code = data[data_index]
data_index += 1
for _ in range(length): length = code & 0x7F
pixels[pixel_index] = color if code & 0x80:
pixel_index += 1 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 return pixels
``` ```
#### RLE125 Encoder (CBDDLP) #### RLE125 Bit-Plane Encoder (Single Plane)
```python ```python
def encode_rle125(pixels: bytes) -> bytes: def encode_rle125_bitplane(bits: list[bool]) -> bytes:
result = bytearray() result = bytearray()
i = 0 run_on = False
run = 0
while i < len(pixels): def flush():
color = pixels[i] nonlocal run
length = 1 if run:
result.append(run | (0x80 if run_on else 0))
run = 0
while (i + length < len(pixels) and for bit in bits:
pixels[i + length] == color and if bit == run_on and run < 0x7D:
length < 125): run += 1
length += 1 continue
result.append(color) flush()
result.append(length) run_on = bit
i += length run = 1
flush()
return result return result
``` ```
@ -883,4 +906,3 @@ License: AGPL-3.0
**Document Version:** 2.0 **Document Version:** 2.0
**Generated:** 2026-05-16 **Generated:** 2026-05-16