Compare commits
4 Commits
f161a25002
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e956e1939 | |||
| 9bbb7038aa | |||
| 800da1872b | |||
| c9c7245dea |
Generated
+2
-2
@@ -235,7 +235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustitch"
|
name = "rustitch"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"png",
|
"png",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@@ -250,7 +250,7 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stitch-peek"
|
name = "stitch-peek"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
mod palette;
|
mod palette;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{ResolvedDesign, StitchCommand};
|
use crate::types::{Color, RawDesign, ResolvedDesign, StitchCommand};
|
||||||
use palette::JEF_PALETTE;
|
use palette::JEF_PALETTE;
|
||||||
|
|
||||||
/// Parse a JEF (Janome) file from raw bytes into stitch commands and color info.
|
/// Parse a JEF (Janome) file from raw bytes into stitch commands and color info.
|
||||||
@@ -15,7 +15,7 @@ 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.
|
||||||
type ParseResult = Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error>;
|
type ParseResult = Result<RawDesign, Error>;
|
||||||
|
|
||||||
pub fn parse(data: &[u8]) -> ParseResult {
|
pub fn parse(data: &[u8]) -> ParseResult {
|
||||||
if data.len() < 116 {
|
if data.len() < 116 {
|
||||||
@@ -38,7 +38,7 @@ pub fn parse(data: &[u8]) -> ParseResult {
|
|||||||
|
|
||||||
// Read color table starting at offset 116
|
// Read color table starting at offset 116
|
||||||
let color_table_start = 116;
|
let color_table_start = 116;
|
||||||
let mut colors = Vec::with_capacity(color_count);
|
let mut colors: Vec<Color> = Vec::with_capacity(color_count);
|
||||||
for i in 0..color_count {
|
for i in 0..color_count {
|
||||||
let entry_offset = color_table_start + i * 4;
|
let entry_offset = color_table_start + i * 4;
|
||||||
if entry_offset + 4 > data.len() {
|
if entry_offset + 4 > data.len() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use crate::types::Color;
|
||||||
|
|
||||||
/// Janome thread color palette (78 entries).
|
/// Janome thread color palette (78 entries).
|
||||||
/// Index 0 is a fallback; indices 1-77 correspond to standard Janome thread colors.
|
/// Index 0 is a fallback; indices 1-77 correspond to standard Janome thread colors.
|
||||||
pub const JEF_PALETTE: [(u8, u8, u8); 78] = [
|
pub const JEF_PALETTE: [Color; 78] = [
|
||||||
(0, 0, 0), // 0: Unknown / Black
|
(0, 0, 0), // 0: Unknown / Black
|
||||||
(0, 0, 0), // 1: Black
|
(0, 0, 0), // 1: Black
|
||||||
(255, 255, 255), // 2: White
|
(255, 255, 255), // 2: White
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use crate::types::Color;
|
||||||
|
|
||||||
/// Brother PEC thread color palette (65 entries).
|
/// Brother PEC thread color palette (65 entries).
|
||||||
/// Index 0 is a fallback; indices 1-64 correspond to standard Brother thread colors.
|
/// Index 0 is a fallback; indices 1-64 correspond to standard Brother thread colors.
|
||||||
pub const PEC_PALETTE: [(u8, u8, u8); 65] = [
|
pub const PEC_PALETTE: [Color; 65] = [
|
||||||
(0, 0, 0), // 0: Unknown
|
(0, 0, 0), // 0: Unknown
|
||||||
(14, 31, 124), // 1: Prussian Blue
|
(14, 31, 124), // 1: Prussian Blue
|
||||||
(10, 85, 163), // 2: Blue
|
(10, 85, 163), // 2: Blue
|
||||||
@@ -70,7 +72,7 @@ pub const PEC_PALETTE: [(u8, u8, u8); 65] = [
|
|||||||
|
|
||||||
/// Default high-contrast palette for formats without embedded color info (DST, EXP).
|
/// Default high-contrast palette for formats without embedded color info (DST, EXP).
|
||||||
/// Colors cycle on each color change.
|
/// Colors cycle on each color change.
|
||||||
pub const DEFAULT_PALETTE: [(u8, u8, u8); 12] = [
|
pub const DEFAULT_PALETTE: [Color; 12] = [
|
||||||
(0, 0, 0), // Black
|
(0, 0, 0), // Black
|
||||||
(237, 23, 31), // Red
|
(237, 23, 31), // Red
|
||||||
(10, 85, 163), // Blue
|
(10, 85, 163), // Blue
|
||||||
@@ -86,12 +88,12 @@ pub const DEFAULT_PALETTE: [(u8, u8, u8); 12] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/// Look up a PEC palette color by index, clamping to valid range.
|
/// Look up a PEC palette color by index, clamping to valid range.
|
||||||
pub fn pec_color(idx: u8) -> (u8, u8, u8) {
|
pub fn pec_color(idx: u8) -> Color {
|
||||||
PEC_PALETTE[(idx as usize).min(PEC_PALETTE.len() - 1)]
|
PEC_PALETTE[(idx as usize).min(PEC_PALETTE.len() - 1)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a color list for `n` thread slots by cycling through `DEFAULT_PALETTE`.
|
/// Build a color list for `n` thread slots by cycling through `DEFAULT_PALETTE`.
|
||||||
pub fn default_colors(n: usize) -> Vec<(u8, u8, u8)> {
|
pub fn default_colors(n: usize) -> Vec<Color> {
|
||||||
(0..n)
|
(0..n)
|
||||||
.map(|i| DEFAULT_PALETTE[i % DEFAULT_PALETTE.len()])
|
.map(|i| DEFAULT_PALETTE[i % DEFAULT_PALETTE.len()])
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::pes::pec::{decode_stitches, parse_pec_header};
|
use crate::pes::pec::{decode_stitches, parse_pec_header};
|
||||||
use crate::types::{ResolvedDesign, StitchCommand};
|
use crate::types::{Color, RawDesign, ResolvedDesign};
|
||||||
|
|
||||||
/// Parse a standalone PEC file (`#PEC0001` prefix + PEC data).
|
/// Parse a standalone PEC file (`#PEC0001` prefix + PEC data).
|
||||||
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
|
pub fn parse(data: &[u8]) -> Result<RawDesign, Error> {
|
||||||
if data.len() < 8 || &data[0..8] != b"#PEC0001" {
|
if data.len() < 8 || &data[0..8] != b"#PEC0001" {
|
||||||
return Err(Error::InvalidHeader("missing #PEC0001 magic".into()));
|
return Err(Error::InvalidHeader("missing #PEC0001 magic".into()));
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Err
|
|||||||
let commands = decode_stitches(&pec_data[stitch_offset..])?;
|
let commands = decode_stitches(&pec_data[stitch_offset..])?;
|
||||||
|
|
||||||
// Map PEC palette indices to RGB colors
|
// Map PEC palette indices to RGB colors
|
||||||
let colors: Vec<(u8, u8, u8)> = header
|
let colors: Vec<Color> = header
|
||||||
.color_indices
|
.color_indices
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&idx| crate::palette::pec_color(idx))
|
.map(|&idx| crate::palette::pec_color(idx))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub use pec::PecHeader;
|
|||||||
// Re-export shared types for backward compatibility
|
// Re-export shared types for backward compatibility
|
||||||
pub use crate::error::Error;
|
pub use crate::error::Error;
|
||||||
pub use crate::palette::PEC_PALETTE;
|
pub use crate::palette::PEC_PALETTE;
|
||||||
pub use crate::types::{BoundingBox, ResolvedDesign, StitchCommand, StitchSegment};
|
pub use crate::types::{BoundingBox, Color, ResolvedDesign, StitchCommand, StitchSegment};
|
||||||
|
|
||||||
pub struct PesDesign {
|
pub struct PesDesign {
|
||||||
pub header: PesHeader,
|
pub header: PesHeader,
|
||||||
@@ -37,7 +37,7 @@ pub fn parse(data: &[u8]) -> Result<PesDesign, Error> {
|
|||||||
|
|
||||||
/// Convert parsed PES design into renderable segments with absolute coordinates.
|
/// Convert parsed PES design into renderable segments with absolute coordinates.
|
||||||
pub fn resolve(design: &PesDesign) -> Result<ResolvedDesign, Error> {
|
pub fn resolve(design: &PesDesign) -> Result<ResolvedDesign, Error> {
|
||||||
let colors: Vec<(u8, u8, u8)> = design
|
let colors: Vec<Color> = design
|
||||||
.pec_header
|
.pec_header
|
||||||
.color_indices
|
.color_indices
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{BoundingBox, ResolvedDesign, StitchCommand, StitchSegment};
|
use crate::types::{BoundingBox, Color, ResolvedDesign, StitchCommand, StitchSegment};
|
||||||
|
|
||||||
/// Convert parsed stitch commands into renderable segments with absolute coordinates.
|
/// Convert parsed stitch commands into renderable segments with absolute coordinates.
|
||||||
pub fn resolve(
|
pub fn resolve(commands: &[StitchCommand], colors: Vec<Color>) -> Result<ResolvedDesign, Error> {
|
||||||
commands: &[StitchCommand],
|
|
||||||
colors: Vec<(u8, u8, u8)>,
|
|
||||||
) -> Result<ResolvedDesign, Error> {
|
|
||||||
let mut segments = Vec::new();
|
let mut segments = Vec::new();
|
||||||
let mut x: f32 = 0.0;
|
let mut x: f32 = 0.0;
|
||||||
let mut y: f32 = 0.0;
|
let mut y: f32 = 0.0;
|
||||||
|
|||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{ResolvedDesign, StitchCommand};
|
use crate::types::{Color, RawDesign, ResolvedDesign, StitchCommand};
|
||||||
|
|
||||||
const STITCH_DATA_OFFSET: usize = 0x1D78;
|
const STITCH_DATA_OFFSET: usize = 0x1D78;
|
||||||
|
|
||||||
/// Janome SEW thread color palette (first 80 entries).
|
/// Janome SEW thread color palette (first 80 entries).
|
||||||
const SEW_PALETTE: [(u8, u8, u8); 80] = [
|
const SEW_PALETTE: [Color; 80] = [
|
||||||
(0, 0, 0), // 0: Unknown
|
(0, 0, 0), // 0: Unknown
|
||||||
(0, 0, 0), // 1: Black
|
(0, 0, 0), // 1: Black
|
||||||
(255, 255, 255), // 2: White
|
(255, 255, 255), // 2: White
|
||||||
@@ -100,7 +100,7 @@ const SEW_PALETTE: [(u8, u8, u8); 80] = [
|
|||||||
/// - 0x10: normal stitch (read 2 signed bytes)
|
/// - 0x10: normal stitch (read 2 signed bytes)
|
||||||
/// - other: end
|
/// - other: end
|
||||||
/// - Y is negated
|
/// - Y is negated
|
||||||
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
|
pub fn parse(data: &[u8]) -> Result<RawDesign, Error> {
|
||||||
if data.len() < STITCH_DATA_OFFSET + 4 {
|
if data.len() < STITCH_DATA_OFFSET + 4 {
|
||||||
return Err(Error::TooShort {
|
return Err(Error::TooShort {
|
||||||
expected: STITCH_DATA_OFFSET + 4,
|
expected: STITCH_DATA_OFFSET + 4,
|
||||||
@@ -114,7 +114,7 @@ pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read thread palette indices
|
// Read thread palette indices
|
||||||
let colors: Vec<(u8, u8, u8)> = (0..color_count)
|
let colors: Vec<Color> = (0..color_count)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let off = 2 + i * 2;
|
let off = 2 + i * 2;
|
||||||
if off + 1 < data.len() {
|
if off + 1 < data.len() {
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ pub enum StitchCommand {
|
|||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Color = (u8, u8, u8);
|
||||||
|
pub type RawDesign = (Vec<StitchCommand>, Vec<Color>);
|
||||||
|
|
||||||
pub struct StitchSegment {
|
pub struct StitchSegment {
|
||||||
pub x0: f32,
|
pub x0: f32,
|
||||||
pub y0: f32,
|
pub y0: f32,
|
||||||
@@ -24,6 +27,6 @@ pub struct BoundingBox {
|
|||||||
|
|
||||||
pub struct ResolvedDesign {
|
pub struct ResolvedDesign {
|
||||||
pub segments: Vec<StitchSegment>,
|
pub segments: Vec<StitchSegment>,
|
||||||
pub colors: Vec<(u8, u8, u8)>,
|
pub colors: Vec<Color>,
|
||||||
pub bounds: BoundingBox,
|
pub bounds: BoundingBox,
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::types::{ResolvedDesign, StitchCommand};
|
use crate::types::{Color, RawDesign, ResolvedDesign, StitchCommand};
|
||||||
|
|
||||||
const HEADER_SIZE: usize = 256;
|
const HEADER_SIZE: usize = 256;
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ const HEADER_SIZE: usize = 256;
|
|||||||
/// - 0x08 or 0x0A..0x17: color change
|
/// - 0x08 or 0x0A..0x17: color change
|
||||||
/// - 0x7F: end of data
|
/// - 0x7F: end of data
|
||||||
/// - Color table after stitch data: skip 2 bytes, then color_count × i32 BE (0x00RRGGBB)
|
/// - Color table after stitch data: skip 2 bytes, then color_count × i32 BE (0x00RRGGBB)
|
||||||
pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Error> {
|
pub fn parse(data: &[u8]) -> Result<RawDesign, Error> {
|
||||||
if data.len() < HEADER_SIZE + 2 {
|
if data.len() < HEADER_SIZE + 2 {
|
||||||
return Err(Error::TooShort {
|
return Err(Error::TooShort {
|
||||||
expected: HEADER_SIZE + 2,
|
expected: HEADER_SIZE + 2,
|
||||||
@@ -99,7 +99,7 @@ pub fn parse(data: &[u8]) -> Result<(Vec<StitchCommand>, Vec<(u8, u8, u8)>), Err
|
|||||||
commands.push(StitchCommand::End);
|
commands.push(StitchCommand::End);
|
||||||
|
|
||||||
// Read color table: color_count × i32 BE (0x00RRGGBB)
|
// Read color table: color_count × i32 BE (0x00RRGGBB)
|
||||||
let colors = if color_table_start + color_count * 4 <= data.len() {
|
let colors: Vec<Color> = if color_table_start + color_count * 4 <= data.len() {
|
||||||
(0..color_count)
|
(0..color_count)
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
let base = color_table_start + c * 4;
|
let base = color_table_start + c * 4;
|
||||||
|
|||||||
Reference in New Issue
Block a user