VIP — Husqvarna / PFAFF

pyembroidery does not have a VIP reader, but VIP is essentially a HUS body with a different header: the command/x/y streams use the same Greg-Hus compression (EmbCompress) as HUS/VP3, without XOR on the data part (verified by decompressing the blocks and comparing the path stitch-for-stitch against the reference files).

Header

OffsetTypeMeaning
0x00uint32magic 0x0190FC5D
0x04int32numberOfStitches (includes the final END record)
0x08int32numberOfColors
0x0Cint16posX (max X)
0x0Eint16posY (max Y)
0x10int16negX (min X)
0x12int16negY (min Y)
0x14int32attributeOffset — start of the command block (compressed)
0x18int32xOffset — start of the delta-X block (compressed)
0x1Cint32yOffset — start of the delta-Y block (compressed)
0x208 bytestring / reserved (zero in the fixtures)
0x2Aint32colorLength = 0x2E + 8·numberOfColors (formula in the fixtures)
0x2E4·n byteencrypted color block (see below)

Color block

Each color is 4 bytes (r, g, b, 0), XOR-chained with a keystream derived from libembroidery's vipDecodingTable (vendored in reference/vendor/, generated with scripts/gen_vip_table.py).

decoded[i] = encoded[i] ^ TABLE[i] ^ encoded[i-1]   (encoded[-1] = 0)
encoded[i] = decoded[i] ^ TABLE[i] ^ encoded[i-1]

Known plaintext in all fixtures: 1a 15 eb 84#348D1A. The multicolor layout formulas (colorLength, attributeOffset as a function of n) are only verified for n = 1 and extrapolated — this is flagged in the writer.

Compressed streams

command_block = file[attributeOffset : xOffset]
x_block       = file[xOffset        : yOffset]
y_block       = file[yOffset        : EOF]

commands = EmbCompress.expand(command_block, numberOfStitches)  // 1 byte/stitch
xs       = EmbCompress.expand(x_block,       numberOfStitches)  // signed8 delta
ys       = EmbCompress.expand(y_block,       numberOfStitches)  // signed8 delta

Commands (same as HUS)

bytemeaning
0x80STITCH
0x81JUMP
0x84COLOR CHANGE
0x88TRIM
0x90END

For the project's 52 monogram letters, all commands are 0x80 terminated by a single 0x90; numberOfColors == 1 in every file.

Writing (TS core)

packages/core/src/writers/vip.ts and hus.ts share hus-vip-body.ts. The streams use a stored EmbCompress block (degenerate literal Huffman table, ≤ 65535 records — gated). Status: software-verified (round-trip + read-back via pyembroidery scripts/verify_hus_vip.py); acceptance on a real machine / Artist Toolkit still pending.

pyembroidery writes the EmbCompress block count little-endian while every decoder reads it MSB-first — it works by pure lucky size. Our writer writes it in decoder order, verified for every size.

Compression: EmbCompress

The classic "Greg's HUS compression". The proven Python source is pyembroidery/EmbCompress.py (MIT license). To read VIP/HUS/VP3 you only need expand(); to write you also need compress(). See the EmbCompress page.

← All formats ZHS →