Skip to content

Commit ba95f93

Browse files
authored
Merge pull request #76 from EmbroidePy/tatarize-hus
Adding Hus
2 parents 5cb7ffe + f5b5e5a commit ba95f93

File tree

14 files changed

+340
-23
lines changed

14 files changed

+340
-23
lines changed

pyembroidery/DstWriter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ def encode_record(x, y, flags):
8989
y += 1
9090
if y != 0:
9191
raise ValueError("The dy value given to the writer exceeds maximum allowed.")
92-
elif flags is COLOR_CHANGE:
92+
elif flags == COLOR_CHANGE:
9393
b2 = 0b11000011
94-
elif flags is STOP:
94+
elif flags == STOP:
9595
b2 = 0b11000011
96-
elif flags is END:
96+
elif flags == END:
9797
b2 = 0b11110011
98-
elif flags is SEQUIN_MODE:
98+
elif flags == SEQUIN_MODE:
9999
b2 = 0b01000011
100100
return bytes(bytearray([b0, b1, b2]))
101101

pyembroidery/EmbCompress.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
def expand(data, uncompressed_size=None):
2+
compress = EmbCompress()
3+
return compress.decompress(data, uncompressed_size)
4+
5+
6+
class Huffman:
7+
def __init__(self, lengths=None, value=0):
8+
self.default_value = value
9+
self.lengths = lengths
10+
self.table = None
11+
self.table_width = 0
12+
13+
def build_table(self):
14+
"""Build an index huffman table based on the lengths. lowest index value wins in a tie."""
15+
self.table_width = max(self.lengths)
16+
self.table = []
17+
size = (1 << self.table_width)
18+
for bit_length in range(1, self.table_width + 1):
19+
size /= 2
20+
for len_index in range(0, len(self.lengths)):
21+
length = self.lengths[len_index]
22+
if length == bit_length:
23+
self.table += [len_index] * size
24+
25+
def lookup(self, byte_lookup):
26+
"""lookup into the index, returns value and length
27+
must be requested with 2 bytes."""
28+
if self.table is None:
29+
return self.default_value, 0
30+
v = self.table[byte_lookup >> (16 - self.table_width)]
31+
return v, self.lengths[v]
32+
33+
34+
class EmbCompress:
35+
def __init__(self):
36+
self.bit_position = 0
37+
self.input_data = None
38+
self.block_elements = None
39+
self.character_huffman = None
40+
self.distance_huffman = None
41+
42+
def get_bits(self, start_pos_in_bits, length):
43+
end_pos_in_bits = start_pos_in_bits + length - 1
44+
start_pos_in_bytes = int(start_pos_in_bits / 8)
45+
end_pos_in_bytes = int(end_pos_in_bits / 8)
46+
value = 0
47+
for i in range(start_pos_in_bytes, end_pos_in_bytes + 1):
48+
value <<= 8
49+
try:
50+
value |= self.input_data[i] & 0xFF
51+
except IndexError:
52+
pass
53+
unused_bits_right_of_sample = (8 - (end_pos_in_bits + 1) % 8) % 8
54+
mask_sample_bits = (1 << length) - 1
55+
original = (value >> unused_bits_right_of_sample) & mask_sample_bits
56+
return original
57+
58+
def pop(self, bit_count):
59+
value = self.peek(bit_count)
60+
self.slide(bit_count)
61+
return value
62+
63+
def peek(self, bit_count):
64+
return self.get_bits(self.bit_position, bit_count)
65+
66+
def slide(self, bit_count):
67+
self.bit_position += bit_count
68+
69+
def read_variable_length(self):
70+
m = self.pop(3)
71+
if m != 7:
72+
return m
73+
for q in range(0, 13): # max read is 16 bit, 3 bits already used. It can't exceed 16-3
74+
s = self.pop(1)
75+
if s == 1:
76+
m += 1
77+
else:
78+
break
79+
return m
80+
81+
def load_character_length_huffman(self):
82+
count = self.pop(5)
83+
if count == 0:
84+
v = self.pop(5)
85+
huffman = Huffman(value=v)
86+
else:
87+
huffman_code_lengths = [0] * count
88+
index = 0
89+
while index < count:
90+
if index == 3: # Special index 3, skip up to 3 elements.
91+
index += self.pop(2)
92+
huffman_code_lengths[index] = self.read_variable_length()
93+
index += 1
94+
huffman = Huffman(huffman_code_lengths, 8)
95+
huffman.build_table()
96+
return huffman
97+
98+
def load_character_huffman(self, length_huffman):
99+
count = self.pop(9)
100+
if count == 0:
101+
v = self.pop(9)
102+
huffman = Huffman(value=v)
103+
else:
104+
huffman_code_lengths = [0] * count
105+
index = 0
106+
while index < count:
107+
h = length_huffman.lookup(self.peek(16))
108+
c = h[0]
109+
self.slide(h[1])
110+
if c == 0: # C == 0, skip 1.
111+
c = 1
112+
index += c
113+
elif c == 1: # C == 1, skip 3 + read(4)
114+
c = 3 + self.pop(4)
115+
index += c
116+
elif c == 2: # C == 2, skip 20 + read(9)
117+
c = 20 + self.pop(9)
118+
index += c
119+
else:
120+
c -= 2
121+
huffman_code_lengths[index] = c
122+
index += 1
123+
huffman = Huffman(huffman_code_lengths)
124+
huffman.build_table()
125+
return huffman
126+
127+
def load_distance_huffman(self):
128+
count = self.pop(5)
129+
if count == 0:
130+
v = self.pop(5)
131+
huffman = Huffman(value=v)
132+
else:
133+
index = 0
134+
lengths = [0] * count
135+
for i in range(0, count):
136+
lengths[index] = self.read_variable_length()
137+
index += 1
138+
huffman = Huffman(lengths)
139+
huffman.build_table()
140+
return huffman
141+
142+
def load_block(self):
143+
self.block_elements = self.pop(16)
144+
character_length_huffman = self.load_character_length_huffman()
145+
self.character_huffman = self.load_character_huffman(character_length_huffman)
146+
self.distance_huffman = self.load_distance_huffman()
147+
148+
def get_token(self):
149+
if self.block_elements <= 0:
150+
self.load_block()
151+
self.block_elements -= 1
152+
h = self.character_huffman.lookup(self.peek(16))
153+
self.slide(h[1])
154+
return h[0]
155+
156+
def get_position(self):
157+
h = self.distance_huffman.lookup(self.peek(16))
158+
self.slide(h[1])
159+
if h[0] == 0:
160+
return 0
161+
v = h[0] - 1
162+
v = (1 << v) + self.pop(v)
163+
return v
164+
165+
def decompress(self, input_data, uncompressed_size=None):
166+
self.input_data = input_data
167+
output_data = []
168+
self.block_elements = -1
169+
bits_total = (len(input_data) * 8)
170+
while bits_total > self.bit_position and (uncompressed_size is None or len(output_data) <= uncompressed_size):
171+
character = self.get_token()
172+
if character <= 255: # literal.
173+
output_data.append(character)
174+
elif character == 510:
175+
break # END
176+
else:
177+
length = character - 253 # Min length is 3. 256-253=3.
178+
back = self.get_position() + 1
179+
position = len(output_data) - back
180+
if back > length:
181+
# Entire lookback is already within output data.
182+
output_data += output_data[position:position + length]
183+
else:
184+
# Will read & write the same data at some point.
185+
for i in range(position, position + length):
186+
output_data.append(output_data[i])
187+
return output_data

