Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/cibuildwheel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Build and Test Wheels

on:
pull_request:
push:
tags:
- 'v*'

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Python
uses: actions/setup-python@v5
with:
python-version: '3.x' # cibuildwheel will use its own Python versions

- name: Install cibuildwheel
run: python -m pip install cibuildwheel

- name: Build wheels
run: python -m cibuildwheel --output-dir wheelhouse
env:
CIBW_BUILD_VERBOSITY: 1
CIBW_SKIP: "cp37-* cp38-* pp*" # Skip Python 3.7, 3.8 and PyPy as per existing workflows
CIBW_ARCHS_MACOS: "x86_64 arm64" # Build for both architectures on macOS
CIBW_BEFORE_TEST: >
pip install -r {project}/requirements.txt -r {project}/requirements-dev.txt
CIBW_TEST_COMMAND: >
curl -L -o test_image.jpg "https://raw.githubusercontent.com/mohammadimtiazz/standard-test-images-for-Image-Processing/refs/heads/master/standard_test_images/tulips.png" &&
python {project}/tests/test_all.py --image test_image.jpg --quality 1 --method pillow --tonal-spot &&
python {project}/tests/test_all.py --image test_image.jpg --quality 1 --method cpp --tonal-spot
CIBW_BEFORE_BUILD: >
pip install setuptools wheel
CIBW_ENVIRONMENT: >
PURE_PYTHON=False

