11# SPDX-FileCopyrightText: 2024 Brad Barnett
22#
33# SPDX-License-Identifier: MIT
4- """
5- `palettes`
6- ====================================================
4+ """Color palette toolkit for pydisplay.
5+
6+ Provides named color sets, RGB/HSV wheels, RGB cubes, and Material Design
7+ swatches. Palettes map integer indices to display-ready color values at
8+ several bit depths (4, 8, 16, or 24).
9+
10+ Example:
11+ >>> from palettes import get_palette
12+ >>> palette = get_palette(name="wheel", length=256, saturation=1.0)
13+ >>> palette[0]
14+ >>> palette.color_name(0)
15+
16+ Attributes:
17+ WIN16: Mapping of ``0xRRGGBB`` values to Windows 16-color names. Used as
18+ the default name table for :class:`Palette` and
19+ :class:`~palettes.wheel.WheelPalette`.
720"""
821
9- # The 16 colors of the standard Windows 16-color palette
1022WIN16 = {
1123 0x000000 : "Black" ,
1224 0x000080 : "Navy" ,
2840
2941
3042def get_palette (name = "default" , ** kwargs ):
43+ """Construct a palette by logical name.
44+
45+ Args:
46+ name: Palette type. One of ``"default"`` (Windows 16-color),
47+ ``"wheel"``, ``"cube"``, or ``"material_design"``. Unknown names
48+ fall back to :class:`Palette`.
49+ **kwargs: Forwarded to the palette constructor (for example
50+ ``color_depth``, ``length``, ``size``, ``saturation``).
51+
52+ Returns:
53+ A :class:`Palette` subclass instance.
54+
55+ Example:
56+ >>> get_palette(name="cube", size=3, color_depth=16)
57+ """
3158 if name == "wheel" :
3259 from .wheel import WheelPalette as MyPalette
3360 elif name == "material_design" :
@@ -40,8 +67,21 @@ def get_palette(name="default", **kwargs):
4067
4168
4269class Palette :
43- """
44- A class to represent a color palette.
70+ """Indexed color palette with optional named color attributes.
71+
72+ Subclasses override :meth:`_get_rgb` to define how each index maps to
73+ red, green, and blue components. :meth:`__getitem__` converts those
74+ components to the configured ``color_depth``.
75+
76+ Named colors from the palette's name table (for example ``palette.RED``)
77+ are attached as attributes during initialization.
78+
79+ Args:
80+ name: Optional label stored in :attr:`name`.
81+ color_depth: Output format for :meth:`__getitem__`: ``4`` (24-bit
82+ index), ``8`` (RGB332), ``16`` (RGB565), or ``24`` (``0xRRGGBB``).
83+ swapped: If ``True``, byte-swap 16-bit colors (little-endian displays).
84+ cached: If ``True``, memoize computed index colors in an internal dict.
4585 """
4686
4787 def __init__ (self , name = "" , color_depth = 16 , swapped = False , cached = False ):
@@ -69,16 +109,32 @@ def _define_named_colors(self):
69109
70110 @property
71111 def name (self ):
112+ """Human-readable palette label."""
72113 return self ._name
73114
74115 def __iter__ (self ):
116+ """Yield each palette entry in index order."""
75117 for i in range (len (self )):
76118 yield self [i ]
77119
78120 def __len__ (self ):
121+ """Number of colors in the palette."""
79122 return self ._length
80123
81124 def __getitem__ (self , index ):
125+ """Return the color at ``index`` in the configured bit depth.
126+
127+ Negative indices and indices beyond the palette length wrap around.
128+
129+ Args:
130+ index: Color index (supports negative and out-of-range values).
131+
132+ Returns:
133+ Color value as an integer (format depends on ``color_depth``).
134+
135+ Raises:
136+ ValueError: If ``color_depth`` is not 4, 8, 16, or 24.
137+ """
82138 index = self ._normalize (index )
83139
84140 if self ._cache is not None and index in self ._cache :
@@ -101,6 +157,17 @@ def _normalize(self, index):
101157 return index
102158
103159 def color565 (self , r , g = None , b = None ):
160+ """Convert RGB to a 16-bit RGB565 value.
161+
162+ Args:
163+ r: Red component (0–255), a 24-bit ``0xRRGGBB`` integer, or an
164+ ``(r, g, b)`` sequence.
165+ g: Green component when ``r`` is passed separately.
166+ b: Blue component when ``r`` is passed separately.
167+
168+ Returns:
169+ 16-bit color, optionally byte-swapped when ``swapped`` is ``True``.
170+ """
104171 if isinstance (r , (tuple , list )):
105172 # r is a tuple or list
106173 r , g , b = r
@@ -115,6 +182,17 @@ def color565(self, r, g=None, b=None):
115182 return color
116183
117184 def color332 (self , r , g = None , b = None ):
185+ """Convert RGB to an 8-bit RGB332 value.
186+
187+ Args:
188+ r: Red component (0–255), a 24-bit ``0xRRGGBB`` integer, or an
189+ ``(r, g, b)`` sequence.
190+ g: Green component when ``r`` is passed separately.
191+ b: Blue component when ``r`` is passed separately.
192+
193+ Returns:
194+ 8-bit RGB332 color.
195+ """
118196 # Convert r, g, b to 8-bit
119197 if isinstance (r , (tuple , list )):
120198 # r is a tuple or list
@@ -127,8 +205,14 @@ def color332(self, r, g=None, b=None):
127205 return color
128206
129207 def color_rgb (self , color ):
130- """
131- color can be an 16-bit integer or a tuple, list or bytearray of length 2 or 3.
208+ """Expand a packed color to an ``(r, g, b)`` tuple.
209+
210+ Args:
211+ color: A 16-bit integer, or a 2- or 3-byte sequence in display
212+ byte order.
213+
214+ Returns:
215+ ``(red, green, blue)`` with each component in ``0``–``255``.
132216 """
133217 if isinstance (color , int ):
134218 # convert 16-bit int color to 2 bytes
@@ -142,18 +226,53 @@ def color_rgb(self, color):
142226 return (r , g , b )
143227
144228 def color_name (self , index ):
229+ """Return the name of the color at ``index``.
230+
231+ Args:
232+ index: Palette index (supports wrapping).
233+
234+ Returns:
235+ A name from the palette name table, or a ``"#RRGGBB"`` hex string
236+ when no name matches.
237+ """
145238 return self .rgb_name (self ._get_rgb (self ._normalize (index )))
146239
147240 def rgb_name (self , r , g = None , b = None ):
241+ """Look up a color name from RGB components.
242+
243+ Args:
244+ r: Red (0–255), a 24-bit integer, or an ``(r, g, b)`` sequence.
245+ g: Green when ``r`` is passed separately.
246+ b: Blue when ``r`` is passed separately.
247+
248+ Returns:
249+ Matching name from :attr:`_names`, or ``"#RRGGBB"`` if unknown.
250+ """
148251 if isinstance (r , (tuple , list )):
149252 r , g , b = r
150253 return self ._names .get (r << 16 | g << 8 | b , f"#{ r :02X} { g :02X} { b :02X} " )
151254
152255 def luminance (self , index ):
256+ """Perceived brightness of the color at ``index`` (ITU-R BT.601).
257+
258+ Args:
259+ index: Palette index.
260+
261+ Returns:
262+ Luminance in ``0.0``–``255.0``.
263+ """
153264 r , g , b = self ._get_rgb (index )
154265 return 0.299 * r + 0.587 * g + 0.114 * b
155266
156267 def brightness (self , index ):
268+ """Average channel brightness of the color at ``index``.
269+
270+ Args:
271+ index: Palette index.
272+
273+ Returns:
274+ Normalized brightness in ``0.0``–``1.0``.
275+ """
157276 r , g , b = self ._get_rgb (index )
158277 return (r + g + b ) / 3 / 255
159278
@@ -163,8 +282,17 @@ def _get_rgb(self, index):
163282
164283
165284class MappedPalette (Palette ):
166- """
167- A class to represent a color palette with a color map.
285+ """Palette backed by a flat RGB byte map.
286+
287+ Each color occupies three consecutive bytes ``(r, g, b)`` in
288+ ``color_map``. Subclasses such as :class:`~palettes.material_design.MDPalette`
289+ supply a pre-built map and named-color attributes.
290+
291+ Args:
292+ name: Optional label stored in :attr:`name`.
293+ color_depth: Output format for :meth:`Palette.__getitem__`.
294+ swapped: Byte-swap 16-bit colors when ``True``.
295+ color_map: ``bytes`` or buffer of RGB triplets, length ``3 * n_colors``.
168296 """
169297
170298 def __init__ (self , name , color_depth , swapped , color_map ):
0 commit comments