pyembroidery/EmbConstant.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
NEEDLE_MASK = 0x00FF0000
77
ORDER_MASK = 0xFF000000
88

9+
FLAGS_MASK = 0x0000FF00
10+
911
NO_COMMAND = -1
1012
STITCH = 0
1113
JUMP = 1
@@ -65,3 +67,5 @@
6567
CONTINGENCY_SEQUIN_JUMP = 0xF6
6668
CONTINGENCY_SEQUIN_STITCH = 0xF7
6769
CONTINGENCY_SEQUIN_REMOVE = 0xF8
70+
71+
ALTERNATIVE = 0x100 # Generic flag for an alternative form.

pyembroidery/EmbEncoder.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(self, settings=None):
7575
self.state_jumping = False
7676
self.needle_x = 0
7777
self.needle_y = 0
78+
self.high_flags = 0
7879

7980
def transcode(self, source_pattern, destination_pattern):
8081
if source_pattern is destination_pattern:
@@ -198,6 +199,7 @@ def transcode_main(self):
198199
x = round(x)
199200
y = round(y)
200201
flags = self.stitch[2] & COMMAND_MASK
202+
self.high_flags = self.stitch[2] & FLAGS_MASK
201203

202204
if flags == NO_COMMAND:
203205
continue
@@ -351,13 +353,17 @@ def declare_not_trimmed(self):
351353
self.state_trimmed = False
352354

