fixed bug with color layering issue
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -235,7 +235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustitch"
|
name = "rustitch"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"png",
|
"png",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|||||||
16
data/pes.xml
16
data/pes.xml
@@ -7,4 +7,20 @@
|
|||||||
<match type="string" offset="0" value="#PES"/>
|
<match type="string" offset="0" value="#PES"/>
|
||||||
</magic>
|
</magic>
|
||||||
</mime-type>
|
</mime-type>
|
||||||
|
<mime-type type="application/x-dst">
|
||||||
|
<comment>DST Tajima embroidery file</comment>
|
||||||
|
<glob pattern="*.dst"/>
|
||||||
|
</mime-type>
|
||||||
|
<mime-type type="application/x-exp">
|
||||||
|
<comment>EXP Melco embroidery file</comment>
|
||||||
|
<glob pattern="*.exp"/>
|
||||||
|
</mime-type>
|
||||||
|
<mime-type type="application/x-jef">
|
||||||
|
<comment>JEF Janome embroidery file</comment>
|
||||||
|
<glob pattern="*.jef"/>
|
||||||
|
</mime-type>
|
||||||
|
<mime-type type="application/x-vp3">
|
||||||
|
<comment>VP3 Pfaff embroidery file</comment>
|
||||||
|
<glob pattern="*.vp3"/>
|
||||||
|
</mime-type>
|
||||||
</mime-info>
|
</mime-info>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[Thumbnailer Entry]
|
[Thumbnailer Entry]
|
||||||
TryExec=stitch-peek
|
TryExec=stitch-peek
|
||||||
Exec=stitch-peek -i %i -o %o -s %s
|
Exec=stitch-peek -i %i -o %o -s %s
|
||||||
MimeType=application/x-pes
|
MimeType=application/x-pes;application/x-dst;application/x-exp;application/x-jef;application/x-vp3
|
||||||
|
|||||||
@@ -77,16 +77,36 @@ pub fn parse(data: &[u8]) -> Result<Vec<StitchCommand>, Error> {
|
|||||||
/// Standard bit layout for dx across bytes b0, b1, b2.
|
/// Standard bit layout for dx across bytes b0, b1, b2.
|
||||||
fn decode_dx(b0: u8, b1: u8, b2: u8) -> i16 {
|
fn decode_dx(b0: u8, b1: u8, b2: u8) -> i16 {
|
||||||
let mut x: i16 = 0;
|
let mut x: i16 = 0;
|
||||||
if b0 & 0x01 != 0 { x += 1; }
|
if b0 & 0x01 != 0 {
|
||||||
if b0 & 0x02 != 0 { x -= 1; }
|
x += 1;
|
||||||
if b0 & 0x04 != 0 { x += 9; }
|
}
|
||||||
if b0 & 0x08 != 0 { x -= 9; }
|
if b0 & 0x02 != 0 {
|
||||||
if b1 & 0x01 != 0 { x += 3; }
|
x -= 1;
|
||||||
if b1 & 0x02 != 0 { x -= 3; }
|
}
|
||||||
if b1 & 0x04 != 0 { x += 27; }
|
if b0 & 0x04 != 0 {
|
||||||
if b1 & 0x08 != 0 { x -= 27; }
|
x += 9;
|
||||||
if b2 & 0x04 != 0 { x += 81; }
|
}
|
||||||
if b2 & 0x08 != 0 { x -= 81; }
|
if b0 & 0x08 != 0 {
|
||||||
|
x -= 9;
|
||||||
|
}
|
||||||
|
if b1 & 0x01 != 0 {
|
||||||
|
x += 3;
|
||||||
|
}
|
||||||
|
if b1 & 0x02 != 0 {
|
||||||
|
x -= 3;
|
||||||
|
}
|
||||||
|
if b1 & 0x04 != 0 {
|
||||||
|
x += 27;
|
||||||
|
}
|
||||||
|
if b1 & 0x08 != 0 {
|
||||||
|
x -= 27;
|
||||||
|
}
|
||||||
|
if b2 & 0x04 != 0 {
|
||||||
|
x += 81;
|
||||||
|
}
|
||||||
|
if b2 & 0x08 != 0 {
|
||||||
|
x -= 81;
|
||||||
|
}
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,16 +114,36 @@ fn decode_dx(b0: u8, b1: u8, b2: u8) -> i16 {
|
|||||||
/// Standard bit layout for dy across bytes b0, b1, b2.
|
/// Standard bit layout for dy across bytes b0, b1, b2.
|
||||||
fn decode_dy(b0: u8, b1: u8, b2: u8) -> i16 {
|
fn decode_dy(b0: u8, b1: u8, b2: u8) -> i16 {
|
||||||
let mut y: i16 = 0;
|
let mut y: i16 = 0;
|
||||||
if b0 & 0x80 != 0 { y += 1; }
|
if b0 & 0x80 != 0 {
|
||||||
if b0 & 0x40 != 0 { y -= 1; }
|
y += 1;
|
||||||
if b0 & 0x20 != 0 { y += 9; }
|
}
|
||||||
if b0 & 0x10 != 0 { y -= 9; }
|
if b0 & 0x40 != 0 {
|
||||||
if b1 & 0x80 != 0 { y += 3; }
|
y -= 1;
|
||||||
if b1 & 0x40 != 0 { y -= 3; }
|
}
|
||||||
if b1 & 0x20 != 0 { y += 27; }
|
if b0 & 0x20 != 0 {
|
||||||
if b1 & 0x10 != 0 { y -= 27; }
|
y += 9;
|
||||||
if b2 & 0x20 != 0 { y += 81; }
|
}
|
||||||
if b2 & 0x10 != 0 { y -= 81; }
|
if b0 & 0x10 != 0 {
|
||||||
|
y -= 9;
|
||||||
|
}
|
||||||
|
if b1 & 0x80 != 0 {
|
||||||
|
y += 3;
|
||||||
|
}
|
||||||
|
if b1 & 0x40 != 0 {
|
||||||
|
y -= 3;
|
||||||
|
}
|
||||||
|
if b1 & 0x20 != 0 {
|
||||||
|
y += 27;
|
||||||
|
}
|
||||||
|
if b1 & 0x10 != 0 {
|
||||||
|
y -= 27;
|
||||||
|
}
|
||||||
|
if b2 & 0x20 != 0 {
|
||||||
|
y += 81;
|
||||||
|
}
|
||||||
|
if b2 & 0x10 != 0 {
|
||||||
|
y -= 81;
|
||||||
|
}
|
||||||
// DST Y axis is inverted (positive = up in machine coords, down in screen coords)
|
// DST Y axis is inverted (positive = up in machine coords, down in screen coords)
|
||||||
-y
|
-y
|
||||||
}
|
}
|
||||||
@@ -166,7 +206,7 @@ mod tests {
|
|||||||
assert_eq!(decode_dx(0x04, 0x00, 0x00), 9);
|
assert_eq!(decode_dx(0x04, 0x00, 0x00), 9);
|
||||||
assert_eq!(decode_dx(0x00, 0x04, 0x00), 27);
|
assert_eq!(decode_dx(0x00, 0x04, 0x00), 27);
|
||||||
assert_eq!(decode_dx(0x00, 0x00, 0x04), 81);
|
assert_eq!(decode_dx(0x00, 0x00, 0x04), 81);
|
||||||
assert_eq!(decode_dx(0x05, 0x05, 0x04), 1 + 9 + 27 + 81); // 118
|
assert_eq!(decode_dx(0x05, 0x05, 0x04), 1 + 9 + 3 + 27 + 81); // 121
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -88,10 +88,7 @@ mod tests {
|
|||||||
fn parse_simple_stitches() {
|
fn parse_simple_stitches() {
|
||||||
let data = [0x0A, 0x14, 0x05, 0x03];
|
let data = [0x0A, 0x14, 0x05, 0x03];
|
||||||
let cmds = parse(&data).unwrap();
|
let cmds = parse(&data).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: 20 }));
|
||||||
cmds[0],
|
|
||||||
StitchCommand::Stitch { dx: 10, dy: 20 }
|
|
||||||
));
|
|
||||||
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: 3 }));
|
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: 3 }));
|
||||||
assert!(matches!(cmds[2], StitchCommand::End));
|
assert!(matches!(cmds[2], StitchCommand::End));
|
||||||
}
|
}
|
||||||
@@ -120,10 +117,7 @@ mod tests {
|
|||||||
fn parse_jump() {
|
fn parse_jump() {
|
||||||
let data = [0x80, 0x04, 0x0A, 0x14];
|
let data = [0x80, 0x04, 0x0A, 0x14];
|
||||||
let cmds = parse(&data).unwrap();
|
let cmds = parse(&data).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: 20 }));
|
||||||
cmds[0],
|
|
||||||
StitchCommand::Jump { dx: 10, dy: 20 }
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ use palette::JEF_PALETTE;
|
|||||||
///
|
///
|
||||||
/// Stitch data: 2 bytes per stitch (signed i8 dx, dy).
|
/// Stitch data: 2 bytes per stitch (signed i8 dx, dy).
|
||||||
/// Control codes: 0x80 0x01 = color change, 0x80 0x02 = jump, 0x80 0x10 = end.
|
/// Control codes: 0x80 0x01 = color change, 0x80 0x02 = jump, 0x80 0x10 = end.
|
||||||
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
|
type ParseResult = Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error>;
|
||||||
|
|
||||||
|
pub fn parse(data: &[u8]) -> ParseResult {
|
||||||
if data.len() < 116 {
|
if data.len() < 116 {
|
||||||
return Err(Error::TooShort {
|
return Err(Error::TooShort {
|
||||||
expected: 116,
|
expected: 116,
|
||||||
@@ -146,10 +148,7 @@ mod tests {
|
|||||||
fn decode_simple_stitches() {
|
fn decode_simple_stitches() {
|
||||||
let data = [0x0A, 0x14, 0x05, 0x03, 0x80, 0x10];
|
let data = [0x0A, 0x14, 0x05, 0x03, 0x80, 0x10];
|
||||||
let cmds = decode_stitches(&data).unwrap();
|
let cmds = decode_stitches(&data).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(cmds[0], StitchCommand::Stitch { dx: 10, dy: -20 }));
|
||||||
cmds[0],
|
|
||||||
StitchCommand::Stitch { dx: 10, dy: -20 }
|
|
||||||
));
|
|
||||||
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: -3 }));
|
assert!(matches!(cmds[1], StitchCommand::Stitch { dx: 5, dy: -3 }));
|
||||||
assert!(matches!(cmds[2], StitchCommand::End));
|
assert!(matches!(cmds[2], StitchCommand::End));
|
||||||
}
|
}
|
||||||
@@ -165,9 +164,6 @@ mod tests {
|
|||||||
fn decode_jump() {
|
fn decode_jump() {
|
||||||
let data = [0x80, 0x02, 0x0A, 0x14, 0x80, 0x10];
|
let data = [0x80, 0x02, 0x0A, 0x14, 0x80, 0x10];
|
||||||
let cmds = decode_stitches(&data).unwrap();
|
let cmds = decode_stitches(&data).unwrap();
|
||||||
assert!(matches!(
|
assert!(matches!(cmds[0], StitchCommand::Jump { dx: 10, dy: -20 }));
|
||||||
cmds[0],
|
|
||||||
StitchCommand::Jump { dx: 10, dy: -20 }
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ pub mod format;
|
|||||||
pub mod palette;
|
pub mod palette;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
|
pub mod dst;
|
||||||
|
pub mod exp;
|
||||||
|
pub mod jef;
|
||||||
pub mod pes;
|
pub mod pes;
|
||||||
|
pub mod vp3;
|
||||||
|
|
||||||
mod render;
|
mod render;
|
||||||
mod resolve;
|
mod resolve;
|
||||||
@@ -32,6 +36,9 @@ fn parse_and_resolve(data: &[u8], fmt: Format) -> Result<ResolvedDesign, Error>
|
|||||||
let design = pes::parse(data)?;
|
let design = pes::parse(data)?;
|
||||||
pes::resolve(&design)
|
pes::resolve(&design)
|
||||||
}
|
}
|
||||||
_ => Err(Error::UnsupportedFormat),
|
Format::Dst => dst::parse_and_resolve(data),
|
||||||
|
Format::Exp => exp::parse_and_resolve(data),
|
||||||
|
Format::Jef => jef::parse_and_resolve(data),
|
||||||
|
Format::Vp3 => vp3::parse_and_resolve(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,19 @@ pub fn decode_stitches(data: &[u8]) -> Result<Vec<StitchCommand>, Error> {
|
|||||||
let (dx, dx_flags, bytes_dx) = decode_coordinate(data, i)?;
|
let (dx, dx_flags, bytes_dx) = decode_coordinate(data, i)?;
|
||||||
i += bytes_dx;
|
i += bytes_dx;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
if i < data.len() && data[i] == 0xFF {
|
||||||
|
commands.push(StitchCommand::End);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if i < data.len() && data[i] == 0xFE {
|
||||||
|
commands.push(StitchCommand::ColorChange);
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let (dy, dy_flags, bytes_dy) = decode_coordinate(data, i)?;
|
let (dy, dy_flags, bytes_dy) = decode_coordinate(data, i)?;
|
||||||
i += bytes_dy;
|
i += bytes_dy;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ use crate::types::{ResolvedDesign, StitchCommand};
|
|||||||
///
|
///
|
||||||
/// Byte order: mixed, but length-prefixed strings and section sizes use big-endian.
|
/// Byte order: mixed, but length-prefixed strings and section sizes use big-endian.
|
||||||
/// Stitch encoding: variable-length (1 byte for small moves, 3 bytes for large).
|
/// Stitch encoding: variable-length (1 byte for small moves, 3 bytes for large).
|
||||||
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
|
type ParseResult = Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error>;
|
||||||
|
|
||||||
|
pub fn parse(data: &[u8]) -> ParseResult {
|
||||||
if data.len() < 20 {
|
if data.len() < 20 {
|
||||||
return Err(Error::TooShort {
|
return Err(Error::TooShort {
|
||||||
expected: 20,
|
expected: 20,
|
||||||
@@ -93,12 +95,13 @@ fn skip_vp3_header(reader: &mut Reader) -> Result<(), Error> {
|
|||||||
// Skip another potential string
|
// Skip another potential string
|
||||||
if reader.remaining() >= 2 {
|
if reader.remaining() >= 2 {
|
||||||
let peek = reader.peek_u16_be();
|
let peek = reader.peek_u16_be();
|
||||||
if let Ok(len) = peek {
|
if let Ok(len) = peek
|
||||||
if len < 1000 && (len as usize) + 2 <= reader.remaining() {
|
&& len < 1000
|
||||||
|
&& (len as usize) + 2 <= reader.remaining()
|
||||||
|
{
|
||||||
skip_string(reader)?;
|
skip_string(reader)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ categories = ["graphics", "command-line-utilities"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustitch = { version = "0.1.1", path = "../rustitch" }
|
rustitch = { version = "0.2", path = "../rustitch" }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ use clap::Parser;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "stitch-peek", about = "PES embroidery file thumbnailer")]
|
#[command(
|
||||||
|
name = "stitch-peek",
|
||||||
|
about = "Embroidery file thumbnailer (PES, DST, EXP, JEF, VP3)"
|
||||||
|
)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Input PES file path
|
/// Input embroidery file path
|
||||||
#[arg(short = 'i', long = "input")]
|
#[arg(short = 'i', long = "input")]
|
||||||
input: std::path::PathBuf,
|
input: std::path::PathBuf,
|
||||||
|
|
||||||
@@ -21,11 +24,14 @@ struct Args {
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let format = rustitch::format::detect_from_extension(&args.input)
|
||||||
|
.with_context(|| format!("unsupported file extension: {}", args.input.display()))?;
|
||||||
|
|
||||||
let data = fs::read(&args.input)
|
let data = fs::read(&args.input)
|
||||||
.with_context(|| format!("failed to read {}", args.input.display()))?;
|
.with_context(|| format!("failed to read {}", args.input.display()))?;
|
||||||
|
|
||||||
let png =
|
let png = rustitch::thumbnail_format(&data, args.size, format)
|
||||||
rustitch::thumbnail(&data, args.size).with_context(|| "failed to generate thumbnail")?;
|
.with_context(|| "failed to generate thumbnail")?;
|
||||||
|
|
||||||
fs::write(&args.output, &png)
|
fs::write(&args.output, &png)
|
||||||
.with_context(|| format!("failed to write {}", args.output.display()))?;
|
.with_context(|| format!("failed to write {}", args.output.display()))?;
|
||||||
|
|||||||
Reference in New Issue
Block a user