Compare commits
19 Commits
v0.0.4
...
2b054c4ffe
Author | SHA1 | Date | |
---|---|---|---|
2b054c4ffe | |||
c9e16ea6da | |||
8b133c8af1 | |||
b64619d22e | |||
8f222f1cb8 | |||
0a395afd24 | |||
e7a098bdf5
|
|||
b67b23036d
|
|||
d5f4bd79be | |||
9aed62189d | |||
67b5be094c | |||
7afb325ebd | |||
95ed004805 | |||
70d0279cb9
|
|||
62ef828e4b
|
|||
ce752d30e2
|
|||
1365273ff0
|
|||
2f4f61ef82
|
|||
7df479c59e
|
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- main
|
- master
|
||||||
tags-ignore:
|
tags-ignore:
|
||||||
- v*
|
- v*
|
||||||
pull_request:
|
pull_request:
|
||||||
|
24
.renovaterc
Normal file
24
.renovaterc
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"$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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -10,5 +10,7 @@
|
|||||||
},
|
},
|
||||||
"[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
Normal file
33
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
// 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": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
56
README.md
56
README.md
@@ -11,57 +11,23 @@ Mastodon [huebot](https://botsin.space/@huebot).
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Nix
|
```shell
|
||||||
|
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] [-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]
|
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]
|
||||||
|
|
||||||
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
|
||||||
-s SIZE, --size SIZE size of huepaper in the form WIDTHxHEIGHT (default: 1920x1080)
|
--width WIDTH width of the image (default: 1920)
|
||||||
|
--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]
|
||||||
@@ -81,6 +47,14 @@ 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`).
|
||||||
|
|
||||||
@@ -141,7 +115,7 @@ huepaper -hue 1.0 -lmin 0.3 -lmax 0.6 -smin 0.8 -smax 1.0
|
|||||||

|

|
||||||
|
|
||||||
``` 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
|
||||||
```
|
```
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
colour==0.1.5
|
colour==0.1.5
|
||||||
numpy~=1.24.0,<1.25.0
|
numpy~=1.24.0,<1.25.0
|
||||||
pillow~=10.0.0
|
pillow~=10.2.0
|
@@ -1,9 +1,9 @@
|
|||||||
black==23.7.0
|
black==23.12.1
|
||||||
build==0.10.0
|
build==1.0.3
|
||||||
isort==5.12.0
|
isort==5.13.2
|
||||||
mypy==1.4.1
|
mypy==1.8.0
|
||||||
pylint==2.17.5
|
pylint==2.17.7
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.1.0
|
||||||
pytest==7.4.0
|
pytest==7.4.4
|
||||||
tox==4.7.0
|
tox==4.7.0
|
||||||
twine==4.0.2
|
twine==4.0.2
|
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "0.0.4"
|
__version__ = "0.0.5"
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .generator import generate
|
from .generator import generate
|
||||||
|
@@ -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 = False if color else True
|
random_color = not color
|
||||||
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("Selected random base color: {}".format(base_color.hex))
|
print(f"Selected random base color: {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.mode = "RGB"
|
image = image.convert(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(str(e))
|
print(e)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
@@ -4,6 +4,7 @@ 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(
|
||||||
@@ -23,9 +24,9 @@ def get_base_color(
|
|||||||
base_color = Color(color_string)
|
base_color = Color(color_string)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
base_color = Color("#{}".format(color_string))
|
base_color = Color(f"#{color_string}")
|
||||||
except:
|
except:
|
||||||
raise Exception("Invalid color expression: {}".format(color_string))
|
raise Exception(f"Invalid color expression: {color_string}")
|
||||||
|
|
||||||
return base_color
|
return base_color
|
||||||
|
|
||||||
@@ -43,7 +44,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 i in range(0, 4):
|
for _ in range(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
|
||||||
@@ -60,7 +61,7 @@ def create_colors(
|
|||||||
return tuple(colors)
|
return tuple(colors)
|
||||||
|
|
||||||
|
|
||||||
def create_base_image(c1, c2, c3, c4, width=1920, height=1080):
|
def create_base_image(c1, c2, c3, c4, width=1920, height=1080) -> Image:
|
||||||
"""Create a base huepaper by four corner colors.
|
"""Create a base huepaper by four corner colors.
|
||||||
|
|
||||||
c1 - top left
|
c1 - top left
|
||||||
@@ -79,12 +80,11 @@ def create_base_image(c1, c2, c3, c4, width=1920, height=1080):
|
|||||||
)
|
)
|
||||||
|
|
||||||
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, color):
|
def add_lines(image: Image, color: Tuple[float, float, float, Union[float, None]]) -> Image:
|
||||||
"""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,7 +103,8 @@ def add_lines(image, color):
|
|||||||
)
|
)
|
||||||
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)
|
||||||
@@ -138,7 +139,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("Failed to load emblem: {}".format(e))
|
raise Exception(f"Failed to load emblem: {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:
|
||||||
@@ -161,11 +162,10 @@ 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(
|
||||||
"The file {} already exists. Do you want to overwrite it? [y/N] ".format(
|
f"The file {filepath} already exists. Do you want to overwrite it? [y/N] "
|
||||||
filepath
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if overwrite != "y" and overwrite != "Y":
|
|
||||||
|
if overwrite not in ["y", "Y"]:
|
||||||
save = False
|
save = False
|
||||||
|
|
||||||
if save:
|
if save:
|
||||||
@@ -175,9 +175,10 @@ def save_image(image, filepath):
|
|||||||
image.save(filepath)
|
image.save(filepath)
|
||||||
stop = True
|
stop = True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Failed to save wallpaper: {}".format(e))
|
print(f"Failed to save wallpaper: {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(
|
||||||
|
BIN
tests/assets/emblem.png
Normal file
BIN
tests/assets/emblem.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
@@ -1,36 +0,0 @@
|
|||||||
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"))
|
|
84
tests/test_generator.py
Normal file
84
tests/test_generator.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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)
|
Reference in New Issue
Block a user