353355
def add_thread_change(self, command, thread=None, needle=None, order=None):
354-
self.add(encode_thread_change(command, thread, needle, order))
356+
x = self.needle_x
357+
y = self.needle_y
358+
cmd = encode_thread_change(command, thread, needle, order)
359+
self.destination_pattern.stitches.append([x, y, cmd])
355360

356361
def add(self, flags, x=None, y=None):
357362
if x is None:
358363
x = self.needle_x
359364
if y is None:
360365
y = self.needle_y
366+
flags |= self.high_flags
361367
self.destination_pattern.stitches.append([x, y, flags])
362368

363369
def lookahead_stitch(self):
@@ -615,7 +621,7 @@ def interpolate_gap_stitches(self, x0, y0, x1, y1, max_length, data):
615621
# we need the gap stitches only, not start or end stitch.
616622
qx += step_size_x
617623
qy += step_size_y
618-
stitch = [qx, qy, data]
624+
stitch = [qx, qy, data | self.high_flags]
619625
transcode.append(stitch)
620626
self.update_needle_position(stitch[0], stitch[1])
621627

pyembroidery/EmbThread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def __str__(self):
147147
if self.description is None:
148148
return "EmbThread %s" % self.hex_color()
149149
else:
150-
return "EmbThread %s %s" % self.description, self.hex_color()
150+
return "EmbThread %s %s" % (self.description, self.hex_color())
151151

152152
def set_color(self, r, g, b):
153153
self.color = color_rgb(r, g, b)

pyembroidery/EmbThreadHus.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from .EmbThread import EmbThread
2+
3+
4+
def get_thread_set():
5+
return [
6+
EmbThreadHus("#000000", "Black", "026"),
7+
EmbThreadHus("#0000e7", "Blue", "005"),
8+
EmbThreadHus("#00c600", "Green", "002"),
9+
EmbThreadHus("#ff0000", "Red", "014"),
10+
EmbThreadHus("#840084", "Purple", "008"),
11+
EmbThreadHus("#ffff00", "Yellow", "020"),
12+
EmbThreadHus("#848484", "Grey", "024"),
13+
EmbThreadHus("#8484e7", "Light Blue", "006"),
14+
EmbThreadHus("#00ff84", "Light Green", "003"),
15+
EmbThreadHus("#ff7b31", "Orange", "017"),
16+
EmbThreadHus("#ff8ca5", "Pink", "011"),
17+
EmbThreadHus("#845200", "Brown", "028"),
18+
EmbThreadHus("#ffffff", "White", "022"),
19+
EmbThreadHus("#000084", "Dark Blue", "004"),
20+
EmbThreadHus("#008400", "Dark Green", "001"),
21+
EmbThreadHus("#7b0000", "Dark Red", "013"),
22+
EmbThreadHus("#ff6384", "Light Red", "015"),
23+
EmbThreadHus("#522952", "Dark Purple", "007"),
24+
EmbThreadHus("#ff00ff", "Light Purple", "009"),
25+
EmbThreadHus("#ffde00", "Dark Yellow", "019"),
26+
EmbThreadHus("#ffff9c", "Light Yellow", "021"),
27+
EmbThreadHus("#525252", "Dark Grey", "025"),
28+
EmbThreadHus("#d6d6d6", "Light Grey", "023"),
29+
EmbThreadHus("#ff5208", "Dark Orange", "016"),
30+
EmbThreadHus("#ff9c5a", "Light Orange", "018"),
31+
EmbThreadHus("#ff52b5", "Dark Pink", "010"),
32+
EmbThreadHus("#ffc6de", "Light Pink", "012"),
33+
EmbThreadHus("#523100", "Dark Brown", "027"),
34+
EmbThreadHus("#b5a584", "Light Brown", "029")
35+
]
36+
37+
38+
class EmbThreadHus(EmbThread):
39+
def __init__(self, color, description, catalog_number=None):
40+
EmbThread.__init__(self)
41+
self.set(color)
42+
self.description = description
43+
self.catalog_number = catalog_number
44+
self.brand = "Hus"
45+
self.chart = "Hus"
46+

