Compare commits

..

No commits in common. "master" and "v0.0.4" have entirely different histories.

15 changed files with 120 additions and 204 deletions

View File

@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- dev - dev
- master - main
tags-ignore: tags-ignore:
- v* - v*
pull_request: pull_request:
@ -15,7 +15,7 @@ jobs:
container: catthehacker/ubuntu:act-latest container: catthehacker/ubuntu:act-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"] python-version: ["3.8", "3.9", "3.10", "3.11"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -1,27 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"pip_requirements": {
"fileMatch": [
"requirements/.*\\.txt$"
],
"enabled": true
},
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true
}
],
"ignoreDeps": [
"numpy"
]
}

View File

@ -10,7 +10,5 @@
}, },
"[toml]": { "[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml" "editor.defaultFormatter": "tamasfe.even-better-toml"
}, }
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true
} }

33
.vscode/tasks.json vendored
View File

@ -1,33 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"type": "shell",
"linux": {
"command": "./.venv/bin/python -m build"
},
"windows": {
"command": ".\\.venv\\Scripts\\python.exe -m build"
},
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Publish",
"type": "shell",
"linux": {
"command": "./.venv/bin/python -m twine upload --repository gitea ./dist/*"
},
"windows": {
"command": ".\\.venv\\Scripts\\python.exe -m twine upload --repository gitea ./dist/*"
},
"problemMatcher": []
}
]
}

View File

@ -11,23 +11,57 @@ Mastodon [huebot](https://botsin.space/@huebot).
## Installation ## Installation
```shell ### Nix
pip install --index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple/ huepaper
This project is a [Nix Flake](https://nixos.wiki/wiki/Flakes). If you
have a recent version of the [Nix package manager](https://nixos.org/)
installed and Flakes are enabled, run huepaper like this:
``` example
nix run github:Deleh/huepaper
```
Parameters can be passed by appending a double-dash:
``` example
nix run github:Deleh/huepaper -- -hue 0.5 --color lightblue
```
Global installation can be done by including this flake in your flaked
NixOS configuration as always :)
### Legacy
Execute the following steps to run huepaper:
``` example
pip install -r requirements.txt
./huepaper.py
```
```{=org}
#+end_example
```
To install it in your Python environment run:
``` example
python setup.py install
``` ```
## Usage ## Usage
```example ``` example
usage: huepaper [-h] [--width WIDTH] [--height HEIGHT] [-c COLOR] [-np] [-o OUTPUT] [-l [LINES]] [-lb [LINES_BRIGHT]] [-ld [LINES_DARK]] [-p [PIXELATE]] [-e EMBLEM] [-hue HUE] [-smin SMIN] [-smax SMAX] [-lmin LMIN] [-lmax LMAX] usage: huepaper [-h] [-s SIZE] [-c COLOR] [-np] [-o OUTPUT] [-l [LINES]] [-lb [LINES_BRIGHT]] [-ld [LINES_DARK]] [-P [PIXELATE]] [-e EMBLEM] [-hue HUE] [-smin SMIN] [-smax SMAX] [-lmin LMIN] [-lmax LMAX]
Create wallpapers based on color hues. Create wallpapers based on color hues.
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--width WIDTH width of the image (default: 1920) -s SIZE, --size SIZE size of huepaper in the form WIDTHxHEIGHT (default: 1920x1080)
--height HEIGHT height of the image (default: 1080)
-c COLOR, --color COLOR -c COLOR, --color COLOR
base color from which the huepaper is generated (default: random color) base color from which the huepaper is generated (default: random color)
-np, --no-preview don't preview the huepaper
-o OUTPUT, --output OUTPUT -o OUTPUT, --output OUTPUT
filepath where the huepaper will be saved filepath where the huepaper will be saved
-l [LINES], --lines [LINES] -l [LINES], --lines [LINES]
@ -47,14 +81,6 @@ optional arguments:
-lmax LMAX maximum luminance for colors in range [0, 1] (default: 0.9) -lmax LMAX maximum luminance for colors in range [0, 1] (default: 0.9)
``` ```
...or as a Python module
```python
from huepaper import generate
image = generate(width=500, height=500, hue_max=1.0, lum_min=0.3, lum_max=0.6, sat_min=0.8, sat_max=1.0)
```
All image operations are called in order of the help file. E.g. pixelate All image operations are called in order of the help file. E.g. pixelate
(`-p`) is called after adding lines (`-l`). (`-p`) is called after adding lines (`-l`).
@ -115,7 +141,7 @@ huepaper -hue 1.0 -lmin 0.3 -lmax 0.6 -smin 0.8 -smax 1.0
![Huepaper 5](images/huepaper_5.png) ![Huepaper 5](images/huepaper_5.png)
``` example ``` example
huepaper -hue 0.3 -lmin 0.5 -lmax 0.5 -l 0.5 -p 64x36 huepaper -hue 0.3 -lmin 0.5 -lmax 0.5 -l 0.5 -P 64x36
``` ```
------------------------------------------------------------------------ ------------------------------------------------------------------------

View File

@ -9,7 +9,7 @@ authors = [{ name = "Denis Lehmann" }]
maintainers = [{ name = "Profitroll" }] maintainers = [{ name = "Profitroll" }]
description = "A colorful wallpaper generator" description = "A colorful wallpaper generator"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" requires-python = ">=3.8"
license = { text = "GPL3" } license = { text = "GPL3" }
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
@ -17,10 +17,11 @@ classifiers = [
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities", "Topic :: Utilities",
] ]
@ -43,7 +44,7 @@ dev = { file = "requirements/dev.txt" }
where = ["src"] where = ["src"]
[tool.black] [tool.black]
target-version = ['py39', 'py310', 'py311', 'py312'] target-version = ['py38', 'py39', 'py310', 'py311']
[tool.isort] [tool.isort]
profile = "black" profile = "black"
@ -61,7 +62,7 @@ strict = true
show_error_codes = true show_error_codes = true
[tool.pylint.main] [tool.pylint.main]
py-version = 3.9 py-version = 3.8
[tool.coverage.run] [tool.coverage.run]
source = ["huepaper"] source = ["huepaper"]

