@thlg057/mo5-rag-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/index.js +592 -0
- package/package.json +32 -0
- package/scripts/fd2sd.py +59 -0
- package/scripts/makefd.py +407 -0
- package/scripts/png2mo5.py +478 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Convertisseur PNG vers sprite Thomson MO5
|
|
4
|
+
|
|
5
|
+
Convertit une image PNG en tableaux C pour le Thomson MO5
|
|
6
|
+
Format: 1 octet = 8 pixels de 1 bit (forme/fond)
|
|
7
|
+
Génère 2 tableaux: FORME (bitmap) et COULEUR (attributs par groupe de 8 pixels)
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python png_to_mo5_v2.py image.png [--name SPRITE_NAME] [--bg-color 0-15] [--transparent]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
try:
|
|
18
|
+
from PIL import Image
|
|
19
|
+
except ImportError:
|
|
20
|
+
print("Erreur: PIL (Pillow) n'est pas installé.")
|
|
21
|
+
print("Installez-le avec: pip install Pillow")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
# Palette MO5 complète (16 couleurs, RGB approximatif)
|
|
25
|
+
MO5_PALETTE = {
|
|
26
|
+
0: {'R': 0, 'G': 0, 'B': 0, 'Name': 'C_BLACK'},
|
|
27
|
+
1: {'R': 255, 'G': 0, 'B': 0, 'Name': 'C_RED'},
|
|
28
|
+
2: {'R': 0, 'G': 255, 'B': 0, 'Name': 'C_GREEN'},
|
|
29
|
+
3: {'R': 255, 'G': 255, 'B': 0, 'Name': 'C_YELLOW'},
|
|
30
|
+
4: {'R': 0, 'G': 0, 'B': 255, 'Name': 'C_BLUE'},
|
|
31
|
+
5: {'R': 255, 'G': 0, 'B': 255, 'Name': 'C_MAGENTA'},
|
|
32
|
+
6: {'R': 0, 'G': 255, 'B': 255, 'Name': 'C_CYAN'},
|
|
33
|
+
7: {'R': 255, 'G': 255, 'B': 255, 'Name': 'C_WHITE'},
|
|
34
|
+
8: {'R': 128, 'G': 128, 'B': 128, 'Name': 'C_GRAY'},
|
|
35
|
+
9: {'R': 255, 'G': 128, 'B': 128, 'Name': 'C_LIGHT_RED'},
|
|
36
|
+
10: {'R': 128, 'G': 255, 'B': 128, 'Name': 'C_LIGHT_GREEN'},
|
|
37
|
+
11: {'R': 255, 'G': 255, 'B': 128, 'Name': 'C_LIGHT_YELLOW'},
|
|
38
|
+
12: {'R': 128, 'G': 128, 'B': 255, 'Name': 'C_LIGHT_BLUE'},
|
|
39
|
+
13: {'R': 255, 'G': 128, 'B': 255, 'Name': 'C_PURPLE'},
|
|
40
|
+
14: {'R': 128, 'G': 255, 'B': 255, 'Name': 'C_LIGHT_CYAN'},
|
|
41
|
+
15: {'R': 255, 'G': 128, 'B': 0, 'Name': 'C_ORANGE'}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_color_distance(r1, g1, b1, r2, g2, b2):
|
|
46
|
+
"""Calcule la distance euclidienne entre deux couleurs RGB"""
|
|
47
|
+
return (r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_closest_mo5_color(r, g, b, a):
|
|
51
|
+
"""Trouve la couleur MO5 la plus proche d'une couleur RGBA donnée"""
|
|
52
|
+
# Si transparent, retourner None
|
|
53
|
+
if a < 128:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
min_distance = float('inf')
|
|
57
|
+
closest_color = 0
|
|
58
|
+
|
|
59
|
+
for color_idx in range(16):
|
|
60
|
+
palette = MO5_PALETTE[color_idx]
|
|
61
|
+
distance = get_color_distance(r, g, b, palette['R'], palette['G'], palette['B'])
|
|
62
|
+
|
|
63
|
+
if distance < min_distance:
|
|
64
|
+
min_distance = distance
|
|
65
|
+
closest_color = color_idx
|
|
66
|
+
|
|
67
|
+
return closest_color
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_dominant_colors(pixels, default_bg):
|
|
71
|
+
"""Détermine les 2 couleurs dominantes d'un groupe de 8 pixels"""
|
|
72
|
+
# Compter les occurrences de chaque couleur
|
|
73
|
+
color_count = {}
|
|
74
|
+
|
|
75
|
+
for pixel in pixels:
|
|
76
|
+
r, g, b, a = pixel
|
|
77
|
+
if a < 128:
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
color = get_closest_mo5_color(r, g, b, a)
|
|
81
|
+
if color is not None:
|
|
82
|
+
color_count[color] = color_count.get(color, 0) + 1
|
|
83
|
+
|
|
84
|
+
# Si pas de pixels visibles, retourner couleur par défaut
|
|
85
|
+
if not color_count:
|
|
86
|
+
return {
|
|
87
|
+
'Background': default_bg,
|
|
88
|
+
'Foreground': default_bg,
|
|
89
|
+
'IsSingleColor': True
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Si une seule couleur
|
|
93
|
+
if len(color_count) == 1:
|
|
94
|
+
color = list(color_count.keys())[0]
|
|
95
|
+
return {
|
|
96
|
+
'Background': default_bg,
|
|
97
|
+
'Foreground': color,
|
|
98
|
+
'IsSingleColor': True
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Le fond est toujours default_bg — chercher la couleur fg la plus fréquente
|
|
102
|
+
# parmi les couleurs non-fond
|
|
103
|
+
non_bg = {c: n for c, n in color_count.items() if c != default_bg}
|
|
104
|
+
|
|
105
|
+
if not non_bg:
|
|
106
|
+
# Toutes les couleurs du bloc sont le fond
|
|
107
|
+
return {
|
|
108
|
+
'Background': default_bg,
|
|
109
|
+
'Foreground': default_bg,
|
|
110
|
+
'IsSingleColor': True
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fg = max(non_bg, key=lambda c: non_bg[c])
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
'Background': default_bg,
|
|
117
|
+
'Foreground': fg,
|
|
118
|
+
'IsSingleColor': False
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def get_dominant_colors_transparent(pixels):
|
|
122
|
+
"""Version spécifique : force le fond à 0 pour mo5_sprite_bg"""
|
|
123
|
+
color_count = {}
|
|
124
|
+
for pixel in pixels:
|
|
125
|
+
r, g, b, a = pixel
|
|
126
|
+
if a < 128: continue
|
|
127
|
+
color = get_closest_mo5_color(r, g, b, a)
|
|
128
|
+
if color is not None:
|
|
129
|
+
color_count[color] = color_count.get(color, 0) + 1
|
|
130
|
+
if not color_count:
|
|
131
|
+
return {'Background': 0, 'Foreground': 0, 'IsSingleColor': True}
|
|
132
|
+
sorted_colors = sorted(color_count.items(), key=lambda x: x[1], reverse=True)
|
|
133
|
+
fg = sorted_colors[0][0]
|
|
134
|
+
return {'Background': 0, 'Foreground': fg, 'IsSingleColor': True}
|
|
135
|
+
|
|
136
|
+
def convert_png_to_mo5_sprite(image_path, sprite_name=None, default_bg=0, quiet=False, transparent=False):
|
|
137
|
+
"""Convertit une image PNG en sprite MO5"""
|
|
138
|
+
|
|
139
|
+
if not os.path.exists(image_path):
|
|
140
|
+
print(f"[ERREUR] Le fichier '{image_path}' n'existe pas.")
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
if not quiet:
|
|
144
|
+
print(f"[INFO] Chargement de l'image: {image_path}")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
img = Image.open(image_path)
|
|
148
|
+
# Convertir en RGBA si nécessaire
|
|
149
|
+
if img.mode != 'RGBA':
|
|
150
|
+
img = img.convert('RGBA')
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f"[ERREUR] Erreur lors du chargement: {e}")
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
width, height = img.size
|
|
156
|
+
if not quiet:
|
|
157
|
+
print(f" Dimensions: {width}x{height} pixels")
|
|
158
|
+
|
|
159
|
+
# Vérifier que la largeur est multiple de 8
|
|
160
|
+
original_width = width
|
|
161
|
+
if width % 8 != 0:
|
|
162
|
+
if not quiet:
|
|
163
|
+
print(f"[ATTENTION] Largeur ({width}) non multiple de 8.")
|
|
164
|
+
width = (width // 8) * 8
|
|
165
|
+
if not quiet:
|
|
166
|
+
print(f" Ajustée à {width} pixels")
|
|
167
|
+
|
|
168
|
+
bytes_per_line = width // 8
|
|
169
|
+
|
|
170
|
+
# Gérer le nom du sprite et le chemin de sortie
|
|
171
|
+
output_path = None
|
|
172
|
+
if sprite_name:
|
|
173
|
+
# Si un nom est fourni, il peut contenir un chemin
|
|
174
|
+
name_path = Path(sprite_name)
|
|
175
|
+
output_path = name_path # Conserver le chemin complet pour la sortie
|
|
176
|
+
sprite_name = name_path.stem # Extraire juste le nom pour les variables C
|
|
177
|
+
else:
|
|
178
|
+
sprite_name = Path(image_path).stem
|
|
179
|
+
|
|
180
|
+
# Remplacer les caractères non alphanumériques par des underscores (pour les noms de variables C)
|
|
181
|
+
sprite_name_clean = ''.join(c if c.isalnum() or c == '_' else '_' for c in sprite_name)
|
|
182
|
+
|
|
183
|
+
if not quiet:
|
|
184
|
+
print("[INFO] Analyse de l'image...")
|
|
185
|
+
|
|
186
|
+
# Tableaux pour stocker les données
|
|
187
|
+
form_data = []
|
|
188
|
+
color_data = []
|
|
189
|
+
color_stats = {}
|
|
190
|
+
total_blocks = 0
|
|
191
|
+
multi_color_blocks = 0
|
|
192
|
+
|
|
193
|
+
# Charger tous les pixels
|
|
194
|
+
pixels = img.load()
|
|
195
|
+
|
|
196
|
+
# Traiter chaque ligne
|
|
197
|
+
for y in range(height):
|
|
198
|
+
line_form_bytes = []
|
|
199
|
+
line_color_bytes = []
|
|
200
|
+
visual = ""
|
|
201
|
+
|
|
202
|
+
# Traiter les pixels 8 par 8
|
|
203
|
+
for x in range(0, width, 8):
|
|
204
|
+
# Récupérer les 8 pixels
|
|
205
|
+
pixel_group = []
|
|
206
|
+
for i in range(8):
|
|
207
|
+
if x + i < width:
|
|
208
|
+
pixel_group.append(pixels[x + i, y])
|
|
209
|
+
else:
|
|
210
|
+
pixel_group.append((0, 0, 0, 0))
|
|
211
|
+
|
|
212
|
+
# Déterminer les 2 couleurs dominantes
|
|
213
|
+
if transparent:
|
|
214
|
+
colors = get_dominant_colors_transparent(pixel_group)
|
|
215
|
+
else:
|
|
216
|
+
colors = get_dominant_colors(pixel_group, default_bg)
|
|
217
|
+
|
|
218
|
+
bg = colors['Background']
|
|
219
|
+
fg = colors['Foreground']
|
|
220
|
+
|
|
221
|
+
total_blocks += 1
|
|
222
|
+
if not colors['IsSingleColor']:
|
|
223
|
+
multi_color_blocks += 1
|
|
224
|
+
|
|
225
|
+
# Statistiques
|
|
226
|
+
color_key = f"{bg}-{fg}"
|
|
227
|
+
color_stats[color_key] = color_stats.get(color_key, 0) + 1
|
|
228
|
+
|
|
229
|
+
# Créer l'octet de COULEUR (FFFFBBBB: Forme en haut, Fond en bas)
|
|
230
|
+
color_byte = (bg & 0x0F) | ((fg & 0x0F) << 4)
|
|
231
|
+
line_color_bytes.append(f"0x{color_byte:02X}")
|
|
232
|
+
|
|
233
|
+
# Créer l'octet de FORME (bitmap: 1=forme, 0=fond)
|
|
234
|
+
form_byte = 0
|
|
235
|
+
for i in range(8):
|
|
236
|
+
if x + i < width:
|
|
237
|
+
pixel = pixel_group[i]
|
|
238
|
+
r, g, b, a = pixel
|
|
239
|
+
|
|
240
|
+
if a < 128:
|
|
241
|
+
# Transparent = fond (bit 0)
|
|
242
|
+
pixel_bit = 0
|
|
243
|
+
visual += "-"
|
|
244
|
+
else:
|
|
245
|
+
# Déterminer si c'est la couleur de forme ou de fond
|
|
246
|
+
pixel_color = get_closest_mo5_color(r, g, b, a)
|
|
247
|
+
|
|
248
|
+
if pixel_color is not None:
|
|
249
|
+
if fg == bg:
|
|
250
|
+
# Bloc monochrome fond : tous les pixels = fond
|
|
251
|
+
pixel_bit = 0
|
|
252
|
+
visual += "-"
|
|
253
|
+
else:
|
|
254
|
+
# Si la couleur est plus proche de fg que de bg
|
|
255
|
+
dist_fg = get_color_distance(r, g, b,
|
|
256
|
+
MO5_PALETTE[fg]['R'],
|
|
257
|
+
MO5_PALETTE[fg]['G'],
|
|
258
|
+
MO5_PALETTE[fg]['B'])
|
|
259
|
+
dist_bg = get_color_distance(r, g, b,
|
|
260
|
+
MO5_PALETTE[bg]['R'],
|
|
261
|
+
MO5_PALETTE[bg]['G'],
|
|
262
|
+
MO5_PALETTE[bg]['B'])
|
|
263
|
+
|
|
264
|
+
if dist_fg < dist_bg:
|
|
265
|
+
pixel_bit = 1 # Forme
|
|
266
|
+
visual += "█"
|
|
267
|
+
else:
|
|
268
|
+
pixel_bit = 0 # Fond
|
|
269
|
+
visual += "-"
|
|
270
|
+
else:
|
|
271
|
+
pixel_bit = 0
|
|
272
|
+
visual += "-"
|
|
273
|
+
|
|
274
|
+
# Positionner le bit (MSB = pixel de gauche)
|
|
275
|
+
shift = 7 - i
|
|
276
|
+
form_byte |= (pixel_bit << shift)
|
|
277
|
+
else:
|
|
278
|
+
visual += "-"
|
|
279
|
+
|
|
280
|
+
line_form_bytes.append(f"0x{form_byte:02X}")
|
|
281
|
+
|
|
282
|
+
# Ajouter les lignes
|
|
283
|
+
form_line = " " + ", ".join(line_form_bytes)
|
|
284
|
+
color_line = " " + ", ".join(line_color_bytes)
|
|
285
|
+
|
|
286
|
+
if y < height - 1:
|
|
287
|
+
form_line += ","
|
|
288
|
+
color_line += ","
|
|
289
|
+
|
|
290
|
+
form_line += f" // {y} {visual}"
|
|
291
|
+
color_line += f" // {y}"
|
|
292
|
+
|
|
293
|
+
form_data.append(form_line)
|
|
294
|
+
color_data.append(color_line)
|
|
295
|
+
|
|
296
|
+
# Construire le code C avec include guards
|
|
297
|
+
guard_name = f"SPRITE_{sprite_name_clean.upper()}_H"
|
|
298
|
+
|
|
299
|
+
output = []
|
|
300
|
+
output.append(f"#ifndef {guard_name}")
|
|
301
|
+
output.append(f"#define {guard_name}")
|
|
302
|
+
output.append("")
|
|
303
|
+
output.append("// =============================================")
|
|
304
|
+
output.append(f"// Sprite: {sprite_name_clean}")
|
|
305
|
+
output.append(f"// Source: {os.path.basename(image_path)}")
|
|
306
|
+
output.append(f"// Taille: {width}x{height} pixels ({bytes_per_line} octets x {height} lignes)")
|
|
307
|
+
output.append("// Format: 1 octet = 8 pixels (1 bit/pixel)")
|
|
308
|
+
output.append("// Contrainte: 2 couleurs par groupe de 8 pixels")
|
|
309
|
+
output.append("// =============================================")
|
|
310
|
+
output.append("")
|
|
311
|
+
|
|
312
|
+
# Ajouter les defines pour les dimensions
|
|
313
|
+
output.append(f"#define SPRITE_{sprite_name_clean.upper()}_WIDTH_BYTES {bytes_per_line}")
|
|
314
|
+
output.append(f"#define SPRITE_{sprite_name_clean.upper()}_HEIGHT {height}")
|
|
315
|
+
output.append("")
|
|
316
|
+
|
|
317
|
+
output.append("// Données de FORME (bitmap: 1=forme, 0=fond)")
|
|
318
|
+
output.append(f"unsigned char sprite_{sprite_name_clean}_form[{bytes_per_line * height}] = {{")
|
|
319
|
+
output.extend(form_data)
|
|
320
|
+
output.append("};")
|
|
321
|
+
output.append("")
|
|
322
|
+
output.append("// Données de COULEUR (attributs par groupe de 8 pixels)")
|
|
323
|
+
output.append("// Format: FFFFBBBB (Forme bits 4-7, Fond bits 0-3)")
|
|
324
|
+
output.append(f"unsigned char sprite_{sprite_name_clean}_color[{bytes_per_line * height}] = {{")
|
|
325
|
+
output.extend(color_data)
|
|
326
|
+
output.append("};")
|
|
327
|
+
output.append("")
|
|
328
|
+
output.append(f"// Taille totale: {bytes_per_line * height} octets par tableau")
|
|
329
|
+
|
|
330
|
+
if total_blocks > 0:
|
|
331
|
+
percentage = round(multi_color_blocks * 100.0 / total_blocks, 1)
|
|
332
|
+
output.append(f"// Blocs multi-couleurs: {multi_color_blocks} / {total_blocks} ({percentage}%)")
|
|
333
|
+
output.append("")
|
|
334
|
+
|
|
335
|
+
if color_stats:
|
|
336
|
+
output.append("// Combinaisons de couleurs utilisées:")
|
|
337
|
+
for key in sorted(color_stats.keys()):
|
|
338
|
+
parts = key.split('-')
|
|
339
|
+
bg = int(parts[0])
|
|
340
|
+
fg = int(parts[1])
|
|
341
|
+
count = color_stats[key]
|
|
342
|
+
bg_name = MO5_PALETTE[bg]['Name']
|
|
343
|
+
fg_name = MO5_PALETTE[fg]['Name']
|
|
344
|
+
output.append(f"// Fond={bg_name}, Forme={fg_name} : {count} blocs de 8 pixels")
|
|
345
|
+
output.append("")
|
|
346
|
+
|
|
347
|
+
# Macro d'initialisation MO5_Sprite
|
|
348
|
+
sn = sprite_name_clean
|
|
349
|
+
SN = sprite_name_clean.upper()
|
|
350
|
+
output.append(f"// Macro d'initialisation pour MO5_Sprite (voir mo5_sprite.h)")
|
|
351
|
+
output.append(f"#define SPRITE_{SN}_INIT \\")
|
|
352
|
+
output.append(f" {{ sprite_{sn}_form, sprite_{sn}_color, \\")
|
|
353
|
+
output.append(f" SPRITE_{SN}_WIDTH_BYTES, SPRITE_{SN}_HEIGHT }}")
|
|
354
|
+
output.append("")
|
|
355
|
+
output.append(f"// Utilisation:")
|
|
356
|
+
output.append(f"// MO5_Sprite sprite_{sn} = SPRITE_{SN}_INIT;")
|
|
357
|
+
output.append("")
|
|
358
|
+
output.append(f"#endif // {guard_name}")
|
|
359
|
+
|
|
360
|
+
img.close()
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
'Code': '\n'.join(output),
|
|
364
|
+
'SpriteName': sprite_name_clean,
|
|
365
|
+
'OutputPath': output_path, # Chemin de sortie si spécifié
|
|
366
|
+
'Width': width,
|
|
367
|
+
'Height': height,
|
|
368
|
+
'BytesPerLine': bytes_per_line,
|
|
369
|
+
'ColorStats': color_stats,
|
|
370
|
+
'MultiColorBlocks': multi_color_blocks,
|
|
371
|
+
'TotalBlocks': total_blocks
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def main():
|
|
376
|
+
parser = argparse.ArgumentParser(
|
|
377
|
+
description='Convertisseur PNG vers sprite Thomson MO5',
|
|
378
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
379
|
+
epilog="""
|
|
380
|
+
Exemples:
|
|
381
|
+
python png_to_mo5_v2.py mon_sprite.png
|
|
382
|
+
python png_to_mo5_v2.py hero.png --name hero --bg-color 4
|
|
383
|
+
"""
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
parser.add_argument('image_path', help='Chemin vers l\'image PNG à convertir')
|
|
387
|
+
parser.add_argument('--name', dest='sprite_name', help='Nom du sprite (optionnel)')
|
|
388
|
+
parser.add_argument('--bg-color', dest='bg_color', type=int, default=0,
|
|
389
|
+
choices=range(16), metavar='0-15',
|
|
390
|
+
help='Couleur de fond par défaut (0-15, défaut: 0=noir)')
|
|
391
|
+
parser.add_argument('--transparent', action='store_true',
|
|
392
|
+
help='Force le fond à 0 pour mo5_sprite_bg')
|
|
393
|
+
parser.add_argument('--quiet', '-q', action='store_true',
|
|
394
|
+
help='Mode silencieux (affiche uniquement le message final)')
|
|
395
|
+
|
|
396
|
+
args = parser.parse_args()
|
|
397
|
+
|
|
398
|
+
if not args.quiet:
|
|
399
|
+
print()
|
|
400
|
+
print("=" * 60)
|
|
401
|
+
print(" Convertisseur PNG -> Sprite Thomson MO5 (Multi-couleurs)")
|
|
402
|
+
print(" Format: 1 octet = 8 pixels (1 bit/pixel)")
|
|
403
|
+
print(" 2 couleurs auto-détectées par groupe de 8 pixels")
|
|
404
|
+
print("=" * 60)
|
|
405
|
+
print()
|
|
406
|
+
|
|
407
|
+
result = convert_png_to_mo5_sprite(args.image_path, args.sprite_name, args.bg_color, args.quiet, args.transparent)
|
|
408
|
+
|
|
409
|
+
if result:
|
|
410
|
+
if not args.quiet:
|
|
411
|
+
# Afficher le résultat
|
|
412
|
+
print("=" * 60)
|
|
413
|
+
print(result['Code'])
|
|
414
|
+
print("=" * 60)
|
|
415
|
+
print()
|
|
416
|
+
print("[OK] Sprite généré avec succès!")
|
|
417
|
+
print()
|
|
418
|
+
print("[INFO] Le sprite utilise 2 tableaux:")
|
|
419
|
+
print(f" - sprite_{result['SpriteName']}_form (bitmap 1 bit/pixel)")
|
|
420
|
+
print(f" - sprite_{result['SpriteName']}_color (attributs couleur)")
|
|
421
|
+
print()
|
|
422
|
+
print("[STATS] Analyse:")
|
|
423
|
+
print(f" Blocs multi-couleurs: {result['MultiColorBlocks']}/{result['TotalBlocks']}")
|
|
424
|
+
if result['TotalBlocks'] > 0:
|
|
425
|
+
percentage = round(result['MultiColorBlocks'] * 100.0 / result['TotalBlocks'], 1)
|
|
426
|
+
print(f" Pourcentage: {percentage}%")
|
|
427
|
+
print()
|
|
428
|
+
print("[INFO] Utilisation:")
|
|
429
|
+
print(f" draw_sprite_multicolor(x, y,")
|
|
430
|
+
print(f" sprite_{result['SpriteName']}_form,")
|
|
431
|
+
print(f" sprite_{result['SpriteName']}_color,")
|
|
432
|
+
print(f" {result['BytesPerLine']}, {result['Height']});")
|
|
433
|
+
print()
|
|
434
|
+
|
|
435
|
+
# Sauvegarder
|
|
436
|
+
if result['OutputPath']:
|
|
437
|
+
# Si --name a été spécifié, utiliser ce chemin
|
|
438
|
+
output_path = result['OutputPath']
|
|
439
|
+
# Si pas d'extension .c ou .h, ajouter .c
|
|
440
|
+
if output_path.suffix not in ['.c', '.h']:
|
|
441
|
+
output_path = output_path.with_suffix('.c')
|
|
442
|
+
# Créer les répertoires parents si nécessaire
|
|
443
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
444
|
+
else:
|
|
445
|
+
# Sinon, créer dans le même répertoire que l'image source
|
|
446
|
+
source_path = Path(args.image_path)
|
|
447
|
+
output_path = source_path.parent / (source_path.stem + '_sprite_mc.c')
|
|
448
|
+
|
|
449
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
450
|
+
f.write(result['Code'])
|
|
451
|
+
|
|
452
|
+
if not args.quiet:
|
|
453
|
+
print(f"[OK] Sprite sauvegardé dans: {output_path}")
|
|
454
|
+
print()
|
|
455
|
+
|
|
456
|
+
# Tenter de copier dans le presse-papier (optionnel)
|
|
457
|
+
if not args.quiet:
|
|
458
|
+
try:
|
|
459
|
+
import pyperclip
|
|
460
|
+
pyperclip.copy(result['Code'])
|
|
461
|
+
print("[OK] Code copié dans le presse-papier!")
|
|
462
|
+
except ImportError:
|
|
463
|
+
pass # pyperclip n'est pas installé, ce n'est pas grave
|
|
464
|
+
except Exception:
|
|
465
|
+
pass # Échec de la copie, ce n'est pas grave
|
|
466
|
+
|
|
467
|
+
# Message final (toujours affiché, même en mode quiet)
|
|
468
|
+
print(f"[OK] Fichier généré: {output_path}")
|
|
469
|
+
else:
|
|
470
|
+
print("[ERREUR] Échec de la conversion")
|
|
471
|
+
sys.exit(1)
|
|
472
|
+
|
|
473
|
+
if not args.quiet:
|
|
474
|
+
print()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
if __name__ == '__main__':
|
|
478
|
+
main()
|