Skip to content

Commit 2621d3b

Browse files
authored
kurt/generate-pngs
kurt/generate-pngs
2 parents 9f98636 + 82289c0 commit 2621d3b

37 files changed

+438
-247
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
!*.pattern
1010
!*.h
1111
!*.cpp
12+
!*.py
1213
!*.atsln
1314
!*.componentinfo.xml
1415
!*.cppproj
@@ -29,3 +30,6 @@
2930
# Ignore bitmap files
3031
*.bmp
3132

33+
# Ignore png
34+
*.png
35+

Helios/Patterns.cpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void Patterns::make_pattern(PatternID id, Pattern &pat)
8282
default:
8383

8484
case PATTERN_RIBBON:
85-
args.on_dur = 9;
85+
args.on_dur = 9; // 10 for flashing pattern circles
8686
break;
8787

8888
case PATTERN_ULTRA_DOPS:
@@ -97,7 +97,7 @@ void Patterns::make_pattern(PatternID id, Pattern &pat)
9797

9898
case PATTERN_STROBE:
9999
args.on_dur = 5;
100-
args.off_dur = 8;
100+
args.off_dur = 8; // 10 for flashing pattern circles
101101
break;
102102

103103
case PATTERN_HYPNOSTROBE:
@@ -107,87 +107,87 @@ void Patterns::make_pattern(PatternID id, Pattern &pat)
107107

108108
case PATTERN_STROBIE:
109109
args.on_dur = 3;
110-
args.off_dur = 23;
110+
args.off_dur = 23; // 21 for flashing pattern circles
111111
break;
112112

113113
case PATTERN_RAZOR:
114114
args.on_dur = 3;
115115
args.off_dur = 1;
116-
args.gap_dur = 30;
116+
args.gap_dur = 30; // 29 for flashing pattern circles
117117
break;
118118

119119
case PATTERN_FLARE:
120120
args.on_dur = 2;
121-
args.off_dur = 30;
121+
args.off_dur = 30; // 28 for flashing pattern circles
122122
break;
123123

124124
case PATTERN_BURST:
125125
args.on_dur = 3;
126-
args.off_dur = 40;
126+
args.off_dur = 40; // 37 for flashing pattern circles
127127
break;
128128

129129
case PATTERN_GLOW:
130130
args.on_dur = 2;
131-
args.gap_dur = 40;
131+
args.gap_dur = 40; // 39 for flashing pattern circles
132132
break;
133133

134134
case PATTERN_FLICKER:
135135
args.on_dur = 1;
136-
args.off_dur = 50;
136+
args.off_dur = 50; // 44 for flashing pattern circles
137137
break;
138138

139139
case PATTERN_FLASH:
140140
args.on_dur = 10;
141-
args.off_dur = 250;
141+
args.off_dur = 250; // 120 for flashing pattern circles
142142
break;
143143

144144
case PATTERN_MORPH:
145145
args.on_dur = 9;
146-
args.blend_speed = 5;
146+
args.blend_speed = 5; // 14 for flashing pattern circles
147147
break;
148148

149149
case PATTERN_MORPH_STROBE:
150150
args.on_dur = 5;
151151
args.off_dur = 8;
152-
args.blend_speed = 10;
152+
args.blend_speed = 10; // 19 for flashing pattern circles
153153
break;
154154

155155
case PATTERN_MORPH_STROBIE:
156156
args.on_dur = 3;
157157
args.off_dur = 23;
158-
args.blend_speed = 10;
158+
args.blend_speed = 10; // 35 for flashing pattern circles
159159
break;
160160

161161
case PATTERN_MORPH_GLOW:
162162
args.on_dur = 1;
163163
args.off_dur = 3;
164-
args.gap_dur = 40;
164+
args.gap_dur = 40; // 36 for flashing pattern circles
165165
args.blend_speed = 30;
166166
break;
167167

168168
case PATTERN_DASH_DOPS:
169169
args.on_dur = 1;
170170
args.off_dur = 9;
171171
args.gap_dur = 6;
172-
args.dash_dur = 15;
172+
args.dash_dur = 15; // 17 for flashing pattern circles
173173
break;
174174

