#-----------------------------------------------------------------------------# Copyright (c) Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------''' Provide a base class for representing color values.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsimportcolorsysfromabcimportABCMeta,abstractmethodfrommathimportsqrtfromreimportmatchfromtypingimportTYPE_CHECKING,TypeAlias,cast# Bokeh importsfrom..core.serializationimportAnyRep,Serializable,SerializerifTYPE_CHECKING:importnumpyasnpfromtyping_extensionsimportSelf#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('Color','ColorLike',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------RGBTuple=tuple[int,int,int]|tuple[int,int,int,float]
[docs]classColor(Serializable,metaclass=ABCMeta):''' A base class for representing color objects. '''def__repr__(self)->str:returnself.to_css()
[docs]@staticmethoddefclamp(value:float,maximum:float|None=None)->float:''' Clamp numeric values to be non-negative, an optionally, less than a given maximum. Args: value (float) : A number to clamp. maximum (float, optional) : A max bound to to clamp to. If None, there is no upper bound, and values are only clamped to be non-negative. (default: None) Returns: float '''value=max(value,0)ifmaximumisnotNone:returnmin(value,maximum)else:returnvalue
[docs]@abstractmethoddefcopy(self)->Self:''' Copy this color. *Subclasses must implement this method.* '''raiseNotImplementedError
[docs]defdarken(self,amount:float)->Self:''' Darken (reduce the luminance) of this color. *Subclasses must implement this method.* Args: amount (float) : Amount to reduce the luminance by (clamped above zero) Returns: Color '''returnself.lighten(-amount)
[docs]@classmethod@abstractmethoddeffrom_hsl(cls,value:HSL)->Self:''' Create a new color by converting from an HSL color. *Subclasses must implement this method.* Args: value (HSL) : A color to convert from HSL Returns: Color '''raiseNotImplementedError
[docs]@classmethod@abstractmethoddeffrom_rgb(cls,value:RGB)->Self:''' Create a new color by converting from an RGB color. *Subclasses must implement this method.* Args: value (:class:`~bokeh.colors.RGB`) : A color to convert from RGB Returns: Color '''raiseNotImplementedError
[docs]deflighten(self,amount:float)->Self:''' Lighten (increase the luminance) of this color. *Subclasses must implement this method.* Args: amount (float) : Amount to increase the luminance by (clamped above zero) Returns: Color '''rgb=self.to_rgb()h,l,s=colorsys.rgb_to_hls(float(rgb.r)/255,float(rgb.g)/255,float(rgb.b)/255)new_l=self.clamp(l+amount,1)r,g,b=colorsys.hls_to_rgb(h,new_l,s)rgb.r=round(r*255)rgb.g=round(g*255)rgb.b=round(b*255)returnself.from_rgb(rgb)
[docs]@abstractmethoddefto_css(self)->str:''' Return a CSS representation of this color. *Subclasses must implement this method.* Returns: str '''raiseNotImplementedError
[docs]@abstractmethoddefto_hsl(self)->HSL:''' Create a new HSL color by converting from this color. *Subclasses must implement this method.* Returns: HSL '''raiseNotImplementedError
[docs]@abstractmethoddefto_rgb(self)->RGB:''' Create a new HSL color by converting from this color. *Subclasses must implement this method.* Returns: :class:`~bokeh.colors.RGB` '''raiseNotImplementedError
classRGB(Color):''' Represent colors by specifying their Red, Green, and Blue channels. Alpha values may also optionally be provided. Otherwise, alpha values default to 1. '''r:intg:intb:inta:floatdef__init__(self,r:int|np.integer,g:int|np.integer,b:int|np.integer,a:float|np.floating=1.0)->None:''' Args: r (int) : The value for the red channel in [0, 255] g (int) : The value for the green channel in [0, 255] b (int) : The value for the blue channel in [0, 255] a (float, optional) : An alpha value for this color in [0, 1] (default: 1.0) '''self.r=cast(int,r)self.g=cast(int,g)self.b=cast(int,b)self.a=cast(float,a)defcopy(self)->RGB:''' Return a copy of this color value. Returns: :class:`~bokeh.colors.RGB` '''returnRGB(self.r,self.g,self.b,self.a)@classmethoddeffrom_hsl(cls,value:HSL)->RGB:''' Create an RGB color from an HSL color value. Args: value (HSL) : The HSL color to convert. Returns: :class:`~bokeh.colors.RGB` '''returnvalue.to_rgb()@classmethoddeffrom_hex_string(cls,hex_string:str)->RGB:''' Create an RGB color from a RGB(A) hex string. Args: hex_string (str) : String containing hex-encoded RGBA(A) values. Valid formats are '#rrggbb', '#rrggbbaa', '#rgb' and '#rgba'. Returns: :class:`~bokeh.colors.RGB` '''ifisinstance(hex_string,str):# Hex color as #rrggbbaa or #rrggbbifmatch(r"#([\da-fA-F]{2}){3,4}\Z",hex_string):r=int(hex_string[1:3],16)g=int(hex_string[3:5],16)b=int(hex_string[5:7],16)a=int(hex_string[7:9],16)/255.0iflen(hex_string)>7else1.0returnRGB(r,g,b,a)# Hex color as #rgb or #rgbaifmatch(r"#[\da-fA-F]{3,4}\Z",hex_string):r=int(hex_string[1]*2,16)g=int(hex_string[2]*2,16)b=int(hex_string[3]*2,16)a=int(hex_string[4]*2,16)/255.0iflen(hex_string)>4else1.0returnRGB(r,g,b,a)raiseValueError(f"'{hex_string}' is not an RGB(A) hex color string")@classmethoddeffrom_tuple(cls,value:RGBTuple)->RGB:''' Initialize ``RGB`` instance from a 3- or 4-tuple. '''iflen(value)==3:r,g,b=valuereturnRGB(r,g,b)else:r,g,b,a=valuereturnRGB(r,g,b,a)@classmethoddeffrom_rgb(cls,value:RGB)->RGB:''' Copy an RGB color from another RGB color value. Args: value (:class:`~bokeh.colors.RGB`) : The RGB color to copy. Returns: :class:`~bokeh.colors.RGB` '''returnvalue.copy()defto_css(self)->str:''' Generate the CSS representation of this RGB color. Returns: str, ``"rgb(...)"`` or ``"rgba(...)"`` '''ifself.a==1.0:returnf"rgb({self.r}, {self.g}, {self.b})"else:returnf"rgba({self.r}, {self.g}, {self.b}, {self.a})"defto_hex(self)->str:''' Return a hex color string for this RGB(A) color. Any alpha value is only included in the output string if it is less than 1. Returns: str, ``"#RRGGBBAA"`` if alpha is less than 1 and ``"#RRGGBB"`` otherwise '''ifself.a<1.0:returnf"#{self.r:02x}{self.g:02x}{self.b:02x}{int(round(self.a*255)):02x}"else:returnf"#{self.r:02x}{self.g:02x}{self.b:02x}"defto_hsl(self)->HSL:''' Return a corresponding HSL color for this RGB color. Returns: :class:`~bokeh.colors.HSL` '''h,l,s=colorsys.rgb_to_hls(float(self.r)/255,float(self.g)/255,float(self.b)/255)returnHSL(round(h*360),s,l,self.a)defto_rgb(self)->RGB:''' Return a RGB copy for this RGB color. Returns: :class:`~bokeh.colors.RGB` '''returnself.copy()@propertydefbrightness(self)->float:""" Perceived brightness of a color in [0, 1] range. """# http://alienryderflex.com/hsp.htmlr,g,b=self.r,self.g,self.breturnsqrt(0.299*r**2+0.587*g**2+0.114*b**2)/255@propertydefluminance(self)->float:""" Perceived luminance of a color in [0, 1] range. """# https://en.wikipedia.org/wiki/Relative_luminancer,g,b=self.r,self.g,self.breturn(0.2126*r**2.2+0.7152*g**2.2+0.0722*b**2.2)/255**2.2classHSL(Color):''' Represent colors by specifying their Hue, Saturation, and lightness. Alpha values may also optionally be provided. Otherwise, alpha values default to 1. '''def__init__(self,h:float,s:float,l:float,a:float=1.0)->None:''' Args: h (int) : The Hue, in [0, 360] s (int) : The Saturation, in [0, 1] l (int) : The lightness, in [0, 1] a (float, optional) : An alpha value for this color in [0, 1] (default: 1.0) '''self.h=hself.s=sself.l=lself.a=adefcopy(self)->HSL:''' Return a copy of this color value. Returns: :class:`~bokeh.colors.HSL` '''returnHSL(self.h,self.s,self.l,self.a)defdarken(self,amount:float)->HSL:''' Darken (reduce the luminance) of this color. Args: amount (float) : Amount to reduce the luminance by (clamped above zero) Returns: :class:`~bokeh.colors.HSL` '''returnself.lighten(-amount)@classmethoddeffrom_hsl(cls,value:HSL)->HSL:''' Copy an HSL color from another HSL color value. Args: value (HSL) : The HSL color to copy. Returns: :class:`~bokeh.colors.hsl.HSL` '''returnvalue.copy()@classmethoddeffrom_rgb(cls,value:RGB)->HSL:''' Create an HSL color from an RGB color value. Args: value (:class:`~bokeh.colors.RGB`) : The RGB color to convert. Returns: :class:`~bokeh.colors.HSL` '''returnvalue.to_hsl()defto_css(self)->str:''' Generate the CSS representation of this HSL color. Returns: str, ``"hsl(...)"`` or ``"hsla(...)"`` '''ifself.a==1.0:returnf"hsl({self.h}, {self.s*100}%, {self.l*100}%)"else:returnf"hsla({self.h}, {self.s*100}%, {self.l*100}%, {self.a})"defto_hsl(self)->HSL:''' Return a HSL copy for this HSL color. Returns: :class:`~bokeh.colors.HSL` '''returnself.copy()defto_rgb(self)->RGB:''' Return a corresponding :class:`~bokeh.colors.RGB` color for this HSL color. Returns: :class:`~bokeh.colors.RGB` '''r,g,b=colorsys.hls_to_rgb(float(self.h)/360,self.l,self.s)returnRGB(round(r*255),round(g*255),round(b*255),self.a)deflighten(self,amount:float)->HSL:''' Lighten (increase the luminance) of this color. Args: amount (float) : Amount to increase the luminance by (clamped above zero) Returns: :class:`~bokeh.colors.HSL` '''hsl=self.copy()hsl.l=self.clamp(hsl.l+amount,1)returnself.from_hsl(hsl)ColorLike:TypeAlias=str|Color|RGBTuple#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------