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