- uses: actions/upload-artifact@v4
with:
name: cibuildwheel-wheels-${{ matrix.os }}
path: wheelhouse/*.whl

deploy:
name: Publish to PyPI
needs: build_wheels
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')

steps:
- uses: actions/checkout@v4

- name: Download all wheels
uses: actions/download-artifact@v4
with:
path: dist

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: dist
67 changes: 0 additions & 67 deletions .github/workflows/default.yml

This file was deleted.

48 changes: 0 additions & 48 deletions .github/workflows/linux.yml

This file was deleted.

Binary file added .setup.py.kate-swp
Binary file not shown.
133 changes: 90 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/b29c17d1-6c02-4c07-9a72-5b0198034760)

# [Material You color algorithms](https://m3.material.io/styles/color/overview) for python!
It is built in reference with offical [typescript implementation](https://github.com/material-foundation/material-color-utilities/tree/main/typescript) except it's color quantization part, which is based on [c++ implementation](https://github.com/material-foundation/material-color-utilities/tree/main/cpp) thanks to [pybind](https://github.com/pybind).

Expand All @@ -13,19 +11,43 @@ It is built in reference with offical [typescript implementation](https://github
Run file `tests/test_all.py` as:

```console
python3 test_all.py <image path> <quality>

usage: test_all.py [-h] [--tonal-spot] [--expressive] [--fidelity] [--fruit-salad] [--monochrome] [--neutral] [--rainbow] [--vibrant]
[--content] [--all] [--image IMAGE] [--quality QUALITY] [--method {pillow,cpp}]

Material You Color Scheme Test

options:
-h, --help show this help message and exit
--tonal-spot Print the tonal-spot dynamic scheme
--expressive Print the expressive dynamic scheme
--fidelity Print the fidelity dynamic scheme
--fruit-salad Print the fruit-salad dynamic scheme
--monochrome Print the monochrome dynamic scheme
--neutral Print the neutral dynamic scheme
--rainbow Print the rainbow dynamic scheme
--vibrant Print the vibrant dynamic scheme
--content Print the content dynamic scheme
--all Print all dynamic schemes (default)
--image IMAGE Path to an image file for color extraction
--quality QUALITY Quality for image quantization (default: 5)
--method {pillow,cpp}
Method for color quantization (default: cpp)
```
Maximum quality is `1` that means use all pixels, and quality number more than `1` means how many pixels to skip in between while reading, also you can see it as compression.

<details>
<summary>Click to view result</summary>

[Image Used, size was 8MB](https://unsplash.com/photos/zFMbpChjZGg/)

![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/9d5374c9-00b4-4b70-b82a-6792dd5c910f)
![image](https://github.com/T-Dynamos/materialyoucolor-pyhton/assets/68729523/2edd819f-8600-4c82-a18a-3b759f63a552)
<img width="666" height="516" alt="image" src="https://github.com/user-attachments/assets/68bc415b-3eb5-41d4-96e3-f36ced7411a8" />


It is recommended to use the `cpp` backend, as the `pillow` backend is extremely memory-inefficient for large images.
Newer Pillow APIs such as `Image.get_flattened_data()` eagerly materialize the entire image into a full list,
so quality-based subsampling is applied only after full expansion and does not reduce memory usage.
While `Image.getdata()` allows sampling during iteration, it is deprecated.

The `cpp` backend avoids these issues by operating directly on compact pixel buffers.

</details>

Expand All @@ -50,7 +72,7 @@ pip3 install https://github.com/T-Dynamos/materialyoucolor-python/archive/master
```
### OS Specific

#### Arch Linux
#### Arch Linux (OUTDATED)

```console
yay -S python-materialyoucolor
Expand All @@ -62,7 +84,7 @@ Thanks :heart: to [@midn8hustlr](https://github.com/midn8hustlr) for this [AUR p

Ensure these lines in `buildozer.spec`:
```python
requirements = materialyoucolor
requirements = materialyoucolor==3.0.0
p4a.branch = develop
```

Expand Down Expand Up @@ -103,6 +125,7 @@ print(SchemeAndroid.dark(color).props)
```python
# Color in hue, chroma, tone form
from materialyoucolor.hct import Hct
from materialyoucolor.dynamiccolor.color_spec import COLOR_NAMES
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors

# There are 9 different variants of scheme.
Expand All @@ -114,60 +137,84 @@ scheme = SchemeTonalSpot( # choose any scheme here
Hct.from_int(0xff4181EE), # source color in hct form
True, # dark mode
0.0, # contrast
spec_version="2025"
)

for color in vars(MaterialDynamicColors).keys():
color_name = getattr(MaterialDynamicColors, color)
if hasattr(color_name, "get_hct"): # is a color
print(color, color_name.get_hct(scheme).to_rgba()) # print name of color and value in rgba format

# background [14, 20, 21, 255]
# onBackground [222, 227, 229, 255]
# surface [14, 20, 21, 255]
# surfaceDim [14, 20, 21, 255]
# ...
mdc = MaterialDynamicColors(spec="2025")

for color in COLOR_NAMES:
_color = getattr(mdc, color)
print(color, _color.get_rgba(scheme), _color.get_hex(scheme))

# background [13, 14, 18, 255] #0D0E12FF
# onBackground [227, 229, 240, 255] #E3E5F0FF
# surface [13, 14, 18, 255] #0D0E12FF
# surfaceDim [13, 14, 18, 255] #0D0E12FF
# surfaceBright [41, 44, 52, 255] #292C34FF
# surfaceContainerLowest [0, 0, 0, 255] #000000FF
# surfaceContainerLow [17, 19, 24, 255] #111318FF
# surfaceContainer [23, 25, 31, 255] #17191FFF
# surfaceContainerHigh [29, 31, 38, 255] #1D1F26FF
# surfaceContainerHighest [35, 38, 45, 255] #23262DFF
# onSurface [227, 229, 240, 255] #E3E5F0FF
# surfaceVariant [35, 38, 45, 255] #23262DFF
# onSurfaceVariant [196, 198, 208, 255] #C4C6D0FF
# outline [142, 144, 154, 255] #8E909AFF
...
```

- Generate and score colors from image

```python
# Pillow is required to open image to array of pixels
from PIL import Image
# C++ QuantizeCelebi
from materialyoucolor.quantize import QuantizeCelebi, ImageQuantizeCelebi
# Material You's default scoring of colors
from materialyoucolor.score.score import Score

# Open image
image = Image.open("path_to_some_image.jpg")
pixel_len = image.width * image.height
image_data = image.getdata()

# Quality 1 means skip no pixels
# Pixel subsampling factor (quality = 1 processes all pixels)
quality = 1
pixel_array = [image_data[_] for _ in range(0, pixel_len, quality)]

# Run algorithm
result = QuantizeCelebi(pixel_array, 128) # 128 -> number desired colors, default 128

# Alternate C++ method
# this is generally faster, but gives less control over image
# result = ImageQuantizeCelebi("path_to_some_image.jpg", quality, 128)
# Run Celebi color quantization on an image.
# Returns a dict: {ARGB_color_int: population}
result = ImageQuantizeCelebi(
"example.jpg",
quality,
128, # maximum number of colors
)

print(result)
# {4278722365: 2320, 4278723396: 2405, 4278723657: 2366,...
# result is a dict where key is
# color in integer form (which you can convert later), and value is population

print(Score.score(result))
# [4278722365, 4278723657]
# list of selected colors in integer form
# Rank and select the best theme colors.
# Returns a list of ARGB color integers.
selected_colors = Score.score(result)

print(selected_colors)
# {4278911493: 276721,
# 4280550664: 164247,
# 4280683034: 144830,
# ...
```
</details>


## Gallery

These are some libraries which use this library for color generattion.

* [`dots-hyprland`](https://github.com/end-4/dots-hyprland)

<img width="480" height="270" alt="image" src="https://github.com/user-attachments/assets/b08ca7ac-61a1-480f-852d-cad82590006b" />

* [`kde-material-you-colors`](https://github.com/luisbocanegra/kde-material-you-colors)

<img width="480" height="270" alt="image" src="https://images.pling.com/img/00/00/67/61/06/2136963/frame-51.png" />

* [`EarthFM CLONE`]

<img width="270" height="480" alt="image" src="https://private-user-images.githubusercontent.com/68729523/516392752-7f8a2b2e-d81f-4c2c-8550-c55dea07c5bb.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Njk4NzQyNTksIm5iZiI6MTc2OTg3Mzk1OSwicGF0aCI6Ii82ODcyOTUyMy81MTYzOTI3NTItN2Y4YTJiMmUtZDgxZi00YzJjLTg1NTAtYzU1ZGVhMDdjNWJiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAxMzElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMTMxVDE1MzkxOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQyZmVlMTllZThjYjgxN2M1ZjVhYmFjYjRjMmM3MTZmMDQ3ZTdkODI2YTQwZGNmNWNhNWE3MjJmMWFhZDg2YmMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._zSJgVinO01zMsG8pgJ7x0NZ914vVNy2vNnN8b7BoCA" />

> This is my private project where the theme colors change based on the album art.

## FAQ

1. How it is different from `avanisubbiah/material-color-utilities`?

See https://github.com/T-Dynamos/materialyoucolor-python/issues/3
This library is up to date, fast, and uses a hybrid system for image generation.
2 changes: 1 addition & 1 deletion materialyoucolor/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.0.10"
__version__ = "3.0.0"
Loading
Loading