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
| Offset | Type | Meaning |
|---|---|---|
| 0x00 | uint32 | magic 0x0190FC5D |
| 0x04 | int32 | numberOfStitches (includes the final END record) |
| 0x08 | int32 | numberOfColors |
| 0x0C | int16 | posX (max X) |
| 0x0E | int16 | posY (max Y) |
| 0x10 | int16 | negX (min X) |
| 0x12 | int16 | negY (min Y) |
| 0x14 | int32 | attributeOffset — start of the command block (compressed) |
| 0x18 | int32 | xOffset — start of the delta-X block (compressed) |
| 0x1C | int32 | yOffset — start of the delta-Y block (compressed) |
| 0x20 | 8 byte | string / reserved (zero in the fixtures) |
| 0x2A | int32 | colorLength = 0x2E + 8·numberOfColors (formula in the fixtures) |
| 0x2E | 4·n byte | encrypted 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)
| byte | meaning |
|---|---|
0x80 | STITCH |
0x81 | JUMP |
0x84 | COLOR CHANGE |
0x88 | TRIM |
0x90 | END |
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.