11#!/usr/bin/env python3
22import argparse
3+ import sys
4+ import io
35from pathlib import Path
46from PIL import Image
57import cairosvg
68
7- # Take build directory as argument to save generated C files and PNG files.
89parser = argparse .ArgumentParser (description = "Convert image files to C structs." )
9- parser .add_argument ("dest" , help = "Destination build folder for PNG files." )
10+ parser .add_argument ("-o" , "--output" , type = Path , help = "Output file name for C structs." )
11+ parser .add_argument (
12+ "--decls" , type = Path , help = "Output file name for struct declarations."
13+ )
14+ parser .add_argument (
15+ "--decls-include" ,
16+ help = "Struct declaration include file name, generated from output file if given." ,
17+ )
18+ parser .add_argument ("--attrs" , type = Path , help = "Output file name for attribute list." )
19+ parser .add_argument ("images" , type = Path , nargs = "+" , help = "Input file name(s)." )
1020args = parser .parse_args ()
1121
12- build_dir = Path (args .dest )
13- build_dir .mkdir (parents = True , exist_ok = True )
14- media_dir = Path (__file__ ).parent / "media"
15-
16- # Convert all SVG files in media_dir to PNG and save in build_dir if not already present.
17- svg_files = media_dir .rglob ("*.svg" )
18- for svg in svg_files :
19- png = svg .with_suffix (".png" ).name
20- png_path = build_dir / png
21- if png_path .exists () and png_path .stat ().st_mtime >= svg .stat ().st_mtime :
22- continue
23- with open (svg , "rb" ) as svg_file :
24- png_bytes = cairosvg .svg2png (file_obj = svg_file )
25- with open (png_path , "wb" ) as out_png :
26- out_png .write (png_bytes )
27-
28- # Collect all image files in media_dir (png, bmp, jpg) and build_dir (png), including subfolders.
29- media_images = (
30- list (media_dir .rglob ("*.png" ))
31- + list (media_dir .rglob ("*.bmp" ))
32- + list (media_dir .rglob ("*.jpg" ))
33- + list (build_dir .rglob ("*.png" ))
34- )
22+ if args .attrs :
23+ if args .decls_include is not None :
24+ decls_include = args .decls_include
25+ elif args .decls is not None :
26+ decls_include = args .decls .name
27+ else :
28+ parser .error ("Need struct declaration include file name." )
29+
30+
31+ def load_image (path ):
32+ if path .suffix == ".svg" :
33+ with open (path , "rb" ) as f :
34+ png_bytes = cairosvg .svg2png (file_obj = f )
35+ return Image .open (io .BytesIO (png_bytes ))
36+ else :
37+ return Image .open (path )
3538
3639
3740# Convert rgba to monochrome, treating fully transparent pixels as white.
@@ -61,11 +64,14 @@ def image_to_8bit_map(img):
6164
6265# Process each image.
6366results = {}
64- for img_path in media_images :
65- with Image .open (img_path ) as img :
66- name = Path (img_path .name ).stem
67- width , height , bin_data = image_to_8bit_map (img )
68- results [name ] = (width , height , bin_data )
67+ for img_path in args .images :
68+ name = img_path .stem
69+ if args .output :
70+ with load_image (img_path ) as img :
71+ width , height , bin_data = image_to_8bit_map (img )
72+ results [name ] = (width , height , bin_data )
73+ else :
74+ results [name ] = (0 , 0 , None )
6975
7076
7177externs = ""
@@ -75,57 +81,60 @@ def image_to_8bit_map(img):
7581for name in sorted (results ):
7682 width , height , bin_data = results [name ]
7783
78- # Parse bytes for printing.
79- bytes_per_line = 12
80- lines = []
81- for i in range (0 , len (bin_data ), bytes_per_line ):
82- chunk = bin_data [i : i + bytes_per_line ]
83- line = " " + ", " .join (f"0x{ val :02x} " for val in chunk )
84- lines .append (line )
85- data_literal = ",\n " .join (lines ) + ","
86-
87- # Printed C structs.
88- structs += f"static const uint8_t { name } _data[] = {{\n { data_literal } \n }};\n \n "
89- structs += (
90- f"const pbio_image_monochrome_t pbio_image_media_{ name } = {{\n "
91- f" .width = { width } ,\n "
92- f" .height = { height } ,\n "
93- f" .data = { name } _data,\n "
94- f"}};\n "
95- )
84+ if args .output :
85+ # Parse bytes for printing.
86+ bytes_per_line = 12
87+ lines = []
88+ for i in range (0 , len (bin_data ), bytes_per_line ):
89+ chunk = bin_data [i : i + bytes_per_line ]
90+ line = " " + ", " .join (f"0x{ val :02x} " for val in chunk )
91+ lines .append (line )
92+ data_literal = ",\n " .join (lines ) + ","
93+
94+ # Printed C structs.
95+ structs += f"static const uint8_t { name } _data[] = {{\n { data_literal } \n }};\n \n "
96+ structs += (
97+ f"const pbio_image_monochrome_t pbio_image_media_{ name } = {{\n "
98+ f" .width = { width } ,\n "
99+ f" .height = { height } ,\n "
100+ f" .data = { name } _data,\n "
101+ f"}};\n "
102+ )
96103
97104 # Printed header and QSTR table entries.
98105 externs += f"extern const pbio_image_monochrome_t pbio_image_media_{ name } ;\n \n "
99106 qstrtab += f" {{ MP_ROM_QSTR(MP_QSTR_{ name .upper ()} ), MP_ROM_PTR(&pbio_image_media_{ name } ) }},\n "
100107
101108
102- HEADER = """// SPDX-License-Identifier: MIT
103- //Copyright (c) 2025 The Pybricks Authors
109+ HEADER = """// Generated file, edit with care.
104110
105111#include <pbio/image.h>
106112"""
107113
108- with open (build_dir / "pbio_image_media.c" , "w" ) as f :
109- f .write (HEADER )
110- f .write ('#include "pbio_image_media.h"\n \n ' )
111- f .write (structs )
112-
113- with open (build_dir / "pbio_image_media.h" , "w" ) as f :
114- f .write (HEADER )
115- f .write ("#ifndef _PBIO_IMAGE_MEDIA_H_\n " )
116- f .write ("#define _PBIO_IMAGE_MEDIA_H_\n \n " )
117- f .write (externs )
118- f .write ("#endif // _PBIO_IMAGE_MEDIA_H_\n " )
119-
120- with open (build_dir / "pb_type_image_attributes.c" , "w" ) as f :
121- f .write (HEADER )
122- f .write ('#include "pbio_image_media.h"\n \n ' )
123- f .write ("#include <py/obj.h>\n \n " )
124- f .write (
125- "static const mp_rom_map_elem_t pb_type_image_attributes_dict_table[] = {\n "
126- )
127- f .write (qstrtab )
128- f .write ("};\n " )
129- f .write (
130- "MP_DEFINE_CONST_DICT(pb_type_image_attributes_dict, pb_type_image_attributes_dict_table);"
131- )
114+ if args .output :
115+ with open (args .output , "w" ) as f :
116+ f .write (HEADER )
117+ f .write (structs )
118+
119+ if args .decls :
120+ guard = args .decls .name .upper ().replace ("." , "_" )
121+ with open (args .decls , "w" ) as f :
122+ f .write (HEADER )
123+ f .write (f"#ifndef _{ guard } _\n " )
124+ f .write (f"#define _{ guard } _\n \n " )
125+ f .write (externs )
126+ f .write (f"#endif // _{ guard } _\n " )
127+
128+ if args .attrs :
129+ with open (args .attrs , "w" ) as f :
130+ f .write (HEADER )
131+ f .write (f'#include "{ decls_include } "\n \n ' )
132+ f .write ("#include <py/obj.h>\n \n " )
133+ f .write (
134+ "static const mp_rom_map_elem_t pb_type_image_attributes_dict_table[] = {\n "
135+ )
136+ f .write (qstrtab )
137+ f .write ("};\n " )
138+ f .write (
139+ "MP_DEFINE_CONST_DICT(pb_type_image_attributes_dict, pb_type_image_attributes_dict_table);"
140+ )
0 commit comments