View File

@ -1,3 +1,3 @@
colour==0.1.5 colour==0.1.5
numpy~=2.0.1,<2.1.0 numpy~=1.24.0,<1.25.0
pillow~=11.0.0 pillow~=10.0.0

View File

@ -1,9 +1,9 @@
black==24.10.0 black==23.7.0
build==1.2.2.post1 build==0.10.0
isort==5.13.2 isort==5.12.0
mypy==1.14.0 mypy==1.4.1
pylint==3.3.2 pylint==2.17.5
pytest-cov==6.0.0 pytest-cov==4.1.0
pytest==8.3.4 pytest==7.4.0
tox==4.23.2 tox==4.7.0
twine==6.0.1 twine==4.0.2

View File

@ -1,4 +1,4 @@
__version__ = "0.0.5" __version__ = "0.0.4"
from . import utils from . import utils
from .generator import generate from .generator import generate

View File

@ -44,10 +44,10 @@ def generate(
raise ValueError("Pixelation value must be set in form: 42x42") raise ValueError("Pixelation value must be set in form: 42x42")
try: try:
random_color = not color random_color = False if color else True
base_color = get_base_color(color, sat_min, sat_max, lum_min, lum_max) base_color = get_base_color(color, sat_min, sat_max, lum_min, lum_max)
if random_color: if random_color:
print(f"Selected random base color: {base_color.hex}") print("Selected random base color: {}".format(base_color.hex))
c1, c2, c3, c4 = create_colors( c1, c2, c3, c4 = create_colors(
base_color, hue_max, sat_min, sat_max, lum_min, lum_max base_color, hue_max, sat_min, sat_max, lum_min, lum_max
@ -68,7 +68,7 @@ def generate(
if emblem: if emblem:
image = add_emblem(image, emblem) image = add_emblem(image, emblem)
image = image.convert(mode="RGB") image.mode = "RGB"
if _output: if _output:
save_image(image, _output) save_image(image, _output)
@ -77,5 +77,5 @@ def generate(
return image return image
except Exception as e: except Exception as e:
print(e) print(str(e))
exit(1) exit(1)

View File

@ -4,7 +4,6 @@ import random
import numpy as np import numpy as np
from colour import Color from colour import Color
from PIL import Image, ImageDraw, ImageOps from PIL import Image, ImageDraw, ImageOps
from typing import Tuple, Union
def get_base_color( def get_base_color(
@ -24,9 +23,9 @@ def get_base_color(
base_color = Color(color_string) base_color = Color(color_string)
except: except:
try: try:
base_color = Color(f"#{color_string}") base_color = Color("#{}".format(color_string))
except: except:
raise Exception(f"Invalid color expression: {color_string}") raise Exception("Invalid color expression: {}".format(color_string))
return base_color return base_color
@ -44,7 +43,7 @@ def create_colors(
max_lum_diff = 0.1 max_lum_diff = 0.1
# Create four random colors similar to the given base_color # Create four random colors similar to the given base_color
for _ in range(4): for i in range(0, 4):
tmp_hue = base_color.hue + random.uniform(-hue_max / 2.0, hue_max / 2.0) tmp_hue = base_color.hue + random.uniform(-hue_max / 2.0, hue_max / 2.0)
if tmp_hue > 1.0: if tmp_hue > 1.0:
tmp_hue -= 1 tmp_hue -= 1
@ -61,7 +60,7 @@ def create_colors(
return tuple(colors) return tuple(colors)
def create_base_image(c1, c2, c3, c4, width=1920, height=1080) -> Image: def create_base_image(c1, c2, c3, c4, width=1920, height=1080):
"""Create a base huepaper by four corner colors. """Create a base huepaper by four corner colors.
c1 - top left c1 - top left
@ -80,11 +79,12 @@ def create_base_image(c1, c2, c3, c4, width=1920, height=1080) -> Image:
) )
im_arr = np.array([r, g, b]).T im_arr = np.array([r, g, b]).T
image = Image.fromarray(np.uint8(im_arr * 255)).convert("RGBA")
return Image.fromarray(np.uint8(im_arr * 255)).convert("RGBA")
return image
def add_lines(image: Image, color: Tuple[float, float, float, Union[float, None]]) -> Image: def add_lines(image, color):
"""Add one to three random lines to an image with given color.""" """Add one to three random lines to an image with given color."""
width, height = image.size width, height = image.size
@ -103,8 +103,7 @@ def add_lines(image: Image, color: Tuple[float, float, float, Union[float, None]
) )
space = rand_width() // 2 space = rand_width() // 2
offset = random.randint(0, space) offset = random.randint(0, space)
for i in range(0, number_of_lines):
for _ in range(number_of_lines):
line_width = rand_width() line_width = rand_width()
x = offset + space + (line_width // 2) x = offset + space + (line_width // 2)
draw.line((x, 0, x, height), fill=color, width=line_width) draw.line((x, 0, x, height), fill=color, width=line_width)
@ -139,7 +138,7 @@ def add_emblem(image, filepath):
try: try:
emblem_image = Image.open(filepath) emblem_image = Image.open(filepath)
except Exception as e: except Exception as e:
raise Exception(f"Failed to load emblem: {e}") raise Exception("Failed to load emblem: {}".format(e))
# Exit if emblem is too big # Exit if emblem is too big
if emblem_image.size[0] > width or emblem_image.size[1] > height: if emblem_image.size[0] > width or emblem_image.size[1] > height:
@ -162,10 +161,11 @@ def save_image(image, filepath):
# Check whether file exists # Check whether file exists
if os.path.isfile(filepath): if os.path.isfile(filepath):
overwrite = input( overwrite = input(
f"The file {filepath} already exists. Do you want to overwrite it? [y/N] " "The file {} already exists. Do you want to overwrite it? [y/N] ".format(
filepath
)
) )
if overwrite != "y" and overwrite != "Y":
if overwrite not in ["y", "Y"]:
save = False save = False
if save: if save:
@ -175,10 +175,9 @@ def save_image(image, filepath):
image.save(filepath) image.save(filepath)
stop = True stop = True
except Exception as e: except Exception as e:
print(f"Failed to save wallpaper: {e}") print("Failed to save wallpaper: {}".format(e))
again = input("Do you want to try again? [Y/n] ") again = input("Do you want to try again? [Y/n] ")
if again == "n" or again == "N":
if again in ["n", "N"]:
stop = True stop = True
else: else:
filepath = input( filepath = input(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

36
tests/test_500x500.py Normal file
View File

@ -0,0 +1,36 @@
from os import remove
from pathlib import Path
from PIL.Image import Image
from huepaper import generate
from huepaper.utils import save_image
def test_generation():
image = generate(
width=500,
height=500,
hue_max=1.0,
lum_min=0.3,
lum_max=0.6,
sat_min=0.8,
sat_max=1.0,
)
assert isinstance(image, Image)
def test_saving():
image = generate(
500,
500,
hue_max=1.0,
lum_min=0.3,
lum_max=0.6,
sat_min=0.8,
sat_max=1.0,
lines=0.0,
)
save_image(image, Path("tests/image.jpg"))
assert Path("tests/image.jpg").exists()
remove(Path("tests/image.jpg"))

View File

@ -1,84 +0,0 @@
from os import remove
from pathlib import Path
from subprocess import run
from PIL.Image import Image
from huepaper import generate
from huepaper.utils import save_image
def test_generation():
image = generate()
assert isinstance(image, Image)
def test_saving():
image = generate(
500,
500,
hue_max=1.0,
lum_min=0.3,
lum_max=0.6,
sat_min=0.8,
sat_max=1.0,
lines=0.0,
)
save_image(image, Path("tests/image.jpg"))
assert Path("tests/image.jpg").exists()
remove(Path("tests/image.jpg"))
def test_shell():
assert (
run(
[
"huepaper",
"-hue",
"0.3",
"-lmin",
"0.5",
"-lmax",
"0.3",
"-l",
"0.5",
"-p",
"64x36",
"-o",
"tests/image.jpg",
],
check=False,
).returncode
== 0
)
remove(Path("tests/image.jpg"))
def test_generation_example_1():
image = generate(color="lightgreen")
assert isinstance(image, Image)
def test_generation_example_2():
image = generate(color="#ff7f50", lines_bright=0.05)
assert isinstance(image, Image)
def test_generation_example_3():
image = generate(hue_max=1.0, lum_min=0.3, lum_max=0.6, sat_min=0.8, sat_max=1.0)
assert isinstance(image, Image)
def test_generation_example_4():
image = generate(hue_max=0.3, lum_min=0.5, lum_max=0.5, lines=0.5, pixelate="64x36")
assert isinstance(image, Image)
def test_generation_example_5():
image = generate(
lines=0.3,
lines_bright=0.1,
lines_dark=0.1,
emblem=Path("tests/assets/emblem.png"),
)
assert isinstance(image, Image)

View File

@ -1,14 +1,14 @@
[tox] [tox]
minversion = 3.9.0 minversion = 3.8.0
envlist = py39, py310, py311, py312 envlist = py38, py39, py310, py311
isolated_build = true isolated_build = true
[gh-actions] [gh-actions]
python = python =
3.8: py38
3.9: py39 3.9: py39
3.10: py310 3.10: py310
3.11: py311 3.11: py311
3.12: py312
[testenv] [testenv]
setenv = setenv =