././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739419103.2991571 spectra-0.1.0/0000755000076500000240000000000014753266737012413 5ustar00jeremystaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418307.0 spectra-0.1.0/LICENSE.txt0000644000076500000240000000207614753265303014227 0ustar00jeremystaffThe MIT License (MIT) Copyright (c) 2015, Jeremy Singer-Vine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418307.0 spectra-0.1.0/MANIFEST.in0000644000076500000240000000007714753265303014141 0ustar00jeremystaffinclude LICENSE.txt include README.md include requirements.txt ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1739419103.298821 spectra-0.1.0/PKG-INFO0000644000076500000240000000163714753266737013517 0ustar00jeremystaffMetadata-Version: 2.2 Name: spectra Version: 0.1.0 Summary: Color scales and color conversion made easy for Python. Home-page: http://github.com/jsvine/spectra Author: Jeremy Singer-Vine Author-email: jsvine@gmail.com License: MIT Keywords: color colors colorspace scale spectrum Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 License-File: LICENSE.txt Requires-Dist: colormath2>=3.0.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: requires-dist Dynamic: summary ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418492.0 spectra-0.1.0/README.md0000644000076500000240000001203014753265574013664 0ustar00jeremystaff# Spectra Spectra is a Python library that makes color math, color scales, and color-space conversion easy. Support for: - Color scales - Color ranges - Color blending - Brightening/darkening colors - Saturating/desaturating colors - Conversion to/from multiple [color spaces](http://en.wikipedia.org/wiki/Color_space) Spectra is built on [colormath2](https://github.com/bkmgit/python-colormath2) and [grapefruit](https://github.com/xav/Grapefruit). Spectra is *enormously* inspired by [chroma.js](https://github.com/gka/chroma.js) and [d3's scales](https://github.com/mbostock/d3/wiki/Quantitative-Scales). ## Installation ```sh pip install spectra ``` ## Walkthrough See [this walkthrough](docs/walkthrough.ipynb) to see what Spectra can do. ## API ### Creating color objects from web-colors or hexcode strings ##### `spectra.html(html_string)` E.g., `spectra.html("papayawhip")`, `spectra.html("#BAABAA")` `spectra.html("#FFF")` ### Creating color objects from color space values ##### `spectra.rgb(r, g, b)` Specifically: [sRGB](http://en.wikipedia.org/wiki/SRGB) --- ##### `spectra.lab(L, a, b)` Specifically: [CIELAB](http://en.wikipedia.org/wiki/Lab_color_space#CIELAB) --- ##### `spectra.lch(L, c, h)` Also known elsewhere as "hcl" --- ##### `spectra.hsl(h, s, l)` --- ##### `spectra.hsv(h, s, v)` --- ##### `spectra.xyz(x, y, z)` --- ##### `spectra.cmy(c, m, y)` --- ##### `spectra.cmky(c, m, y, k)` --- ### Getting color values Instances of `spectra.Color` have four main properties: - __`.values`__: An array representation of the color's values in its own color space, e.g. `(L, a, b)` for an `lab` color. - __`.hexcode`__: The hex encoding of this color, e.g. `#ffffff` for `rgb(255, 255, 255)`/`html(\"white\")`. - __`.rgb`__: The `(r, g, b)` values for this color in the `rgb` color space; these are allowed to go out of gamut. - __`.clamped_rgb`__: The \"clamped\" `(r, g, b)` values for this color in the `rgb` color space. Note on `.rgb` and `.rgb_clamped`: Spectra follows [colormath](http://python-colormath.readthedocs.org/en/latest/conversions.html?highlight=clamp#rgb-conversions-and-out-of-gamut-coordinates)'s convention: > RGB spaces tend to have a smaller gamut than some of the CIE color spaces. When converting to RGB, this can cause some of the coordinates to end up being out of the acceptable range (0.0-1.0 or 1-255, depending on whether your RGB color is upscaled). [...] Rather than clamp these for you, we leave them as-is. ### Modifying colors ##### `color.to(space)` Convert this color to another color space. ```python teal_lab = spectra.html("teal").to("lab") print(teal_lab.values) >>> (48.25453959565715, -28.843707890081394, -8.48135382506432) ``` --- ##### `color.blend(other_color, ratio=0.5)` Blend this color with another color, using `ratio` of that other color. ```python yellow, red = spectra.html("red"), spectra.html("yellow") orange = yellow.blend(red) print(orange.hexcode) >>> '#ff8000' ``` --- ##### `color.brighten(amount=10)` Brighten this color by `amount` luminance. (Converts this color to the LCH color space, and then increases the `L` parameter by `amount`.) ```python teal = spectra.html("teal") light_teal = light_teal.brighten(30) print(light_teal.hexcode) >>> '#75d1d0' ``` --- ##### `color.darken(amount=10)` The opposite of `color.brighten`; *reduces* color by `amount` luminance. --- ##### `color.saturate(amount=10)` Saturate this color by `amount` chroma. (Converts this color to the LCH color space, and then increases the `C` parameter by `amount`.) --- ##### `color.desaturate(amount=10)` The opposite of `color.saturate`; *reduces* color by `amount` chroma. --- ### Creating color scales ##### `spectra.scale(colors)` `colors` should be a list of two or more colors (created by any of the methods above), web-color names, or hexcodes. Returns a `spectra.Scale` object, which translates numbers to their corresponding colors: ```python my_scale = spectra.scale([ "gray", "red" ]) halfway = my_scale(0.5) print(halfway.hexcode) >>> '#c04040' ``` --- ### Modifying color scales ##### `scale.domain(numbers)` By default, a scale's domain is [ 0, 1 ]. But you can change it to be anything else, e.g.: ```python my_scale = spectra.scale([ "gray", "red" ]).domain([ 10, 20 ]) halfway = my_scale(15) print(halfway.hexcode) >>> '#c04040' ``` --- ### Creating color ranges ##### `scale.range(count)` This function returns a list of `spectra.Color` objects evenly spaced between a scale's `colors`. For example: ```python my_scale = spectra.scale([ "gray", "red" ]) my_range = my_scale.range(5) print(my_range) >>> [, , , , ] print([ c.hexcode for c in my_range ]) >>> ['#808080', '#a06060', '#c04040', '#df2020', '#ff0000'] ``` Alternatively, as a shortcut, you can use `spectra.range(colors, count)`. --- ## Feedback/Suggestions Issues and pull requests very much appreciated. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418312.0 spectra-0.1.0/requirements.txt0000644000076500000240000000002214753265310015653 0ustar00jeremystaffcolormath2>=3.0.0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739419103.2992167 spectra-0.1.0/setup.cfg0000644000076500000240000000004614753266737014234 0ustar00jeremystaff[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418307.0 spectra-0.1.0/setup.py0000644000076500000240000000250714753265303014115 0ustar00jeremystaffimport sys, os from setuptools import setup, find_packages import subprocess NAME = "spectra" HERE = os.path.abspath(os.path.dirname(__file__)) version_ns = {} with open(os.path.join(HERE, NAME, "_version.py")) as f: exec(f.read(), {}, version_ns) with open(os.path.join(HERE, "requirements.txt")) as f: reqs = f.read().strip().split("\n") setup( name=NAME, version=version_ns["__version__"], description="Color scales and color conversion made easy for Python.", long_description="", classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6" ], keywords="color colors colorspace scale spectrum", author="Jeremy Singer-Vine", author_email="jsvine@gmail.com", url="http://github.com/jsvine/spectra", license="MIT", packages=find_packages(exclude=["test",]), namespace_packages=[], include_package_data=False, zip_safe=False, install_requires=reqs, tests_require=[], test_suite="test" ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739419103.2955709 spectra-0.1.0/spectra/0000755000076500000240000000000014753266737014054 5ustar00jeremystaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418307.0 spectra-0.1.0/spectra/__init__.py0000644000076500000240000000776314753265303016166 0ustar00jeremystafffrom .core import COLOR_SPACES, Color, Scale from ._version import __version__ def lab(L, a, b): """ Create a spectra.Color object in the CIELAB color space. :param float L: L coordinate. :param float a: a coordinate. :param float b: b coordinate. :rtype: Color :returns: A spectra.Color object in the CIELAB color space. """ return Color("lab", L, a, b) def lch(L, c, h): """ Create a spectra.Color object in the CIE LCH color space. Based on `colormath`'s LCHabColor class. :param float L: L coordinate. :param float c: c coordinate. :param float h: h coordinate. :rtype: Color :returns: A spectra.Color object in the CIE LCH color space. """ return Color("lch", L, c, h) def xyz(x, y, z): """ Create a spectra.Color object in the XYZ color space. Based on `colormath`'s LCHabColor class. :param float x: x coordinate. :param float y: y coordinate. :param float z: z coordinate. :rtype: Color :returns: A spectra.Color object in the XYZ color space. """ return Color("xyz", x, y, z) def rgb(r, g, b): """ Create a spectra.Color object in the sRGB color space. Per `colormath`: "If you pass in upscaled values, we automatically scale them down to 0.0-1.0." :param float r: r coordinate. :param float g: g coordinate. :param float b: b coordinate. :rtype: Color :returns: A spectra.Color object in the sRGB color space. """ return Color("rgb", r, g, b) def cmyk(c, m, y, k): """ Create a spectra.Color object in the CMYK color space. :param float c: c coordinate. :param float m: m coordinate. :param float y: y coordinate. :param float k: k coordinate. :rtype: Color :returns: A spectra.Color object in the CMYK color space. """ return Color("cmyk", c, m, y, k) def cmy(c, m, y): """ Create a spectra.Color object in the CMY color space. :param float c: c coordinate. :param float m: m coordinate. :param float y: y coordinate. :rtype: Color :returns: A spectra.Color object in the CMY color space. """ return Color("cmyk", c, m, y) def hsl(h, s, l): """ Create a spectra.Color object in the HSL color space. :param float h: h coordinate. :param float s: s coordinate. :param float l: l coordinate. :rtype: Color :returns: A spectra.Color object in the HSL color space. """ return Color("hsl", h, s, l) def hsv(h, s, v): """ Create a spectra.Color object in the HSV color space. :param float h: h coordinate. :param float s: s coordinate. :param float v: v coordinate. :rtype: Color :returns: A spectra.Color object in the HSV color space. """ return Color("hsv", h, s, v) def html(html_string): """ Create an RGB spectra.Color object from a web-color or hexcode. E.g.: "papayawhip", "#FFF", "#ffffff", "FFEFD5" :param str html_string: Web-color or hexcode. :rtype: Color :returns: A spectra.Color object in the sRGB color space. """ return Color.from_html(html_string) def scale(colors): """ Create a color scale, based on a list of spectra.Color objects. The most common use-case involves passing two colors (a start and an end), but any number of colors is acceptable. Instead of spectra.Color objects, you can also pass a list of web-color or hexcode strings. :param list colors: spectra.Color objects or web-color/hexcode strings. :rtype: Scale :returns: A spectra.Scale object """ return Scale(colors) def range(colors, count): """ Create a range of `count` colors between two or more base colors. Colors should be a list of spectra.Color objects, web-color strings, or hexcode strings. :param list colors: spectra.Color objects or web-color/hexcode strings. :param int count: the number of colors to return. :rtype: list :returns: A list of `count` spectra.Color objects. """ return Scale(colors).range(count) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418393.0 spectra-0.1.0/spectra/_version.py0000644000076500000240000000011014753265431016230 0ustar00jeremystaffversion_info = (0, 1, 0) __version__ = '.'.join(map(str, version_info)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418312.0 spectra-0.1.0/spectra/core.py0000644000076500000240000001620014753265310015337 0ustar00jeremystafffrom colormath2 import color_objects, color_conversions from spectra.grapefruit import Color as GC convert_color = color_conversions.convert_color COLOR_SPACES = { "lab": color_objects.LabColor, "rgb": color_objects.sRGBColor, "lch": color_objects.LCHabColor, "xyz": color_objects.XYZColor, "hsl": color_objects.HSLColor, "hsv": color_objects.HSVColor, "cmy": color_objects.CMYColor, "cmyk": color_objects.CMYKColor } class Color(object): """ Represents a color in a given color space. """ def __init__(self, space, *values): """ :param str space: Name of the color space. """ self.values = values self.space = space self.color_object = COLOR_SPACES[space](*values) _rgb = self.color_object if space == "rgb" else self.to("rgb").color_object self.rgb = _rgb.get_value_tuple() self.clamped_rgb = (_rgb.clamped_rgb_r, _rgb.clamped_rgb_g, _rgb.clamped_rgb_b) self.rbg_clamped = self.clamped_rgb @classmethod def from_html(cls, html_string): """ Create sRGB color from a web-color name or hexcode. :param str html_string: Web-color name or hexcode. :rtype: Color :returns: A spectra.Color in the sRGB color space. """ rgb = GC.NewFromHtml(html_string).rgb return cls("rgb", *rgb) def to(self, space): """ Convert color to a different color space. :param str space: Name of the color space. :rtype: Color :returns: A new spectra.Color in the given color space. """ if space == self.space: return self new_color = convert_color(self.color_object, COLOR_SPACES[space]) return self.__class__(space, *new_color.get_value_tuple()) @property def hexcode(self): """ Get this color's corresponding RGB hex. :rtype: str :returns: A six-character string. """ return COLOR_SPACES["rgb"](*self.clamped_rgb).get_rgb_hex() def blend(self, other, ratio=0.5): """ Blend this color with another color in the same color space. By default, blends the colors half-and-half (ratio: 0.5). :param Color other: The color to blend. :param float ratio: How much to blend (0 -> 1). :rtype: Color :returns: A new spectra.Color """ keep = 1.0 - ratio if not self.space == other.space: raise Exception("Colors must belong to the same color space.") values = tuple(((u * keep) + (v * ratio) for u, v in zip(self.values, other.values))) return self.__class__(self.space, *values) def brighten(self, amount=10): """ Brighten this color by `amount` luminance. Converts this color to the LCH color space, and then increases the `L` parameter by `amount`. :param float amount: Amount to increase the luminance. :rtype: Color :returns: A new spectra.Color """ lch = self.to("lch") l, c, h = lch.values new_lch = self.__class__("lch", l + amount, c, h) return new_lch.to(self.space) def darken(self, amount=10): """ Darken this color by `amount` luminance. Converts this color to the LCH color space, and then decreases the `L` parameter by `amount`. :param float amount: Amount to decrease the luminance. :rtype: Color :returns: A new spectra.Color """ return self.brighten(amount=-amount) def saturate(self, amount=10): """ Saturate this color by `amount` chroma. Converts this color to the LCH color space, and then increases the `C` parameter by `amount`. :param float amount: Amount to increase the chroma. :rtype: Color :returns: A new spectra.Color """ lch = self.to("lch") l, c, h = lch.values new_lch = self.__class__("lch", l, c + amount, h) return new_lch.to(self.space) def desaturate(self, amount=10): """ Desaturate this color by `amount` chroma. Converts this color to the LCH color space, and then decreases the `C` parameter by `amount`. :param float amount: Amount to decrease the chroma. :rtype: Color :returns: A new spectra.Color """ return self.saturate(amount=-amount) class Scale(object): """ Represents a color scale. """ def __init__(self, colors, domain=None): """ :param list colors: List of two or more spectra.Colors, or web-color/hexcode strings. :param domain: List of two or more numbers. :type domain: list or None """ _colors = [ c if isinstance(c, Color) else Color.from_html(c) for c in colors ] self.colors = _colors # Set domain n = len(_colors) self._domain = domain or [ float(x) / (n - 1) for x in range(n) ] # Check whether domain is correct length. if len(self._domain) != n: raise ValueError("len(domain) must equal len(colors)") def __call__(self, number): """ Return the color corresponding to the given `number`. :param float number: The number to color-ify. :rtype: Color :returns: A spectra.Color """ if number < self._domain[0] or number > self._domain[-1]: msg = "Number ({0}) not in domain ({1} -> {2})." raise ValueError(msg.format(number, self._domain[0], self._domain[-1])) segments = zip(self._domain[:-1], self._domain[1:]) for i, seg in enumerate(segments): x0, x1 = seg if number >= x0 and number <= x1: num_range = x1 - x0 prop = float(number - x0) / num_range return self.colors[i].blend(self.colors[i+1], prop) def domain(self, domain): """ Create a new scale with the given domain. :param list domain: A list of floats. :rtype: Scale :returns: A new color.Scale object. """ return self.__class__(self.colors, domain) def get_domain(self): """ List this scale's domain. :rtype: list :returns: A list of numbers. """ return self._domain def colorspace(self, space): """ Create a new scale in the given color space. :param str space: The new color space. :rtype: Scale :returns: A new color.Scale object. """ new_colors = [ c.to(space) for c in self.colors ] return self.__class__(new_colors, self._domain) def range(self, count): """ Create a list of colors evenly spaced along this scale's domain. :param int count: The number of colors to return. :rtype: list :returns: A list of spectra.Color objects. """ if count <= 1: raise ValueError("Range size must be greater than 1.") dom = self._domain distance = dom[-1] - dom[0] props = [ self(dom[0] + distance * float(x)/(count-1)) for x in range(count) ] return props ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418312.0 spectra-0.1.0/spectra/grapefruit.py0000644000076500000240000015414214753265310016567 0ustar00jeremystaff#!/usr/bin/python # -*- coding: utf-8 -*-# # Copyright (c) 2008, Xavier Basty # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. '''GrapeFruit - Color manipulation in Python''' from __future__ import division import sys # $Id$ __author__ = 'Xavier Basty ' __version__ = '0.1a3' # The default white reference, use 2° Standard Observer, D65 (daylight) _DEFAULT_WREF = (0.95043, 1.00000, 1.08890) _oneThird = 1.0 / 3 _srgbGammaCorrInv = 0.03928 / 12.92 _sixteenHundredsixteenth = 16.0 / 116 _RybWheel = ( 0, 26, 52, 83, 120, 130, 141, 151, 162, 177, 190, 204, 218, 232, 246, 261, 275, 288, 303, 317, 330, 338, 345, 352, 360) _RgbWheel = ( 0, 8, 17, 26, 34, 41, 48, 54, 60, 81, 103, 123, 138, 155, 171, 187, 204, 219, 234, 251, 267, 282, 298, 329, 360) class Color: '''Hold a color value. Example usage: To create an instance of the grapefruit.Color from RGB values: >>> import grapefruit >>> r, g, b = 1, 0.5, 0 >>> col = grapefruit.Color.NewFromRgb(r, g, b) To get the values of the color in another colorspace: >>> h, s, v = col.hsv >>> l, a, b = col.lab To get the complementary of a color: >>> compl = col.ComplementaryColor(mode='rgb') >>> print(compl.hsl) (210.0, 1.0, 0.5) To directly convert RGB values to their HSL equivalent: >>> h, s, l = Color.RgbToHsl(r, g, b) ''' WHITE_REFERENCE = { 'std_A' : (1.09847, 1.00000, 0.35582), 'std_B' : (0.99093, 1.00000, 0.85313), 'std_C' : (0.98071, 1.00000, 1.18225), 'std_D50' : (0.96421, 1.00000, 0.82519), 'std_D55' : (0.95680, 1.00000, 0.92148), 'std_D65' : (0.95043, 1.00000, 1.08890), 'std_D75' : (0.94972, 1.00000, 1.22639), 'std_E' : (1.00000, 1.00000, 1.00000), 'std_F1' : (0.92834, 1.00000, 1.03665), 'std_F2' : (0.99145, 1.00000, 0.67316), 'std_F3' : (1.03753, 1.00000, 0.49861), 'std_F4' : (1.09147, 1.00000, 0.38813), 'std_F5' : (0.90872, 1.00000, 0.98723), 'std_F6' : (0.97309, 1.00000, 0.60191), 'std_F7' : (0.95017, 1.00000, 1.08630), 'std_F8' : (0.96413, 1.00000, 0.82333), 'std_F9' : (1.00365, 1.00000, 0.67868), 'std_F10' : (0.96174, 1.00000, 0.81712), 'std_F11' : (1.00899, 1.00000, 0.64262), 'std_F12' : (1.08046, 1.00000, 0.39228), 'sup_A' : (1.11142, 1.00000, 0.35200), 'sup_B' : (0.99178, 1.00000, 0.84349), 'sup_C' : (0.97286, 1.00000, 1.16145), 'sup_D50' : (0.96721, 1.00000, 0.81428), 'sup_D55' : (0.95797, 1.00000, 0.90925), 'sup_D65' : (0.94810, 1.00000, 1.07305), 'sup_D75' : (0.94417, 1.00000, 1.20643), 'sup_E' : (1.00000, 1.00000, 1.00000), 'sup_F1' : (0.94791, 1.00000, 1.03191), 'sup_F2' : (1.03245, 1.00000, 0.68990), 'sup_F3' : (1.08968, 1.00000, 0.51965), 'sup_F4' : (1.14961, 1.00000, 0.40963), 'sup_F5' : (0.93369, 1.00000, 0.98636), 'sup_F6' : (1.02148, 1.00000, 0.62074), 'sup_F7' : (0.95780, 1.00000, 1.07618), 'sup_F8' : (0.97115, 1.00000, 0.81135), 'sup_F9' : (1.02116, 1.00000, 0.67826), 'sup_F10' : (0.99001, 1.00000, 0.83134), 'sup_F11' : (1.03820, 1.00000, 0.65555), 'sup_F12' : (1.11428, 1.00000, 0.40353)} NAMED_COLOR = { 'aliceblue': '#f0f8ff', 'antiquewhite': '#faebd7', 'aqua': '#00ffff', 'aquamarine': '#7fffd4', 'azure': '#f0ffff', 'beige': '#f5f5dc', 'bisque': '#ffe4c4', 'black': '#000000', 'blanchedalmond': '#ffebcd', 'blue': '#0000ff', 'blueviolet': '#8a2be2', 'brown': '#a52a2a', 'burlywood': '#deb887', 'cadetblue': '#5f9ea0', 'chartreuse': '#7fff00', 'chocolate': '#d2691e', 'coral': '#ff7f50', 'cornflowerblue': '#6495ed', 'cornsilk': '#fff8dc', 'crimson': '#dc143c', 'cyan': '#00ffff', 'darkblue': '#00008b', 'darkcyan': '#008b8b', 'darkgoldenrod': '#b8860b', 'darkgray': '#a9a9a9', 'darkgrey': '#a9a9a9', 'darkgreen': '#006400', 'darkkhaki': '#bdb76b', 'darkmagenta': '#8b008b', 'darkolivegreen': '#556b2f', 'darkorange': '#ff8c00', 'darkorchid': '#9932cc', 'darkred': '#8b0000', 'darksalmon': '#e9967a', 'darkseagreen': '#8fbc8f', 'darkslateblue': '#483d8b', 'darkslategray': '#2f4f4f', 'darkslategrey': '#2f4f4f', 'darkturquoise': '#00ced1', 'darkviolet': '#9400d3', 'deeppink': '#ff1493', 'deepskyblue': '#00bfff', 'dimgray': '#696969', 'dimgrey': '#696969', 'dodgerblue': '#1e90ff', 'firebrick': '#b22222', 'floralwhite': '#fffaf0', 'forestgreen': '#228b22', 'fuchsia': '#ff00ff', 'gainsboro': '#dcdcdc', 'ghostwhite': '#f8f8ff', 'gold': '#ffd700', 'goldenrod': '#daa520', 'gray': '#808080', 'grey': '#808080', 'green': '#008000', 'greenyellow': '#adff2f', 'honeydew': '#f0fff0', 'hotpink': '#ff69b4', 'indianred': '#cd5c5c', 'indigo': '#4b0082', 'ivory': '#fffff0', 'khaki': '#f0e68c', 'lavender': '#e6e6fa', 'lavenderblush': '#fff0f5', 'lawngreen': '#7cfc00', 'lemonchiffon': '#fffacd', 'lightblue': '#add8e6', 'lightcoral': '#f08080', 'lightcyan': '#e0ffff', 'lightgoldenrodyellow': '#fafad2', 'lightgreen': '#90ee90', 'lightgray': '#d3d3d3', 'lightgrey': '#d3d3d3', 'lightpink': '#ffb6c1', 'lightsalmon': '#ffa07a', 'lightseagreen': '#20b2aa', 'lightskyblue': '#87cefa', 'lightslategray': '#778899', 'lightslategrey': '#778899', 'lightsteelblue': '#b0c4de', 'lightyellow': '#ffffe0', 'lime': '#00ff00', 'limegreen': '#32cd32', 'linen': '#faf0e6', 'magenta': '#ff00ff', 'maroon': '#800000', 'mediumaquamarine': '#66cdaa', 'mediumblue': '#0000cd', 'mediumorchid': '#ba55d3', 'mediumpurple': '#9370db', 'mediumseagreen': '#3cb371', 'mediumslateblue': '#7b68ee', 'mediumspringgreen': '#00fa9a', 'mediumturquoise': '#48d1cc', 'mediumvioletred': '#c71585', 'midnightblue': '#191970', 'mintcream': '#f5fffa', 'mistyrose': '#ffe4e1', 'moccasin': '#ffe4b5', 'navajowhite': '#ffdead', 'navy': '#000080', 'oldlace': '#fdf5e6', 'olive': '#808000', 'olivedrab': '#6b8e23', 'orange': '#ffa500', 'orangered': '#ff4500', 'orchid': '#da70d6', 'palegoldenrod': '#eee8aa', 'palegreen': '#98fb98', 'paleturquoise': '#afeeee', 'palevioletred': '#db7093', 'papayawhip': '#ffefd5', 'peachpuff': '#ffdab9', 'peru': '#cd853f', 'pink': '#ffc0cb', 'plum': '#dda0dd', 'powderblue': '#b0e0e6', 'purple': '#800080', 'red': '#ff0000', 'rosybrown': '#bc8f8f', 'royalblue': '#4169e1', 'saddlebrown': '#8b4513', 'salmon': '#fa8072', 'sandybrown': '#f4a460', 'seagreen': '#2e8b57', 'seashell': '#fff5ee', 'sienna': '#a0522d', 'silver': '#c0c0c0', 'skyblue': '#87ceeb', 'slateblue': '#6a5acd', 'slategray': '#708090', 'slategrey': '#708090', 'snow': '#fffafa', 'springgreen': '#00ff7f', 'steelblue': '#4682b4', 'tan': '#d2b48c', 'teal': '#008080', 'thistle': '#d8bfd8', 'tomato': '#ff6347', 'turquoise': '#40e0d0', 'violet': '#ee82ee', 'wheat': '#f5deb3', 'white': '#ffffff', 'whitesmoke': '#f5f5f5', 'yellow': '#ffff00', 'yellowgreen': '#9acd32'} def __init__(self, values, mode='rgb', alpha=1.0, wref=_DEFAULT_WREF): '''Instantiate a new grapefruit.Color object. Parameters: :values: The values of this color, in the specified representation. :mode: The representation mode used for values. :alpha: the alpha value (transparency) of this color. :wref: The whitepoint reference, default is 2° D65. ''' if not(isinstance(values, tuple)): raise TypeError('values must be a tuple') if mode=='rgb': self.__rgb = values self.__hsl = Color.RgbToHsl(*values) elif mode=='hsl': self.__hsl = values self.__rgb = Color.HslToRgb(*values) else: raise ValueError('Invalid color mode: ' + mode) self.__a = alpha self.__wref = wref def __ne__(self, other): return not self.__eq__(other) def __eq__(self, other): try: if isinstance(other, Color): return (self.__rgb==other.__rgb) and (self.__a==other.__a) if len(other) != 4: return False return list(self.__rgb + (self.__a,)) == list(other) except TypeError: return False except AttributeError: return False def __repr__(self): return str(self.__rgb + (self.__a,)) def __str__(self): '''A string representation of this grapefruit.Color instance. Returns: The RGBA representation of this grapefruit.Color instance. ''' return '(%g, %g, %g, %g)' % (self.__rgb + (self.__a,)) if sys.version_info[0] < 3: def __unicode__(self): '''A unicode string representation of this grapefruit.Color instance. Returns: The RGBA representation of this grapefruit.Color instance. ''' return unicode('%g, %g, %g, %g)') % (self.__rgb + (self.__a,)) def __iter__(self): return iter(self.__rgb + (self.__a,)) def __len__(self): return 4 def __GetIsLegal(self): return all(0.0 <= v <= 1.0 for v in self) isLegal = property(fget=__GetIsLegal, doc='Boolean indicating whether the color is within the legal gamut.') def __GetNearestLegal(self): def clamp(x, lo, hi): if x < lo: return lo elif x > hi: return hi else: return x return Color.NewFromRgb(*[clamp(v, 0.0, 1.0) for v in self]) nearestLegal = property(fget=__GetNearestLegal, doc='The nearest legal color.') @staticmethod def RgbToHsl(r, g, b): '''Convert the color from RGB coordinates to HSL. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (h, s, l) tuple in the range: h[0...360], s[0...1], l[0...1] >>> Color.RgbToHsl(1, 0.5, 0) (30.0, 1.0, 0.5) ''' minVal = min(r, g, b) # min RGB value maxVal = max(r, g, b) # max RGB value l = (maxVal + minVal) / 2.0 if minVal==maxVal: return (0.0, 0.0, l) # achromatic (gray) d = maxVal - minVal # delta RGB value if l < 0.5: s = d / (maxVal + minVal) else: s = d / (2.0 - maxVal - minVal) dr, dg, db = [(maxVal-val) / d for val in (r, g, b)] if r==maxVal: h = db - dg elif g==maxVal: h = 2.0 + dr - db else: h = 4.0 + dg - dr h = (h*60.0) % 360.0 return (h, s, l) @staticmethod def _HueToRgb(n1, n2, h): h %= 6.0 if h < 1.0: return n1 + ((n2-n1) * h) if h < 3.0: return n2 if h < 4.0: return n1 + ((n2-n1) * (4.0 - h)) return n1 @staticmethod def HslToRgb(h, s, l): '''Convert the color from HSL coordinates to RGB. Parameters: :h: The Hue component value [0...1] :s: The Saturation component value [0...1] :l: The Lightness component value [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> Color.HslToRgb(30.0, 1.0, 0.5) (1.0, 0.5, 0.0) ''' if s==0: return (l, l, l) # achromatic (gray) if l<0.5: n2 = l * (1.0 + s) else: n2 = l+s - (l*s) n1 = (2.0 * l) - n2 h /= 60.0 hueToRgb = Color._HueToRgb r = hueToRgb(n1, n2, h + 2) g = hueToRgb(n1, n2, h) b = hueToRgb(n1, n2, h - 2) return (r, g, b) @staticmethod def RgbToHsv(r, g, b): '''Convert the color from RGB coordinates to HSV. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (h, s, v) tuple in the range: h[0...360], s[0...1], v[0...1] >>> Color.RgbToHsv(1, 0.5, 0) (30.0, 1.0, 1.0) ''' v = float(max(r, g, b)) d = v - min(r, g, b) if d==0: return (0.0, 0.0, v) s = d / v dr, dg, db = [(v - val) / d for val in (r, g, b)] if r==v: h = db - dg # between yellow & magenta elif g==v: h = 2.0 + dr - db # between cyan & yellow else: # b==v h = 4.0 + dg - dr # between magenta & cyan h = (h*60.0) % 360.0 return (h, s, v) @staticmethod def HsvToRgb(h, s, v): '''Convert the color from RGB coordinates to HSV. Parameters: :h: The Hus component value [0...1] :s: The Saturation component value [0...1] :v: The Value component [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> Color.HslToRgb(30.0, 1.0, 0.5) (1.0, 0.5, 0.0) ''' if s==0: return (v, v, v) # achromatic (gray) h /= 60.0 h = h % 6.0 i = int(h) f = h - i if not(i&1): f = 1-f # if i is even m = v * (1.0 - s) n = v * (1.0 - (s * f)) if i==0: return (v, n, m) if i==1: return (n, v, m) if i==2: return (m, v, n) if i==3: return (m, n, v) if i==4: return (n, m, v) return (v, m, n) @staticmethod def RgbToYiq(r, g, b): '''Convert the color from RGB to YIQ. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (y, i, q) tuple in the range: y[0...1], i[0...1], q[0...1] >>> '(%g, %g, %g)' % Color.RgbToYiq(1, 0.5, 0) '(0.592263, 0.458874, -0.0499818)' ''' y = (r * 0.29895808) + (g * 0.58660979) + (b *0.11443213) i = (r * 0.59590296) - (g * 0.27405705) - (b *0.32184591) q = (r * 0.21133576) - (g * 0.52263517) + (b *0.31129940) return (y, i, q) @staticmethod def YiqToRgb(y, i, q): '''Convert the color from YIQ coordinates to RGB. Parameters: :y: Tte Y component value [0...1] :i: The I component value [0...1] :q: The Q component value [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.YiqToRgb(0.592263, 0.458874, -0.0499818) '(1, 0.5, 5.442e-07)' ''' r = y + (i * 0.9562) + (q * 0.6210) g = y - (i * 0.2717) - (q * 0.6485) b = y - (i * 1.1053) + (q * 1.7020) return (r, g, b) @staticmethod def RgbToYuv(r, g, b): '''Convert the color from RGB coordinates to YUV. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (y, u, v) tuple in the range: y[0...1], u[-0.436...0.436], v[-0.615...0.615] >>> '(%g, %g, %g)' % Color.RgbToYuv(1, 0.5, 0) '(0.5925, -0.29156, 0.357505)' ''' y = (r * 0.29900) + (g * 0.58700) + (b * 0.11400) u = -(r * 0.14713) - (g * 0.28886) + (b * 0.43600) v = (r * 0.61500) - (g * 0.51499) - (b * 0.10001) return (y, u, v) @staticmethod def YuvToRgb(y, u, v): '''Convert the color from YUV coordinates to RGB. Parameters: :y: The Y component value [0...1] :u: The U component value [-0.436...0.436] :v: The V component value [-0.615...0.615] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.YuvToRgb(0.5925, -0.2916, 0.3575) '(0.999989, 0.500015, -6.3276e-05)' ''' r = y + (v * 1.13983) g = y - (u * 0.39465) - (v * 0.58060) b = y + (u * 2.03211) return (r, g, b) @staticmethod def RgbToXyz(r, g, b): '''Convert the color from sRGB to CIE XYZ. The methods assumes that the RGB coordinates are given in the sRGB colorspace (D65). .. note:: Compensation for the sRGB gamma correction is applied before converting. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (x, y, z) tuple in the range: x[0...1], y[0...1], z[0...1] >>> '(%g, %g, %g)' % Color.RgbToXyz(1, 0.5, 0) '(0.488941, 0.365682, 0.0448137)' ''' r, g, b = [((v <= 0.03928) and [v / 12.92] or [((v+0.055) / 1.055) **2.4])[0] for v in (r, g, b)] x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805) y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722) z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505) return (x, y, z) @staticmethod def XyzToRgb(x, y, z): '''Convert the color from CIE XYZ coordinates to sRGB. .. note:: Compensation for sRGB gamma correction is applied before converting. Parameters: :x: The X component value [0...1] :y: The Y component value [0...1] :z: The Z component value [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.XyzToRgb(0.488941, 0.365682, 0.0448137) '(1, 0.5, 6.81883e-08)' ''' r = (x * 3.2406255) - (y * 1.5372080) - (z * 0.4986286) g = -(x * 0.9689307) + (y * 1.8757561) + (z * 0.0415175) b = (x * 0.0557101) - (y * 0.2040211) + (z * 1.0569959) return tuple((((v <= _srgbGammaCorrInv) and [v * 12.92] or [(1.055 * (v ** (1/2.4))) - 0.055])[0] for v in (r, g, b))) @staticmethod def XyzToLab(x, y, z, wref=_DEFAULT_WREF): '''Convert the color from CIE XYZ to CIE L*a*b*. Parameters: :x: The X component value [0...1] :y: The Y component value [0...1] :z: The Z component value [0...1] :wref: The whitepoint reference, default is 2° D65. Returns: The color as an (L, a, b) tuple in the range: L[0...100], a[-1...1], b[-1...1] >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137) '(66.9518, 0.43084, 0.739692)' >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137, Color.WHITE_REFERENCE['std_D50']) '(66.9518, 0.411663, 0.67282)' ''' # White point correction x /= wref[0] y /= wref[1] z /= wref[2] # Nonlinear distortion and linear transformation x, y, z = [((v > 0.008856) and [v**_oneThird] or [(7.787 * v) + _sixteenHundredsixteenth])[0] for v in (x, y, z)] # Vector scaling l = (116 * y) - 16 a = 5.0 * (x - y) b = 2.0 * (y - z) return (l, a, b) @staticmethod def LabToXyz(l, a, b, wref=_DEFAULT_WREF): '''Convert the color from CIE L*a*b* to CIE 1931 XYZ. Parameters: :l: The L component [0...100] :a: The a component [-1...1] :b: The a component [-1...1] :wref: The whitepoint reference, default is 2° D65. Returns: The color as an (x, y, z) tuple in the range: x[0...q], y[0...1], z[0...1] >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.43084, 0.739692) '(0.488941, 0.365682, 0.0448137)' >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.411663, 0.67282, Color.WHITE_REFERENCE['std_D50']) '(0.488941, 0.365682, 0.0448138)' ''' y = (l + 16) / 116 x = (a / 5.0) + y z = y - (b / 2.0) return tuple((((v > 0.206893) and [v**3] or [(v - _sixteenHundredsixteenth) / 7.787])[0] * w for v, w in zip((x, y, z), wref))) @staticmethod def CmykToCmy(c, m, y, k): '''Convert the color from CMYK coordinates to CMY. Parameters: :c: The Cyan component value [0...1] :m: The Magenta component value [0...1] :y: The Yellow component value [0...1] :k: The Black component value [0...1] Returns: The color as an (c, m, y) tuple in the range: c[0...1], m[0...1], y[0...1] >>> '(%g, %g, %g)' % Color.CmykToCmy(1, 0.32, 0, 0.5) '(1, 0.66, 0.5)' ''' mk = 1-k return ((c*mk + k), (m*mk + k), (y*mk + k)) @staticmethod def CmyToCmyk(c, m, y): '''Convert the color from CMY coordinates to CMYK. Parameters: :c: The Cyan component value [0...1] :m: The Magenta component value [0...1] :y: The Yellow component value [0...1] Returns: The color as an (c, m, y, k) tuple in the range: c[0...1], m[0...1], y[0...1], k[0...1] >>> '(%g, %g, %g, %g)' % Color.CmyToCmyk(1, 0.66, 0.5) '(1, 0.32, 0, 0.5)' ''' k = min(c, m, y) if k==1.0: return (0.0, 0.0, 0.0, 1.0) mk = 1-k return ((c-k) / mk, (m-k) / mk, (y-k) / mk, k) @staticmethod def RgbToCmy(r, g, b): '''Convert the color from RGB coordinates to CMY. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (c, m, y) tuple in the range: c[0...1], m[0...1], y[0...1] >>> Color.RgbToCmy(1, 0.5, 0) (0, 0.5, 1) ''' return (1-r, 1-g, 1-b) @staticmethod def CmyToRgb(c, m, y): '''Convert the color from CMY coordinates to RGB. Parameters: :c: The Cyan component value [0...1] :m: The Magenta component value [0...1] :y: The Yellow component value [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> Color.CmyToRgb(0, 0.5, 1) (1, 0.5, 0) ''' return (1-c, 1-m, 1-y) @staticmethod def RgbToIntTuple(r, g, b): '''Convert the color from (r, g, b) to an int tuple. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (r, g, b) tuple in the range: r[0...255], g[0...2551], b[0...2551] >>> Color.RgbToIntTuple(1, 0.5, 0) (255, 128, 0) ''' return tuple(int(round(v*255)) for v in (r, g, b)) @staticmethod def IntTupleToRgb(intTuple): '''Convert a tuple of ints to (r, g, b). Parameters: The color as an (r, g, b) integer tuple in the range: r[0...255], g[0...255], b[0...255] Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.IntTupleToRgb((255, 128, 0)) '(1, 0.501961, 0)' ''' return tuple(v / 255 for v in intTuple) @staticmethod def RgbToHtml(r, g, b): '''Convert the color from (r, g, b) to #RRGGBB. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: A CSS string representation of this color (#RRGGBB). >>> Color.RgbToHtml(1, 0.5, 0) '#ff8000' ''' return '#%02x%02x%02x' % tuple((min(round(v*255), 255) for v in (r, g, b))) @staticmethod def HtmlToRgb(html): '''Convert the HTML color to (r, g, b). Parameters: :html: the HTML definition of the color (#RRGGBB or #RGB or a color name). Returns: The color as an (r, g, b) tuple in the range: r[0...1], g[0...1], b[0...1] Throws: :ValueError: If html is neither a known color name or a hexadecimal RGB representation. >>> '(%g, %g, %g)' % Color.HtmlToRgb('#ff8000') '(1, 0.501961, 0)' >>> '(%g, %g, %g)' % Color.HtmlToRgb('ff8000') '(1, 0.501961, 0)' >>> '(%g, %g, %g)' % Color.HtmlToRgb('#f60') '(1, 0.4, 0)' >>> '(%g, %g, %g)' % Color.HtmlToRgb('f60') '(1, 0.4, 0)' >>> '(%g, %g, %g)' % Color.HtmlToRgb('lemonchiffon') '(1, 0.980392, 0.803922)' ''' html = html.strip().lower() if html[0]=='#': html = html[1:] elif html in Color.NAMED_COLOR: html = Color.NAMED_COLOR[html][1:] if len(html)==6: rgb = html[:2], html[2:4], html[4:] elif len(html)==3: rgb = ['%c%c' % (v,v) for v in html] else: raise ValueError('input #%s is not in #RRGGBB format' % html) return tuple(((int(n, 16) / 255.0) for n in rgb)) @staticmethod def RgbToPil(r, g, b): '''Convert the color from RGB to a PIL-compatible integer. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: A PIL compatible integer (0xBBGGRR). >>> '0x%06x' % Color.RgbToPil(1, 0.5, 0) '0x0080ff' ''' r, g, b = [min(int(round(v*255)), 255) for v in (r, g, b)] return (b << 16) + (g << 8) + r @staticmethod def PilToRgb(pil): '''Convert the color from a PIL-compatible integer to RGB. Parameters: pil: a PIL compatible color representation (0xBBGGRR) Returns: The color as an (r, g, b) tuple in the range: the range: r: [0...1] g: [0...1] b: [0...1] >>> '(%g, %g, %g)' % Color.PilToRgb(0x0080ff) '(1, 0.501961, 0)' ''' r = 0xff & pil g = 0xff & (pil >> 8) b = 0xff & (pil >> 16) return tuple((v / 255.0 for v in (r, g, b))) @staticmethod def _WebSafeComponent(c, alt=False): '''Convert a color component to its web safe equivalent. Parameters: :c: The component value [0...1] :alt: If True, return the alternative value instead of the nearest one. Returns: The web safe equivalent of the component value. ''' # This sucks, but floating point between 0 and 1 is quite fuzzy... # So we just change the scale a while to make the equality tests # work, otherwise it gets wrong at some decimal far to the right. sc = c * 100.0 # If the color is already safe, return it straight away d = sc % 20 if d==0: return c # Get the lower and upper safe values l = sc - d u = l + 20 # Return the 'closest' value according to the alt flag if alt: if (sc-l) >= (u-sc): return l/100.0 else: return u/100.0 else: if (sc-l) >= (u-sc): return u/100.0 else: return l/100.0 @staticmethod def RgbToWebSafe(r, g, b, alt=False): '''Convert the color from RGB to 'web safe' RGB Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] :alt: If True, use the alternative color instead of the nearest one. Can be used for dithering. Returns: The color as an (r, g, b) tuple in the range: the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.RgbToWebSafe(1, 0.55, 0.0) '(1, 0.6, 0)' ''' webSafeComponent = Color._WebSafeComponent return tuple((webSafeComponent(v, alt) for v in (r, g, b))) @staticmethod def RgbToGreyscale(r, g, b): '''Convert the color from RGB to its greyscale equivalent Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] Returns: The color as an (r, g, b) tuple in the range: the range: r[0...1], g[0...1], b[0...1] >>> '(%g, %g, %g)' % Color.RgbToGreyscale(1, 0.8, 0) '(0.6, 0.6, 0.6)' ''' v = (r + g + b) / 3.0 return (v, v, v) @staticmethod def RgbToRyb(hue): '''Maps a hue on the RGB color wheel to Itten's RYB wheel. Parameters: :hue: The hue on the RGB color wheel [0...360] Returns: An approximation of the corresponding hue on Itten's RYB wheel. >>> Color.RgbToRyb(15) 26.0 ''' d = hue % 15 i = int(hue / 15) x0 = _RybWheel[i] x1 = _RybWheel[i+1] return x0 + (x1-x0) * d / 15 @staticmethod def RybToRgb(hue): '''Maps a hue on Itten's RYB color wheel to the standard RGB wheel. Parameters: :hue: The hue on Itten's RYB color wheel [0...360] Returns: An approximation of the corresponding hue on the standard RGB wheel. >>> Color.RybToRgb(15) 8.0 ''' d = hue % 15 i = int(hue / 15) x0 = _RgbWheel[i] x1 = _RgbWheel[i+1] return x0 + (x1-x0) * d / 15 @staticmethod def NewFromRgb(r, g, b, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified RGB values. Parameters: :r: The Red component value [0...1] :g: The Green component value [0...1] :b: The Blue component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> Color.NewFromRgb(1.0, 0.5, 0.0) (1.0, 0.5, 0.0, 1.0) >>> Color.NewFromRgb(1.0, 0.5, 0.0, 0.5) (1.0, 0.5, 0.0, 0.5) ''' return Color((r, g, b), 'rgb', alpha, wref) @staticmethod def NewFromHsl(h, s, l, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified HSL values. Parameters: :h: The Hue component value [0...1] :s: The Saturation component value [0...1] :l: The Lightness component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5) (1.0, 0.5, 0.0, 1.0) >>> Color.NewFromHsl(30, 1, 0.5, 0.5) (1.0, 0.5, 0.0, 0.5) ''' return Color((h, s, l), 'hsl', alpha, wref) @staticmethod def NewFromHsv(h, s, v, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified HSV values. Parameters: :h: The Hus component value [0...1] :s: The Saturation component value [0...1] :v: The Value component [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> Color.NewFromHsv(30, 1, 1) (1.0, 0.5, 0.0, 1.0) >>> Color.NewFromHsv(30, 1, 1, 0.5) (1.0, 0.5, 0.0, 0.5) ''' h2, s, l = Color.RgbToHsl(*Color.HsvToRgb(h, s, v)) return Color((h, s, l), 'hsl', alpha, wref) @staticmethod def NewFromYiq(y, i, q, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified YIQ values. Parameters: :y: The Y component value [0...1] :i: The I component value [0...1] :q: The Q component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05)) '(0.999902, 0.499955, -6.6905e-05, 1)' >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05, 0.5)) '(0.999902, 0.499955, -6.6905e-05, 0.5)' ''' return Color(Color.YiqToRgb(y, i, q), 'rgb', alpha, wref) @staticmethod def NewFromYuv(y, u, v, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified YUV values. Parameters: :y: The Y component value [0...1] :u: The U component value [-0.436...0.436] :v: The V component value [-0.615...0.615] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575)) '(0.999989, 0.500015, -6.3276e-05, 1)' >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575, 0.5)) '(0.999989, 0.500015, -6.3276e-05, 0.5)' ''' return Color(Color.YuvToRgb(y, u, v), 'rgb', alpha, wref) @staticmethod def NewFromXyz(x, y, z, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified CIE-XYZ values. Parameters: :x: The Red component value [0...1] :y: The Green component value [0...1] :z: The Blue component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137)) '(1, 0.5, 6.81883e-08, 1)' >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137, 0.5)) '(1, 0.5, 6.81883e-08, 0.5)' ''' return Color(Color.XyzToRgb(x, y, z), 'rgb', alpha, wref) @staticmethod def NewFromLab(l, a, b, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified CIE-LAB values. Parameters: :l: The L component [0...100] :a: The a component [-1...1] :b: The a component [-1...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692)) '(1, 0.5, 1.09491e-08, 1)' >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, wref=Color.WHITE_REFERENCE['std_D50'])) '(1.01238, 0.492011, -0.14311, 1)' >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5)) '(1, 0.5, 1.09491e-08, 0.5)' >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5, Color.WHITE_REFERENCE['std_D50'])) '(1.01238, 0.492011, -0.14311, 0.5)' ''' return Color(Color.XyzToRgb(*Color.LabToXyz(l, a, b, wref)), 'rgb', alpha, wref) @staticmethod def NewFromCmy(c, m, y, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified CMY values. Parameters: :c: The Cyan component value [0...1] :m: The Magenta component value [0...1] :y: The Yellow component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> Color.NewFromCmy(0, 0.5, 1) (1, 0.5, 0, 1.0) >>> Color.NewFromCmy(0, 0.5, 1, 0.5) (1, 0.5, 0, 0.5) ''' return Color(Color.CmyToRgb(c, m, y), 'rgb', alpha, wref) @staticmethod def NewFromCmyk(c, m, y, k, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified CMYK values. Parameters: :c: The Cyan component value [0...1] :m: The Magenta component value [0...1] :y: The Yellow component value [0...1] :k: The Black component value [0...1] :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5)) '(0, 0.34, 0.5, 1)' >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5, 0.5)) '(0, 0.34, 0.5, 0.5)' ''' return Color(Color.CmyToRgb(*Color.CmykToCmy(c, m, y, k)), 'rgb', alpha, wref) @staticmethod def NewFromHtml(html, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified HTML color definition. Parameters: :html: The HTML definition of the color (#RRGGBB or #RGB or a color name). :alpha: The color transparency [0...1], default is opaque. :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromHtml('#ff8000')) '(1, 0.501961, 0, 1)' >>> str(Color.NewFromHtml('ff8000')) '(1, 0.501961, 0, 1)' >>> str(Color.NewFromHtml('#f60')) '(1, 0.4, 0, 1)' >>> str(Color.NewFromHtml('f60')) '(1, 0.4, 0, 1)' >>> str(Color.NewFromHtml('lemonchiffon')) '(1, 0.980392, 0.803922, 1)' >>> str(Color.NewFromHtml('#ff8000', 0.5)) '(1, 0.501961, 0, 0.5)' ''' return Color(Color.HtmlToRgb(html), 'rgb', alpha, wref) @staticmethod def NewFromPil(pil, alpha=1.0, wref=_DEFAULT_WREF): '''Create a new instance based on the specified PIL color. Parameters: :pil: A PIL compatible color representation (0xBBGGRR) :alpha: The color transparency [0...1], default is opaque :wref: The whitepoint reference, default is 2° D65. Returns: A grapefruit.Color instance. >>> str(Color.NewFromPil(0x0080ff)) '(1, 0.501961, 0, 1)' >>> str(Color.NewFromPil(0x0080ff, 0.5)) '(1, 0.501961, 0, 0.5)' ''' return Color(Color.PilToRgb(pil), 'rgb', alpha, wref) def __GetAlpha(self): return self.__a alpha = property(fget=__GetAlpha, doc='The transparency of this color. 0.0 is transparent and 1.0 is fully opaque.') def __GetWRef(self): return self.__wref whiteRef = property(fget=__GetWRef, doc='the white reference point of this color.') def __GetRGB(self): return self.__rgb rgb = property(fget=__GetRGB, doc='The RGB values of this Color.') def __GetHue(self): return self.__hsl[0] hue = property(fget=__GetHue, doc='The hue of this color.') def __GetHSL(self): return self.__hsl hsl = property(fget=__GetHSL, doc='The HSL values of this Color.') def __GetHSV(self): h, s, v = Color.RgbToHsv(*self.__rgb) return (self.__hsl[0], s, v) hsv = property(fget=__GetHSV, doc='The HSV values of this Color.') def __GetYIQ(self): return Color.RgbToYiq(*self.__rgb) yiq = property(fget=__GetYIQ, doc='The YIQ values of this Color.') def __GetYUV(self): return Color.RgbToYuv(*self.__rgb) yuv = property(fget=__GetYUV, doc='The YUV values of this Color.') def __GetXYZ(self): return Color.RgbToXyz(*self.__rgb) xyz = property(fget=__GetXYZ, doc='The CIE-XYZ values of this Color.') def __GetLAB(self): return Color.XyzToLab(wref=self.__wref, *Color.RgbToXyz(*self.__rgb)) lab = property(fget=__GetLAB, doc='The CIE-LAB values of this Color.') def __GetCMY(self): return Color.RgbToCmy(*self.__rgb) cmy = property(fget=__GetCMY, doc='The CMY values of this Color.') def __GetCMYK(self): return Color.CmyToCmyk(*Color.RgbToCmy(*self.__rgb)) cmyk = property(fget=__GetCMYK, doc='The CMYK values of this Color.') def __GetIntTuple(self): return Color.RgbToIntTuple(*self.__rgb) intTuple = property(fget=__GetIntTuple, doc='This Color as a tuple of integers in the range [0...255]') def __GetHTML(self): return Color.RgbToHtml(*self.__rgb) html = property(fget=__GetHTML, doc='This Color as an HTML color definition.') def __GetPIL(self): return Color.RgbToPil(*self.__rgb) pil = property(fget=__GetPIL, doc='This Color as a PIL compatible value.') def __GetwebSafe(self): return Color.RgbToWebSafe(*self.__rgb) webSafe = property(fget=__GetwebSafe, doc='The web safe color nearest to this one (RGB).') def __GetGreyscale(self): return Color.RgbToGreyscale(*self.rgb) greyscale = property(fget=__GetGreyscale, doc='The greyscale equivalent to this color (RGB).') def ColorWithAlpha(self, alpha): '''Create a new instance based on this one with a new alpha value. Parameters: :alpha: The transparency of the new color [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromRgb(1.0, 0.5, 0.0, 1.0).ColorWithAlpha(0.5) (1.0, 0.5, 0.0, 0.5) ''' return Color(self.__rgb, 'rgb', alpha, self.__wref) def ColorWithWhiteRef(self, wref, labAsRef=False): '''Create a new instance based on this one with a new white reference. Parameters: :wref: The whitepoint reference. :labAsRef: If True, the L*a*b* values of the current instance are used as reference for the new color; otherwise, the RGB values are used as reference. Returns: A grapefruit.Color instance. >>> c = Color.NewFromRgb(1.0, 0.5, 0.0, 1.0, Color.WHITE_REFERENCE['std_D65']) >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50']) >>> c2.rgb (1.0, 0.5, 0.0) >>> '(%g, %g, %g)' % c2.whiteRef '(0.96721, 1, 0.81428)' >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'], labAsRef=True) >>> '(%g, %g, %g)' % c2.rgb '(1.01463, 0.490339, -0.148131)' >>> '(%g, %g, %g)' % c2.whiteRef '(0.96721, 1, 0.81428)' >>> '(%g, %g, %g)' % c.lab '(66.9518, 0.43084, 0.739692)' >>> '(%g, %g, %g)' % c2.lab '(66.9518, 0.43084, 0.739693)' ''' if labAsRef: l, a, b = self.__GetLAB() return Color.NewFromLab(l, a, b, self.__a, wref) else: return Color(self.__rgb, 'rgb', self.__a, wref) def ColorWithHue(self, hue): '''Create a new instance based on this one with a new hue. Parameters: :hue: The hue of the new color [0...360]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60) (1.0, 1.0, 0.0, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60).hsl (60, 1, 0.5) ''' h, s, l = self.__hsl return Color((hue, s, l), 'hsl', self.__a, self.__wref) def ColorWithSaturation(self, saturation): '''Create a new instance based on this one with a new saturation value. .. note:: The saturation is defined for the HSL mode. Parameters: :saturation: The saturation of the new color [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5) (0.75, 0.5, 0.25, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5).hsl (30, 0.5, 0.5) ''' h, s, l = self.__hsl return Color((h, saturation, l), 'hsl', self.__a, self.__wref) def ColorWithLightness(self, lightness): '''Create a new instance based on this one with a new lightness value. Parameters: :lightness: The lightness of the new color [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25) (0.5, 0.25, 0.0, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25).hsl (30, 1, 0.25) ''' h, s, l = self.__hsl return Color((h, s, lightness), 'hsl', self.__a, self.__wref) def DarkerColor(self, level): '''Create a new instance based on this one but darker. Parameters: :level: The amount by which the color should be darkened to produce the new one [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25) (0.5, 0.25, 0.0, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25).hsl (30, 1, 0.25) ''' h, s, l = self.__hsl return Color((h, s, max(l - level, 0)), 'hsl', self.__a, self.__wref) def LighterColor(self, level): '''Create a new instance based on this one but lighter. Parameters: :level: The amount by which the color should be lightened to produce the new one [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25) (1.0, 0.75, 0.5, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25).hsl (30, 1, 0.75) ''' h, s, l = self.__hsl return Color((h, s, min(l + level, 1)), 'hsl', self.__a, self.__wref) def Saturate(self, level): '''Create a new instance based on this one but more saturated. Parameters: :level: The amount by which the color should be saturated to produce the new one [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25) (0.875, 0.5, 0.125, 1.0) >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25).hsl (30, 0.75, 0.5) ''' h, s, l = self.__hsl return Color((h, min(s + level, 1), l), 'hsl', self.__a, self.__wref) def Desaturate(self, level): '''Create a new instance based on this one but less saturated. Parameters: :level: The amount by which the color should be desaturated to produce the new one [0...1]. Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25) (0.625, 0.5, 0.375, 1.0) >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25).hsl (30, 0.25, 0.5) ''' h, s, l = self.__hsl return Color((h, max(s - level, 0), l), 'hsl', self.__a, self.__wref) def WebSafeDither(self): '''Return the two websafe colors nearest to this one. Returns: A tuple of two grapefruit.Color instances which are the two web safe colors closest this one. >>> c = Color.NewFromRgb(1.0, 0.45, 0.0) >>> c1, c2 = c.WebSafeDither() >>> str(c1) '(1, 0.4, 0, 1)' >>> str(c2) '(1, 0.6, 0, 1)' ''' return ( Color(Color.RgbToWebSafe(*self.__rgb), 'rgb', self.__a, self.__wref), Color(Color.RgbToWebSafe(alt=True, *self.__rgb), 'rgb', self.__a, self.__wref)) def Gradient(self, target, steps=100): '''Create a list with the gradient colors between this and the other color. Parameters: :target: The grapefruit.Color at the other end of the gradient. :steps: The number of gradients steps to create. Returns: A list of grapefruit.Color instances. >>> c1 = Color.NewFromRgb(1.0, 0.0, 0.0, alpha=1) >>> c2 = Color.NewFromRgb(0.0, 1.0, 0.0, alpha=0) >>> c1.Gradient(c2, 3) [(0.75, 0.25, 0.0, 0.75), (0.5, 0.5, 0.0, 0.5), (0.25, 0.75, 0.0, 0.25)] ''' gradient = [] rgba1 = self.__rgb + (self.__a,) rgba2 = target.__rgb + (target.__a,) steps += 1 for n in range(1, steps): d = 1.0*n/steps r = (rgba1[0]*(1-d)) + (rgba2[0]*d) g = (rgba1[1]*(1-d)) + (rgba2[1]*d) b = (rgba1[2]*(1-d)) + (rgba2[2]*d) a = (rgba1[3]*(1-d)) + (rgba2[3]*d) gradient.append(Color((r, g, b), 'rgb', a, self.__wref)) return gradient def ComplementaryColor(self, mode='ryb'): '''Create a new instance which is the complementary color of this one. Parameters: :mode: Select which color wheel to use for the generation (ryb/rgb). Returns: A grapefruit.Color instance. >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor(mode='rgb') (0.0, 0.5, 1.0, 1.0) >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor(mode='rgb').hsl (210, 1, 0.5) ''' h, s, l = self.__hsl if mode == 'ryb': h = Color.RgbToRyb(h) h = (h+180)%360 if mode == 'ryb': h = Color.RybToRgb(h) return Color((h, s, l), 'hsl', self.__a, self.__wref) def MonochromeScheme(self): '''Return 4 colors in the same hue with varying saturation/lightness. Returns: A tuple of 4 grapefruit.Color in the same hue as this one, with varying saturation/lightness. >>> c = Color.NewFromHsl(30, 0.5, 0.5) >>> ['(%g, %g, %g)' % clr.hsl for clr in c.MonochromeScheme()] ['(30, 0.2, 0.8)', '(30, 0.5, 0.3)', '(30, 0.2, 0.6)', '(30, 0.5, 0.8)'] ''' def _wrap(x, min, thres, plus): if (x-min) < thres: return x + plus else: return x-min h, s, l = self.__hsl s1 = _wrap(s, 0.3, 0.1, 0.3) l1 = _wrap(l, 0.5, 0.2, 0.3) s2 = s l2 = _wrap(l, 0.2, 0.2, 0.6) s3 = s1 l3 = max(0.2, l + (1-l)*0.2) s4 = s l4 = _wrap(l, 0.5, 0.2, 0.3) return ( Color((h, s1, l1), 'hsl', self.__a, self.__wref), Color((h, s2, l2), 'hsl', self.__a, self.__wref), Color((h, s3, l3), 'hsl', self.__a, self.__wref), Color((h, s4, l4), 'hsl', self.__a, self.__wref)) def TriadicScheme(self, angle=120, mode='ryb'): '''Return two colors forming a triad or a split complementary with this one. Parameters: :angle: The angle between the hues of the created colors. The default value makes a triad. :mode: Select which color wheel to use for the generation (ryb/rgb). Returns: A tuple of two grapefruit.Color forming a color triad with this one or a split complementary. >>> c1 = Color.NewFromHsl(30, 1, 0.5) >>> c2, c3 = c1.TriadicScheme(mode='rgb') >>> c2.hsl (150.0, 1, 0.5) >>> c3.hsl (270.0, 1, 0.5) >>> c2, c3 = c1.TriadicScheme(angle=40, mode='rgb') >>> c2.hsl (190.0, 1, 0.5) >>> c3.hsl (230.0, 1, 0.5) ''' h, s, l = self.__hsl angle = min(angle, 120) / 2.0 if mode == 'ryb': h = Color.RgbToRyb(h) h += 180 h1 = (h - angle) % 360 h2 = (h + angle) % 360 if mode == 'ryb': h1 = Color.RybToRgb(h1) h2 = Color.RybToRgb(h2) return ( Color((h1, s, l), 'hsl', self.__a, self.__wref), Color((h2, s, l), 'hsl', self.__a, self.__wref)) def TetradicScheme(self, angle=30, mode='ryb'): '''Return three colors froming a tetrad with this one. Parameters: :angle: The angle to subtract from the adjacent colors hues [-90...90]. You can use an angle of zero to generate a square tetrad. :mode: Select which color wheel to use for the generation (ryb/rgb). Returns: A tuple of three grapefruit.Color forming a color tetrad with this one. >>> col = Color.NewFromHsl(30, 1, 0.5) >>> [c.hsl for c in col.TetradicScheme(mode='rgb', angle=30)] [(90, 1, 0.5), (210, 1, 0.5), (270, 1, 0.5)] ''' h, s, l = self.__hsl if mode == 'ryb': h = Color.RgbToRyb(h) h1 = (h + 90 - angle) % 360 h2 = (h + 180) % 360 h3 = (h + 270 - angle) % 360 if mode == 'ryb': h1 = Color.RybToRgb(h1) h2 = Color.RybToRgb(h2) h3 = Color.RybToRgb(h3) return ( Color((h1, s, l), 'hsl', self.__a, self.__wref), Color((h2, s, l), 'hsl', self.__a, self.__wref), Color((h3, s, l), 'hsl', self.__a, self.__wref)) def AnalogousScheme(self, angle=30, mode='ryb'): '''Return two colors analogous to this one. Args: :angle: The angle between the hues of the created colors and this one. :mode: Select which color wheel to use for the generation (ryb/rgb). Returns: A tuple of grapefruit.Colors analogous to this one. >>> c1 = Color.NewFromHsl(30, 1, 0.5) >>> c2, c3 = c1.AnalogousScheme(angle=60, mode='rgb') >>> c2.hsl (330, 1, 0.5) >>> c3.hsl (90, 1, 0.5) >>> c2, c3 = c1.AnalogousScheme(angle=10, mode='rgb') >>> c2.hsl (20, 1, 0.5) >>> c3.hsl (40, 1, 0.5) ''' h, s, l = self.__hsl if mode == 'ryb': h = Color.RgbToRyb(h) h += 360 h1 = (h - angle) % 360 h2 = (h + angle) % 360 if mode == 'ryb': h1 = Color.RybToRgb(h1) h2 = Color.RybToRgb(h2) return (Color((h1, s, l), 'hsl', self.__a, self.__wref), Color((h2, s, l), 'hsl', self.__a, self.__wref)) def AlphaBlend(self, other): '''Alpha-blend this color on the other one. Args: :other: The grapefruit.Color to alpha-blend with this one. Returns: A grapefruit.Color instance which is the result of alpha-blending this color on the other one. >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2) >>> c2 = Color.NewFromRgb(1, 1, 1, 0.8) >>> c3 = c1.AlphaBlend(c2) >>> str(c3) '(1, 0.875, 0.75, 0.84)' ''' # get final alpha channel fa = self.__a + other.__a - (self.__a * other.__a) # get percentage of source alpha compared to final alpha if fa==0: sa = 0 else: sa = min(1.0, self.__a/other.__a) # destination percentage is just the additive inverse da = 1.0 - sa sr, sg, sb = [v * sa for v in self.__rgb] dr, dg, db = [v * da for v in other.__rgb] return Color((sr+dr, sg+dg, sb+db), 'rgb', fa, self.__wref) def Blend(self, other, percent=0.5): '''Blend this color with the other one. Args: :other: the grapefruit.Color to blend with this one. Returns: A grapefruit.Color instance which is the result of blending this color on the other one. >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2) >>> c2 = Color.NewFromRgb(1, 1, 1, 0.6) >>> c3 = c1.Blend(c2) >>> str(c3) '(1, 0.75, 0.5, 0.4)' ''' dest = 1.0 - percent rgb = tuple(((u * percent) + (v * dest) for u, v in zip(self.__rgb, other.__rgb))) a = (self.__a * percent) + (other.__a * dest) return Color(rgb, 'rgb', a, self.__wref) def _test(): import doctest reload(doctest) doctest.testmod() if __name__=='__main__': _test() # vim: ts=2 sts=2 sw=2 et ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1739419103.298504 spectra-0.1.0/spectra.egg-info/0000755000076500000240000000000014753266737015546 5ustar00jeremystaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/PKG-INFO0000644000076500000240000000163714753266737016652 0ustar00jeremystaffMetadata-Version: 2.2 Name: spectra Version: 0.1.0 Summary: Color scales and color conversion made easy for Python. Home-page: http://github.com/jsvine/spectra Author: Jeremy Singer-Vine Author-email: jsvine@gmail.com License: MIT Keywords: color colors colorspace scale spectrum Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 License-File: LICENSE.txt Requires-Dist: colormath2>=3.0.0 Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: requires-dist Dynamic: summary ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/SOURCES.txt0000777000076500000240000000057414753266737017447 0ustar00jeremystaffLICENSE.txt MANIFEST.in README.md requirements.txt setup.py spectra/__init__.py spectra/_version.py spectra/core.py spectra/grapefruit.py spectra.egg-info/PKG-INFO spectra.egg-info/SOURCES.txt spectra.egg-info/dependency_links.txt spectra.egg-info/namespace_packages.txt spectra.egg-info/not-zip-safe spectra.egg-info/requires.txt spectra.egg-info/top_level.txt test/test_basic.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/dependency_links.txt0000777000076500000240000000000114753266737021623 0ustar00jeremystaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/namespace_packages.txt0000777000076500000240000000000114753266737022077 0ustar00jeremystaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1427036919.0 spectra-0.1.0/spectra.egg-info/not-zip-safe0000777000076500000240000000000112503555367017772 0ustar00jeremystaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/requires.txt0000777000076500000240000000002214753266737020147 0ustar00jeremystaffcolormath2>=3.0.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739419103.0 spectra-0.1.0/spectra.egg-info/top_level.txt0000777000076500000240000000001014753266737020276 0ustar00jeremystaffspectra ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1739419103.2981408 spectra-0.1.0/test/0000755000076500000240000000000014753266737013372 5ustar00jeremystaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1739418312.0 spectra-0.1.0/test/test_basic.py0000644000076500000240000000114414753265310016046 0ustar00jeremystaffimport pytest import spectra def test_polylinear(): """ via: https://github.com/jsvine/spectra/issues/4 """ colors = ['yellow', 'red', 'black'] domain = [0, 50, 100] color_scale = spectra.scale(colors).domain(domain) r = color_scale.range(5) results = [ c.hexcode for c in r ] goal = ['#ffff00', '#ff8000', '#ff0000', '#800000', '#000000'] assert(results == goal) def test_polylinear_fail(): colors = ['yellow', 'red', 'black'] domain = [ 0, 50 ] # Domain has one too few items with pytest.raises(ValueError): spectra.scale(colors).domain(domain)