├── adafruit_fancyled ├── __init__.py ├── fastled_helpers.py └── adafruit_fancyled.py ├── docs ├── _static │ ├── favicon.ico │ ├── favicon.ico.license │ └── custom.css ├── api.rst.license ├── examples.rst.license ├── index.rst.license ├── requirements.txt ├── api.rst ├── examples.rst ├── index.rst └── conf.py ├── README.rst.license ├── requirements.txt ├── optional_requirements.txt ├── .gitattributes ├── .github ├── workflows │ ├── build.yml │ ├── release_pypi.yml │ ├── release_gh.yml │ └── failure-help-text.yml └── PULL_REQUEST_TEMPLATE │ └── adafruit_circuitpython_pr.md ├── .readthedocs.yaml ├── .pre-commit-config.yaml ├── LICENSE ├── LICENSES ├── MIT.txt ├── Unlicense.txt └── CC-BY-4.0.txt ├── examples ├── fancyled_cpx_rotate.py ├── fancyled_neopixel_rotate_simpletest.py └── fancyled_cpx_helper_example.py ├── pyproject.toml ├── .gitignore ├── README.rst ├── ruff.toml └── CODE_OF_CONDUCT.md /adafruit_fancyled/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_FancyLED/HEAD/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/api.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /docs/examples.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /docs/index.rst.license: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /README.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | -------------------------------------------------------------------------------- /optional_requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2018 Phillip Torrone for Adafruit Industries 2 | 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | sphinx 6 | sphinxcontrib-jquery 7 | sphinx-rtd-theme 8 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | 2 | .. If you created a package, create one automodule per module in the package. 3 | 4 | .. automodule:: adafruit_fancyled.adafruit_fancyled 5 | :members: 6 | 7 | .. automodule:: adafruit_fancyled.fastled_helpers 8 | :members: 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | .py text eol=lf 6 | .rst text eol=lf 7 | .txt text eol=lf 8 | .yaml text eol=lf 9 | .toml text eol=lf 10 | .license text eol=lf 11 | .md text eol=lf 12 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* SPDX-FileCopyrightText: 2025 Sam Blenny 2 | * SPDX-License-Identifier: MIT 3 | */ 4 | 5 | /* Monkey patch the rtd theme to prevent horizontal stacking of short items 6 | * see https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 7 | */ 8 | .py.property{display: block !important;} 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Build CI 6 | 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Run Build CI workflow 14 | uses: adafruit/workflows-circuitpython-libs/build@main 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | # Read the Docs configuration file 6 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 7 | 8 | # Required 9 | version: 2 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | 14 | build: 15 | os: ubuntu-lts-latest 16 | tools: 17 | python: "3" 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | - requirements: requirements.txt 23 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Simple test 2 | ------------ 3 | 4 | Ensure your device works with this simple test. 5 | 6 | .. literalinclude:: ../examples/fancyled_neopixel_rotate_simpletest.py 7 | :caption: examples/fancyled_neopixel_rotate_simpletest.py 8 | :linenos: 9 | 10 | .. literalinclude:: ../examples/fancyled_cpx_helper_example.py 11 | :caption: examples/fancyled_cpx_helper_example.py 12 | :linenos: 13 | 14 | .. literalinclude:: ../examples/fancyled_cpx_rotate.py 15 | :caption: examples/fancyled_cpx_rotate.py 16 | :linenos: 17 | -------------------------------------------------------------------------------- /.github/workflows/release_pypi.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: PyPI Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run PyPI Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-pypi@main 17 | with: 18 | pypi-username: ${{ secrets.pypi_username }} 19 | pypi-password: ${{ secrets.pypi_password }} 20 | -------------------------------------------------------------------------------- /.github/workflows/release_gh.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: GitHub Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run GitHub Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-gh@main 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | upload-url: ${{ github.event.release.upload_url }} 20 | -------------------------------------------------------------------------------- /.github/workflows/failure-help-text.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Failure help text 6 | 7 | on: 8 | workflow_run: 9 | workflows: ["Build CI"] 10 | types: 11 | - completed 12 | 13 | jobs: 14 | post-help: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} 17 | steps: 18 | - name: Post comment to help 19 | uses: adafruit/circuitpython-action-library-ci-failed@v1 20 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020 Diego Elio Pettenò 2 | # SPDX-FileCopyrightText: 2024 Justin Myers 3 | # 4 | # SPDX-License-Identifier: Unlicense 5 | 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v4.5.0 9 | hooks: 10 | - id: check-yaml 11 | - id: end-of-file-fixer 12 | - id: trailing-whitespace 13 | - repo: https://github.com/astral-sh/ruff-pre-commit 14 | rev: v0.3.4 15 | hooks: 16 | - id: ruff-format 17 | - id: ruff 18 | args: ["--fix"] 19 | - repo: https://github.com/fsfe/reuse-tool 20 | rev: v3.0.1 21 | hooks: 22 | - id: reuse 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | Thank you for contributing! Before you submit a pull request, please read the following. 6 | 7 | Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html 8 | 9 | If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs 10 | 11 | Before submitting the pull request, make sure you've run Pylint and Black locally on your code. You can do this manually or using pre-commit. Instructions are available here: https://adafru.it/check-your-code 12 | 13 | Please remove all of this text before submitting. Include an explanation or list of changes included in your PR, as well as, if applicable, a link to any related issues. 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | 3 | Table of Contents 4 | ================= 5 | 6 | .. toctree:: 7 | :maxdepth: 4 8 | :hidden: 9 | 10 | self 11 | 12 | .. toctree:: 13 | :caption: Examples 14 | 15 | examples 16 | 17 | .. toctree:: 18 | :caption: API Reference 19 | :maxdepth: 3 20 | 21 | api 22 | 23 | .. toctree:: 24 | :caption: Tutorials 25 | 26 | .. toctree:: 27 | :caption: Related Products 28 | 29 | .. toctree:: 30 | :caption: Other Links 31 | 32 | Download from GitHub 33 | Download Library Bundle 34 | CircuitPython Reference Documentation 35 | CircuitPython Support Forum 36 | Discord Chat 37 | Adafruit Learning System 38 | Adafruit Blog 39 | Adafruit Store 40 | 41 | Indices and tables 42 | ================== 43 | 44 | * :ref:`genindex` 45 | * :ref:`modindex` 46 | * :ref:`search` 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 PaintYourDragon for Adafruit Industries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSES/Unlicense.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute 4 | this software, either in source code form or as a compiled binary, for any 5 | purpose, commercial or non-commercial, and by any means. 6 | 7 | In jurisdictions that recognize copyright laws, the author or authors of this 8 | software dedicate any and all copyright interest in the software to the public 9 | domain. We make this dedication for the benefit of the public at large and 10 | to the detriment of our heirs and successors. We intend this dedication to 11 | be an overt act of relinquishment in perpetuity of all present and future 12 | rights to this software under copyright law. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 19 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, 20 | please refer to 21 | -------------------------------------------------------------------------------- /examples/fancyled_cpx_rotate.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # SPDX-License-Identifier: MIT 3 | 4 | """Simple FancyLED example for Circuit Playground Express""" 5 | 6 | from adafruit_circuitplayground.express import cpx 7 | 8 | import adafruit_fancyled.adafruit_fancyled as fancy 9 | 10 | cpx.pixels.auto_write = False # Refresh pixels only when we say 11 | cpx.pixels.brightness = 1.0 # We'll use FancyLED's brightness controls 12 | 13 | # Declare a 4-element color palette, this one happens to be a 14 | # 'blackbody' palette -- good for heat maps and firey effects. 15 | palette = [ 16 | fancy.CRGB(1.0, 1.0, 1.0), # White 17 | fancy.CRGB(1.0, 1.0, 0.0), # Yellow 18 | fancy.CRGB(1.0, 0.0, 0.0), # Red 19 | fancy.CRGB(0.0, 0.0, 0.0), 20 | ] # Black 21 | 22 | offset = 0 # Positional offset into color palette to get it to 'spin' 23 | levels = (0.25, 0.3, 0.15) # Color balance / brightness for gamma function 24 | 25 | while True: 26 | for i in range(10): 27 | # Load each pixel's color from the palette using an offset, run it 28 | # through the gamma function, pack RGB value and assign to pixel. 29 | color = fancy.palette_lookup(palette, offset + i / 10) 30 | color = fancy.gamma_adjust(color, brightness=levels) 31 | cpx.pixels[i] = color.pack() 32 | cpx.pixels.show() 33 | 34 | offset += 0.033 # Bigger number = faster spin 35 | -------------------------------------------------------------------------------- /examples/fancyled_neopixel_rotate_simpletest.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # SPDX-License-Identifier: MIT 3 | 4 | """Simple FancyLED example for NeoPixel strip""" 5 | 6 | import board 7 | import neopixel 8 | 9 | import adafruit_fancyled.adafruit_fancyled as fancy 10 | 11 | num_leds = 20 12 | 13 | # Declare a 6-element RGB rainbow palette 14 | palette = [ 15 | fancy.CRGB(1.0, 0.0, 0.0), # Red 16 | fancy.CRGB(0.5, 0.5, 0.0), # Yellow 17 | fancy.CRGB(0.0, 1.0, 0.0), # Green 18 | fancy.CRGB(0.0, 0.5, 0.5), # Cyan 19 | fancy.CRGB(0.0, 0.0, 1.0), # Blue 20 | fancy.CRGB(0.5, 0.0, 0.5), 21 | ] # Magenta 22 | 23 | # Declare a NeoPixel object on pin D6 with num_leds pixels, no auto-write. 24 | # Set brightness to max because we'll be using FancyLED's brightness control. 25 | pixels = neopixel.NeoPixel(board.D6, num_leds, brightness=1.0, auto_write=False) 26 | 27 | offset = 0 # Positional offset into color palette to get it to 'spin' 28 | 29 | while True: 30 | for i in range(num_leds): 31 | # Load each pixel's color from the palette using an offset, run it 32 | # through the gamma function, pack RGB value and assign to pixel. 33 | color = fancy.palette_lookup(palette, offset + i / num_leds) 34 | color = fancy.gamma_adjust(color, brightness=0.25) 35 | pixels[i] = color.pack() 36 | pixels.show() 37 | 38 | offset += 0.02 # Bigger number = faster spin 39 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | [build-system] 6 | requires = [ 7 | "setuptools", 8 | "wheel", 9 | "setuptools-scm", 10 | ] 11 | 12 | [project] 13 | name = "adafruit-circuitpython-fancyled" 14 | description = "CircuitPython FancyLED helper library for LED animations." 15 | version = "0.0.0+auto.0" 16 | readme = "README.rst" 17 | authors = [ 18 | {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} 19 | ] 20 | urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_FancyLED"} 21 | keywords = [ 22 | "adafruit", 23 | "fancyled", 24 | "fastled", 25 | "led", 26 | "animation", 27 | "rgb", 28 | "hardware", 29 | "micropython", 30 | "circuitpython", 31 | ] 32 | license = {text = "MIT"} 33 | classifiers = [ 34 | "Intended Audience :: Developers", 35 | "Topic :: Software Development :: Libraries", 36 | "Topic :: Software Development :: Embedded Systems", 37 | "Topic :: System :: Hardware", 38 | "License :: OSI Approved :: MIT License", 39 | "Programming Language :: Python :: 3", 40 | ] 41 | dynamic = ["dependencies", "optional-dependencies"] 42 | 43 | [tool.setuptools] 44 | packages = ["adafruit_fancyled"] 45 | 46 | [tool.setuptools.dynamic] 47 | dependencies = {file = ["requirements.txt"]} 48 | optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # Do not include files and directories created by your personal work environment, such as the IDE 6 | # you use, except for those already listed here. Pull requests including changes to this file will 7 | # not be accepted. 8 | 9 | # This .gitignore file contains rules for files generated by working with CircuitPython libraries, 10 | # including building Sphinx, testing with pip, and creating a virual environment, as well as the 11 | # MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. 12 | 13 | # If you find that there are files being generated on your machine that should not be included in 14 | # your git commit, you should create a .gitignore_global file on your computer to include the 15 | # files created by your personal setup. To do so, follow the two steps below. 16 | 17 | # First, create a file called .gitignore_global somewhere convenient for you, and add rules for 18 | # the files you want to exclude from git commits. 19 | 20 | # Second, configure Git to use the exclude file for all Git repositories by running the 21 | # following via commandline, replacing "path/to/your/" with the actual path to your newly created 22 | # .gitignore_global file: 23 | # git config --global core.excludesfile path/to/your/.gitignore_global 24 | 25 | # CircuitPython-specific files 26 | *.mpy 27 | 28 | # Python-specific files 29 | __pycache__ 30 | *.pyc 31 | 32 | # Sphinx build-specific files 33 | _build 34 | 35 | # This file results from running `pip -e install .` in a local repository 36 | *.egg-info 37 | 38 | # Virtual environment-specific files 39 | .env 40 | .venv 41 | 42 | # MacOS-specific files 43 | *.DS_Store 44 | 45 | # IDE-specific files 46 | .idea 47 | .vscode 48 | *~ 49 | -------------------------------------------------------------------------------- /examples/fancyled_cpx_helper_example.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # SPDX-License-Identifier: MIT 3 | 4 | """FancyLED example for Circuit Playground Express using fastled_helpers""" 5 | 6 | from adafruit_circuitplayground.express import cpx 7 | 8 | import adafruit_fancyled.fastled_helpers as helper 9 | 10 | cpx.pixels.auto_write = False # Refresh pixels only when we say 11 | 12 | # A dynamic gradient palette is a compact way of representing a palette with 13 | # non-equal spacing between elements. This one's a blackbody palette with a 14 | # longer red 'tail'. The helper functions let us declare this as a list of 15 | # bytes, so they're easier to copy over from existing FastLED projects. 16 | # fmt: off 17 | heatmap_gp = bytes([ 18 | 0, 255, 255, 255, # White 19 | 64, 255, 255, 0, # Yellow 20 | 128, 255, 0, 0, # Red 21 | 255, 0, 0, 0]) # Black 22 | # fmt: on 23 | 24 | # Convert the gradient palette into a normal palette w/16 elements: 25 | palette = helper.loadDynamicGradientPalette(heatmap_gp, 16) 26 | 27 | offset = 0 # Positional offset into color palette to get it to 'spin' 28 | 29 | while True: 30 | for i in range(10): 31 | # Load each pixel's color from the palette. FastLED uses 16-step 32 | # in-between blending...so for a 16-color palette, there's 256 33 | # steps total. With 10 pixels, multiply the pixel index by 25.5 34 | # (and add our offset) to get FastLED-style palette position. 35 | color = helper.ColorFromPalette(palette, int(offset + i * 25.5), blend=True) 36 | # Apply gamma using the FastLED helper syntax 37 | color = helper.applyGamma_video(color) 38 | # 'Pack' color and assign to NeoPixel #i 39 | cpx.pixels[i] = color.pack() 40 | cpx.pixels.show() 41 | 42 | offset += 8 # Bigger number = faster spin 43 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | Introduction 3 | ============ 4 | 5 | .. image:: https://readthedocs.org/projects/adafruit-circuitpython-fancyled/badge/?version=latest 6 | :target: https://docs.circuitpython.org/projects/fancyled/en/latest/ 7 | :alt: Documentation Status 8 | 9 | .. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg 10 | :target: https://adafru.it/discord 11 | :alt: Discord 12 | 13 | .. image:: https://github.com/adafruit/Adafruit_CircuitPython_FancyLED/workflows/Build%20CI/badge.svg 14 | :target: https://github.com/adafruit/Adafruit_CircuitPython_FancyLED/actions/ 15 | :alt: Build Status 16 | 17 | .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json 18 | :target: https://github.com/astral-sh/ruff 19 | :alt: Code Style: Ruff 20 | 21 | FancyLED is a CircuitPython library to assist in creating buttery smooth LED animation. It's loosely inspired by the FastLED library for Arduino, and in fact we have a "helper" library using similar function names to assist with porting of existing Arduino FastLED projects to CircuitPython. 22 | 23 | Dependencies 24 | ============= 25 | This driver depends on: 26 | 27 | * `Adafruit CircuitPython `_ 28 | 29 | Please ensure all dependencies are available on the CircuitPython filesystem. 30 | This is easily achieved by downloading 31 | `the Adafruit library and driver bundle `_. 32 | 33 | Installing from PyPI 34 | ==================== 35 | 36 | On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from 37 | PyPI `_. To install for current user: 38 | 39 | .. code-block:: shell 40 | 41 | pip3 install adafruit-circuitpython-fancyled 42 | 43 | To install system-wide (this may be required in some cases): 44 | 45 | .. code-block:: shell 46 | 47 | sudo pip3 install adafruit-circuitpython-fancyled 48 | 49 | To install in a virtual environment in your current project: 50 | 51 | .. code-block:: shell 52 | 53 | mkdir project-name && cd project-name 54 | python3 -m venv .venv 55 | source .venv/bin/activate 56 | pip3 install adafruit-circuitpython-fancyled 57 | 58 | Usage Example 59 | ============= 60 | 61 | See the examples in the examples/ folder. 62 | 63 | Documentation 64 | ============= 65 | 66 | API documentation for this library can be found on `Read the Docs `_. 67 | 68 | For information on building library documentation, please check out `this guide `_. 69 | 70 | Contributing 71 | ============ 72 | 73 | Contributions are welcome! Please read our `Code of Conduct 74 | `_ 75 | before contributing to help this project stay welcoming. 76 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | target-version = "py38" 6 | line-length = 100 7 | 8 | [lint] 9 | select = ["I", "PL", "UP"] 10 | 11 | extend-select = [ 12 | "D419", # empty-docstring 13 | "E501", # line-too-long 14 | "W291", # trailing-whitespace 15 | "PLC0414", # useless-import-alias 16 | "PLC2401", # non-ascii-name 17 | "PLC2801", # unnecessary-dunder-call 18 | "PLC3002", # unnecessary-direct-lambda-call 19 | "PLE0101", # return-in-init 20 | "F706", # return-outside-function 21 | "F704", # yield-outside-function 22 | "PLE0116", # continue-in-finally 23 | "PLE0117", # nonlocal-without-binding 24 | "PLE0241", # duplicate-bases 25 | "PLE0302", # unexpected-special-method-signature 26 | "PLE0604", # invalid-all-object 27 | "PLE0605", # invalid-all-format 28 | "PLE0643", # potential-index-error 29 | "PLE0704", # misplaced-bare-raise 30 | "PLE1141", # dict-iter-missing-items 31 | "PLE1142", # await-outside-async 32 | "PLE1205", # logging-too-many-args 33 | "PLE1206", # logging-too-few-args 34 | "PLE1307", # bad-string-format-type 35 | "PLE1310", # bad-str-strip-call 36 | "PLE1507", # invalid-envvar-value 37 | "PLE2502", # bidirectional-unicode 38 | "PLE2510", # invalid-character-backspace 39 | "PLE2512", # invalid-character-sub 40 | "PLE2513", # invalid-character-esc 41 | "PLE2514", # invalid-character-nul 42 | "PLE2515", # invalid-character-zero-width-space 43 | "PLR0124", # comparison-with-itself 44 | "PLR0202", # no-classmethod-decorator 45 | "PLR0203", # no-staticmethod-decorator 46 | "UP004", # useless-object-inheritance 47 | "PLR0206", # property-with-parameters 48 | "PLR0904", # too-many-public-methods 49 | "PLR0911", # too-many-return-statements 50 | "PLR0912", # too-many-branches 51 | "PLR0913", # too-many-arguments 52 | "PLR0914", # too-many-locals 53 | "PLR0915", # too-many-statements 54 | "PLR0916", # too-many-boolean-expressions 55 | "PLR1702", # too-many-nested-blocks 56 | "PLR1704", # redefined-argument-from-local 57 | "PLR1711", # useless-return 58 | "C416", # unnecessary-comprehension 59 | "PLR1733", # unnecessary-dict-index-lookup 60 | "PLR1736", # unnecessary-list-index-lookup 61 | 62 | # ruff reports this rule is unstable 63 | #"PLR6301", # no-self-use 64 | 65 | "PLW0108", # unnecessary-lambda 66 | "PLW0120", # useless-else-on-loop 67 | "PLW0127", # self-assigning-variable 68 | "PLW0129", # assert-on-string-literal 69 | "B033", # duplicate-value 70 | "PLW0131", # named-expr-without-context 71 | "PLW0245", # super-without-brackets 72 | "PLW0406", # import-self 73 | "PLW0602", # global-variable-not-assigned 74 | "PLW0603", # global-statement 75 | "PLW0604", # global-at-module-level 76 | 77 | # fails on the try: import typing used by libraries 78 | #"F401", # unused-import 79 | 80 | "F841", # unused-variable 81 | "E722", # bare-except 82 | "PLW0711", # binary-op-exception 83 | "PLW1501", # bad-open-mode 84 | "PLW1508", # invalid-envvar-default 85 | "PLW1509", # subprocess-popen-preexec-fn 86 | "PLW2101", # useless-with-lock 87 | "PLW3301", # nested-min-max 88 | ] 89 | 90 | ignore = [ 91 | "PLR2004", # magic-value-comparison 92 | "UP030", # format literals 93 | "PLW1514", # unspecified-encoding 94 | "UP007", # Use `X | Y` for type annotations 95 | 96 | ] 97 | 98 | [format] 99 | line-ending = "lf" 100 | -------------------------------------------------------------------------------- /adafruit_fancyled/fastled_helpers.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 PaintYourDragon for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_fancyled.fastled_helpers` 7 | ==================================================== 8 | 9 | CircuitPython "helper" library based on the Arduino FastLED library. 10 | Uses similar function names to assist with porting of existing Arduino FastLED 11 | projects to CircuitPython. 12 | 13 | * Author(s): PaintYourDragon 14 | """ 15 | 16 | # imports 17 | 18 | __version__ = "0.0.0+auto.0" 19 | __repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_FancyLED.git" 20 | 21 | from math import floor 22 | 23 | from adafruit_fancyled import adafruit_fancyled as fancy 24 | 25 | # These are helper functions that provide more FastLED-like calls for 26 | # fancyled functions. 27 | 28 | GFACTOR = 2.5 # Default gamma-correction factor for function below 29 | 30 | 31 | def applyGamma_video(n, g_r=GFACTOR, g_g=None, g_b=None, inplace=False): 32 | """Approximates various invocations of FastLED's many-ways-overloaded 33 | applyGamma_video() function. 34 | 35 | ACCEPTS: One of three ways: 36 | 1. A single brightness level (0-255) and optional gamma-correction 37 | factor (float usu. > 1.0, default if unspecified is 2.5). 38 | 2. A single CRGB, CHSV or packed integer type and optional gamma 39 | factor or separate R, G, B gamma values. 40 | 3. A list of CRGB, CHSV or packed integer types (and optional gamma(s)). 41 | 42 | In the tuple/list cases, the 'inplace' flag determines whether 43 | a new tuple/list is calculated and returned, or the existing 44 | value is modified in-place. By default this is 'False'. 45 | Can also use the napplyGamma_video() function to more directly 46 | approximate FastLED syntax/behavior. 47 | 48 | RETURNS: Corresponding to above cases: 49 | 1. Single gamma-corrected brightness level (0-255). 50 | 2. A gamma-corrected CRGB value (even if input is CHSV or packed). 51 | 3. A list of gamma-corrected CRGB values. 52 | 53 | In the tuple/list cases, there is NO return value if 'inplace' 54 | is true -- the original values are modified. 55 | """ 56 | 57 | # If single gamma value is passed, keep that, otherwise convert 58 | # gamma values to tuple for gamma_adjust function. 59 | if g_g is not None and g_b is not None: 60 | g_r = (g_r, g_g, g_b) 61 | 62 | return fancy.gamma_adjust(n, g_r, inplace=inplace) 63 | 64 | 65 | def napplyGamma_video(n, g_r=GFACTOR, g_g=None, g_b=None): 66 | """In-place version of applyGamma_video() (to mimic FastLED function 67 | name). This is for RGB tuples and tuple lists (not the prior function's 68 | integer case) 69 | """ 70 | 71 | return applyGamma_video(n, g_r, g_g, g_b, inplace=True) 72 | 73 | 74 | def loadDynamicGradientPalette(src, size): 75 | """Kindasorta like FastLED's loadDynamicGradientPalette() function, 76 | with some gotchas. 77 | 78 | ACCEPTS: Gradient palette data as a 'bytes' type (makes it easier to copy 79 | over gradient palettes from existing FastLED Arduino sketches)... 80 | each palette entry is four bytes: a relative position (0-255) 81 | within the overall resulting palette (whatever its size), and 82 | 3 values for R, G and B...and a length for a new palette list 83 | to be allocated. 84 | 85 | RETURNS: list of CRGB colors. 86 | """ 87 | 88 | # Convert gradient from bytelist (groups of 4) to list of tuples, 89 | # each consisting of a position (0.0 to 1.0) and CRGB color. 90 | # (This is what FancyLED's expand_gradient needs for input.) 91 | grad = [] 92 | for i in range(0, len(src), 4): 93 | grad.append((src[i] / 255.0, fancy.CRGB(src[i + 1], src[i + 2], src[i + 3]))) 94 | 95 | # Create palette (CRGB list) matching 'size' length 96 | return fancy.expand_gradient(grad, size) 97 | 98 | 99 | def ColorFromPalette(pal, pos, brightness=255, blend=False): 100 | """Approximates the FastLED ColorFromPalette() function 101 | 102 | ACCEPTS: color palette (list of CRGB, CSHV and/or packed ints), 103 | palette index (x16) + blend factor of next index (0-15) -- 104 | e.g. pass 32 to retrieve palette index 2, or 40 for an 105 | interpolated value between palette index 2 and 3, optional 106 | brightness (0-255), optional blend flag (True/False) 107 | 108 | RETURNS: CRGB color, no gamma correction 109 | """ 110 | 111 | # Alter 'pos' from FastLED-like behavior to fancyled range 112 | if blend: 113 | # Continuous interpolation 0.0 to 1.0 114 | pos = (pos / 16.0) / len(pal) 115 | else: 116 | # No blending -- quantize to nearest palette bin 117 | pos = floor(pos / 16.0) / len(pal) 118 | 119 | color = fancy.palette_lookup(pal, pos) 120 | 121 | if brightness < 1.0: 122 | brightness /= 255.0 123 | if isinstance(color, fancy.CHSV): 124 | color = fancy.CRGB(color) 125 | elif isinstance(color, int): 126 | color = fancy.unpack(color) 127 | color.red *= brightness 128 | color.green *= brightness 129 | color.blue *= brightness 130 | 131 | return color 132 | 133 | 134 | def hsv2rgb_spectrum(hue, sat, val): 135 | """This is named the same thing as FastLED's simpler HSV to RGB function 136 | (spectrum, vs rainbow) but implementation is a bit different for the 137 | sake of getting something running (adapted from some NeoPixel code). 138 | 139 | ACCEPTS: hue, saturation, value in range 0 to 255 140 | RETURNS: CRGB color. 141 | """ 142 | 143 | return fancy.CRGB(fancy.CHSV(hue / 255, sat / 255, val / 255)) 144 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | import datetime 6 | import os 7 | import sys 8 | 9 | sys.path.insert(0, os.path.abspath("..")) 10 | 11 | # -- General configuration ------------------------------------------------ 12 | 13 | # Add any Sphinx extension module names here, as strings. They can be 14 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 15 | # ones. 16 | extensions = [ 17 | "sphinx.ext.autodoc", 18 | "sphinxcontrib.jquery", 19 | "sphinx.ext.intersphinx", 20 | "sphinx.ext.viewcode", 21 | ] 22 | 23 | # Uncomment the below if you use native CircuitPython modules such as 24 | # digitalio, micropython and busio. List the modules you use. Without it, the 25 | # autodoc module docs will fail to generate with a warning. 26 | # autodoc_mock_imports = ["digitalio", "busio"] 27 | 28 | intersphinx_mapping = { 29 | "python": ("https://docs.python.org/3", None), 30 | "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), 31 | } 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ["_templates"] 35 | 36 | source_suffix = ".rst" 37 | 38 | # The master toctree document. 39 | master_doc = "index" 40 | 41 | # General information about the project. 42 | project = "Adafruit fancyled Library" 43 | creation_year = "2017" 44 | current_year = str(datetime.datetime.now().year) 45 | year_duration = ( 46 | current_year if current_year == creation_year else creation_year + " - " + current_year 47 | ) 48 | copyright = year_duration + " PaintYourDragon" 49 | author = "PaintYourDragon" 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = "1.0" 57 | # The full version, including alpha/beta/rc tags. 58 | release = "1.0" 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | # 63 | # This is also used if you do content translation via gettext catalogs. 64 | # Usually you set "language" from the command line for these cases. 65 | language = "en" 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | # This patterns also effect to html_static_path and html_extra_path 70 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] 71 | 72 | # The reST default role (used for this markup: `text`) to use for all 73 | # documents. 74 | # 75 | default_role = "any" 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | # 79 | add_function_parentheses = True 80 | 81 | # The name of the Pygments (syntax highlighting) style to use. 82 | pygments_style = "sphinx" 83 | 84 | # If true, `todo` and `todoList` produce output, else they produce nothing. 85 | todo_include_todos = False 86 | 87 | # If this is True, todo emits a warning for each TODO entries. The default is False. 88 | todo_emit_warnings = True 89 | 90 | 91 | # -- Options for HTML output ---------------------------------------------- 92 | 93 | # The theme to use for HTML and HTML Help pages. See the documentation for 94 | # a list of builtin themes. 95 | # 96 | import sphinx_rtd_theme 97 | 98 | html_theme = "sphinx_rtd_theme" 99 | 100 | # Add any paths that contain custom static files (such as style sheets) here, 101 | # relative to this directory. They are copied after the builtin static files, 102 | # so a file named "default.css" will overwrite the builtin "default.css". 103 | html_static_path = ["_static"] 104 | 105 | # Include extra css to work around rtd theme glitches 106 | html_css_files = ["custom.css"] 107 | 108 | # The name of an image file (relative to this directory) to use as a favicon of 109 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 110 | # pixels large. 111 | # 112 | html_favicon = "_static/favicon.ico" 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = "AdafruitFancyledLibrarydoc" 116 | 117 | # -- Options for LaTeX output --------------------------------------------- 118 | 119 | latex_elements = { 120 | # The paper size ('letterpaper' or 'a4paper'). 121 | # 122 | # 'papersize': 'letterpaper', 123 | # The font size ('10pt', '11pt' or '12pt'). 124 | # 125 | # 'pointsize': '10pt', 126 | # Additional stuff for the LaTeX preamble. 127 | # 128 | # 'preamble': '', 129 | # Latex figure (float) alignment 130 | # 131 | # 'figure_align': 'htbp', 132 | } 133 | 134 | # Grouping the document tree into LaTeX files. List of tuples 135 | # (source start file, target name, title, 136 | # author, documentclass [howto, manual, or own class]). 137 | latex_documents = [ 138 | ( 139 | master_doc, 140 | "AdafruitfancyledLibrary.tex", 141 | "Adafruitfancyled Library Documentation", 142 | author, 143 | "manual", 144 | ) 145 | ] 146 | 147 | # -- Options for manual page output --------------------------------------- 148 | 149 | # One entry per manual page. List of tuples 150 | # (source start file, name, description, authors, manual section). 151 | man_pages = [ 152 | ( 153 | master_doc, 154 | "Adafruitfancyledlibrary", 155 | "Adafruit fancyled Library Documentation", 156 | [author], 157 | 1, 158 | ) 159 | ] 160 | 161 | # -- Options for Texinfo output ------------------------------------------- 162 | 163 | # Grouping the document tree into Texinfo files. List of tuples 164 | # (source start file, target name, title, author, 165 | # dir menu entry, description, category) 166 | texinfo_documents = [ 167 | ( 168 | master_doc, 169 | "AdafruitfancyledLibrary", 170 | "Adafruit fancyled Library Documentation", 171 | author, 172 | "AdafruitfancyledLibrary", 173 | "One line description of project.", 174 | "Miscellaneous", 175 | ) 176 | ] 177 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # Adafruit Community Code of Conduct 8 | 9 | ## Our Pledge 10 | 11 | In the interest of fostering an open and welcoming environment, we as 12 | contributors and leaders pledge to making participation in our project and 13 | our community a harassment-free experience for everyone, regardless of age, body 14 | size, disability, ethnicity, gender identity and expression, level or type of 15 | experience, education, socio-economic status, nationality, personal appearance, 16 | race, religion, or sexual identity and orientation. 17 | 18 | ## Our Standards 19 | 20 | We are committed to providing a friendly, safe and welcoming environment for 21 | all. 22 | 23 | Examples of behavior that contributes to creating a positive environment 24 | include: 25 | 26 | * Be kind and courteous to others 27 | * Using welcoming and inclusive language 28 | * Being respectful of differing viewpoints and experiences 29 | * Collaborating with other community members 30 | * Gracefully accepting constructive criticism 31 | * Focusing on what is best for the community 32 | * Showing empathy towards other community members 33 | 34 | Examples of unacceptable behavior by participants include: 35 | 36 | * The use of sexualized language or imagery and sexual attention or advances 37 | * The use of inappropriate images, including in a community member's avatar 38 | * The use of inappropriate language, including in a community member's nickname 39 | * Any spamming, flaming, baiting or other attention-stealing behavior 40 | * Excessive or unwelcome helping; answering outside the scope of the question 41 | asked 42 | * Trolling, insulting/derogatory comments, and personal or political attacks 43 | * Promoting or spreading disinformation, lies, or conspiracy theories against 44 | a person, group, organisation, project, or community 45 | * Public or private harassment 46 | * Publishing others' private information, such as a physical or electronic 47 | address, without explicit permission 48 | * Other conduct which could reasonably be considered inappropriate 49 | 50 | The goal of the standards and moderation guidelines outlined here is to build 51 | and maintain a respectful community. We ask that you don’t just aim to be 52 | "technically unimpeachable", but rather try to be your best self. 53 | 54 | We value many things beyond technical expertise, including collaboration and 55 | supporting others within our community. Providing a positive experience for 56 | other community members can have a much more significant impact than simply 57 | providing the correct answer. 58 | 59 | ## Our Responsibilities 60 | 61 | Project leaders are responsible for clarifying the standards of acceptable 62 | behavior and are expected to take appropriate and fair corrective action in 63 | response to any instances of unacceptable behavior. 64 | 65 | Project leaders have the right and responsibility to remove, edit, or 66 | reject messages, comments, commits, code, issues, and other contributions 67 | that are not aligned to this Code of Conduct, or to ban temporarily or 68 | permanently any community member for other behaviors that they deem 69 | inappropriate, threatening, offensive, or harmful. 70 | 71 | ## Moderation 72 | 73 | Instances of behaviors that violate the Adafruit Community Code of Conduct 74 | may be reported by any member of the community. Community members are 75 | encouraged to report these situations, including situations they witness 76 | involving other community members. 77 | 78 | You may report in the following ways: 79 | 80 | In any situation, you may send an email to . 81 | 82 | On the Adafruit Discord, you may send an open message from any channel 83 | to all Community Moderators by tagging @community moderators. You may 84 | also send an open message from any channel, or a direct message to 85 | @kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, 86 | @sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175. 87 | 88 | Email and direct message reports will be kept confidential. 89 | 90 | In situations on Discord where the issue is particularly egregious, possibly 91 | illegal, requires immediate action, or violates the Discord terms of service, 92 | you should also report the message directly to Discord. 93 | 94 | These are the steps for upholding our community’s standards of conduct. 95 | 96 | 1. Any member of the community may report any situation that violates the 97 | Adafruit Community Code of Conduct. All reports will be reviewed and 98 | investigated. 99 | 2. If the behavior is an egregious violation, the community member who 100 | committed the violation may be banned immediately, without warning. 101 | 3. Otherwise, moderators will first respond to such behavior with a warning. 102 | 4. Moderators follow a soft "three strikes" policy - the community member may 103 | be given another chance, if they are receptive to the warning and change their 104 | behavior. 105 | 5. If the community member is unreceptive or unreasonable when warned by a 106 | moderator, or the warning goes unheeded, they may be banned for a first or 107 | second offense. Repeated offenses will result in the community member being 108 | banned. 109 | 110 | ## Scope 111 | 112 | This Code of Conduct and the enforcement policies listed above apply to all 113 | Adafruit Community venues. This includes but is not limited to any community 114 | spaces (both public and private), the entire Adafruit Discord server, and 115 | Adafruit GitHub repositories. Examples of Adafruit Community spaces include 116 | but are not limited to meet-ups, audio chats on the Adafruit Discord, or 117 | interaction at a conference. 118 | 119 | This Code of Conduct applies both within project spaces and in public spaces 120 | when an individual is representing the project or its community. As a community 121 | member, you are representing our community, and are expected to behave 122 | accordingly. 123 | 124 | ## Attribution 125 | 126 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 127 | version 1.4, available at 128 | , 129 | and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). 130 | 131 | For other projects adopting the Adafruit Community Code of 132 | Conduct, please contact the maintainers of those projects for enforcement. 133 | If you wish to use this code of conduct for your own project, consider 134 | explicitly mentioning your moderation policy or making a copy with your 135 | own moderation policy so as to avoid confusion. 136 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Creative Commons Corporation 2 | ("Creative Commons") is not a law firm and does not provide legal services 3 | or legal advice. Distribution of Creative Commons public licenses does not 4 | create a lawyer-client or other relationship. Creative Commons makes its licenses 5 | and related information available on an "as-is" basis. Creative Commons gives 6 | no warranties regarding its licenses, any material licensed under their terms 7 | and conditions, or any related information. Creative Commons disclaims all 8 | liability for damages resulting from their use to the fullest extent possible. 9 | 10 | Using Creative Commons Public Licenses 11 | 12 | Creative Commons public licenses provide a standard set of terms and conditions 13 | that creators and other rights holders may use to share original works of 14 | authorship and other material subject to copyright and certain other rights 15 | specified in the public license below. The following considerations are for 16 | informational purposes only, are not exhaustive, and do not form part of our 17 | licenses. 18 | 19 | Considerations for licensors: Our public licenses are intended for use by 20 | those authorized to give the public permission to use material in ways otherwise 21 | restricted by copyright and certain other rights. Our licenses are irrevocable. 22 | Licensors should read and understand the terms and conditions of the license 23 | they choose before applying it. Licensors should also secure all rights necessary 24 | before applying our licenses so that the public can reuse the material as 25 | expected. Licensors should clearly mark any material not subject to the license. 26 | This includes other CC-licensed material, or material used under an exception 27 | or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors 28 | 29 | Considerations for the public: By using one of our public licenses, a licensor 30 | grants the public permission to use the licensed material under specified 31 | terms and conditions. If the licensor's permission is not necessary for any 32 | reason–for example, because of any applicable exception or limitation to copyright–then 33 | that use is not regulated by the license. Our licenses grant only permissions 34 | under copyright and certain other rights that a licensor has authority to 35 | grant. Use of the licensed material may still be restricted for other reasons, 36 | including because others have copyright or other rights in the material. A 37 | licensor may make special requests, such as asking that all changes be marked 38 | or described. Although not required by our licenses, you are encouraged to 39 | respect those requests where reasonable. More considerations for the public 40 | : wiki.creativecommons.org/Considerations_for_licensees Creative Commons Attribution 41 | 4.0 International Public License 42 | 43 | By exercising the Licensed Rights (defined below), You accept and agree to 44 | be bound by the terms and conditions of this Creative Commons Attribution 45 | 4.0 International Public License ("Public License"). To the extent this Public 46 | License may be interpreted as a contract, You are granted the Licensed Rights 47 | in consideration of Your acceptance of these terms and conditions, and the 48 | Licensor grants You such rights in consideration of benefits the Licensor 49 | receives from making the Licensed Material available under these terms and 50 | conditions. 51 | 52 | Section 1 – Definitions. 53 | 54 | a. Adapted Material means material subject to Copyright and Similar Rights 55 | that is derived from or based upon the Licensed Material and in which the 56 | Licensed Material is translated, altered, arranged, transformed, or otherwise 57 | modified in a manner requiring permission under the Copyright and Similar 58 | Rights held by the Licensor. For purposes of this Public License, where the 59 | Licensed Material is a musical work, performance, or sound recording, Adapted 60 | Material is always produced where the Licensed Material is synched in timed 61 | relation with a moving image. 62 | 63 | b. Adapter's License means the license You apply to Your Copyright and Similar 64 | Rights in Your contributions to Adapted Material in accordance with the terms 65 | and conditions of this Public License. 66 | 67 | c. Copyright and Similar Rights means copyright and/or similar rights closely 68 | related to copyright including, without limitation, performance, broadcast, 69 | sound recording, and Sui Generis Database Rights, without regard to how the 70 | rights are labeled or categorized. For purposes of this Public License, the 71 | rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 72 | 73 | d. Effective Technological Measures means those measures that, in the absence 74 | of proper authority, may not be circumvented under laws fulfilling obligations 75 | under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, 76 | and/or similar international agreements. 77 | 78 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other 79 | exception or limitation to Copyright and Similar Rights that applies to Your 80 | use of the Licensed Material. 81 | 82 | f. Licensed Material means the artistic or literary work, database, or other 83 | material to which the Licensor applied this Public License. 84 | 85 | g. Licensed Rights means the rights granted to You subject to the terms and 86 | conditions of this Public License, which are limited to all Copyright and 87 | Similar Rights that apply to Your use of the Licensed Material and that the 88 | Licensor has authority to license. 89 | 90 | h. Licensor means the individual(s) or entity(ies) granting rights under this 91 | Public License. 92 | 93 | i. Share means to provide material to the public by any means or process that 94 | requires permission under the Licensed Rights, such as reproduction, public 95 | display, public performance, distribution, dissemination, communication, or 96 | importation, and to make material available to the public including in ways 97 | that members of the public may access the material from a place and at a time 98 | individually chosen by them. 99 | 100 | j. Sui Generis Database Rights means rights other than copyright resulting 101 | from Directive 96/9/EC of the European Parliament and of the Council of 11 102 | March 1996 on the legal protection of databases, as amended and/or succeeded, 103 | as well as other essentially equivalent rights anywhere in the world. 104 | 105 | k. You means the individual or entity exercising the Licensed Rights under 106 | this Public License. Your has a corresponding meaning. 107 | 108 | Section 2 – Scope. 109 | 110 | a. License grant. 111 | 112 | 1. Subject to the terms and conditions of this Public License, the Licensor 113 | hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, 114 | irrevocable license to exercise the Licensed Rights in the Licensed Material 115 | to: 116 | 117 | A. reproduce and Share the Licensed Material, in whole or in part; and 118 | 119 | B. produce, reproduce, and Share Adapted Material. 120 | 121 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions 122 | and Limitations apply to Your use, this Public License does not apply, and 123 | You do not need to comply with its terms and conditions. 124 | 125 | 3. Term. The term of this Public License is specified in Section 6(a). 126 | 127 | 4. Media and formats; technical modifications allowed. The Licensor authorizes 128 | You to exercise the Licensed Rights in all media and formats whether now known 129 | or hereafter created, and to make technical modifications necessary to do 130 | so. The Licensor waives and/or agrees not to assert any right or authority 131 | to forbid You from making technical modifications necessary to exercise the 132 | Licensed Rights, including technical modifications necessary to circumvent 133 | Effective Technological Measures. For purposes of this Public License, simply 134 | making modifications authorized by this Section 2(a)(4) never produces Adapted 135 | Material. 136 | 137 | 5. Downstream recipients. 138 | 139 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed 140 | Material automatically receives an offer from the Licensor to exercise the 141 | Licensed Rights under the terms and conditions of this Public License. 142 | 143 | B. No downstream restrictions. You may not offer or impose any additional 144 | or different terms or conditions on, or apply any Effective Technological 145 | Measures to, the Licensed Material if doing so restricts exercise of the Licensed 146 | Rights by any recipient of the Licensed Material. 147 | 148 | 6. No endorsement. Nothing in this Public License constitutes or may be construed 149 | as permission to assert or imply that You are, or that Your use of the Licensed 150 | Material is, connected with, or sponsored, endorsed, or granted official status 151 | by, the Licensor or others designated to receive attribution as provided in 152 | Section 3(a)(1)(A)(i). 153 | 154 | b. Other rights. 155 | 156 | 1. Moral rights, such as the right of integrity, are not licensed under this 157 | Public License, nor are publicity, privacy, and/or other similar personality 158 | rights; however, to the extent possible, the Licensor waives and/or agrees 159 | not to assert any such rights held by the Licensor to the limited extent necessary 160 | to allow You to exercise the Licensed Rights, but not otherwise. 161 | 162 | 2. Patent and trademark rights are not licensed under this Public License. 163 | 164 | 3. To the extent possible, the Licensor waives any right to collect royalties 165 | from You for the exercise of the Licensed Rights, whether directly or through 166 | a collecting society under any voluntary or waivable statutory or compulsory 167 | licensing scheme. In all other cases the Licensor expressly reserves any right 168 | to collect such royalties. 169 | 170 | Section 3 – License Conditions. 171 | 172 | Your exercise of the Licensed Rights is expressly made subject to the following 173 | conditions. 174 | 175 | a. Attribution. 176 | 177 | 1. If You Share the Licensed Material (including in modified form), You must: 178 | 179 | A. retain the following if it is supplied by the Licensor with the Licensed 180 | Material: 181 | 182 | i. identification of the creator(s) of the Licensed Material and any others 183 | designated to receive attribution, in any reasonable manner requested by the 184 | Licensor (including by pseudonym if designated); 185 | 186 | ii. a copyright notice; 187 | 188 | iii. a notice that refers to this Public License; 189 | 190 | iv. a notice that refers to the disclaimer of warranties; 191 | 192 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 193 | 194 | B. indicate if You modified the Licensed Material and retain an indication 195 | of any previous modifications; and 196 | 197 | C. indicate the Licensed Material is licensed under this Public License, and 198 | include the text of, or the URI or hyperlink to, this Public License. 199 | 200 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner 201 | based on the medium, means, and context in which You Share the Licensed Material. 202 | For example, it may be reasonable to satisfy the conditions by providing a 203 | URI or hyperlink to a resource that includes the required information. 204 | 205 | 3. If requested by the Licensor, You must remove any of the information required 206 | by Section 3(a)(1)(A) to the extent reasonably practicable. 207 | 208 | 4. If You Share Adapted Material You produce, the Adapter's License You apply 209 | must not prevent recipients of the Adapted Material from complying with this 210 | Public License. 211 | 212 | Section 4 – Sui Generis Database Rights. 213 | 214 | Where the Licensed Rights include Sui Generis Database Rights that apply to 215 | Your use of the Licensed Material: 216 | 217 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, 218 | reuse, reproduce, and Share all or a substantial portion of the contents of 219 | the database; 220 | 221 | b. if You include all or a substantial portion of the database contents in 222 | a database in which You have Sui Generis Database Rights, then the database 223 | in which You have Sui Generis Database Rights (but not its individual contents) 224 | is Adapted Material; and 225 | 226 | c. You must comply with the conditions in Section 3(a) if You Share all or 227 | a substantial portion of the contents of the database. 228 | 229 | For the avoidance of doubt, this Section 4 supplements and does not replace 230 | Your obligations under this Public License where the Licensed Rights include 231 | other Copyright and Similar Rights. 232 | 233 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 234 | 235 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, 236 | the Licensor offers the Licensed Material as-is and as-available, and makes 237 | no representations or warranties of any kind concerning the Licensed Material, 238 | whether express, implied, statutory, or other. This includes, without limitation, 239 | warranties of title, merchantability, fitness for a particular purpose, non-infringement, 240 | absence of latent or other defects, accuracy, or the presence or absence of 241 | errors, whether or not known or discoverable. Where disclaimers of warranties 242 | are not allowed in full or in part, this disclaimer may not apply to You. 243 | 244 | b. To the extent possible, in no event will the Licensor be liable to You 245 | on any legal theory (including, without limitation, negligence) or otherwise 246 | for any direct, special, indirect, incidental, consequential, punitive, exemplary, 247 | or other losses, costs, expenses, or damages arising out of this Public License 248 | or use of the Licensed Material, even if the Licensor has been advised of 249 | the possibility of such losses, costs, expenses, or damages. Where a limitation 250 | of liability is not allowed in full or in part, this limitation may not apply 251 | to You. 252 | 253 | c. The disclaimer of warranties and limitation of liability provided above 254 | shall be interpreted in a manner that, to the extent possible, most closely 255 | approximates an absolute disclaimer and waiver of all liability. 256 | 257 | Section 6 – Term and Termination. 258 | 259 | a. This Public License applies for the term of the Copyright and Similar Rights 260 | licensed here. However, if You fail to comply with this Public License, then 261 | Your rights under this Public License terminate automatically. 262 | 263 | b. Where Your right to use the Licensed Material has terminated under Section 264 | 6(a), it reinstates: 265 | 266 | 1. automatically as of the date the violation is cured, provided it is cured 267 | within 30 days of Your discovery of the violation; or 268 | 269 | 2. upon express reinstatement by the Licensor. 270 | 271 | c. For the avoidance of doubt, this Section 6(b) does not affect any right 272 | the Licensor may have to seek remedies for Your violations of this Public 273 | License. 274 | 275 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material 276 | under separate terms or conditions or stop distributing the Licensed Material 277 | at any time; however, doing so will not terminate this Public License. 278 | 279 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 280 | 281 | Section 7 – Other Terms and Conditions. 282 | 283 | a. The Licensor shall not be bound by any additional or different terms or 284 | conditions communicated by You unless expressly agreed. 285 | 286 | b. Any arrangements, understandings, or agreements regarding the Licensed 287 | Material not stated herein are separate from and independent of the terms 288 | and conditions of this Public License. 289 | 290 | Section 8 – Interpretation. 291 | 292 | a. For the avoidance of doubt, this Public License does not, and shall not 293 | be interpreted to, reduce, limit, restrict, or impose conditions on any use 294 | of the Licensed Material that could lawfully be made without permission under 295 | this Public License. 296 | 297 | b. To the extent possible, if any provision of this Public License is deemed 298 | unenforceable, it shall be automatically reformed to the minimum extent necessary 299 | to make it enforceable. If the provision cannot be reformed, it shall be severed 300 | from this Public License without affecting the enforceability of the remaining 301 | terms and conditions. 302 | 303 | c. No term or condition of this Public License will be waived and no failure 304 | to comply consented to unless expressly agreed to by the Licensor. 305 | 306 | d. Nothing in this Public License constitutes or may be interpreted as a limitation 307 | upon, or waiver of, any privileges and immunities that apply to the Licensor 308 | or You, including from the legal processes of any jurisdiction or authority. 309 | 310 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative 311 | Commons may elect to apply one of its public licenses to material it publishes 312 | and in those instances will be considered the "Licensor." The text of the 313 | Creative Commons public licenses is dedicated to the public domain under the 314 | CC0 Public Domain Dedication. Except for the limited purpose of indicating 315 | that material is shared under a Creative Commons public license or as otherwise 316 | permitted by the Creative Commons policies published at creativecommons.org/policies, 317 | Creative Commons does not authorize the use of the trademark "Creative Commons" 318 | or any other trademark or logo of Creative Commons without its prior written 319 | consent including, without limitation, in connection with any unauthorized 320 | modifications to any of its public licenses or any other arrangements, understandings, 321 | or agreements concerning use of licensed material. For the avoidance of doubt, 322 | this paragraph does not form part of the public licenses. 323 | 324 | Creative Commons may be contacted at creativecommons.org. 325 | -------------------------------------------------------------------------------- /adafruit_fancyled/adafruit_fancyled.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 PaintYourDragon for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | """ 6 | `adafruit_fancyled.adafruit_fancyled` 7 | ==================================================== 8 | 9 | FancyLED is a CircuitPython library to assist in creating buttery smooth LED animation. 10 | It's loosely inspired by the FastLED library for Arduino, and in fact we have a "helper" 11 | library using similar function names to assist with porting of existing Arduino FastLED 12 | projects to CircuitPython. 13 | 14 | * Author(s): PaintYourDragon 15 | """ 16 | 17 | from __future__ import annotations 18 | 19 | __version__ = "0.0.0+auto.0" 20 | __repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_FancyLED.git" 21 | 22 | # imports 23 | from math import floor 24 | 25 | try: 26 | from typing import Any, Optional, Union 27 | 28 | from circuitpython_typing.led import FillBasedColorUnion 29 | except ImportError: 30 | pass 31 | 32 | 33 | # FancyLED provides color- and palette-related utilities for LED projects, 34 | # offering a buttery smooth look instead of the usual 8-bit-like "blip blip" 35 | # effects often seen with LEDs. It's loosely inspired by, but NOT a drop-in 36 | # replacement for, the FastLED library for Arduino. 37 | 38 | 39 | class CRGB: 40 | """Color stored in Red, Green, Blue color space. 41 | 42 | One of two ways: separate red, gren, blue values (either as integers 43 | (0 to 255 range) or floats (0.0 to 1.0 range), either type is 44 | 'clamped' to valid range and stored internally in the normalized 45 | (float) format), OR can accept a CHSV color as input, which will be 46 | converted and stored in RGB format. 47 | 48 | Following statements are equivalent - all return red: 49 | 50 | .. code-block:: python 51 | 52 | c = CRGB(255, 0, 0) 53 | c = CRGB(1.0, 0.0, 0.0) 54 | c = CRGB(CHSV(0.0, 1.0, 1.0)) 55 | """ 56 | 57 | def __init__(self, red: CHSV, green: float = 0.0, blue: float = 0.0) -> None: 58 | if isinstance(red, CHSV): 59 | # If first/only argument is a CHSV type, perform HSV to RGB 60 | # conversion. 61 | hsv: CHSV = red # 'red' is CHSV, this is just more readable 62 | hue: float = hsv.hue * 6.0 # Hue circle = 0.0 to 6.0 63 | sxt: int = floor(hue) # Sextant index is next-lower integer of hue 64 | frac: float = hue - sxt # Fraction-within-sextant is 0.0 to <1.0 65 | sxt: int = int(sxt) % 6 # mod6 the sextant so it's always 0 to 5 66 | 67 | if sxt == 0: # Red to tuple[int, int, int]: 92 | return (self.red, self.green, self.blue) 93 | 94 | def __str__(self) -> str: 95 | return f"({self.red}, {self.green}, {self.blue})" 96 | 97 | def __len__(self) -> int: 98 | """Retrieve total number of color-parts available.""" 99 | return 3 100 | 101 | def __getitem__(self, key: int) -> float: 102 | """Retrieve red, green or blue value as iterable.""" 103 | if key == 0: 104 | return self.red 105 | if key == 1: 106 | return self.green 107 | if key == 2: 108 | return self.blue 109 | raise IndexError 110 | 111 | def pack(self, white: Optional[float] = None) -> FillBasedColorUnion: 112 | """'Pack' a `CRGB` color into a 24-bit RGB integer, OR, optionally 113 | assign a white element for RGBW NeoPixels and return as a 4-tuple, 114 | either of which can be passed to the NeoPixel setter. 115 | WITH REGARD TO RGBW PIXELS, THIS PROBABLY DOESN'T DO WHAT YOU THINK. 116 | FancyLED is currently RGB-focused through and through and has no 117 | concept of RGBW. This function does NOT perform white component 118 | replacement on the RGB elements -- those values are returned 119 | unmodified, this just allows appending a white element to pass 120 | through to the NeoPixel setter with RGBW pixels. 121 | The reason for this peculiar return option is that the core NeoPixel 122 | library can't accept packed 32-bit values for RGBW, only 4-tuples. 123 | This is intentional and by design, because space-constrained devices 124 | don't support the full 32-bit integer range in CircuitPython (but 125 | 24-bit RGB fits). 126 | Also note, if gamma_adjust() was applied to an RGB color that's then 127 | passed to this function, that adjustment is NOT automatically applied 128 | to the white element -- this must be explicitly handled in user code 129 | (gamma_adjust() can accept both tuples (for RGB) and single values 130 | (for white)). 131 | :param white: integer 0 to 255, float 0.0 to 1.0, or None (default). 132 | If specified, this value is returned as the last element of an 133 | integer 4-tuple. Values outside these ranges will be clamped, not 134 | throw an exception. 135 | :returns: 24-bit integer a la ``0x00RRGGBB`` if no argument passed, 136 | or 4-element integer tuple a la ``(R,G,B,W)`` if argument for fourth 137 | element is provided. 138 | :rtype: integer or 4-tuple. 139 | """ 140 | 141 | if white: 142 | # So really this is a quick-fix to the FancyLED + RGBW NeoPixel 143 | # combination, which is rare and has only come up once. But if 144 | # this were to become a common thing in the future, a generally 145 | # more robust approach would be to implement a distinct CRGBW 146 | # class, which could then do things like gamma_adjust() on all 147 | # elements, perhaps white component replacement, etc., and would 148 | # do away with this gross special kludge case. 149 | # Initially this was done as an __add__ function before moving 150 | # it here into pack(), as the CRGB + value syntax was guaranteed 151 | # to cause confusion (it would be easily assumed that it increases 152 | # brightness, not appends a value). So, note to future self, 153 | # don't try to be clever that way, this was on purpose. 154 | if isinstance(white, float): 155 | white = denormalize(white) 156 | else: 157 | white = clamp(white, 0, 255) 158 | return ( 159 | denormalize(self.red), 160 | denormalize(self.green), 161 | denormalize(self.blue), 162 | white, 163 | ) 164 | return ( 165 | (denormalize(self.red) << 16) 166 | | (denormalize(self.green) << 8) 167 | | (denormalize(self.blue)) 168 | ) 169 | 170 | 171 | class CHSV: 172 | """Color stored in Hue, Saturation, Value color space. 173 | 174 | Accepts hue as float (any range) or integer (0-256 -> 0.0-1.0) with 175 | no clamping performed (hue can 'wrap around'), saturation and value 176 | as float (0.0 to 1.0) or integer (0 to 255), both are clamped and 177 | stored internally in the normalized (float) format. Latter two are 178 | optional, can pass juse hue and saturation/value will default to 1.0. 179 | 180 | Unlike `CRGB` (which can take a `CHSV` as input), there's currently 181 | no equivalent RGB-to-HSV conversion, mostly because it's a bit like 182 | trying to reverse a hash...there may be multiple HSV solutions for a 183 | given RGB input. 184 | 185 | This might be OK as long as conversion precedence is documented, 186 | but otherwise (and maybe still) could cause confusion as certain 187 | HSV->RGB->HSV translations won't have the same input and output. 188 | """ 189 | 190 | def __init__(self, h: float, s: float = 1.0, v: float = 1.0) -> None: 191 | if isinstance(h, float): 192 | self.hue: float = h # Don't clamp! Hue can wrap around forever. 193 | else: 194 | self.hue: float = float(h) / 256.0 195 | self.saturation: float = clamp_norm(s) 196 | self.value: float = clamp_norm(v) 197 | 198 | def __repr__( 199 | self, 200 | ) -> tuple[float, float, float]: 201 | return (self.hue, self.saturation, self.value) 202 | 203 | def __str__(self) -> str: 204 | return f"({self.hue}, {self.saturation}, {self.value})" 205 | 206 | def __len__(self) -> int: 207 | """Retrieve total number of 'color-parts' available.""" 208 | return 3 209 | 210 | def __getitem__(self, key: int) -> float: 211 | """Retrieve hue, saturation or value as iterable.""" 212 | if key == 0: 213 | return self.hue 214 | if key == 1: 215 | return self.saturation 216 | if key == 2: 217 | return self.value 218 | raise IndexError 219 | 220 | def pack(self, white: Optional[float] = None) -> FillBasedColorUnion: 221 | """'Pack' a `CHSV` color into a 24-bit RGB integer, OR, optionally 222 | assign a white element for RGBW NeoPixels and return as a 4-tuple, 223 | either of which can be passed to the NeoPixel setter. 224 | Please see notes accompanying CRGB.pack() for important RGBW 225 | peculiarities. 226 | :param white: integer 0 to 255, float 0.0 to 1.0, or None (default). 227 | If specified, this value is returned as the last element of a 4-tuple. 228 | Values outside these ranges will be clamped, not throw an exception. 229 | :returns: 24-bit integer a la ``0x00RRGGBB`` if no argument passed, 230 | or 4-element integer tuple a la ``(R,G,B,W)`` if argument for fourth 231 | element is provided. 232 | :rtype: integer or 4-tuple. 233 | """ 234 | 235 | # Convert CHSV to CRGB, return packed result 236 | return CRGB(self).pack(white) 237 | 238 | 239 | def clamp( 240 | val: Union[int, float], lower: Union[int, float], upper: Union[int, float] 241 | ) -> Union[int, float]: 242 | """Constrain value within a numeric range (inclusive).""" 243 | return max(lower, min(val, upper)) 244 | 245 | 246 | def normalize(val: int, inplace: Optional[bool] = False) -> Union[None, float, list[float]]: 247 | """Convert 8-bit (0 to 255) value to normalized (0.0 to 1.0) value. 248 | 249 | Accepts integer, 0 to 255 range (input is clamped) or a list or tuple 250 | of integers. In list case, 'inplace' can be used to control whether 251 | the original list is modified (True) or a new list is generated and 252 | returned (False). 253 | 254 | Returns float, 0.0 to 1.0 range, or list of floats (or None if inplace). 255 | """ 256 | 257 | if isinstance(val, int): 258 | # Divide by 255 (not 256) so maximum level is 1.0. 259 | return clamp(val, 0, 255) / 255.0 260 | 261 | # If not int, is assumed list or tuple. 262 | if inplace: 263 | # Modify list in-place (OK for lists, NOT tuples, no check made) 264 | for i, n in enumerate(val): 265 | val[i] = normalize(n) 266 | return None 267 | 268 | # Generate new list 269 | return [normalize(n) for n in val] 270 | 271 | 272 | def clamp_norm(val: Union[float, int]) -> Union[float, int]: 273 | """Clamp or normalize a value as appropriate to its type. If a float is 274 | received, the return value is the input clamped to a 0.0 to 1.0 range. 275 | If an integer is received, a range of 0-255 is scaled to a float value 276 | of 0.0 to 1.0 (also clamped). 277 | """ 278 | if isinstance(val, float): 279 | return clamp(val, 0.0, 1.0) 280 | return normalize(val) 281 | 282 | 283 | def denormalize( 284 | val: Union[float, list[float], tuple[float]], inplace: bool = False 285 | ) -> Union[int, list[int]]: 286 | """Convert normalized (0.0 to 1.0) value to 8-bit (0 to 255) value 287 | 288 | Accepts float, 0.0 to 1.0 range or a list or tuple of floats. In 289 | list case, 'inplace' can be used to control whether the original list 290 | is modified (True) or a new list is generated and returned (False). 291 | 292 | Returns integer, 0 to 255 range, or list of integers (or None if 293 | inplace). 294 | """ 295 | 296 | # 'Denormalizing' math varies slightly from normalize(). This is on 297 | # purpose. Multiply by 256 (NOT 255) and clip. This ensures that all 298 | # fractional values fall into the correct 'buckets' -- e.g. 0.999 299 | # should return 255, not 254 -- and that the buckets are all equal- 300 | # sized (usu. method of adding 0.5 before int() would miss this). 301 | if isinstance(val, float): 302 | return clamp(int(val * 256.0), 0, 255) 303 | 304 | # If not int, is assumed list or tuple. 305 | if inplace: 306 | # Modify the list in-place (OK for lists, NOT tuples, no check made) 307 | for i, n in enumerate(val): 308 | val[i] = denormalize(n) 309 | return None 310 | 311 | # Generate new list 312 | return [denormalize(n) for n in val] 313 | 314 | 315 | def unpack(val: int) -> CRGB: 316 | """'Unpack' a 24-bit color into a `CRGB` instance. 317 | 318 | :param int val: 24-bit integer a la ``0x00RRGGBB``. 319 | :returns: CRGB color. 320 | :rtype: CRGB 321 | """ 322 | 323 | # See notes in normalize() for math explanation. Large constants here 324 | # avoid the usual shift-right step, e.g. 16711680.0 is 255 * 256 * 256, 325 | # so we can just mask out the red and divide by this for 0.0 to 1.0. 326 | return CRGB( 327 | (val & 0xFF0000) / 16711680.0, # Red 328 | (val & 0x00FF00) / 65280.0, # Green 329 | (val & 0x0000FF) / 255.0, 330 | ) # Blue 331 | 332 | 333 | def mix(color1: Union[CRGB, CHSV], color2: Union[CRGB, CHSV], weight2: float = 0.5) -> CRGB: 334 | """Blend between two colors using given ratio. Accepts two colors (each 335 | may be `CRGB`, `CHSV` or packed integer), and weighting (0.0 to 1.0) 336 | of second color. 337 | 338 | :returns: `CRGB` color in most cases, `CHSV` if both inputs are `CHSV`. 339 | """ 340 | 341 | clamp(weight2, 0.0, 1.0) 342 | weight1: float = 1.0 - weight2 343 | 344 | if isinstance(color1, CHSV): 345 | if isinstance(color2, CHSV): 346 | # Both colors are CHSV -- interpolate in HSV color space 347 | # because of the way hue can cross the unit boundary... 348 | # e.g. if the hues are 0.25 and 0.75, the center point is 349 | # 0.5 (cyan)...but if you want hues to wrap the other way 350 | # (with red at the center), you can have hues of 1.25 and 0.75. 351 | hue = color1.hue + ((color2.hue - color1.hue) * weight2) 352 | sat = color1.saturation * weight1 + color2.saturation * weight2 353 | val = color1.value * weight1 + color2.value * weight2 354 | return CHSV(hue, sat, val) 355 | # Else color1 is HSV, color2 is RGB. Convert color1 to RGB 356 | # before doing interpolation in RGB space. 357 | color1 = CRGB(color1) 358 | # If color2 is a packed integer, convert to CRGB instance. 359 | if isinstance(color2, int): 360 | color2 = unpack(color2) 361 | else: 362 | if isinstance(color2, CHSV): 363 | # color1 is RGB, color2 is HSV. Convert color2 to RGB 364 | # before interpolating in RGB space. 365 | color2 = CRGB(color2) 366 | elif isinstance(color2, int): 367 | # If color2 is a packed integer, convert to CRGB instance. 368 | color2 = unpack(color2) 369 | # If color1 is a packed integer, convert to CRGB instance. 370 | if isinstance(color1, int): 371 | color1 = unpack(color1) 372 | 373 | # Interpolate and return as CRGB type 374 | return CRGB( 375 | (color1.red * weight1 + color2.red * weight2), 376 | (color1.green * weight1 + color2.green * weight2), 377 | (color1.blue * weight1 + color2.blue * weight2), 378 | ) 379 | 380 | 381 | GFACTOR = 2.7 # Default gamma-correction factor for function below 382 | 383 | 384 | def gamma_adjust( # noqa: PLR0912, too-many-branches 385 | val: Any, 386 | gamma_value: Any = None, 387 | brightness: Optional[Union[float, tuple[int, int, int]]] = 1.0, 388 | inplace: Optional[bool] = False, 389 | ) -> Union[float, CRGB, list[Union[float, CRGB]]]: 390 | """Provides gamma adjustment for single values, `CRGB` and `CHSV` types 391 | and lists of any of these. 392 | 393 | Works in one of three ways: 394 | 1. Accepts a single normalized level (0.0 to 1.0) and optional 395 | gamma-adjustment factor (float usu. > 1.0, default if 396 | unspecified is GFACTOR) and brightness (float 0.0 to 1.0, 397 | default is 1.0). Returns a single normalized gamma-corrected 398 | brightness level (0.0 to 1.0). 399 | 2. Accepts a single `CRGB` or `CHSV` type, optional single gamma 400 | factor OR a (R,G,B) gamma tuple (3 values usu. > 1.0), optional 401 | single brightness factor OR a (R,G,B) brightness tuple. The 402 | input tuples are RGB even when a `CHSV` color is passed. Returns 403 | a normalized gamma-corrected `CRGB` type (NOT `CHSV`!). 404 | 3. Accept a list or tuple of normalized levels, `CRGB` or `CHSV` 405 | types (and optional gamma and brightness levels or tuples 406 | applied to all). Returns a list of gamma-corrected values or 407 | `CRGB` types (NOT `CHSV`!). 408 | 409 | In cases 2 and 3, if the input is a list (NOT a tuple!), the 'inplace' 410 | flag determines whether a new tuple/list is calculated and returned, 411 | or the existing value is modified in-place. By default this is 412 | 'False'. If you try to inplace-modify a tuple, an exception is raised. 413 | 414 | In cases 2 and 3, there is NO return value if 'inplace' is True -- 415 | the original values are modified. 416 | """ 417 | 418 | if isinstance(val, float): 419 | # Input value appears to be a single float 420 | if gamma_value is None: 421 | gamma_value = GFACTOR 422 | return pow(val, gamma_value) * brightness 423 | 424 | if isinstance(val, (list, tuple)): 425 | # List or tuple of values 426 | if isinstance(val[0], float): 427 | # Input appears to be a list of floats 428 | if gamma_value is None: 429 | gamma_value = GFACTOR 430 | if inplace: 431 | for i, x in enumerate(val): 432 | val[i] = pow(val[i], gamma_value) * brightness 433 | return None 434 | newlist = [] 435 | for x in val: 436 | newlist.append(pow(x, gamma_value) * brightness) 437 | return newlist 438 | # List of CRGB or CHSV...we'll get back to that in a moment... 439 | # but first determine gamma-correction factors for R,G,B: 440 | if gamma_value is None: 441 | # No gamma specified, use default 442 | gamma_red, gamma_green, gamma_blue = GFACTOR, GFACTOR, GFACTOR 443 | elif isinstance(gamma_value, float): 444 | # Single gamma value provided, apply to R,G,B 445 | gamma_red, gamma_green, gamma_blue = (gamma_value, gamma_value, gamma_value) 446 | else: 447 | gamma_red, gamma_green, gamma_blue = ( 448 | gamma_value[0], 449 | gamma_value[1], 450 | gamma_value[2], 451 | ) 452 | if isinstance(brightness, float): 453 | # Single brightness value provided, apply to R,G,B 454 | brightness_red, brightness_green, brightness_blue = ( 455 | brightness, 456 | brightness, 457 | brightness, 458 | ) 459 | else: 460 | brightness_red, brightness_green, brightness_blue = ( 461 | brightness[0], 462 | brightness[1], 463 | brightness[2], 464 | ) 465 | if inplace: 466 | for i, x in enumerate(val): 467 | if isinstance(x, CHSV): 468 | x = CRGB(x) # noqa: PLW2901 loop variable overwritten 469 | val[i] = CRGB( 470 | pow(x.red, gamma_red) * brightness_red, 471 | pow(x.green, gamma_green) * brightness_green, 472 | pow(x.blue, gamma_blue) * brightness_blue, 473 | ) 474 | return None 475 | newlist = [] 476 | for x in val: 477 | if isinstance(x, CHSV): 478 | x = CRGB(x) # noqa: PLW2901 loop variable overwritten 479 | newlist.append( 480 | CRGB( 481 | pow(x.red, gamma_red) * brightness_red, 482 | pow(x.green, gamma_green) * brightness_green, 483 | pow(x.blue, gamma_blue) * brightness_blue, 484 | ) 485 | ) 486 | return newlist 487 | 488 | # Single CRGB or CHSV value 489 | if gamma_value is None: 490 | # No gamma specified, use default 491 | gamma_red, gamma_green, gamma_blue = GFACTOR, GFACTOR, GFACTOR 492 | elif isinstance(gamma_value, float): 493 | # Single gamma value provided, apply to R,G,B 494 | gamma_red, gamma_green, gamma_blue = (gamma_value, gamma_value, gamma_value) 495 | else: 496 | gamma_red, gamma_green, gamma_blue = ( 497 | gamma_value[0], 498 | gamma_value[1], 499 | gamma_value[2], 500 | ) 501 | if isinstance(brightness, float): 502 | # Single brightness value provided, apply to R,G,B 503 | brightness_red, brightness_green, brightness_blue = ( 504 | brightness, 505 | brightness, 506 | brightness, 507 | ) 508 | else: 509 | brightness_red, brightness_green, brightness_blue = ( 510 | brightness[0], 511 | brightness[1], 512 | brightness[2], 513 | ) 514 | 515 | if isinstance(val, CHSV): 516 | val = CRGB(val) 517 | 518 | return CRGB( 519 | pow(val.red, gamma_red) * brightness_red, 520 | pow(val.green, gamma_green) * brightness_green, 521 | pow(val.blue, gamma_blue) * brightness_blue, 522 | ) 523 | 524 | 525 | def palette_lookup( 526 | palette: Union[list[CRGB], list[CHSV], list[int]], position: float 527 | ) -> Union[CRGB, CHSV]: 528 | """Fetch color from color palette, with interpolation. 529 | 530 | :param palette: color palette (list of CRGB, CHSV and/or packed integers) 531 | :param float position: palette position (0.0 to 1.0, wraps around). 532 | 533 | :returns: `CRGB` or `CHSV` instance, no gamma correction applied. 534 | """ 535 | 536 | position %= 1.0 # Wrap palette position in 0.0 to <1.0 range 537 | 538 | weight2 = position * len(palette) # Scale position to palette length 539 | idx = int(floor(weight2)) # Index of 'lower' color (0 to len-1) 540 | weight2 -= idx # Weighting of 'upper' color 541 | 542 | color1 = palette[idx] # Fetch 'lower' color 543 | idx = (idx + 1) % len(palette) # Get index of 'upper' color 544 | color2 = palette[idx] # Fetch 'upper' color 545 | 546 | return mix(color1, color2, weight2) 547 | 548 | 549 | def expand_gradient( 550 | gradient: Union[ 551 | list[list[float, Union[int, CRGB, CHSV]]], 552 | tuple[tuple[float, Union[int, CRGB, CHSV]]], 553 | ], 554 | length: float, 555 | ) -> list[CRGB]: 556 | """Convert gradient palette into standard equal-interval palette. 557 | 558 | :param sequence gradient: List or tuple of of 2-element lists/tuples 559 | containing position (0.0 to 1.0) and color (packed int, CRGB or CHSV). 560 | It's OK if the list/tuple elements are either lists OR tuples, but 561 | don't mix and match lists and tuples -- use all one or the other. 562 | 563 | :returns: CRGB list, can be used with palette_lookup() function. 564 | """ 565 | 566 | gradient = sorted(gradient) # Sort list by position values 567 | least = gradient[0][0] # Lowest position value (ostensibly 0.0) 568 | most = gradient[-1][0] # Highest position value (ostensibly 1.0) 569 | newlist = [] 570 | 571 | for i in range(length): 572 | pos = i / float(length - 1) # 0.0 to 1.0 in 'length' steps 573 | # Determine indices in list of item 'below' and 'above' pos 574 | if pos <= least: 575 | # Off bottom of list - use lowest index 576 | below, above = 0, 0 577 | elif pos >= most: 578 | # Off top of list - use highest index 579 | below, above = -1, -1 580 | else: 581 | # Seek position between two items in list 582 | below, above = 0, -1 583 | for n, x in enumerate(gradient): 584 | if pos >= x[0]: 585 | below = n 586 | for n, x in enumerate(gradient[-1:0:-1]): 587 | if pos <= x[0]: 588 | above = -1 - n 589 | 590 | # Range between below, above 591 | r = gradient[above][0] - gradient[below][0] 592 | if r <= 0: 593 | newlist.append(gradient[below][1]) # Use 'below' color only 594 | else: 595 | weight2 = (pos - gradient[below][0]) / r # Weight of 'above' color 596 | color1 = gradient[below][1] 597 | color2 = gradient[above][1] 598 | # Interpolate and add to list 599 | newlist.append(mix(color1, color2, weight2)) 600 | 601 | return newlist 602 | --------------------------------------------------------------------------------