Skip to content

Commit 930950e

Browse files
committed
update: 3.0.0
- Replaced existing GitHub Actions workflows (, ) with a new for cross-platform wheel building and testing. - Configured to build wheels for Linux, macOS, and Windows, and to run tests using ============================= test session starts ============================== platform linux -- Python 3.13.11, pytest-8.3.5, pluggy-1.6.0 rootdir: /home/tdynamos/materialyoucolor-python plugins: timeout-2.3.1, anyio-3.7.1, Faker-33.3.1, cov-6.1.1, asyncio-0.26.0, typeguard-4.4.4 asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function collected 0 items ============================ no tests ran in 3.53s ============================= for both Pillow and C++ image processing methods. - Modified to ensure is always defined as a list, improving compatibility with . - Added to to support RAM usage tracking in . - Implemented peak RAM usage measurement in for image generation using both Pillow and C++ methods.
1 parent 36bd0f4 commit 930950e

File tree

115 files changed

+17329
-2012
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

115 files changed

+17329
-2012
lines changed

.github/workflows/cibuildwheel.yml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
name: Build and Test Wheels
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
tags:
9+
- 'v*'
10+
11+
jobs:
12+
build_wheels:
13+
name: Build wheels on ${{ matrix.os }}
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
matrix:
17+
os: [ubuntu-latest, macos-latest, windows-latest]
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
with:
22+
submodules: recursive
23+
24+
- name: Install Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.x' # cibuildwheel will use its own Python versions
28+
29+
- name: Install cibuildwheel
30+
run: python -m pip install cibuildwheel==2.17.0
31+
32+
- name: Download test image file
33+
run: |
34+
# Photo by Dillon Hunt (https://unsplash.com/@dillon_hunt) on https://unsplash.com/photos/an-aerial-view-of-the-ocean-and-rocks-zQLd8RXbenw
35+
curl -L -o test_image.jpg "https://unsplash.com/photos/zQLd8RXbenw/download?ixid=M3wxMjA3fDB8MXx0b3BpY3x8NnNNVmpUTFNrZVF8fHx8fDJ8fDE3MzY0MDA3NTd8&force=true&w=2400"
36+
# This step needs to be available for the test command
37+
# For Windows, use PowerShell equivalent
38+
shell: bash
39+
40+
- name: Build wheels
41+
run: python -m cibuildwheel --output-dir wheelhouse
42+
env:
43+
CIBW_BUILD_VERBOSITY: 1
44+
CIBW_SKIP: "cp37-* pp*" # Skip Python 3.7 and PyPy as per existing workflows
45+
CIBW_ARCHS_MACOS: "x86_64 arm64" # Build for both architectures on macOS
46+
CIBW_TEST_COMMAND: >
47+
pip install psutil &&
48+
pip install pillow &&
49+
python -m pytest {project}/tests/test_all.py --image test_image.jpg --method pillow &&
50+
python -m pytest {project}/tests/test_all.py --image test_image.jpg --method cpp
51+
CIBW_BEFORE_BUILD: >
52+
pip install setuptools wheel rich requests pillow
53+
CIBW_ENVIRONMENT: >
54+
PATH="$PATH:/opt/python/cp39-cp39/bin" # Example for manylinux, adjust as needed
55+
PATH="$PATH:/opt/python/cp310-cp310/bin"
56+
PATH="$PATH:/opt/python/cp311-cp311/bin"
57+
PATH="$PATH:/opt/python/cp312-cp312/bin"
58+
PATH="$PATH:/opt/python/cp313-cp313/bin"
59+
PATH="$PATH:/opt/python/cp314-cp314/bin"
60+
# Ensure PURE_PYTHON is not set for C++ extension builds
61+
PURE_PYTHON=False
62+
63+
- uses: actions/upload-artifact@v4
64+
with:
65+
name: cibuildwheel-wheels-${{ matrix.os }}
66+
path: wheelhouse/*.whl
67+
68+
deploy:
69+
name: Publish to PyPI
70+
needs: build_wheels
71+
runs-on: ubuntu-latest
72+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
73+
74+
steps:
75+
- uses: actions/checkout@v4
76+
77+
- name: Download all wheels
78+
uses: actions/download-artifact@v4
79+
with:
80+
path: dist
81+
82+
- name: Publish to PyPI
83+
uses: pypa/gh-action-pypi-publish@release/v1
84+
with:
85+
user: __token__
86+
password: ${{ secrets.PYPI_API_TOKEN }}
87+
packages_dir: dist

.github/workflows/default.yml

Lines changed: 0 additions & 67 deletions
This file was deleted.

.github/workflows/linux.yml

Lines changed: 0 additions & 48 deletions
This file was deleted.

materialyoucolor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.0.10"
1+
__version__ = "3.0.0"

materialyoucolor/blend/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from .blend import Blend

materialyoucolor/blend/blend.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
from materialyoucolor.hct import Hct
21
from materialyoucolor.hct.cam16 import Cam16
2+
from materialyoucolor.hct.hct import Hct
3+
from materialyoucolor.utils.color_utils import lstar_from_argb
34
from materialyoucolor.utils.math_utils import (
4-
sanitize_degrees_double,
55
difference_degrees,
66
rotation_direction,
7+
sanitize_degrees_double,
78
)
8-
from materialyoucolor.utils.color_utils import lstar_from_argb
99

1010

1111
class Blend:
12+
"""
13+
Functions for blending in HCT and CAM16.
14+
"""
15+
1216
@staticmethod
1317
def harmonize(design_color: int, source_color: int) -> int:
18+
"""
19+
Blend the design color's HCT hue towards the key color's HCT
20+
hue, in a way that leaves the original color recognizable and
21+
recognizably shifted towards the key color.
22+
"""
1423
from_hct = Hct.from_int(design_color)
1524
to_hct = Hct.from_int(source_color)
1625
difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue)
@@ -22,17 +31,24 @@ def harmonize(design_color: int, source_color: int) -> int:
2231
return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int()
2332

2433
@staticmethod
25-
def hct_hue(from_: int, to: int, amount: int) -> int:
26-
ucs = Blend.cam16_ucs(from_, to, amount)
34+
def hct_hue(from_argb: int, to_argb: int, amount: float) -> int:
35+
"""
36+
Blends hue from one color into another. The chroma and tone of
37+
the original color are maintained.
38+
"""
39+
ucs = Blend.cam16_ucs(from_argb, to_argb, amount)
2740
ucs_cam = Cam16.from_int(ucs)
28-
from_cam = Cam16.from_int(from_)
29-
blended = Hct.from_hct(ucs_cam.hue, from_cam.chroma, lstar_from_argb(from_))
41+
from_cam = Cam16.from_int(from_argb)
42+
blended = Hct.from_hct(ucs_cam.hue, from_cam.chroma, lstar_from_argb(from_argb))
3043
return blended.to_int()
3144

3245
@staticmethod
33-
def cam16_ucs(from_: int, to: int, amount: float) -> int:
34-
from_cam = Cam16.from_int(from_)
35-
to_cam = Cam16.from_int(to)
46+
def cam16_ucs(from_argb: int, to_argb: int, amount: float) -> int:
47+
"""
48+
Blend in CAM16-UCS space.
49+
"""
50+
from_cam = Cam16.from_int(from_argb)
51+
to_cam = Cam16.from_int(to_argb)
3652
from_j = from_cam.jstar
3753
from_a = from_cam.astar
3854
from_b = from_cam.bstar
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from .contrast import Contrast

materialyoucolor/contrast/contrast.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
from materialyoucolor.utils.color_utils import lstar_from_y, y_from_lstar
12
from materialyoucolor.utils.math_utils import clamp_double
2-
from materialyoucolor.utils.color_utils import y_from_lstar, lstar_from_y
33

44

55
class Contrast:
@@ -11,7 +11,7 @@ def ratio_of_tones(tone_a: float, tone_b: float) -> float:
1111

1212
@staticmethod
1313
def ratio_of_ys(y1: float, y2: float) -> float:
14-
lighter = y1 if y1 > y2 else y2
14+
lighter = max(y1, y2)
1515
darker = y1 if lighter == y2 else y2
1616
return (lighter + 5.0) / (darker + 5.0)
1717

@@ -25,11 +25,12 @@ def lighter(tone: float, ratio: float) -> float:
2525
real_contrast = Contrast.ratio_of_ys(light_y, dark_y)
2626
delta = abs(real_contrast - ratio)
2727
if real_contrast < ratio and delta > 0.04:
28-
return -1
28+
return -1.0
2929

3030
return_value = lstar_from_y(light_y) + 0.4
3131
if return_value < 0 or return_value > 100:
3232
return -1.0
33+
3334
return return_value
3435

3536
@staticmethod
@@ -38,24 +39,25 @@ def darker(tone: float, ratio: float) -> float:
3839
return -1.0
3940

4041
light_y = y_from_lstar(tone)
41-
dark_y = (light_y + 5.0) / ratio - 5.0
42+
dark_y = ((light_y + 5.0) / ratio) - 5.0
4243
real_contrast = Contrast.ratio_of_ys(light_y, dark_y)
4344

4445
delta = abs(real_contrast - ratio)
4546
if real_contrast < ratio and delta > 0.04:
46-
return -1
47+
return -1.0
4748

4849
return_value = lstar_from_y(dark_y) - 0.4
49-
if return_value < 0 or return_value > 100:
50-
return -1
50+
if return_value < 0.0 or return_value > 100.0:
51+
return -1.0
52+
5153
return return_value
5254

5355
@staticmethod
54-
def lighter_unsafe(tone, ratio):
56+
def lighter_unsafe(tone: float, ratio: float) -> float:
5557
lighter_safe = Contrast.lighter(tone, ratio)
5658
return 100.0 if lighter_safe < 0.0 else lighter_safe
5759

5860
@staticmethod
59-
def darker_unsafe(tone, ratio):
61+
def darker_unsafe(tone: float, ratio: float) -> float:
6062
darker_safe = Contrast.darker(tone, ratio)
6163
return 0.0 if darker_safe < 0.0 else darker_safe

materialyoucolor/dislike/dislike_analyzer.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
1-
from materialyoucolor.hct import Hct
1+
from materialyoucolor.hct.hct import Hct
22

33

44
class DislikeAnalyzer:
5+
"""
6+
Check and/or fix universally disliked colors.
7+
Color science studies of color preference indicate universal distaste for
8+
dark yellow-greens, and also show this is correlated to distate for
9+
biological waste and rotting food.
10+
11+
See Palmer and Schloss, 2010 or Schloss and Palmer's Chapter 21 in Handbook
12+
of Color Psychology (2015).
13+
"""
14+
515
@staticmethod
616
def is_disliked(hct: Hct) -> bool:
17+
"""
18+
Returns true if a color is disliked.
19+
20+
:param hct: A color to be judged.
21+
:return: Whether the color is disliked.
22+
23+
Disliked is defined as a dark yellow-green that is not neutral.
24+
"""
725
hue_passes = round(hct.hue) >= 90.0 and round(hct.hue) <= 111.0
826
chroma_passes = round(hct.chroma) > 16.0
927
tone_passes = round(hct.tone) < 65.0
1028
return hue_passes and chroma_passes and tone_passes
1129

1230
@staticmethod
1331
def fix_if_disliked(hct: Hct) -> Hct:
32+
"""
33+
If a color is disliked, lighten it to make it likable.
34+
35+
:param hct: A color to be judged.
36+
:return: A new color if the original color is disliked, or the original
37+
color if it is acceptable.
38+
"""
1439
if DislikeAnalyzer.is_disliked(hct):
1540
return Hct.from_hct(
1641
hct.hue,

0 commit comments

Comments
 (0)