Skip to main content

PEC file structure

PEC is the Brother-family machine stitch payload that Satin Studio reads directly or inside PES files. This page documents the bytes Satin Studio currently reads or writes, using Rust-like struct syntax.

All integer fields are little-endian unless the field name says _be. PEC stitch coordinates are relative movement units; Satin Studio maps one PEC unit to 100 µm (0.1 mm).

Standalone versus embedded

A standalone .pec file may start with #PEC0001:

struct PecStandaloneFile {
signature: [u8; 8], // *b"#PEC0001"
payload: PecPayloadNoSignature,
}

Inside a PES file, the PES prefix points to either PecStandaloneFile::signature or directly to PecPayloadNoSignature beginning with b"LA:".

struct PecPayloadNoSignature<const COLORS: usize, const STITCH_BYTES: usize, const THREADS: usize> {
/// Exactly 512 bytes.
header: PecHeader512<COLORS>,

/// Stitch section immediately after the 512-byte header.
stitch_section: PecStitchSection<STITCH_BYTES>,

/// Preview/icon bytes. Satin Studio writes zero-filled placeholder graphics.
graphics: PecGraphics<THREADS>,
}

512-byte PEC header

Satin Studio writes this header exactly. The color table uses COLORS = color_count_minus_one + 1 when colors are present.

struct PecHeader512<const COLORS: usize> {
/// Bytes 0..19. Starts with `b"LA:"`, then a design label padded with spaces.
/// Satin Studio writes `b"LA:StitchCodecs "` (19 bytes total).
label: [u8; 19],

/// Byte 19.
carriage_return: u8, // 0x0d

/// Bytes 20..32.
pre_palette_spaces: [u8; 12], // [0x20; 12]

/// Byte 32.
unknown_ff: u8, // 0xff

/// Byte 33.
unknown_zero: u8, // 0x00

/// Byte 34. Width of each thumbnail row in bytes.
/// 6 bytes means 48 one-bit pixels per row.
graphic_width_bytes: u8, // 0x06

/// Byte 35. Thumbnail height in pixels.
graphic_height_pixels: u8, // 0x26 (38)

/// Bytes 36..48.
pre_color_table_spaces: [u8; 12], // [0x20; 12]

/// Byte 48 when colors are present.
/// Value is `COLORS - 1`; `0xff` is used by some blank/no-color payloads.
color_count_minus_one: u8,

/// Bytes 49..(49 + COLORS). One Brother/PEC palette index per color block.
palette_indices: [u8; COLORS],

/// Pads the first PEC section to exactly 512 bytes.
/// Length is `512 - 49 - COLORS`.
padding_to_512: [u8; 512 - 49 - COLORS], // all 0x20
}

For the normal non-empty case, the exact header length is:

const PEC_HEADER_LEN: usize = 19 // label
+ 1 // carriage_return
+ 12 // pre_palette_spaces
+ 1 // unknown_ff
+ 1 // unknown_zero
+ 1 // graphic_width_bytes
+ 1 // graphic_height_pixels
+ 12 // pre_color_table_spaces
+ 1 // color_count_minus_one
+ COLORS // palette_indices
+ (512 - 49 - COLORS); // padding_to_512

PEC_HEADER_LEN is always 512.

Some public PEC notes describe a blank/no-color convention: one 0xff color marker followed by padding spaces. Satin Studio normally ensures at least one fallback black thread, so normal exports use the non-empty layout above.

Stitch section

After the 512-byte header comes a stitch section. Satin Studio writes a compact form with no separate min-bound origin fields. Many external PEC/PES files include four additional big-endian origin bytes before the stitch stream; see external origin bytes.

struct PecStitchSection<const STITCH_BYTES: usize> {
/// Bytes 0..2 of the section.
leading_zero: u16, // 0x0000

/// Bytes 2..5. Three-byte little-endian offset/length from the start of
/// this stitch section to the graphics section. In Satin Studio exports:
/// `16 + STITCH_BYTES`.
graphics_offset_u24_le: [u8; 3],

/// Bytes 5..8.
section_marker: [u8; 3], // [0x31, 0xff, 0xf0]

/// Bytes 8..10. Design width in PEC movement units.
width_units: u16,

/// Bytes 10..12. Design height in PEC movement units.
height_units: u16,

/// Bytes 12..14.
unknown_e0_01: u16, // 0x01e0, serialized as e0 01

/// Bytes 14..16.
unknown_b0_01: u16, // 0x01b0, serialized as b0 01

/// Bytes 16..(16 + STITCH_BYTES).
stitches: [u8; STITCH_BYTES],
}

External origin bytes

Some PEC references and many real files include four bytes after unknown_b0_01 and before the encoded stitches:

struct PecExternalOriginFields {
/// Big-endian value commonly described as `0x9000 - min_x`.
origin_x_be: [u8; 2],

/// Big-endian value commonly described as `0x9000 - min_y`.
origin_y_be: [u8; 2],
}

