creek
The AI Image Editor of 2030git clone https://github.com/maydayv7/creek
android/app/src/main/python/stylesheet_generator.py (48011B)
1import json
2import math
3import re
4import numpy as np
5from collections import defaultdict
6from typing import List, Dict, Tuple, Any, Optional
7from sklearn.cluster import KMeans
8
9# ==========================================
10# HYPERPARAMETERS
11# ==========================================
12RANK_DECAY_FACTOR = 0.85 # Priority drops for lower-ranked tags
13MIN_CONSENSUS_THRESHOLD = 0.05 # Filter out noise
14PALETTE_SIZE = 5 # Number of colors in final palette
15
16
17# ==========================================
18# HELPER: COLOR MATH
19# ==========================================
20def hex_to_rgb(hex_str: str) -> List[int]:
21 """Converts '#FF5733' to [255, 87, 51] for math operations."""
22 try:
23 hex_str = hex_str.strip().lstrip("#")
24 if len(hex_str) != 6:
25 return [0, 0, 0]
26 return [int(hex_str[i : i + 2], 16) for i in (0, 2, 4)]
27 except:
28 return [0, 0, 0]
29
30
31def rgb_to_hex(rgb: List[int]) -> str:
32 """Converts [255, 87, 51] back to '#FF5733'."""
33 return "#{:02x}{:02x}{:02x}".format(int(rgb[0]), int(rgb[1]), int(rgb[2]))
34
35
36# ==========================================
37# KNOWLEDGE BASE: FONTS
38# ==========================================
39class FontClusterEngine:
40 def __init__(self):
41 self.full_font_map = {
42 "abeezee": "Sans Serif (Generic)",
43 "abel": "Condensed Sans",
44 "abhayalibre": "Serif (Generic)",
45 "abrilfatface": "Modern Serif",
46 "abyssinicasil": "Serif (Generic)",
47 "aclonica": "Display/Other",
48 "acme": "Display/Other",
49 "actor": "Sans Serif (Generic)",
50 "adamina": "Serif (Generic)",
51 "adventpro": "Display/Other",
52 "aguafinascript": "Script/Handwritten",
53 "akronim": "Display/Other",
54 "aladin": "Display/Other",
55 "aldrich": "Display/Other",
56 "alef": "Sans Serif (Generic)",
57 "alegreya": "Old Style Serif",
58 "alegreyasans": "Humanist Sans",
59 "alegreyasanssc": "Sans Serif (Generic)",
60 "alegreyasc": "Old Style Serif",
61 "alexbrush": "Script/Handwritten",
62 "alfaslabone": "Slab Serif",
63 "alice": "Serif (Generic)",
64 "alike": "Serif (Generic)",
65 "alikeangular": "Serif (Generic)",
66 "allan": "Display/Other",
67 "allerta": "Sans Serif (Generic)",
68 "allertastencil": "Display/Other",
69 "allura": "Script/Handwritten",
70 "almendra": "Display/Other",
71 "almendradisplay": "Display/Other",
72 "almendrasc": "Display/Other",
73 "amarante": "Display/Other",
74 "amaranth": "Display/Other",
75 "amaticasc": "Script/Handwritten",
76 "amaticsc": "Script/Handwritten",
77 "amethysta": "Serif (Generic)",
78 "amiko": "Sans Serif (Generic)",
79 "amiri": "Serif (Generic)",
80 "amita": "Script/Handwritten",
81 "anaheim": "Sans Serif (Generic)",
82 "andada": "Serif (Generic)",
83 "andadasc": "Serif (Generic)",
84 "andika": "Sans Serif (Generic)",
85 "annieuseyourtelescope": "Script/Handwritten",
86 "anonymouspro": "Monospace",
87 "antic": "Sans Serif (Generic)",
88 "anticdidone": "Serif (Generic)",
89 "anticslab": "Slab Serif",
90 "anton": "Grotesque Sans",
91 "antonio": "Sans Serif (Generic)",
92 "arapey": "Serif (Generic)",
93 "arbutus": "Display/Other",
94 "arbutusslab": "Slab Serif",
95 "architectsdaughter": "Script/Handwritten",
96 "archivo": "Grotesque Sans",
97 "archivoblack": "Grotesque Sans",
98 "archivonarrow": "Condensed Sans",
99 "archivovfbeta": "Grotesque Sans",
100 "arefruqaa": "Serif (Generic)",
101 "arimamadurai": "Display/Other",
102 "arimo": "Neo-Grotesque Sans",
103 "arizonia": "Script/Handwritten",
104 "armata": "Sans Serif (Generic)",
105 "arsenal": "Sans Serif (Generic)",
106 "artifika": "Serif (Generic)",
107 "arvo": "Slab Serif",
108 "arya": "Sans Serif (Generic)",
109 "asap": "Rounded Sans",
110 "asapcondensed": "Condensed Sans",
111 "asapvfbeta": "Rounded Sans",
112 "asar": "Serif (Generic)",
113 "asset": "Display/Other",
114 "assistant": "Sans Serif (Generic)",
115 "astloch": "Display/Other",
116 "asul": "Sans Serif (Generic)",
117 "athiti": "Sans Serif (Generic)",
118 "atma": "Display/Other",
119 "atomicage": "Display/Other",
120 "aubrey": "Display/Other",
121 "audiowide": "Display/Other",
122 "autourone": "Display/Other",
123 "average": "Serif (Generic)",
124 "averagesans": "Sans Serif (Generic)",
125 "averiagruesalibre": "Display/Other",
126 "averialibre": "Display/Other",
127 "averiasanslibre": "Sans Serif (Generic)",
128 "averiaseriflibre": "Serif (Generic)",
129 "badscript": "Script/Handwritten",
130 "bahiana": "Display/Other",
131 "baloo": "Display/Other",
132 "baloobhai": "Display/Other",
133 "baloobhaijaan": "Display/Other",
134 "baloobhaina": "Display/Other",
135 "baloochettan": "Display/Other",
136 "balooda": "Display/Other",
137 "baloopaaji": "Display/Other",
138 "balootamma": "Display/Other",
139 "balootammudu": "Display/Other",
140 "baloothambi": "Display/Other",
141 "balthazar": "Serif (Generic)",
142 "bangers": "Display",
143 "barlow": "Grotesque Sans",
144 "barlowcondensed": "Condensed Sans",
145 "barlowsemicondensed": "Condensed Sans",
146 "barrio": "Display/Other",
147 "basic": "Sans Serif (Generic)",
148 "baumans": "Display/Other",
149 "belgrano": "Serif (Generic)",
150 "bellefair": "Serif (Generic)",
151 "belleza": "Sans Serif (Generic)",
152 "benchnine": "Sans Serif (Generic)",
153 "bentham": "Serif (Generic)",
154 "berkshireswash": "Script/Handwritten",
155 "bevan": "Slab Serif",
156 "bhavuka": "Display/Other",
157 "bigelowrules": "Display/Other",
158 "bigshotone": "Display/Other",
159 "bilbo": "Script/Handwritten",
160 "bilboswashcaps": "Script/Handwritten",
161 "biorhyme": "Slab Serif",
162 "biorhymeexpanded": "Slab Serif",
163 "biryani": "Sans Serif (Generic)",
164 "bitter": "Slab Serif",
165 "blackandwhitepicture": "Display/Other",
166 "blackhansans": "Sans Serif (Generic)",
167 "blackopsone": "Display/Other",
168 "bonbon": "Display/Other",
169 "boogaloo": "Display/Other",
170 "bowlbyone": "Display/Other",
171 "bowlbyonesc": "Display/Other",
172 "brawler": "Serif (Generic)",
173 "breeserif": "Slab Serif",
174 "brunoace": "Display/Other",
175 "brunoacesc": "Display/Other",
176 "bubblegumsans": "Display/Other",
177 "bubblerone": "Display/Other",
178 "buenard": "Serif (Generic)",
179 "bungee": "Display/Other",
180 "bungeehairline": "Display/Other",
181 "bungeeinline": "Display/Other",
182 "bungeeoutline": "Display/Other",
183 "bungeeshade": "Display/Other",
184 "butcherman": "Display/Other",
185 "butchermancaps": "Display/Other",
186 "butterflykids": "Script/Handwritten",
187 "cabin": "Humanist Sans",
188 "cabincondensed": "Condensed Sans",
189 "cabinsketch": "Display/Other",
190 "cabinvfbeta": "Humanist Sans",
191 "caesardressing": "Display/Other",
192 "cagliostro": "Sans Serif (Generic)",
193 "cairo": "Grotesque Sans",
194 "calligraffitti": "Script/Handwritten",
195 "cambay": "Sans Serif (Generic)",
196 "cambo": "Serif (Generic)",
197 "cantarell": "Sans Serif (Generic)",
198 "cantataone": "Serif (Generic)",
199 "cantoraone": "Sans Serif (Generic)",
200 "capriola": "Sans Serif (Generic)",
201 "cardo": "Serif (Generic)",
202 "carme": "Sans Serif (Generic)",
203 "carroisgothic": "Sans Serif (Generic)",
204 "carroisgothicsc": "Sans Serif (Generic)",
205 "catamaran": "Sans Serif",
206 "caudex": "Serif (Generic)",
207 "caveat": "Script/Handwritten",
208 "caveatbrush": "Script/Handwritten",
209 "cevicheone": "Display/Other",
210 "changa": "Sans Serif (Generic)",
211 "changaone": "Display/Other",
212 "chango": "Display/Other",
213 "chathura": "Sans Serif (Generic)",
214 "chauphilomeneone": "Display/Other",
215 "chelaone": "Display/Other",
216 "chelseamarket": "Display/Other",
217 "cherrycreamsoda": "Display/Other",
218 "cherryswash": "Display/Other",
219 "chewy": "Display",
220 "chicle": "Display/Other",
221 "chivo": "Grotesque Sans",
222 "chonburi": "Display/Other",
223 "cinzel": "Serif",
224 "cinzeldecorative": "Display/Other",
225 "clickerscript": "Script/Handwritten",
226 "coda": "Display/Other",
227 "codystar": "Display/Other",
228 "coiny": "Display/Other",
229 "combo": "Display/Other",
230 "comfortaa": "Rounded Sans",
231 "comingsoon": "Script/Handwritten",
232 "concertone": "Display/Other",
233 "condiment": "Script/Handwritten",
234 "contrailone": "Display/Other",
235 "convergence": "Sans Serif (Generic)",
236 "cookie": "Script",
237 "copse": "Slab Serif",
238 "cormorant": "Old Style Serif",
239 "cormorantgaramond": "Old Style Serif",
240 "cormorantinfant": "Old Style Serif",
241 "cormorantsc": "Old Style Serif",
242 "cormorantunicase": "Old Style Serif",
243 "cormorantupright": "Old Style Serif",
244 "courgette": "Script",
245 "cousine": "Monospace",
246 "coustard": "Slab Serif",
247 "craftygirls": "Script/Handwritten",
248 "creepster": "Display/Other",
249 "creepstercaps": "Display/Other",
250 "creteround": "Slab Serif",
251 "crimsontext": "Old Style Serif",
252 "croissantone": "Display/Other",
253 "crushed": "Display/Other",
254 "cuprum": "Sans Serif",
255 "cutefont": "Display/Other",
256 "cutive": "Serif (Generic)",
257 "cutivemono": "Monospace",
258 "damion": "Script/Handwritten",
259 "dancingscript": "Script/Handwritten",
260 "daysone": "Display/Other",
261 "decovaralpha": "Display/Other",
262 "dekko": "Script/Handwritten",
263 "delius": "Script/Handwritten",
264 "deliusswashcaps": "Script/Handwritten",
265 "deliusunicase": "Script/Handwritten",
266 "dellarespira": "Serif (Generic)",
267 "denkone": "Display/Other",
268 "devonshire": "Script/Handwritten",
269 "dhurjati": "Display/Other",
270 "dhyana": "Display/Other",
271 "didactgothic": "Sans Serif (Generic)",
272 "digitalnumbers": "Display/Other",
273 "diplomata": "Display/Other",
274 "diplomatasc": "Display/Other",
275 "dohyeon": "Sans Serif (Generic)",
276 "dokdo": "Script/Handwritten",
277 "domine": "Serif",
278 "donegalone": "Display/Other",
279 "doppioone": "Sans Serif (Generic)",
280 "dorsa": "Display/Other",
281 "dosis": "Rounded Sans",
282 "drsugiyama": "Script/Handwritten",
283 "durusans": "Sans Serif (Generic)",
284 "dynalight": "Script/Handwritten",
285 "eaglelake": "Script/Handwritten",
286 "eastseadokdo": "Script/Handwritten",
287 "eater": "Display/Other",
288 "eatercaps": "Display/Other",
289 "ebgaramond": "Old Style Serif",
290 "economica": "Sans Serif (Generic)",
291 "eczar": "Display/Other",
292 "ekmukta": "Sans Serif (Generic)",
293 "electrolize": "Display/Other",
294 "elmessiri": "Sans Serif (Generic)",
295 "elsie": "Display/Other",
296 "elsieswashcaps": "Display/Other",
297 "emblemaone": "Display/Other",
298 "emilyscandy": "Display/Other",
299 "encodesans": "Sans Serif",
300 "encodesanscondensed": "Condensed Sans",
301 "encodesansexpanded": "Sans Serif (Generic)",
302 "encodesanssemicondensed": "Condensed Sans",
303 "encodesanssemiexpanded": "Sans Serif (Generic)",
304 "engagement": "Script/Handwritten",
305 "englebert": "Display/Other",
306 "enriqueta": "Slab Serif",
307 "ericaone": "Display/Other",
308 "esteban": "Serif (Generic)",
309 "euphoriascript": "Script/Handwritten",
310 "ewert": "Display/Other",
311 "exo": "Geometric Sans",
312 "exo2": "Geometric Sans",
313 "expletussans": "Display/Other",
314 "fanwoodtext": "Serif (Generic)",
315 "farsan": "Display/Other",
316 "fascinate": "Display/Other",
317 "fascinateinline": "Display/Other",
318 "fasterone": "Display/Other",
319 "fasthand": "Script/Handwritten",
320 "faunaone": "Serif (Generic)",
321 "faustina": "Serif (Generic)",
322 "faustinavfbeta": "Serif (Generic)",
323 "federant": "Display/Other",
324 "federo": "Display/Other",
325 "felipa": "Script/Handwritten",
326 "fenix": "Serif (Generic)",
327 "fingerpaint": "Display/Other",
328 "firasans": "Humanist Sans",
329 "firasanscondensed": "Condensed Sans",
330 "firasansextracondensed": "Condensed Sans",
331 "fjallaone": "Grotesque Sans",
332 "fjordone": "Serif (Generic)",
333 "flamenco": "Display/Other",
334 "flavors": "Display/Other",
335 "fondamento": "Script/Handwritten",
336 "fontdinerswanky": "Display/Other",
337 "forum": "Display/Other",
338 "francoisone": "Sans Serif",
339 "frankruhllibre": "Serif (Generic)",
340 "freckleface": "Display/Other",
341 "frederickathegreat": "Display/Other",
342 "fredokaone": "Rounded Display",
343 "fresca": "Sans Serif (Generic)",
344 "frijole": "Display/Other",
345 "fruktur": "Display/Other",
346 "fugazone": "Display/Other",
347 "gabriela": "Serif (Generic)",
348 "gaegu": "Script/Handwritten",
349 "gafata": "Sans Serif (Generic)",
350 "galada": "Display/Other",
351 "galdeano": "Sans Serif (Generic)",
352 "galindo": "Display/Other",
353 "gamjaflower": "Script/Handwritten",
354 "gemunulibre": "Sans Serif (Generic)",
355 "genbasb": "Serif (Generic)",
356 "genbasbi": "Serif (Generic)",
357 "genbkbasb": "Serif (Generic)",
358 "genbkbasbi": "Serif (Generic)",
359 "geo": "Display/Other",
360 "geostar": "Display/Other",
361 "geostarfill": "Display/Other",
362 "germaniaone": "Display/Other",
363 "gfsdidot": "Serif (Generic)",
364 "gfsneohellenic": "Sans Serif (Generic)",
365 "gfsneohellenicbold": "Sans Serif (Generic)",
366 "gfsneohellenicbolditalic": "Sans Serif (Generic)",
367 "gidugu": "Display/Other",
368 "gildadisplay": "Serif (Generic)",
369 "glassantiqua": "Display/Other",
370 "glegoo": "Slab Serif",
371 "gochihand": "Script/Handwritten",
372 "gorditas": "Display/Other",
373 "gothica1": "Sans Serif (Generic)",
374 "graduate": "Display/Other",
375 "grandhotel": "Script/Handwritten",
376 "greatvibes": "Script",
377 "griffy": "Display/Other",
378 "gruppo": "Display/Other",
379 "gudea": "Sans Serif (Generic)",
380 "gugi": "Display/Other",
381 "gurajada": "Display/Other",
382 "habibi": "Serif (Generic)",
383 "halant": "Serif (Generic)",
384 "hammersmithone": "Sans Serif (Generic)",
385 "hanalei": "Display/Other",
386 "hanaleifill": "Display/Other",
387 "handlee": "Script/Handwritten",
388 "hannari": "Display/Other",
389 "hanuman": "Serif (Generic)",
390 "happymonkey": "Display/Other",
391 "harmattan": "Sans Serif (Generic)",
392 "headlandone": "Serif (Generic)",
393 "heebo": "Sans Serif",
394 "hennypenny": "Display/Other",
395 "hermeneusone": "Sans Serif (Generic)",
396 "herrvonmuellerhoff": "Script/Handwritten",
397 "himelody": "Script/Handwritten",
398 "hind": "Humanist Sans",
399 "hindcolombo": "Sans Serif (Generic)",
400 "hindguntur": "Sans Serif (Generic)",
401 "hindjalandhar": "Sans Serif (Generic)",
402 "hindkochi": "Sans Serif (Generic)",
403 "hindmadurai": "Sans Serif (Generic)",
404 "hindmysuru": "Sans Serif (Generic)",
405 "hindsiliguri": "Sans Serif (Generic)",
406 "hindvadodara": "Sans Serif (Generic)",
407 "homemadeapple": "Script/Handwritten",
408 "homenaje": "Sans Serif (Generic)",
409 "ibmplexmono": "Monospace",
410 "ibmplexsans": "Neo-Grotesque Sans",
411 "ibmplexsanscondensed": "Condensed Sans",
412 "ibmplexserif": "Transitional Serif",
413 "iceberg": "Display/Other",
414 "iceland": "Display/Other",
415 "imprima": "Sans Serif (Generic)",
416 "inconsolata": "Monospace",
417 "inder": "Sans Serif (Generic)",
418 "indieflower": "Handwritten",
419 "inika": "Serif (Generic)",
420 "inknutantiqua": "Serif (Generic)",
421 "irishgrover": "Display/Other",
422 "istokweb": "Sans Serif (Generic)",
423 "italiana": "Serif (Generic)",
424 "italianno": "Script/Handwritten",
425 "itim": "Script/Handwritten",
426 "jacquesfrancois": "Display/Other",
427 "jacquesfrancoisshadow": "Display/Other",
428 "jaldi": "Sans Serif (Generic)",
429 "jejugothic": "Sans Serif (Generic)",
430 "jejuhallasan": "Display/Other",
431 "jejumyeongjo": "Serif (Generic)",
432 "jimnightshade": "Display/Other",
433 "jockeyone": "Display/Other",
434 "jollylodger": "Display/Other",
435 "jomhuria": "Display/Other",
436 "josefinsans": "Geometric Sans",
437 "josefinslab": "Slab Serif",
438 "jotione": "Display/Other",
439 "jua": "Sans Serif (Generic)",
440 "judson": "Serif (Generic)",
441 "julee": "Script/Handwritten",
442 "juliussansone": "Sans Serif (Generic)",
443 "junge": "Serif (Generic)",
444 "jura": "Sans Serif (Generic)",
445 "justanotherhand": "Script/Handwritten",
446 "kadwa": "Serif (Generic)",
447 "kalam": "Handwritten",
448 "kameron": "Serif (Generic)",
449 "kanit": "Sans Serif",
450 "kantumruy": "Sans Serif (Generic)",
451 "karla": "Sans Serif (Generic)",
452 "karlatamilinclined": "Sans Serif (Generic)",
453 "karlatamilupright": "Sans Serif (Generic)",
454 "karma": "Serif (Generic)",
455 "katibeh": "Display/Other",
456 "kaushanscript": "Script",
457 "kavivanar": "Script/Handwritten",
458 "kavoon": "Display/Other",
459 "kdamthmor": "Display/Other",
460 "keaniaone": "Display/Other",
461 "kellyslab": "Display/Other",
462 "kenia": "Display/Other",
463 "khand": "Sans Serif",
464 "khula": "Sans Serif (Generic)",
465 "khyay": "Display/Other",
466 "kiranghaerang": "Display/Other",
467 "kiteone": "Sans Serif (Generic)",
468 "knewave": "Display/Other",
469 "kokoro": "Display/Other",
470 "kopubbatang": "Serif (Generic)",
471 "kottaone": "Serif (Generic)",
472 "kranky": "Display/Other",
473 "kreon": "Slab Serif",
474 "kristi": "Script/Handwritten",
475 "kronaone": "Sans Serif (Generic)",
476 "kurale": "Serif (Generic)",
477 "laila": "Serif (Generic)",
478 "lakkireddy": "Display/Other",
479 "lalezar": "Display/Other",
480 "lancelot": "Display/Other",
481 "laomuangdon": "Display/Other",
482 "laomuangkhong": "Display/Other",
483 "laosanspro": "Sans Serif (Generic)",
484 "lato": "Humanist Sans",
485 "leckerlione": "Display/Other",
486 "ledger": "Serif (Generic)",
487 "lekton": "Monospace",
488 "lemon": "Display/Other",
489 "lemonada": "Display/Other",
490 "lemonadavfbeta": "Display/Other",
491 "librebaskerville": "Serif (Generic)",
492 "librecaslontext": "Serif (Generic)",
493 "librefranklin": "Neo-Grotesque Sans",
494 "lifesavers": "Display/Other",
495 "lilitaone": "Display/Other",
496 "lilyscriptone": "Display/Other",
497 "limelight": "Display/Other",
498 "lindenhill": "Serif (Generic)",
499 "lobster": "Script",
500 "lobstertwo": "Script/Handwritten",
501 "londrinaoutline": "Display/Other",
502 "londrinashadow": "Display/Other",
503 "londrinasketch": "Display/Other",
504 "londrinasolid": "Display/Other",
505 "lora": "Old Style Serif",
506 "loversquarrel": "Script/Handwritten",
507 "loveyalikeasister": "Script/Handwritten",
508 "luckiestguy": "Display/Other",
509 "lusitana": "Serif (Generic)",
510 "lustria": "Serif (Generic)",
511 "macondo": "Display/Other",
512 "macondoswashcaps": "Display/Other",
513 "mada": "Sans Serif (Generic)",
514 "magra": "Sans Serif (Generic)",
515 "maidenorange": "Display/Other",
516 "maitree": "Serif (Generic)",
517 "mako": "Sans Serif (Generic)",
518 "mallanna": "Display/Other",
519 "mandali": "Sans Serif (Generic)",
520 "manuale": "Serif (Generic)",
521 "marcellus": "Serif (Generic)",
522 "marcellussc": "Serif (Generic)",
523 "marckscript": "Script/Handwritten",
524 "margarine": "Display/Other",
525 "markazitext": "Serif (Generic)",
526 "markoone": "Serif (Generic)",
527 "marmelad": "Sans Serif (Generic)",
528 "martel": "Serif (Generic)",
529 "martelsans": "Sans Serif (Generic)",
530 "marvel": "Sans Serif (Generic)",
531 "mate": "Serif (Generic)",
532 "matesc": "Serif (Generic)",
533 "mavenpro": "Geometric Sans",
534 "mavenprovfbeta": "Geometric Sans",
535 "mclaren": "Display/Other",
536 "medulaone": "Display/Other",
537 "meerainimai": "Display/Other",
538 "meiescript": "Script/Handwritten",
539 "mergeone": "Sans Serif (Generic)",
540 "merienda": "Display/Other",
541 "meriendaone": "Display/Other",
542 "merriweather": "Transitional Serif",
543 "merriweathersans": "Humanist Sans",
544 "mervalescript": "Script/Handwritten",
545 "metalmania": "Display/Other",
546 "metamorphous": "Display/Other",
547 "metrophobic": "Sans Serif (Generic)",
548 "miama": "Script/Handwritten",
549 "milonga": "Display/Other",
550 "miltonian": "Display/Other",
551 "miltoniantattoo": "Display/Other",
552 "mina": "Sans Serif (Generic)",
553 "miniver": "Display/Other",
554 "miriamlibre": "Sans Serif (Generic)",
555 "mirza": "Display/Other",
556 "missfajardose": "Script/Handwritten",
557 "mitr": "Sans Serif (Generic)",
558 "modak": "Display/Other",
559 "modernantiqua": "Serif (Generic)",
560 "mogra": "Display/Other",
561 "molengo": "Sans Serif (Generic)",
562 "molle": "Display/Other",
563 "monda": "Sans Serif (Generic)",
564 "monoton": "Display/Other",
565 "monsieurladoulaise": "Script/Handwritten",
566 "montaga": "Serif (Generic)",
567 "montez": "Script/Handwritten",
568 "montserrat": "Geometric Sans",
569 "montserratalternates": "Geometric Sans",
570 "montserratsubrayada": "Geometric Sans",
571 "mountainsofchristmas": "Display/Other",
572 "mousememoirs": "Display/Other",
573 "mplus1p": "Rounded Sans",
574 "mrbedfort": "Script/Handwritten",
575 "mrdafoe": "Script/Handwritten",
576 "mrdehaviland": "Script/Handwritten",
577 "mrssaintdelafield": "Script/Handwritten",
578 "mrssheppards": "Script/Handwritten",
579 "mukta": "Sans Serif",
580 "muktamahee": "Sans Serif (Generic)",
581 "muktamalar": "Sans Serif (Generic)",
582 "muktavaani": "Sans Serif (Generic)",
583 "muli": "Humanist Sans",
584 "myanmarsanspro": "Sans Serif (Generic)",
585 "mysteryquest": "Display/Other",
586 "nanumbrushscript": "Script/Handwritten",
587 "nanumgothic": "Sans Serif (Generic)",
588 "nanumgothiccoding": "Monospace",
589 "nanummyeongjo": "Serif (Generic)",
590 "nanumpenscript": "Script/Handwritten",
591 "nats": "Display/Other",
592 "neuton": "Serif (Generic)",
593 "newrocker": "Display/Other",
594 "newscycle": "Sans Serif (Generic)",
595 "nicomoji": "Display/Other",
596 "niconne": "Script/Handwritten",
597 "nikukyu": "Display/Other",
598 "nixieone": "Display/Other",
599 "nobile": "Sans Serif (Generic)",
600 "nokora": "Sans Serif (Generic)",
601 "norican": "Script/Handwritten",
602 "nosifer": "Display/Other",
603 "nosifercaps": "Display/Other",
604 "notable": "Display/Other",
605 "noticiatext": "Serif (Generic)",
606 "notosans": "Humanist Sans",
607 "notosanstamil": "Humanist Sans",
608 "notoserif": "Transitional Serif",
609 "novascript": "Display/Other",
610 "ntr": "Sans Serif (Generic)",
611 "numans": "Sans Serif (Generic)",
612 "nunito": "Rounded Sans",
613 "nunitosans": "Rounded Sans",
614 "offside": "Display/Other",
615 "oldenburg": "Display/Other",
616 "oldstandard": "Serif (Generic)",
617 "oleoscript": "Script/Handwritten",
618 "oleoscriptswashcaps": "Script/Handwritten",
619 "opensans": "Humanist Sans",
620 "opensanscondensed": "Condensed Sans",
621 "opensanshebrew": "Humanist Sans",
622 "opensanshebrewcondensed": "Condensed Sans",
623 "oranienbaum": "Serif (Generic)",
624 "orbitron": "Display/Other",
625 "oregano": "Script/Handwritten",
626 "orienta": "Sans Serif (Generic)",
627 "originalsurfer": "Display/Other",
628 "oswald": "Grotesque Sans",
629 "overlock": "Display/Other",
630 "overlocksc": "Display/Other",
631 "overpass": "Sans Serif (Generic)",
632 "overpassmono": "Monospace",
633 "ovo": "Serif (Generic)",
634 "oxygen": "Geometric Sans",
635 "oxygenmono": "Monospace",
636 "pacifico": "Script",
637 "palanquin": "Sans Serif (Generic)",
638 "palanquindark": "Sans Serif (Generic)",
639 "pangolin": "Script/Handwritten",
640 "paprika": "Display/Other",
641 "parisienne": "Script/Handwritten",
642 "passeroone": "Display/Other",
643 "passionone": "Display",
644 "pathwaygothicone": "Condensed Sans",
645 "patrickhand": "Script/Handwritten",
646 "patrickhandsc": "Script/Handwritten",
647 "pattaya": "Display/Other",
648 "patuaone": "Slab Serif",
649 "pavanam": "Sans Serif (Generic)",
650 "paytoneone": "Sans Serif (Generic)",
651 "peddana": "Serif (Generic)",
652 "peralta": "Display/Other",
653 "permanentmarker": "Handwritten",
654 "petitformalscript": "Script/Handwritten",
655 "petrona": "Serif (Generic)",
656 "philosopher": "Sans Serif (Generic)",
657 "piedra": "Display/Other",
658 "pinyonscript": "Script/Handwritten",
659 "pirataone": "Display/Other",
660 "plaster": "Display/Other",
661 "play": "Sans Serif (Generic)",
662 "playball": "Script/Handwritten",
663 "playfairdisplay": "Modern Serif",
664 "playfairdisplaysc": "Modern Serif",
665 "podkova": "Slab Serif",
666 "podkovavfbeta": "Slab Serif",
667 "poetsenone": "Display/Other",
668 "poiretone": "Display/Other",
669 "poly": "Serif (Generic)",
670 "pompiere": "Display/Other",
671 "ponnala": "Display/Other",
672 "pontanosans": "Sans Serif (Generic)",
673 "poorstory": "Display/Other",
674 "poppins": "Geometric Sans",
675 "portersansblock": "Display/Other",
676 "portlligatsans": "Display/Other",
677 "portlligatslab": "Display/Other",
678 "postnobillscolombo": "Display/Other",
679 "postnobillsjaffna": "Display/Other",
680 "pragatinarrow": "Condensed Sans",
681 "prata": "Serif (Generic)",
682 "pressstart2p": "Display/Other",
683 "pridi": "Serif (Generic)",
684 "princesssofia": "Script/Handwritten",
685 "prociono": "Serif (Generic)",
686 "prompt": "Sans Serif",
687 "prostoone": "Display/Other",
688 "prozalibre": "Sans Serif (Generic)",
689 "pt_sans": "Humanist Sans",
690 "pt_serif": "Transitional Serif",
691 "puritan": "Sans Serif (Generic)",
692 "purplepurse": "Display/Other",
693 "questrial": "Geometric Sans",
694 "qwigley": "Script/Handwritten",
695 }
696
697 def get_cluster(self, raw_name: str) -> str:
698 clean = raw_name.lower().split("-")[0].strip()
699 if clean in self.full_font_map:
700 return self.full_font_map[clean]
701 return "Display/Other" # Default fallback
702
703
704# ==========================================
705# UNIFIED CONSENSUS ENGINE
706# ==========================================
707class UnifiedStyleEngine:
708 def __init__(self):
709 self.feature_registry = defaultdict(lambda: defaultdict(list))
710 self.font_engine = FontClusterEngine()
711 self.font_family_scores = defaultdict(float)
712 self.font_individual_scores = defaultdict(float)
713 self.font_members = defaultdict(list)
714 self.color_pool = []
715 self.doc_counter = 0
716
717 self.material_filter = {
718 "glossy",
719 "matte",
720 "metallic",
721 "organic",
722 "wood",
723 "laminated",
724 "porcelain",
725 "wet-look",
726 "denim",
727 "fabric",
728 "leather",
729 }
730
731 self.aliases = {
732 "Colour Palette": "Color Palette",
733 "Texture": "Background/Texture",
734 "Era": "Era/Cultural Reference",
735 "Font": "Typography",
736 "Fonts": "Typography",
737 "Text": "Typography",
738 "Layout": "Composition",
739 }
740
741 def ingest_data(self, data: Dict):
742 """
743 Parses the JSON structure from the app
744 Handles: { "success": true, "data": { "results": { ... } } }
745 """
746 try:
747 self.doc_counter += 1
748
749 # 1. Unwrapping logic to find "results"
750 source_data = {}
751 if "data" in data and isinstance(data["data"], dict):
752 # Check for nested "results" inside "data"
753 if "results" in data["data"]:
754 source_data = data["data"]["results"]
755 else:
756 source_data = data["data"]
757 elif "results" in data:
758 source_data = data["results"]
759 else:
760 source_data = data
761
762 # 2. Iterate through categories
763 for category, payload in source_data.items():
764 if category in ["filename", "meta"]:
765 continue
766
767 # Map "Font" -> "Typography", etc.
768 std_category = self.aliases.get(category, category)
769
770 # PATH A: COLOR PALETTE
771 # NOTE: We intentionally SKIP processing 'scores' (moods) here
772 if std_category == "Color Palette":
773 if isinstance(payload, dict) and "palette" in payload:
774 for hex_code in payload["palette"]:
775 if isinstance(hex_code, str) and hex_code.startswith("#"):
776 self.color_pool.append(hex_to_rgb(hex_code))
777 # Fallback: if payload is just a list of hex strings
778 elif isinstance(payload, list):
779 for item in payload:
780 if isinstance(item, str) and item.startswith("#"):
781 self.color_pool.append(hex_to_rgb(item))
782
783 # PATH B: TYPOGRAPHY
784 elif std_category == "Typography":
785 if isinstance(payload, dict) and "scores" in payload:
786 payload = payload["scores"]
787 vectors = self._normalize(payload)
788
789 for rank, (font_name, score) in enumerate(vectors):
790 # Skip "No Text Detected"
791 if "no text" in font_name.lower():
792 continue
793
794 weight = math.pow(RANK_DECAY_FACTOR, rank) * score
795 cluster = self.font_engine.get_cluster(font_name)
796
797 self.font_family_scores[cluster] += weight
798 self.font_individual_scores[font_name] += weight
799 if font_name not in self.font_members[cluster]:
800 self.font_members[cluster].append(font_name)
801
802 # PATH C: OTHER TAGS
803 else:
804 if isinstance(payload, dict) and "scores" in payload:
805 payload = payload["scores"]
806 vectors = self._normalize(payload)
807
808 for rank, (label, score) in enumerate(vectors):
809 self.feature_registry[std_category][label].append(
810 {"raw_score": score, "rank": rank}
811 )
812
813 except Exception as e:
814 print(f"Warning during ingestion: {e}")
815
816 def _normalize(self, payload) -> List[Tuple[str, float]]:
817 """
818 Robustly converts inputs to [(label, score), ...]
819 Handles mixed types and weird nesting to prevent crashes.
820 """
821 if isinstance(payload, dict):
822 clean_items = []
823 for k, v in payload.items():
824 # Only accept numeric values as scores
825 if isinstance(v, (int, float)):
826 clean_items.append((k, float(v)))
827 return sorted(clean_items, key=lambda x: x[1], reverse=True)
828
829 if isinstance(payload, list):
830 res = []
831 for item in payload:
832 if isinstance(item, dict):
833 try:
834 score = float(item.get("score", 0.9))
835 except (ValueError, TypeError):
836 score = 0.0
837
838 # Try 'label' or 'tag'
839 label = item.get("label", item.get("tag", "unknown"))
840 if not isinstance(label, str):
841 label = str(label)
842
843 res.append((label, score))
844 elif isinstance(item, str):
845 res.append((item, 1.0))
846 return res
847 return []
848
849 def compute_final_stylesheet(self) -> Dict:
850 final_json = {"results": {}}
851
852 # 1. Process Standard Tags
853 for category, tags_map in self.feature_registry.items():
854 candidates = []
855 for tag, occurrences in tags_map.items():
856 # Weighted Sum
857 w_sum = sum(
858 obs["raw_score"] * math.pow(RANK_DECAY_FACTOR, obs["rank"])
859 for obs in occurrences
860 )
861 score = w_sum / max(1, self.doc_counter)
862
863 if score > MIN_CONSENSUS_THRESHOLD:
864 candidates.append({"label": tag, "score": round(score, 2)})
865
866 candidates.sort(key=lambda x: x["score"], reverse=True)
867 if candidates:
868 final_json["results"][category] = candidates[:5]
869
870 # 1.5 Split Texture into 'Background/Texture' and 'Material Look'
871 if "Background/Texture" in final_json["results"]:
872 textures = []
873 materials = []
874
875 for item in final_json["results"]["Background/Texture"]:
876 label = item["label"].lower().replace("_", " ").replace("-", " ")
877 # Check if label or parts of it match material keywords
878 is_material = False
879 if item["label"] in self.material_filter:
880 is_material = True
881 else:
882 # Partial matching (e.g. "wood grain" -> wood)
883 for mat in self.material_filter:
884 if mat in label:
885 is_material = True
886 break
887
888 if is_material:
889 materials.append(item)
890 else:
891 textures.append(item)
892
893 if textures:
894 final_json["results"]["Background/Texture"] = textures
895 else:
896 # Remove empty key if all moved to material
897 del final_json["results"]["Background/Texture"]
898
899 if materials:
900 # Merge with existing Material Look if present
901 if "Material Look" in final_json["results"]:
902 final_json["results"]["Material Look"].extend(materials)
903 final_json["results"]["Material Look"].sort(
904 key=lambda x: x["score"], reverse=True
905 )
906 else:
907 final_json["results"]["Material Look"] = materials
908
909 # 2. Process Typography
910 if self.font_family_scores:
911 best_fam, _ = sorted(
912 self.font_family_scores.items(), key=lambda x: x[1], reverse=True
913 )[0]
914 members = self.font_members[best_fam]
915 members.sort(key=lambda x: self.font_individual_scores[x], reverse=True)
916
917 typo_output = []
918 for font in members[:5]:
919 score = self.font_individual_scores[font] / max(1, self.doc_counter)
920 typo_output.append({"label": font, "score": round(score, 2)})
921
922 final_json["results"]["Typography"] = typo_output
923 final_json["results"]["Typography_Family"] = best_fam
924
925 # 3. Process Color Palette
926 if len(self.color_pool) > 0:
927 try:
928 # If we have very few colors, just use all of them
929 k_clusters = min(len(self.color_pool), PALETTE_SIZE)
930
931 # FIX: n_init='auto' crashes on older sklearn. Used n_init=1.
932 kmeans = KMeans(n_clusters=k_clusters, n_init=1, random_state=42)
933 kmeans.fit(self.color_pool)
934 centers = sorted(kmeans.cluster_centers_.astype(int).tolist(), key=sum)
935
936 palette_output = []
937 for i, rgb in enumerate(centers):
938 palette_output.append(
939 {"label": rgb_to_hex(rgb), "score": round(1.0 - (i * 0.1), 2)}
940 )
941 final_json["results"]["Color Palette"] = palette_output
942 except Exception as e:
943 print(f"Color clustering failed: {e}")
944
945 final_json["results"]["meta"] = {"source_files": self.doc_counter}
946 return final_json
947
948
949# ==========================================
950# ENTRY POINT
951# ==========================================
952def generate_stylesheet(json_strings_list: Any) -> str:
953 """
954 Entry point
955 Accepts a Java ArrayList of JSON strings.
956 """
957 engine = UnifiedStyleEngine()
958
959 try:
960 count = json_strings_list.size()
961 for i in range(count):
962 json_str = json_strings_list.get(i)
963 if not json_str:
964 continue
965 try:
966 data = json.loads(json_str)
967 engine.ingest_data(data)
968 except Exception as e:
969 print(f"Error parsing JSON chunk at index {i}: {e}")
970 continue
971
972 except AttributeError:
973 # Fallback for standard Python list (For testing)
974 for json_str in json_strings_list:
975 if not json_str:
976 continue
977 try:
978 data = json.loads(json_str)
979 engine.ingest_data(data)
980 except:
981 continue
982
983 final_output = engine.compute_final_stylesheet()
984 return json.dumps(final_output)
985
986
987# ==========================================
988# PROMPT GENERATION LOGIC
989# ==========================================
990
991DEFAULT_OPTIONS = {
992 "width": 1024,
993 "height": 1024,
994 "steps": 30,
995 "guidance_scale": 7.5,
996 "sampler": "k_lms",
997 "seed": "random",
998}
999
1000
1001def normalize_sheet(sheet: Dict[str, Any]) -> Dict[str, Dict[str, List[str]]]:
1002 """
1003 Organizes the flat stylesheet tags into Primary and Secondary categories.
1004 """
1005 results = sheet.get("results", sheet)
1006 norm: Dict[str, Dict[str, List[str]]] = {}
1007
1008 for cat, val in results.items():
1009 if cat in (
1010 "filename",
1011 "meta",
1012 "Color Palette",
1013 ): # Explicitly ignore Palette here if desired, or later
1014 continue
1015
1016 items = []
1017 if isinstance(val, list):
1018 items = val
1019 elif isinstance(val, dict) and "scores" in val:
1020 items = val["scores"]
1021
1022 try:
1023 items_sorted = sorted(
1024 items, key=lambda x: float(x.get("score", 0)), reverse=True
1025 )
1026 except Exception:
1027 items_sorted = items
1028
1029 labels = [
1030 str(it.get("label")).strip() for it in items_sorted if it.get("label")
1031 ]
1032 if labels:
1033 norm[cat] = {"Primary": labels[0], "Secondary": labels[1:]}
1034
1035 return norm
1036
1037
1038STYLE_WORDS = [
1039 "hand-drawn",
1040 "hand drawn",
1041 "sketch",
1042 "thin lines",
1043 "minimalistic",
1044 "minimal",
1045 "flat",
1046 "cartoon",
1047 "comic",
1048 "pop-art",
1049 "surreal",
1050 "photorealistic",
1051 "realistic",
1052 "painting",
1053 "oil painting",
1054 "pastel",
1055 "artistic",
1056 "illustration",
1057 "line drawing",
1058 "line art",
1059 "3d effect",
1060 "texture",
1061 "pattern",
1062 "soft light",
1063 "studio light",
1064 "dramatic",
1065 "cinematic",
1066 "moody",
1067 "vintage",
1068 "retro",
1069 "poster",
1070 "render",
1071 "octane render",
1072 "unreal engine",
1073 "digital illustration",
1074]
1075
1076style_words_regex = re.compile(
1077 r"\b(" + "|".join(re.escape(w) for w in STYLE_WORDS) + r")\b",
1078 flags=re.IGNORECASE,
1079)
1080
1081
1082def remove_style_words(caption: str) -> str:
1083 """
1084 Removes specific artistic style descriptions from the caption so they
1085 don't conflict with the Stylesheet.
1086 """
1087 if not caption:
1088 return caption
1089 clean = style_words_regex.sub(" ", caption)
1090 # Collapse multiple spaces into one
1091 clean = re.sub(r"\s{2,}", " ", clean).strip()
1092 return clean
1093
1094
1095def extract_subject(caption_text: str) -> str:
1096 """
1097 Heuristic to guess the main subject (first few words of the first sentence).
1098 """
1099 txt = caption_text.strip()
1100 if not txt:
1101 return "subject"
1102 first_clause = re.split(r"[.?!]\s*", txt)[0]
1103 return " ".join(first_clause.split()[:12])
1104
1105
1106def pick_style_phrases(
1107 norm: Dict[str, Dict[str, List[str]]], max_secondaries=3
1108) -> List[str]:
1109 """
1110 Extracts non-color features from the Stylesheet.
1111 """
1112 cats = [
1113 "Style",
1114 "Background/Texture",
1115 "Lighting",
1116 "Composition",
1117 "Era/Cultural Reference",
1118 "Material Look",
1119 "Typography",
1120 ]
1121 # Mappings for cleaner prompt text
1122 key_map = {
1123 "Style": "art style",
1124 "Background/Texture": "background texture",
1125 "Lighting": "lighting",
1126 "Composition": "composition",
1127 "Era/Cultural Reference": "era",
1128 "Material Look": "material",
1129 "Typography": "typography",
1130 }
1131
1132 out: List[str] = []
1133 for cat in cats:
1134 entry = norm.get(cat)
1135 if not entry:
1136 continue
1137
1138 prim = entry.get("Primary")
1139 secs = entry.get("Secondary", [])
1140 chosen: List[str] = []
1141 if prim:
1142 chosen.append(prim)
1143 for s in secs:
1144 if s and len(chosen) < (1 + max_secondaries):
1145 chosen.append(s)
1146 if chosen:
1147 # Format: "art style: Bauhaus, minimalist"
1148 out.append(f"{key_map.get(cat, cat)}: {', '.join(chosen)}")
1149 return out
1150
1151
1152def style_to_tone(primary: str) -> str:
1153 """
1154 Infers a general tone based on the primary style.
1155 """
1156 s = (primary or "").lower()
1157 if any(k in s for k in ["retro", "collage", "poster", "pop-art", "surreal"]):
1158 return "illustrative, textured, poster-like"
1159 if any(k in s for k in ["photoreal", "realistic", "film"]):
1160 return "photorealistic, high-detail"
1161 return "high detail, sharp focus"
1162
1163
1164# ==========================================
1165# ENTRY POINT FOR PROMPT GENERATION
1166# ==========================================
1167def generate_magic_prompt(
1168 stylesheet_json_str: str, caption: str, user_prompt: str
1169) -> str:
1170 try:
1171 sheet = json.loads(stylesheet_json_str)
1172 except Exception as e:
1173 # Fallback if parsing fails
1174 return f"{user_prompt}. The image features: {caption}"
1175
1176 # 1. CLEAN CAPTION: Remove "Style" words, but KEEP "Color" words.
1177 # We do NOT run any color removal function here.
1178 cleaned_caption = remove_style_words(caption)
1179
1180 # 2. NORMALIZE SHEET: Get the structure from JSON
1181 norm = normalize_sheet(sheet)
1182 subject = extract_subject(cleaned_caption)
1183
1184 # 3. EXTRACT STYLESHEET FEATURES (Ignoring Color)
1185 style_phrases = pick_style_phrases(norm)
1186
1187 # 4. INFER TONE
1188 style_primary = (norm.get("Style") or {}).get("Primary", "")
1189 tone_hint = style_to_tone(style_primary)
1190
1191 # 5. CONSTRUCT PROMPT
1192 style_instruction = ""
1193 if style_phrases:
1194 style_instruction = "Style elements: " + "; ".join(style_phrases)
1195 # Note: We deliberately do NOT add color palette here.
1196
1197 prompt_lines: List[str] = [
1198 f"Main subject: {subject}.",
1199 f"Description: {cleaned_caption}.",
1200 ]
1201
1202 if user_prompt:
1203 prompt_lines.append(f"User instruction: {user_prompt}.")
1204
1205 if style_instruction:
1206 prompt_lines.append(f"{style_instruction}.")
1207
1208 prompt_lines += [
1209 f"Tone: {tone_hint}.",
1210 "Note: Colors should be derived strictly from the Description.",
1211 "Avoid: blurry, deformed, text, watermark, extra limbs, artifacts.",
1212 ]
1213
1214 prompt = "\n".join(prompt_lines)
1215
1216 payload = {
1217 "prompt": prompt,
1218 "negative_prompt": "blurry, low resolution, deformed, text, watermark, extra limbs, artifacts",
1219 "options": DEFAULT_OPTIONS,
1220 "provenance": {"generated_from": "final_output"},
1221 "weights": {"caption": 0.7, "style_sheet": 0.2, "user": 0.1},
1222 }
1223
1224 # Return concatenated prompt and payload as string
1225 return prompt + " " + json.dumps(payload)