2020
2121# Darkest and lightest tone of each block. The shades in between are taken from
2222# the source's own brightness, which is what keeps the speckle pattern.
23+ WHITE = (0xFF , 0xFF , 0xFF )
24+ DARK = (0x1A , 0x14 , 0x0A )
25+
26+ # dark tone, light tone, glyph. Pass None for the glyph to have it chosen by
27+ # contrast, which is the safe default for a new ramp.
2328RAMPS = {
2429 # Gold is a flatter block in game, so its ramp is deliberately short: the
25- # same speckle at full strength reads as sand.
26- "gold" : ((0xC2 , 0x9E , 0x1A ), (0xFA , 0xF0 , 0x62 )),
27- # Stone is speckled exactly like this, so it takes the full contrast.
28- "stone" : ((0x5E , 0x5E , 0x5E ), (0xA8 , 0xA8 , 0xA8 )),
30+ # same speckle at full strength reads as sand. The glyph is dark because
31+ # the white one it inherited from dirt vanished against yellow.
32+ "gold" : ((0xC2 , 0x9E , 0x1A ), (0xFA , 0xF0 , 0x62 ), DARK ),
33+ # Stone is speckled exactly like this, so it takes the full contrast. Its
34+ # glyph is white by choice rather than by the contrast rule, which would
35+ # pick dark here.
36+ "stone" : ((0x5E , 0x5E , 0x5E ), (0xA8 , 0xA8 , 0xA8 ), WHITE ),
2937}
3038
3139
@@ -58,14 +66,15 @@ def glyph_color(dark, light):
5866def is_texture (pixel ):
5967 """True for block pixels, false for the glyph drawn on top of them.
6068
61- The glyph is white or near-grey; every texture tone is saturated, so the
62- spread between the channels separates the two cleanly.
69+ The glyph is pure white and nothing else in the source is. Judging by
70+ saturation instead looks reasonable and is wrong: dirt has seven near-grey
71+ speckles scattered across the block, which are texture, and treating them
72+ as glyph left dark flecks all over the recoloured gold.
6373 """
64- r , g , b = pixel [:3 ]
65- return max (r , g , b ) - min (r , g , b ) > 25
74+ return tuple (pixel [:3 ]) != WHITE
6675
6776
68- def recolor (image , dark , light ):
77+ def recolor (image , dark , light , glyph = None ):
6978 # Read through tobytes rather than getdata, which Pillow has deprecated in
7079 # favour of a method too new to rely on here.
7180 raw = image .tobytes ()
@@ -75,26 +84,13 @@ def recolor(image, dark, light):
7584 high = max (luminance (p ) for p in texture )
7685 span = high - low or 1
7786
78- glyph = glyph_color (dark , light )
79- glyph_low = min (luminance (p ) for p in pixels if not is_texture (p ))
80- glyph_high = max (luminance (p ) for p in pixels if not is_texture (p ))
81- glyph_span = glyph_high - glyph_low or 1
87+ glyph = glyph or glyph_color (dark , light )
8288
8389 out = []
8490 for pixel in pixels :
91+ # The glyph is one flat tone, so it is replaced rather than mapped.
8592 if not is_texture (pixel ):
86- # The glyph has a lighter body and a darker edge. Both are moved
87- # together so the edge keeps softening the corners as before.
88- t = (luminance (pixel ) - glyph_low ) / glyph_span
89- edge = tuple (round (c * 0.55 + 128 * 0.45 ) for c in glyph )
90- out .append (
91- (
92- round (edge [0 ] + (glyph [0 ] - edge [0 ]) * t ),
93- round (edge [1 ] + (glyph [1 ] - edge [1 ]) * t ),
94- round (edge [2 ] + (glyph [2 ] - edge [2 ]) * t ),
95- pixel [3 ],
96- )
97- )
93+ out .append ((* glyph , pixel [3 ]))
9894 continue
9995 t = (luminance (pixel ) - low ) / span
10096 out .append (
@@ -113,9 +109,9 @@ def recolor(image, dark, light):
113109
114110def main ():
115111 source = Image .open (SOURCE ).convert ("RGBA" )
116- for name , (dark , light ) in RAMPS .items ():
112+ for name , (dark , light , glyph ) in RAMPS .items ():
117113 path = os .path .join ("variants" , f"{ name } .png" )
118- recolor (source , dark , light ).save (path )
114+ recolor (source , dark , light , glyph ).save (path )
119115 print (f"wrote { path } " )
120116 source .close ()
121117
0 commit comments