With those fields, the graphics offset is commonly 20 + STITCH_BYTES rather than 16 + STITCH_BYTES:

struct PecStitchSectionWithOrigin<const STITCH_BYTES: usize> {
leading_zero: u16, // 0x0000
graphics_offset_u24_le: [u8; 3], // 20 + STITCH_BYTES
section_marker: [u8; 3], // [0x31, 0xff, 0xf0]
width_units: u16,
height_units: u16,
unknown_e0_01: u16, // 0x01e0
unknown_b0_01: u16, // 0x01b0
origin: PecExternalOriginFields,
stitches: [u8; STITCH_BYTES],
}

Satin Studio's current writer emits the compact 16-byte prelude. The reader is permissive for common payloads and treats the stitch list as the source of geometry.

Encoded stitch stream

The stitch stream is a sequence of relative movements and commands.

enum PecCommand {
ShortMovement(PecShortDx, PecShortDy),
LongMovement(PecLongDx, PecLongDy),
ColorChange(PecColorChange),
End(PecEnd),
}

Short movement values are one byte each:

struct PecShortValue {
/// Bit layout: 0 b6 b5 b4 b3 b2 b1 b0
/// The low seven bits are a signed 7-bit two's-complement delta.
byte: u8,
}

struct PecShortMovement {
dx: PecShortValue,
dy: PecShortValue,
}

Long movement values are two bytes each:

struct PecLongValue {
/// First byte bit layout: 1 c0 c1 c2 b11 b10 b9 b8
/// Second byte bit layout: b7 b6 b5 b4 b3 b2 b1 b0
/// The low 12 bits are a signed 12-bit two's-complement delta.
high: u8,
low: u8,
}

Satin Studio interprets the control bits this way:

const PEC_LONG_FLAG: u8 = 0b1000_0000; // 0x80
const PEC_JUMP_FLAG: u8 = 0b0001_0000; // 0x10 on the first long byte
const PEC_TRIM_FLAG: u8 = 0b0010_0000; // 0x20 on the first long byte

struct PecLongMovement {
dx: PecLongValue,
dy: PecLongValue,
}

Movement command shapes:

// Stitch, short form: both bytes have high bit 0.
struct PecShortStitch {
dx: u8, // 0b0xxxxxxx
dy: u8, // 0b0xxxxxxx
}

// Long stitch: both values have high bit 1 and no jump/trim flag.
struct PecLongStitch {
dx: PecLongValue, // first byte matches 0b1000_xxxx
dy: PecLongValue, // first byte matches 0b1000_xxxx
}

// Jump: Satin Studio writes jump flag on both x and y long values.
struct PecJump {
dx: PecLongValue, // first byte matches 0b1001_xxxx
dy: PecLongValue, // first byte matches 0b1001_xxxx
}

// Trim jump: Satin Studio writes trim flag on both x and y long values.
struct PecTrimJump {
dx: PecLongValue, // first byte matches 0b1010_xxxx
dy: PecLongValue, // first byte matches 0b1010_xxxx
}

Color change is exactly three bytes:

struct PecColorChange {
marker_0: u8, // 0xfe
marker_1: u8, // 0xb0
needle: u8, // Satin Studio alternates 0x02, 0x01, 0x02, 0x01, ...
}

End is one byte:

struct PecEnd {
marker: u8, // 0xff
}

The reader also tolerates files whose declared stitch block continues with trailing zero padding after the practical end of the command stream.

Graphics section

PEC graphics are preview/thumbnail data, not stitch geometry. The header fields graphic_width_bytes = 6 and graphic_height_pixels = 38 make one graphic image exactly 228 bytes:

const PEC_GRAPHIC_BYTES: usize = 6 * 38; // 228

struct PecOneBitGraphic {
/// 6 bytes per row, 38 rows. Each bit is one pixel, most-significant bit first.
bytes: [u8; 228],
}

Satin Studio writes zero-filled placeholder graphics:

struct PecGraphics<const THREADS: usize> {
/// One main graphic plus one graphic per thread/color block.
/// Because Satin Studio always has at least one thread, this is `THREADS + 1`.
graphics: [[u8; 228]; THREADS + 1], // every byte is 0x00
}

Some external references describe a non-zero blank graphic pattern. Satin Studio does not currently generate that pattern; it writes zero-filled placeholders and does not use graphics bytes for import geometry.

Palette notes

The PEC color table stores Brother/PEC palette indices. PES v5/v6 and later-style files may also carry exact RGB thread colors in PES metadata or addendum data. Satin Studio prefers exact PES RGB thread colors when it can read the compact v6 thread table; otherwise it falls back to the PEC palette.

Notes

PEC is partially documented in public format research, but machine and software behavior still varies. Satin Studio documents the exact subset it reads and writes here rather than treating any third-party description as authoritative.