pyembroidery/ExpWriter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def write(pattern, f, settings=None):
1919
dy = int(round(y - yy))
2020
xx += dx
2121
yy += dy
22-
if data is STITCH:
22+
if data == STITCH:
2323
# consider bounds checking the delta_x, delta_y and raising ValueError if exceeds.
2424
delta_x = dx & 0xFF
2525
delta_y = -dy & 0xFF

pyembroidery/HusReader.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from .EmbCompress import expand
2+
from .EmbThreadHus import get_thread_set
3+
from .ReadHelper import signed8, signed16, read_int_32le, read_int_16le, read_string_8
4+
5+
6+
def read(f, out, settings=None):
7+
magic_code = read_int_32le(f)
8+
number_of_stitches = read_int_32le(f)
9+
number_of_colors = read_int_32le(f)
10+
11+
extend_pos_x = signed16(read_int_16le(f))
12+
extend_pos_y = signed16(read_int_16le(f))
13+
extend_neg_x = signed16(read_int_16le(f))
14+
extend_neg_y = signed16(read_int_16le(f))
15+
16+
command_offset = read_int_32le(f)
17+
x_offset = read_int_32le(f)
18+
y_offset = read_int_32le(f)
19+
20+
string_value = read_string_8(f, 8)
21+
22+
unknown_16_bit = read_int_16le(f)
23+
24+
hus_thread_set = get_thread_set()
25+
for i in range(0, number_of_colors):
26+
index = read_int_16le(f)
27+
out.add_thread(hus_thread_set[index])
28+
f.seek(command_offset, 0)
29+
command_compressed = bytearray(f.read(x_offset - command_offset))
30+
f.seek(x_offset, 0)
31+
x_compressed = bytearray(f.read(y_offset - x_offset))
32+
f.seek(y_offset, 0)
33+
y_compressed = bytearray(f.read())
34+
35+
command_decompressed = expand(command_compressed, number_of_stitches)
36+
x_decompressed = expand(x_compressed, number_of_stitches)
37+
y_decompressed = expand(y_compressed, number_of_stitches)
38+
39+
stitch_count = min(len(command_decompressed), len(x_decompressed), len(y_decompressed))
40+
41+
for i in range(0, stitch_count):
42+
cmd = command_decompressed[i]
43+
x = signed8(x_decompressed[i])
44+
y = -signed8(y_decompressed[i])
45+
if cmd == 0x80: # STITCH
46+
out.stitch(x, y)
47+
elif cmd == 0x81: # JUMP
48+
out.move(x, y)
49+
elif cmd == 0x84: # COLOR_CHANGE
50+
out.color_change(x, y)
51+
elif cmd == 0x88: # TRIM
52+
if x != 0 or y != 0:
53+
out.move(x, y)
54+
out.trim()
55+
elif cmd == 0x90: # END
56+
break
57+
else: # UNMAPPED COMMAND
58+
break
59+
out.end()

pyembroidery/PecWriter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def pec_encode(pattern, f):
132132
dy = int(round(y - yy))
133133
xx += dx
134134
yy += dy
135-
if data is STITCH:
135+
if data == STITCH:
136136
if jumping and dx != 0 and dy != 0:
137137
f.write(b'\x00\x00')
138138
jumping = False

0 commit comments

Comments
 (0)