import math
[docs]
class BaseColour( object ):
r_8 = 0
g_8 = 0
b_8 = 0
a_8 = 255
@property
def r( self ) -> float:
return self.r_8/255
@property
def g( self ) -> float:
return self.g_8/255
@property
def b( self ) -> float:
return self.b_8/255
@property
def a( self ) -> float:
return self.a_8/255
@property
def chroma( self ) -> float:
M = max( self.r, self.g, self.b )
m = min( self.r, self.g, self.b )
return M-m
@property
def luma( self ) -> float:
return 0.299*self.r + 0.587*self.g + 0.114*self.b
@property
def rgba( self ):
return (self.r_8, self.g_8, self.b_8, self.a_8)
[docs]
def set_rgb( self, r_8, g_8, b_8 ):
self.r_8 = r_8
self.g_8 = g_8
self.b_8 = b_8
return self
[docs]
def set_a( self, a_8 ):
self.a_8 = a_8
return self
[docs]
def set_rgba( self, r_8, g_8, b_8, a_8 ):
self.r_8 = r_8
self.g_8 = g_8
self.b_8 = b_8
self.a_8 = a_8
return self
[docs]
def clone_data( self, source ):
assert isinstance( source, BaseColour )
self.r_8 = source.r_8
self.g_8 = source.g_8
self.b_8 = source.b_8
self.a_8 = source.a_8
@property
def repr( self ):
return '#{:02X}{:02X}{:02X}{:02X}'.format( self.r_8, self.g_8, self.b_8, self.a_8 )
[docs]
def print( self, *args, **kwargs ):
print( self.ansi_format( *args, **kwargs ) )
def __eq__( self, other ):
return (self.r_8 == other.r_8) and (self.g_8 == other.g_8) and (self.b_8 == other.b_8) and (self.a_8 == other.a_8)
[docs]
class White( BaseColour ):
r_8 = 255
g_8 = 255
b_8 = 255
[docs]
class Black( BaseColour ):
r_8 = 0
g_8 = 0
b_8 = 0
[docs]
class Transparent( BaseColour ):
a_8 = 0
[docs]
def normalise_rgba( raw_colour ):
if raw_colour is None:
return (0, 0, 0, 0)
elif hasattr( raw_colour, 'rgba' ):
return raw_colour.rgba
elif len( raw_colour ) == 3:
return (raw_colour[0], raw_colour[1], raw_colour[2], 255)
elif len( raw_colour ) == 4:
return (raw_colour[0], raw_colour[1], raw_colour[2], raw_colour[3])
raise ValueError( 'raw_colour must be either None, a BaseColour, or a tuple (RGB/RGBA)' )
[docs]
def to_palette_bytes( palette, stride=3, order=(0, 1, 2) ):
assert stride >= max( order )
assert min( order ) >= 0
blanks = tuple((0 for i in range( stride-max( order )-1 )))
ORDER_MAP = {0: 'r_8', 1: 'g_8', 2: 'b_8', 3: 'a_8'}
channel = lambda c, o: getattr( c, ORDER_MAP[o] )
return bytes( itertools.chain( *(tuple((channel( c, o ) for o in order))+blanks for c in palette) ) )
[docs]
def from_palette_bytes( palette_bytes, stride=3, order=(0, 1, 2) ):
assert stride >= max( order )
assert min( order ) >= 0
assert len( order ) in (1, 3, 4)
result = []
for i in range( math.floor( len( palette_bytes )/stride ) ):
if len( order ) == 1:
colour = BaseColour().set_rgb( palette_bytes[stride*i+order[0]], palette_bytes[stride*i+order[0]], palette_bytes[stride*i+order[0]] )
elif len( order ) == 3:
colour = BaseColour().set_rgb( palette_bytes[stride*i+order[0]], palette_bytes[stride*i+order[1]], palette_bytes[stride*i+order[2]] )
elif len( order ) == 4:
colour = BaseColour().set_rgba( palette_bytes[stride*i+order[0]], palette_bytes[stride*i+order[1]], palette_bytes[stride*i+order[2]], palette_bytes[stride*i+order[3]] )
result.append( colour )
return result
[docs]
def mix( a, b, alpha ):
return (b-a)*alpha + a
[docs]
def mix_line( points, alpha ):
count = len( points ) - 1
if alpha == 1:
return points[-1]
return mix(
points[math.floor( alpha*count )],
points[math.floor( alpha*count )+1],
math.fmod( alpha*count, 1 )
)
[docs]
def mix_colour( col_a, col_b, alpha ):
r = round( mix( col_a.r_8, col_b.r_8, alpha ) )
g = round( mix( col_a.g_8, col_b.g_8, alpha ) )
b = round( mix( col_a.b_8, col_b.b_8, alpha ) )
a = round( mix( col_a.a_8, col_b.a_8, alpha ) )
return BaseColour().set_rgb( r, g, b ).set_a( a )
[docs]
def mix_colour_line( points, alpha ):
count = len( points ) - 1
if alpha == 1:
return points[-1]
return mix_colour(
points[math.floor( alpha*count )],
points[math.floor( alpha*count )+1],
math.fmod( alpha*count, 1 )
)
TEST_PALETTE_POINTS = [
BaseColour().set_rgb( 0x00, 0x00, 0x00 ),
BaseColour().set_rgb( 0x70, 0x34, 0x00 ),
BaseColour().set_rgb( 0xe8, 0x6c, 0x00 ),
BaseColour().set_rgb( 0xf0, 0xb0, 0x40 ),
BaseColour().set_rgb( 0xf8, 0xec, 0xa0 ),
]
[docs]
def gradient_to_palette( points=TEST_PALETTE_POINTS, size=256 ):
return [mix_colour_line( points, i/max(size-1, 1) ) for i in range( size )]
TEST_PALETTE = gradient_to_palette()