44
55import secrets
66import time as tym
7+ import atexit
8+ from ColorCalculus import DeepColorMath
79
810class RandomColorHex :
911 """Stateful random color generator.
@@ -22,29 +24,21 @@ class RandomColorHex:
2224 """
2325 AllTheColors = []
2426 EpochCount = {'S' :0 ,'M' :0 ,'L' :0 ,'SL' :0 }
25- EpochSize = {'S' :8400 ,'M' :770 ,'L' :220 ,'SL' :36 }
27+ EpochSize = {'S' :663 ,'M' :68 ,'L' :40 ,'SL' :23 }
28+ _auto_reset_registered = False
2629
2730 @classmethod
28- def reset (cls ):
31+ def _reset (cls ):
2932 """Clear class-level state that tracks previously used colors."""
3033 cls .AllTheColors .clear ()
34+ cls .EpochCount = {'S' :0 ,'M' :0 ,'L' :0 ,'SL' :0 }
3135
3236 @classmethod
33- def _preflight_cycle_reset (cls ):
34- """Reset rules:
35- - If CycleResetInt is odd ⇒ reset before doing anything.
36- - If CycleResetInt == 10 ⇒ reset and wrap to 0 before doing anything.
37- """
38- if cls .CycleResetInt == 10 :
39- cls .reset ()
40- cls .CycleResetInt = 0 # wrap
41- elif cls .CycleResetInt % 2 == 1 :
42- cls .reset ()
43-
44- @classmethod
45- def _bump_cycle (cls ):
46- """Advance 0→1→…→10→0."""
47- cls .CycleResetInt = (cls .CycleResetInt + 1 ) % 11
37+ def _register_auto_reset (cls ):
38+ """Register automatic reset on program exit."""
39+ if not cls ._auto_reset_registered :
40+ atexit .register (cls ._reset )
41+ cls ._auto_reset_registered = True
4842
4943 def __init__ (self ):
5044 """Initialize internal buffers and near-white masks.
@@ -58,6 +52,8 @@ def __init__(self):
5852 """
5953 self .RandomHexCode = [] #So you can access the code later for any instance
6054 self .NearWhiteMasks = ['FHFHFH' ,'FXFXFX' ,'FHFHFX' ,'XFHFHF' ,'EHFHFH' ,'HHHHHH' ] #neutral, warm, cool, very light gray
55+ self ._register_auto_reset ()
56+ self .MassProduction = False
6157
6258 def MatchesMask (self , hex6 , mask ):
6359 """Return True if the 6-char hex string matches a mask.
@@ -101,6 +97,8 @@ def AreColorsClose(self, InputColor, MetricBar):
10197 Uses Euclidean distance in RGB space between the candidate color and
10298 all entries in `self.AllTheColors`. If any distance ≤ `MetricBar`, the
10399 color is considered “too close”.
100+
101+ OUTDATED, USE ONLY FOR BASICMAIN
104102 """
105103 R1 = int (InputColor [0 :2 ],16 )
106104 G1 = int (InputColor [2 :4 ],16 )
@@ -115,6 +113,16 @@ def AreColorsClose(self, InputColor, MetricBar):
115113 return True
116114 return False
117115
116+ def AreColorsClosePerceptual (self , InputColor , threshold ):
117+ """Return True if `InputColor` is within perceptual `threshold` of any prior color.
118+
119+ Uses CIEDE2000 perceptual distance from ColorCalculus.
120+ """
121+ for prev in self .AllTheColors :
122+ if DeepColorMath .ciede2000 (InputColor , prev ) < threshold :
123+ return True
124+ return False
125+
118126 def IsNearWhite (self , hex6 :str ):
119127 """Return True if the color is near white / very light.
120128
@@ -204,16 +212,16 @@ def BasicMain(SuperLightColorsAllowed=True, SuperDarkColorsAllowed=True):
204212 RandomColorHex .EpochCount ['S' ]+= 1
205213 return out
206214
207- def main (self , SuperLightColorsAllowed = True , SuperDarkColorsAllowed = True ,HowDifferentShouldColorsBe = 's ' ):
215+ def main (self , SuperLightColorsAllowed = True , SuperDarkColorsAllowed = True ,HowDifferentShouldColorsBe = 'm ' ):
208216 match HowDifferentShouldColorsBe :
209217 case 'M' | 'm' :
210- MetricBar = 25 ; mode = 'M'
218+ mode = 'M' ; PerceptualThreshold = 25
211219 case 'S' | 's' :
212- MetricBar = 10 ; mode = 'S'
220+ mode = 'S' ; PerceptualThreshold = 10
213221 case "L" | "l" :
214- MetricBar = 40 ; mode = 'L'
222+ mode = 'L' ; PerceptualThreshold = 30
215223 case "SL" | "sl" | "sL" | "Sl" :
216- MetricBar = 80 ; mode = 'SL'
224+ mode = 'SL' ; PerceptualThreshold = 40
217225 case _:
218226 raise ValueError ('Invalid HowDifferentShouldColorsBe parameter! Please type "s" (small), "m" (medium), "l" (large), or "sl" (super large).' )
219227 if RandomColorHex .EpochCount [mode ]>= RandomColorHex .EpochSize [mode ]:
@@ -228,20 +236,30 @@ def main(self, SuperLightColorsAllowed=True, SuperDarkColorsAllowed=True,HowDiff
228236 "Note! It seems you're generating a lot of colors. The algorithm will keep searching, "
229237 "but it's going to take a while!\n "
230238 "This may be because the distance metric is too large (HowDifferentShouldColorsBe).\n "
231- "Generally, anything over 220 colors with L set up starts having trouble.\n "
232- "Super Large starts having trouble at 36 \n "
233- "Small can do ~8400 \n "
234- "Medium can do ~770 \n "
239+ "Generally, anything over 40 colors with L set up starts having trouble.\n "
240+ "Super Large starts having trouble at 23 \n "
241+ "Small can do ~663 \n "
242+ "Medium can do ~68 \n "
235243 "For quicker results, please use either BasicMain() or HowDifferentShouldColorsBe='S' or 'M'."
236244 )
237245 OneNotice = False
246+
247+ if (tym .time ()- start )> 80 or self .MassProduction :
248+ self .MassProduction = True
249+ print ("Timeout reached (80 seconds). Switching to BasicMain mode for remaining colors." )
250+ #Generate color without distance checking
251+ return self .BasicMain (SuperLightColorsAllowed = SuperLightColorsAllowed , SuperDarkColorsAllowed = SuperDarkColorsAllowed )
252+
238253 OutputtedString = '' .join (self .RandomHexCode )
239254 if not SuperLightColorsAllowed and self .IsNearWhite (OutputtedString ):
240- self .RandomHex (); continue
255+ self .RandomHex ()
256+ continue
241257 if not SuperDarkColorsAllowed and self .IsNearBlack (OutputtedString ):
242- self .RandomHex (); continue
243- if self .AreColorsClose (OutputtedString , MetricBar ):
244- self .RandomHex (); continue
258+ self .RandomHex ()
259+ continue
260+ if self .AreColorsClosePerceptual (OutputtedString , PerceptualThreshold ):
261+ self .RandomHex ()
262+ continue
245263 break
246264 self .AllTheColors .append ('' .join (self .RandomHexCode ))
247265 self .RandomHexCode .insert (0 ,'#' )
@@ -295,6 +313,8 @@ def John_3_Verse_16():
295313 c = RandomColorHex ()
296314 print (c .main ())
297315 print (c .main (SuperLightColorsAllowed = False , SuperDarkColorsAllowed = False , HowDifferentShouldColorsBe = 'm' ))
316+ for index in range (5000 ):
317+ print (f"{ index } , { c .main (HowDifferentShouldColorsBe = 'm' )} " )
298318 print (c .BasicMain ())
299319 c .Credits ()
300320 c .Help ()
0 commit comments