From 473da90b01ba886f3ce6818669e3cab79b264879 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 31 Mar 2026 12:41:36 +0200 Subject: [PATCH] fixed rotation issues + updated readme --- README.md | 43 +++++++++++++++------ rustitch/Cargo.toml | 2 +- rustitch/README.md | 46 +++++++++++++++++++---- rustitch/src/exp.rs | 17 ++++----- rustitch/src/pes/pec.rs | 38 +++++++++++-------- rustitch/tests/fixtures/0.3x1 INCHES.EXP | Bin 0 -> 1346 bytes rustitch/tests/fixtures/0.3x1 INCHES.JEF | Bin 0 -> 1660 bytes rustitch/tests/fixtures/0.3x1 INCHES.PES | Bin 0 -> 6502 bytes stitch-peek/README.md | 16 +++++--- 9 files changed, 112 insertions(+), 50 deletions(-) create mode 100644 rustitch/tests/fixtures/0.3x1 INCHES.EXP create mode 100644 rustitch/tests/fixtures/0.3x1 INCHES.JEF create mode 100644 rustitch/tests/fixtures/0.3x1 INCHES.PES diff --git a/README.md b/README.md index 6fa27d2..739bf62 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,15 @@ [![docs.rs](https://img.shields.io/docsrs/rustitch)](https://docs.rs/rustitch) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) -A Nautilus/GNOME thumbnailer for **PES embroidery files**. Browse your embroidery designs in the file manager with automatic thumbnail previews. +A Nautilus/GNOME thumbnailer for **embroidery files**. Browse your embroidery designs in the file manager with automatic thumbnail previews. + +Supported formats: **PES**, **DST**, **EXP**, **JEF**, **VP3** Built as two crates: | Crate | Description | |-------|-------------| -| [**rustitch**](rustitch/) | Library for parsing PES files and rendering stitch data to images | +| [**rustitch**](rustitch/) | Library for parsing embroidery files and rendering stitch data to images | | [**stitch-peek**](stitch-peek/) | CLI thumbnailer that integrates with GNOME/Nautilus | @@ -68,7 +70,7 @@ nautilus -q ### As a thumbnailer -Once installed, Nautilus will automatically generate thumbnails for `.pes` files. No manual action needed -- just open a folder containing PES files. +Once installed, Nautilus will automatically generate thumbnails for embroidery files. No manual action needed -- just open a folder containing `.pes`, `.dst`, `.exp`, `.jef`, or `.vp3` files. ### Standalone CLI @@ -76,11 +78,12 @@ Generate a thumbnail manually: ```sh stitch-peek -i design.pes -o preview.png -s 256 +stitch-peek -i pattern.dst -o preview.png -s 256 ``` | Flag | Description | Default | |------|-------------|---------| -| `-i` | Input PES file | required | +| `-i` | Input embroidery file | required | | `-o` | Output PNG path | required | | `-s` | Thumbnail size (pixels) | 128 | @@ -94,16 +97,26 @@ rustitch = "0.1" ``` ```rust +// PES (auto-detected) let pes_data = std::fs::read("design.pes")?; let png_bytes = rustitch::thumbnail(&pes_data, 256)?; -std::fs::write("preview.png", &png_bytes)?; + +// Any supported format (explicit) +let dst_data = std::fs::read("pattern.dst")?; +let png_bytes = rustitch::thumbnail_format(&dst_data, 256, rustitch::Format::Dst)?; ``` See the [rustitch README](rustitch/README.md) for more API examples. ## Supported formats -**PES** (Brother PE-Design) embroidery files, versions 1 through 10. The PEC section containing stitch data is consistent across versions. +| Format | Manufacturer | Colors | Notes | +|--------|-------------|--------|-------| +| **PES** | Brother PE-Design | Embedded (PEC palette) | Versions 1-10 | +| **DST** | Tajima | Default palette | 3-byte bit-packed records | +| **EXP** | Melco/Bernina | Default palette | Simple 2-byte encoding | +| **JEF** | Janome | Embedded (Janome palette) | Structured header with color table | +| **VP3** | Pfaff/Viking | Embedded (RGB) | Hierarchical format with per-section colors | ## Project structure @@ -112,16 +125,22 @@ stitch-peek-rs/ ├── rustitch/ # Library crate │ └── src/ │ ├── lib.rs # Public API -│ ├── pes/ # PES format parser -│ │ ├── header.rs # File header (#PES magic, version, PEC offset) -│ │ ├── pec.rs # PEC section (colors, stitch decoding) -│ │ └── palette.rs # Brother 65-color thread palette -│ └── render.rs # tiny-skia renderer +│ ├── types.rs # Shared types (StitchCommand, ResolvedDesign, ...) +│ ├── error.rs # Error types +│ ├── format.rs # Format detection (magic bytes, extension) +│ ├── palette.rs # Thread color palettes (PEC, default) +│ ├── resolve.rs # Stitch command to segment resolver +│ ├── render.rs # tiny-skia renderer +│ ├── pes/ # PES (Brother) parser +│ ├── dst/ # DST (Tajima) parser +│ ├── exp.rs # EXP (Melco) parser +│ ├── jef/ # JEF (Janome) parser +│ └── vp3.rs # VP3 (Pfaff/Viking) parser ├── stitch-peek/ # Binary crate (CLI thumbnailer) │ └── src/main.rs └── data/ ├── stitch-peek.thumbnailer # Nautilus integration - └── pes.xml # MIME type definition + └── pes.xml # MIME type definitions ``` ## Development diff --git a/rustitch/Cargo.toml b/rustitch/Cargo.toml index 579abfe..3eff2d1 100644 --- a/rustitch/Cargo.toml +++ b/rustitch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustitch" -version = "0.2.0" +version = "0.2.1" edition = "2024" description = "PES embroidery file parser and thumbnail renderer" license = "MIT" diff --git a/rustitch/README.md b/rustitch/README.md index a549440..8158179 100644 --- a/rustitch/README.md +++ b/rustitch/README.md @@ -4,7 +4,9 @@ [![docs.rs](https://img.shields.io/docsrs/rustitch)](https://docs.rs/rustitch) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE) -A Rust library for parsing **PES embroidery files** and rendering stitch data to images. +A Rust library for parsing **embroidery files** and rendering stitch data to images. + +Supported formats: **PES**, **DST**, **EXP**, **JEF**, **VP3** Part of the [stitch-peek-rs](https://git.narl.io/nvrl/stitch-peek-rs) project. @@ -14,18 +16,24 @@ Add `rustitch` to your `Cargo.toml`: ```toml [dependencies] -rustitch = "0.1" +rustitch = "0.2" ``` ### Generate a thumbnail ```rust +// PES files (backward-compatible API) let pes_data = std::fs::read("design.pes")?; let png_bytes = rustitch::thumbnail(&pes_data, 256)?; std::fs::write("preview.png", &png_bytes)?; + +// Any supported format +let dst_data = std::fs::read("pattern.dst")?; +let png_bytes = rustitch::thumbnail_format(&dst_data, 256, rustitch::Format::Dst)?; +std::fs::write("preview.png", &png_bytes)?; ``` -### Parse and inspect a design +### Parse and inspect a PES design ```rust use rustitch::pes::{self, StitchCommand}; @@ -61,15 +69,39 @@ let png_bytes = rustitch::render_thumbnail(&resolved, 512)?; std::fs::write("large_preview.png", &png_bytes)?; ``` +### Format detection + +```rust +use rustitch::format::{self, Format}; +use std::path::Path; + +// Detect from file extension +let fmt = format::detect_from_extension(Path::new("design.jef")); +assert_eq!(fmt, Some(Format::Jef)); + +// Detect from file content (magic bytes) +let data = std::fs::read("design.pes")?; +let fmt = format::detect_from_bytes(&data); +assert_eq!(fmt, Some(Format::Pes)); +``` + ## Supported formats -**PES** (Brother PE-Design) embroidery files, versions 1 through 10. The PEC section containing stitch data is consistent across versions. +| Format | Manufacturer | Colors | Notes | +|--------|-------------|--------|-------| +| **PES** | Brother PE-Design | Embedded (PEC palette) | Versions 1-10 | +| **DST** | Tajima | Default palette | 3-byte bit-packed records | +| **EXP** | Melco/Bernina | Default palette | Simple 2-byte encoding | +| **JEF** | Janome | Embedded (Janome palette) | Structured header with color table | +| **VP3** | Pfaff/Viking | Embedded (RGB) | Hierarchical format with per-section colors | + +Formats without embedded color info (DST, EXP) use a default palette of 12 high-contrast colors, cycling on each color change. ## How it works -1. **Parse** the PES binary header to locate the PEC section -2. **Decode** the PEC stitch byte stream (7-bit and 12-bit encoded relative movements, jumps, trims, color changes) -3. **Resolve** relative movements into absolute coordinate segments grouped by thread color, using the 65-color Brother PEC palette +1. **Detect** the file format from magic bytes or extension +2. **Parse** the format-specific binary encoding into a common `StitchCommand` stream (stitch, jump, trim, color change, end) +3. **Resolve** relative movements into absolute coordinate segments grouped by thread color 4. **Render** anti-aliased line segments with [tiny-skia](https://github.com/nickel-org/tiny-skia), scaled to fit the requested size 5. **Encode** as PNG with proper alpha handling diff --git a/rustitch/src/exp.rs b/rustitch/src/exp.rs index 19bf7c6..0c654e8 100644 --- a/rustitch/src/exp.rs +++ b/rustitch/src/exp.rs @@ -42,7 +42,7 @@ pub fn parse(data: &[u8]) -> Result, Error> { break; } let dx = data[i] as i8 as i16; - let dy = data[i + 1] as i8 as i16; + let dy = -(data[i + 1] as i8 as i16); commands.push(StitchCommand::Jump { dx, dy }); i += 2; } @@ -53,7 +53,7 @@ pub fn parse(data: &[u8]) -> Result, Error> { } } else { let dx = b1 as i8 as i16; - let dy = b2 as i8 as i16; + let dy = -(b2 as i8 as i16); commands.push(StitchCommand::Stitch { dx, dy }); i += 2; } @@ -88,8 +88,8 @@ mod tests { fn parse_simple_stitches() { let data = [0x0A, 0x14, 0x05, 0x03]; let cmds = parse(&data).unwrap(); - assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: 20 })); - assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: 3 })); + assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: -20 })); + assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: -3 })); assert!(matches!(cmds[2], StitchCommand::End)); } @@ -98,10 +98,7 @@ mod tests { // -10 as i8 = 0xF6, -20 as i8 = 0xEC let data = [0xF6, 0xEC]; let cmds = parse(&data).unwrap(); - assert!(matches!( - cmds[0], - StitchCommand::Stitch { dx: -10, dy: -20 } - )); + assert!(matches!(cmds[0], StitchCommand::Stitch { dx: -10, dy: 20 })); } #[test] @@ -110,14 +107,14 @@ mod tests { let cmds = parse(&data).unwrap(); assert!(matches!(cmds[0], StitchCommand::Stitch { .. })); assert!(matches!(cmds[1], StitchCommand::ColorChange)); - assert!(matches!(cmds[2], StitchCommand::Stitch { dx: 5, dy: 3 })); + assert!(matches!(cmds[2], StitchCommand::Stitch { dx: 5, dy: -3 })); } #[test] fn parse_jump() { let data = [0x80, 0x04, 0x0A, 0x14]; let cmds = parse(&data).unwrap(); - assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: 20 })); + assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: -20 })); } #[test] diff --git a/rustitch/src/pes/pec.rs b/rustitch/src/pes/pec.rs index c520f7d..c41a29a 100644 --- a/rustitch/src/pes/pec.rs +++ b/rustitch/src/pes/pec.rs @@ -61,12 +61,13 @@ pub fn decode_stitches(data: &[u8]) -> Result, Error> { continue; } - let (dx, dx_flags, bytes_dx) = decode_coordinate(data, i)?; - i += bytes_dx; + // PEC encodes coordinates as (Y, X) — read first value as vertical, + // second as horizontal, then swap to (dx, dy) for screen coordinates. + let (val1, flags1, bytes1) = decode_coordinate(data, i)?; + i += bytes1; - // Check for special bytes at dy position — color change or end markers - // can appear between dx and dy when the preceding stitch ends on an - // odd byte boundary relative to the next control byte. + // Check for special bytes at second coordinate position — color change + // or end markers can appear between the two coordinates. if i < data.len() && data[i] == 0xFF { commands.push(StitchCommand::End); break; @@ -77,10 +78,12 @@ pub fn decode_stitches(data: &[u8]) -> Result, Error> { continue; } - let (dy, dy_flags, bytes_dy) = decode_coordinate(data, i)?; - i += bytes_dy; + let (val2, flags2, bytes2) = decode_coordinate(data, i)?; + i += bytes2; - let flags = dx_flags | dy_flags; + let flags = flags1 | flags2; + let dx = val2; + let dy = val1; if flags & 0x20 != 0 { commands.push(StitchCommand::Trim); @@ -147,13 +150,14 @@ mod tests { #[test] fn decode_simple_stitch() { + // PEC stores (Y, X): first=10 → dy, second=20 → dx let data = [0x0A, 0x14, 0xFF]; let cmds = decode_stitches(&data).unwrap(); assert_eq!(cmds.len(), 2); match &cmds[0] { StitchCommand::Stitch { dx, dy } => { - assert_eq!(*dx, 10); - assert_eq!(*dy, 20); + assert_eq!(*dx, 20); + assert_eq!(*dy, 10); } _ => panic!("expected Stitch"), } @@ -161,12 +165,13 @@ mod tests { #[test] fn decode_negative_7bit() { + // PEC stores (Y, X): first=0x50(-48) → dy, second=0x60(-32) → dx let data = [0x50, 0x60, 0xFF]; let cmds = decode_stitches(&data).unwrap(); match &cmds[0] { StitchCommand::Stitch { dx, dy } => { - assert_eq!(*dx, -48); - assert_eq!(*dy, -32); + assert_eq!(*dx, -32); + assert_eq!(*dy, -48); } _ => panic!("expected Stitch"), } @@ -174,24 +179,27 @@ mod tests { #[test] fn decode_color_change() { + // PEC stores (Y, X): first=10 → dy, second=20 → dx let data = [0xFE, 0xB0, 0x0A, 0x14, 0xFF]; let cmds = decode_stitches(&data).unwrap(); assert!(matches!(cmds[0], StitchCommand::ColorChange)); - assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 10, dy: 20 })); + assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 20, dy: 10 })); } #[test] fn decode_extended_12bit() { + // PEC stores (Y, X): first=0x91,0x00(256 with jump flag) → dy, second=0x05(5) → dx let data = [0x91, 0x00, 0x05, 0xFF]; let cmds = decode_stitches(&data).unwrap(); - assert!(matches!(cmds[0], StitchCommand::Jump { dx: 256, dy: 5 })); + assert!(matches!(cmds[0], StitchCommand::Jump { dx: 5, dy: 256 })); } #[test] fn decode_trim_jump() { + // PEC stores (Y, X): first=0xA0,0x0A(10 with trim flag) → dy, second=0x05(5) → dx let data = [0xA0, 0x0A, 0x05, 0xFF]; let cmds = decode_stitches(&data).unwrap(); assert!(matches!(cmds[0], StitchCommand::Trim)); - assert!(matches!(cmds[1], StitchCommand::Jump { dx: 10, dy: 5 })); + assert!(matches!(cmds[1], StitchCommand::Jump { dx: 5, dy: 10 })); } } diff --git a/rustitch/tests/fixtures/0.3x1 INCHES.EXP b/rustitch/tests/fixtures/0.3x1 INCHES.EXP new file mode 100644 index 0000000000000000000000000000000000000000..7b95a5aefd0757bcf7dc7337ef6b4315c6b13f49 GIT binary patch literal 1346 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 literal 0 HcmV?d00001 diff --git a/stitch-peek/README.md b/stitch-peek/README.md index 2395650..7e60fbc 100644 --- a/stitch-peek/README.md +++ b/stitch-peek/README.md @@ -3,9 +3,11 @@ [![crates.io](https://img.shields.io/crates/v/stitch-peek)](https://crates.io/crates/stitch-peek) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE) -A CLI tool and **Nautilus/GNOME thumbnailer** for PES embroidery files. Generates PNG previews of embroidery designs directly in your file manager. +A CLI tool and **Nautilus/GNOME thumbnailer** for embroidery files. Generates PNG previews of embroidery designs directly in your file manager. -Part of the [stitch-peek-rs](https://git.narl.io/nvrl/stitch-peek-rs) project. Uses [rustitch](https://crates.io/crates/rustitch) for PES parsing and rendering. +Supported formats: **PES**, **DST**, **EXP**, **JEF**, **VP3** + +Part of the [stitch-peek-rs](https://git.narl.io/nvrl/stitch-peek-rs) project. Uses [rustitch](https://crates.io/crates/rustitch) for parsing and rendering. ## Installation @@ -49,23 +51,27 @@ nautilus -q ### As a thumbnailer -Once installed with the `.thumbnailer` file in place, Nautilus automatically generates thumbnails for `.pes` files. No manual action needed. +Once installed with the `.thumbnailer` file in place, Nautilus automatically generates thumbnails for `.pes`, `.dst`, `.exp`, `.jef`, and `.vp3` files. No manual action needed. ### Standalone CLI ```sh stitch-peek -i design.pes -o preview.png -s 256 +stitch-peek -i pattern.dst -o preview.png +stitch-peek -i motif.jef -o preview.png -s 512 ``` | Flag | Description | Default | |------|-------------|---------| -| `-i` | Input PES file | required | +| `-i` | Input embroidery file | required | | `-o` | Output PNG path | required | | `-s` | Thumbnail size (pixels) | 128 | +The format is detected automatically from the file extension. + ## How it works -The tool follows the [freedesktop thumbnail specification](https://specifications.freedesktop.org/thumbnail/latest/). Nautilus calls it with an input file, output path, and requested size. It parses the PES file, renders the stitch pattern as anti-aliased colored lines on a transparent background, and writes a PNG. +The tool follows the [freedesktop thumbnail specification](https://specifications.freedesktop.org/thumbnail/latest/). Nautilus calls it with an input file, output path, and requested size. It detects the embroidery format, parses the stitch data, renders the pattern as anti-aliased colored lines on a transparent background, and writes a PNG. ## License