175175
case PATTERN_DASH_DOT:
176176
args.on_dur = 2;
177177
args.off_dur = 3;
178-
args.dash_dur = 24;
178+
args.dash_dur = 24; // 22 for flashing pattern circles
179179
break;
180180

181181
case PATTERN_WAVE_PARTICLE:
182182
args.on_dur = 1;
183183
args.off_dur = 9;
184-
args.dash_dur = 5;
184+
args.dash_dur = 5; // 10 for flashing pattern circles
185185
break;
186186

187187
case PATTERN_LIGHTSPEED:
188188
args.on_dur = 2;
189189
args.off_dur = 3;
190-
args.dash_dur = 24;
190+
args.dash_dur = 24; // 23 for flashing pattern circles
191191
args.blend_speed = 10;
192192
break;
193193
}

HeliosCLI/Makefile

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
.SUFFIXES:
33

44
# List all make targets which are not filenames
5-
.PHONY: all tests clean clean_storage
5+
.PHONY: all tests clean pngs bmps clean_storage
66

77
# compiler tool definitions
88
CC=g++
@@ -105,6 +105,18 @@ FORCE:
105105
clean:
106106
@$(RM) $(DFILES) $(OBJS) $(TARGETS) $(TESTS)
107107

108+
# generate svg
109+
svgs: bmps
110+
./generate_svgs.sh
111+
112+
# generate pngs
113+
pngs: bmps
114+
./generate_pngs.sh
115+
116+
# generate pngs
117+
bmps:
118+
./generate_bmps.sh -c
119+
108120
clean_storage:
109121
@echo "Cleaning Helios storage file..."
110122
@$(RM) Helios.storage

HeliosCLI/cli_main.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ float brightness_scale = 1.0f;
5959
uint8_t minumum_brightness = 75;
6060
std::string initial_colorset_str = "";
6161
std::string initial_pattern_str = "";
62+
std::string initial_pattern_args_str = "";
6263
uint32_t initial_mode_index = 0;
6364

6465
// used to switch terminal to non-blocking and back
@@ -106,6 +107,27 @@ int main(int argc, char *argv[])
106107
// re-initialize the current pattern
107108
Helios::cur_pattern().init();
108109
}
110+
// set initial pattern args based on user arguments
111+
if (initial_pattern_args_str.length() > 0) {
112+
// parse the list of args into an array of ints
113+
std::vector<uint32_t> vals;
114+
std::istringstream ss(initial_pattern_args_str);
115+
// push 6 args into the array
116+
while (vals.size() < 6) {
117+
std::string arg;
118+
uint32_t val = 0;
119+
// try to parse out a number
120+
if (std::getline(ss, arg, ',')) {
121+
val = strtoul(arg.c_str(), NULL, 10);
122+
}
123+
// push the val either 0 or parsed number
124+
vals.push_back(val);
125+
}
126+
// construct pattern args from the array of values
127+
PatternArgs args(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
128+
// set the args of the current pattern
129+
Helios::cur_pattern().setArgs(args);
130+
}
109131
// Set the initial colorset based on user arguments
110132
if (initial_colorset_str.length() > 0) {
111133
std::stringstream ss(initial_colorset_str);
@@ -206,14 +228,15 @@ static void parse_options(int argc, char *argv[])
206228
{"min-brightness", required_argument, nullptr, 'm'},
207229
{"colorset", required_argument, nullptr, 'C'},
208230
{"pattern", required_argument, nullptr, 'P'},
231+
{"pattern-args", required_argument, nullptr, 'A'},
209232
{"mode-index", required_argument, nullptr, 'I'},
210233
{"bmp", optional_argument, nullptr, 'b'},
211234
{"eeprom", no_argument, nullptr, 'E'},
212235
{"parse-save", required_argument, nullptr, 'S'},
213236
{"help", no_argument, nullptr, 'h'},
214237
{nullptr, 0, nullptr, 0}
215238
};
216-
while ((opt = getopt_long(argc, argv, "xcqltisyamC:P:I:b::ES:h", long_options, &option_index)) != -1) {
239+
while ((opt = getopt_long(argc, argv, "xcqltisyamC:P:A:I:b::ES:h", long_options, &option_index)) != -1) {
217240
switch (opt) {
218241
case 'x':
219242
// if the user wants pretty colors or hex codes
@@ -292,6 +315,10 @@ static void parse_options(int argc, char *argv[])
292315
// set the initial pattern from the string
293316
initial_pattern_str = optarg;
294317
break;
318+
case 'A':
319+
// set the initial pattern args from the string
320+
initial_pattern_args_str = optarg;
321+
break;
295322
case 'I':
296323
// set the initial mode index
297324
initial_mode_index = strtoul(optarg, NULL, 10);
@@ -534,6 +561,7 @@ static void print_usage(const char* program_name)
534561
fprintf(stderr, "Initial Pattern and Colorset (optional):\n");
535562
fprintf(stderr, " -C, --colorset Set the colorset of the first mode, ex: red,green,0x0000ff\n");
536563
fprintf(stderr, " -P, --pattern Set the pattern of the first mode, ex: 1 or blend\n");
564+
fprintf(stderr, " -A, --pattern-args Set the pattern args of the first mode, ex: 1,2,3 or 1,2,3,4,5\n");
537565
fprintf(stderr, " -I, --mode-index Set the initial mode index, ex 4\n");
538566
fprintf(stderr, "\n");
539567
fprintf(stderr, "Other Options:\n");

HeliosCLI/convert_bmp_to_png.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from PIL import Image, ImageDraw, ImageFilter
2+
import numpy as np
3+
import argparse
4+
import os
5+
6+
def map_pattern_to_circle(draw, pattern, center, outer_radius, thickness):
7+
num_rings = 3 # Number of rings
8+
gap_between_rings = thickness * 0.5 # Reduced gap between rings
9+
10+
segment_width = 1 # Width of each color segment in degrees
11+
pattern_width = pattern.width
12+
13+
for ring in range(num_rings):
14+
inner_radius = outer_radius - thickness - (ring * (thickness + gap_between_rings))
15+
outer_r = inner_radius + thickness
16+
17+
for angle in range(0, 360, segment_width):
18+
start_angle = angle - 90 # Adjust start angle (PIL uses 3 o'clock as 0 degrees)
19+
end_angle = start_angle + segment_width
20+
21+
pattern_index = (angle // segment_width) % pattern_width
22+
color = pattern.getpixel((pattern_index, 0))
23+
24+
if len(color) == 3: # RGB
25+
if color == (0, 0, 0): # Black color
26+
continue # Skip this segment (make it transparent)
27+
fill_color = color + (255,) # Add full opacity
28+
elif len(color) == 4: # RGBA
29+
if color[:3] == (0, 0, 0): # Black color
30+
continue # Skip this segment (make it transparent)
31+
fill_color = color
32+
33+
# Draw a filled arc
34+
draw.arc([center[0] - outer_r, center[1] - outer_r,
35+
center[0] + outer_r, center[1] + outer_r],
36+
start=start_angle, end=end_angle, fill=fill_color, width=thickness)
37+
38+
def create_circular_pattern(path_to_bmp):
39+
print(f"-- Creating circular pattern for {path_to_bmp}")
40+
pattern_strip = Image.open(path_to_bmp).convert('RGBA') # Convert to RGBA
41+
42+
canvas_size = 1000 # Fixed canvas size for all patterns
43+
canvas = Image.new('RGBA', (canvas_size, canvas_size), (0, 0, 0, 0)) # Transparent background
44+
draw = ImageDraw.Draw(canvas)
45+
46+
center = (canvas_size // 2, canvas_size // 2)
47+
outer_radius = int(canvas_size * 0.45) # Adjusted for better fit
48+
thickness = 50 # Adjusted thickness for balance
49+
50+
print(f"-- Canvas prepared with size {canvas_size}x{canvas_size}, center at {center}, outer radius {outer_radius}, and thickness {thickness}")
51+
map_pattern_to_circle(draw, pattern_strip, center, outer_radius, thickness)
52+
53+
print("-- Pattern mapped successfully")
54+
55+
# Apply smoothing effect
56+
smoothed_canvas = canvas.filter(ImageFilter.GaussianBlur(radius=2))
57+
print("-- Smoothing effect applied")
58+
59+
return smoothed_canvas
60+
61+
def main():
62+
parser = argparse.ArgumentParser(description="Generate a circular pattern from a BMP file.")
63+
parser.add_argument("bmp_file", type=str, help="Path to the BMP file")
64+
parser.add_argument("output_file", type=str, help="Path to the output PNG file")
65+
args = parser.parse_args()
66+
67+
circular_pattern = create_circular_pattern(args.bmp_file)
68+
circular_pattern.save(args.output_file)
69+
print(f"** Saved smoothed circular pattern to {args.output_file}")
70+
71+
if __name__ == "__main__":
72+
main()

HeliosCLI/convert_bmp_to_svg.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import argparse
2+
import os
3+
from PIL import Image
4+
import svgwrite
5+
import math
6+
7+
def create_circular_pattern_svg(path_to_bmp, output_file):
8+
print(f"-- Creating circular pattern for {path_to_bmp}")
9+
pattern_strip = Image.open(path_to_bmp).convert('RGBA')
10+
11+
canvas_size = 1000
12+
center = canvas_size // 2
13+
outer_radius = int(canvas_size * 0.45)
14+
thickness = 50
15+
num_rings = 3
16+
gap_between_rings = thickness * 0.5
17+
18+
dwg = svgwrite.Drawing(output_file, size=(canvas_size, canvas_size))
19+
20+
for ring in range(num_rings):
21+
inner_radius = outer_radius - thickness - (ring * (thickness + gap_between_rings))
22+
outer_r = inner_radius + thickness
23+
24+
current_color = None
25+
start_angle = 0
26+
27+
for angle in range(361): # Go to 361 to close the loop
28+
pattern_index = angle % pattern_strip.width
29+
color = pattern_strip.getpixel((pattern_index, 0))
30+
31+
if len(color) == 3: # RGB
32+
fill_color = f'rgb{color}'
33+
opacity = 1
34+
elif len(color) == 4: # RGBA
35+
fill_color = f'rgb{color[:3]}'
36+
opacity = color[3] / 255.0
37+
38+
if color[:3] == (0, 0, 0): # Black color
39+
fill_color = None
40+
opacity = 0
41+
42+
if fill_color != current_color or angle == 360:
43+
if current_color is not None:
44+
end_angle = angle
45+
path = create_arc_path(center, inner_radius, outer_r, start_angle, end_angle)
46+
dwg.add(dwg.path(d=path, fill=current_color, fill_opacity=current_opacity))
47+
48+
current_color = fill_color
49+
current_opacity = opacity
50+
start_angle = angle
51+
52+
print(f"-- Pattern mapped successfully")
53+
dwg.save()
54+
print(f"** Saved circular pattern to {output_file}")
55+
56+
def create_arc_path(center, inner_radius, outer_radius, start_angle, end_angle):
57+
start_angle_rad = math.radians(start_angle - 90)
58+
end_angle_rad = math.radians(end_angle - 90)
59+
60+
x1 = center + inner_radius * math.cos(start_angle_rad)
61+
y1 = center + inner_radius * math.sin(start_angle_rad)
62+
x2 = center + outer_radius * math.cos(start_angle_rad)
63+
y2 = center + outer_radius * math.sin(start_angle_rad)
64+
x3 = center + outer_radius * math.cos(end_angle_rad)
65+
y3 = center + outer_radius * math.sin(end_angle_rad)
66+
x4 = center + inner_radius * math.cos(end_angle_rad)
67+
y4 = center + inner_radius * math.sin(end_angle_rad)
68+
69+
large_arc_flag = 1 if end_angle - start_angle > 180 else 0
70+
71+
path = f'M {x1} {y1} '
72+
path += f'L {x2} {y2} '
73+
path += f'A {outer_radius} {outer_radius} 0 {large_arc_flag} 1 {x3} {y3} '
74+
path += f'L {x4} {y4} '
75+
path += f'A {inner_radius} {inner_radius} 0 {large_arc_flag} 0 {x1} {y1}'
76+
77+
return path
78+
79+
def main():
80+
parser = argparse.ArgumentParser(description="Generate a circular pattern from a BMP file as SVG.")
81+
parser.add_argument("bmp_file", type=str, help="Path to the BMP file")
82+
parser.add_argument("output_file", type=str, help="Path to the output SVG file")
83+
args = parser.parse_args()
84+
85+
create_circular_pattern_svg(args.bmp_file, args.output_file)
86+
87+
if __name__ == "__main__":
88+
main()

0 commit comments

Comments
 (0)