huepaper/huepaper.py

378 lines
10 KiB
Python
Raw Normal View History

2020-05-08 22:03:02 +03:00
#!/usr/bin/env python
2021-07-14 18:08:24 +03:00
from PIL import Image, ImageDraw, ImageOps
from colour import Color
2020-05-08 22:03:02 +03:00
import argparse
import os.path
import random
import sys
2021-07-14 18:08:24 +03:00
2020-05-08 22:03:02 +03:00
2020-05-09 14:26:16 +03:00
def print_logo():
2020-09-13 16:13:23 +03:00
logo = """
2020-05-09 14:26:16 +03:00
.lk.
cO.
cO.;:lc. ,c. .cc .,',c; .,c.;coc. ;,.,c. ':l.:lo: '',:c. '::.lo.
cO' kd .O; dO ,x...,Ox cO; lO: ;x xk OO. .kO. x;...x0' 0x. .
cO. xx .O; dO ko...... :O. Ox .,..xO kk ;0;;0...... 0d
cO. xx .O; xO dO. .. :O. .O; dk xO kk :O.'0o , 0d
.dk, .kk. okc;,ox' ckxllc. :Oc'.,l' oOl;'dO:. kO;..:l. ,xOolc; ,Ox.
:O. kk
lO, OO
OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO00O0000000000000000;
2020-09-13 16:13:23 +03:00
"""
2020-05-09 14:26:16 +03:00
print(logo)
2021-07-14 18:08:24 +03:00
2020-09-13 16:13:23 +03:00
def get_base_color(color_string=None):
2020-05-08 22:03:02 +03:00
2021-07-14 18:08:24 +03:00
global sat_min, sat_max, lum_min, lum_max
2020-05-08 22:03:02 +03:00
# If no color string is given, create a random color
if not color_string:
hue = random.uniform(0, 1)
sat = random.uniform(sat_min, sat_max)
lum = random.uniform(lum_min, lum_max)
2020-09-13 16:13:23 +03:00
base_color = Color(hue=hue, saturation=sat, luminance=lum)
print("Selected random base color: {}".format(base_color.hex))
2020-05-08 22:03:02 +03:00
# Else try to parse string
else:
try:
base_color = Color(color_string)
except:
2020-09-13 16:13:23 +03:00
print("Not a valid color expression: {}".format(color_string))
2020-05-08 22:03:02 +03:00
sys.exit(1)
return base_color
2021-07-14 18:08:24 +03:00
2020-05-08 22:03:02 +03:00
def create_colors(base_color):
2021-07-14 18:08:24 +03:00
global max_hue, sat_min, sat_max, lum_min, lum_max
2020-05-08 22:03:02 +03:00
colors = []
2020-09-13 15:28:25 +03:00
2020-05-08 22:03:02 +03:00
max_sat_diff = 0.1
max_lum_diff = 0.1
# Create four random colors similar to the given base_color
for i in range(0, 4):
2020-05-09 01:01:43 +03:00
tmp_hue = base_color.hue + random.uniform(-max_hue / 2.0, max_hue / 2.0)
2020-05-08 22:03:02 +03:00
if tmp_hue > 1.0:
tmp_hue -= 1
tmp_sat = base_color.saturation + random.uniform(-max_sat_diff, max_sat_diff)
tmp_sat = min(sat_max, max(sat_min, tmp_sat))
2020-09-13 15:28:25 +03:00
2020-05-08 22:03:02 +03:00
tmp_lum = base_color.luminance + random.uniform(-max_lum_diff, max_lum_diff)
tmp_lum = min(lum_max, max(lum_min, tmp_lum))
2020-09-13 16:13:23 +03:00
color = Color(hue=tmp_hue, saturation=tmp_sat, luminance=tmp_lum)
2020-05-08 22:03:02 +03:00
colors.append(color.rgb)
return tuple(colors)
2021-07-14 18:08:24 +03:00
2020-05-08 23:51:15 +03:00
# c1 - top left
# c2 - top right
# c3 - bottom right
# c4 - bottom left
def create_base_image(c1, c2, c3, c4):
2020-05-08 22:03:02 +03:00
2021-07-14 18:08:24 +03:00
global width, height
2020-05-08 23:51:15 +03:00
# Lambda for adding four colors
2020-09-13 16:13:23 +03:00
add = lambda c1, c2, c3, c4: (
c1[0] + c2[0] + c3[0] + c4[0],
c1[1] + c2[1] + c3[1] + c4[1],
c1[2] + c2[2] + c3[2] + c4[2],
)
2020-05-08 22:03:02 +03:00
2020-05-08 23:51:15 +03:00
# Lambda for multiplying a color with a factor
2020-09-13 16:13:23 +03:00
mul = lambda c, x: (c[0] * x, c[1] * x, c[2] * x)
2020-05-08 22:03:02 +03:00
2020-05-08 23:51:15 +03:00
# Lambda for scaling a color from [0 , 1] to [0, 255]
2020-09-13 16:13:23 +03:00
cor = lambda c: (int(c[0] * 255), int(c[1] * 255), int(c[2] * 255))
2020-05-08 23:51:15 +03:00
# Lambda for calculating a color at x and y in range [0, 1]
# Color limits are set at creation
2020-09-13 16:13:23 +03:00
col = lambda x, y, c1=c1, c2=c2, c3=c3, c4=c4: cor(
add(
mul(c1, (1.0 - x) * (1.0 - y)),
mul(c2, x * (1.0 - y)),
mul(c3, x * y),
mul(c4, (1.0 - x) * y),
)
)
2020-05-08 23:51:15 +03:00
# Create image
2020-09-13 16:13:23 +03:00
image = Image.new("RGBA", (width, height))
2020-05-08 23:51:15 +03:00
pixels = image.load()
2020-09-13 15:28:25 +03:00
2020-05-08 23:51:15 +03:00
for x in range(0, width):
for y in range(0, height):
pixels[x, y] = col(x / (width - 1), y / (height - 1))
2020-05-08 22:03:02 +03:00
return image
2021-07-14 18:08:24 +03:00
2020-05-08 22:03:02 +03:00
def add_lines(image, color):
2021-07-14 18:08:24 +03:00
global width, height
2020-09-13 16:13:23 +03:00
line_image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
2020-05-08 22:03:02 +03:00
draw = ImageDraw.Draw(line_image)
# Set color
2020-09-13 16:13:23 +03:00
color = tuple(map(lambda x: int(x * 255), color))
2020-05-08 22:03:02 +03:00
# Generate lines
number_of_lines = random.randint(1, 3)
scale = width / 100.0
base_width = random.randint(int(2 * scale), int(5 * scale))
2020-09-13 16:13:23 +03:00
rand_width = lambda base_width=base_width: base_width + random.randint(
-base_width // 2, base_width // 2
)
2020-05-08 22:03:02 +03:00
space = rand_width() // 2
offset = random.randint(0, space)
for i in range(0, number_of_lines):
line_width = rand_width()
x = offset + space + (line_width // 2)
2020-09-13 16:13:23 +03:00
draw.line((x, 0, x, height), fill=color, width=line_width)
2020-05-08 22:03:02 +03:00
offset += space + line_width
# Mirror line image eventually
orientation = random.randrange(2)
if orientation == 1:
line_image = ImageOps.mirror(line_image)
# Add line image to input image
image.alpha_composite(line_image, (0, 0))
2020-09-13 15:28:25 +03:00
return image
2020-05-08 22:03:02 +03:00
2021-07-14 18:08:24 +03:00
2020-05-08 22:03:02 +03:00
def add_pixelation(image, x, y):
2021-07-14 18:08:24 +03:00
global width, height
2020-05-08 22:03:02 +03:00
image = image.resize((x, y))
image = image.resize((width, height), Image.BOX)
return image
2021-07-14 18:08:24 +03:00
2020-05-08 23:51:15 +03:00
def add_emblem(image, filepath):
2021-07-14 18:08:24 +03:00
global width, height
2020-05-08 23:51:15 +03:00
# Load image
try:
emblem_image = Image.open(filepath)
except Exception as e:
2020-09-13 16:13:23 +03:00
print("Failed to load emblem: {}".format(e))
2020-05-08 23:51:15 +03:00
sys.exit(1)
# Exit if emblem is too big
if emblem_image.size[0] > width or emblem_image.size[1] > height:
2020-09-13 16:13:23 +03:00
print("Emblem can't be bigger than the wallpaper")
2020-05-08 23:51:15 +03:00
sys.exit(1)
# Insert emblem in the center
2020-09-13 16:13:23 +03:00
offset = (
(image.size[0] - emblem_image.size[0]) // 2,
(image.size[1] - emblem_image.size[1]) // 2,
)
2020-05-08 23:51:15 +03:00
image.alpha_composite(emblem_image, offset)
return image
2021-07-14 18:08:24 +03:00
2020-05-08 22:03:02 +03:00
def save_image(filepath, image):
save = True
# Check whether file exists
if os.path.isfile(filepath):
2020-09-13 16:13:23 +03:00
overwrite = input(
"The file {} already exists. Do you want to overwrite it? [y/N] ".format(
filepath
)
)
if overwrite != "y" and overwrite != "Y":
2020-05-08 22:03:02 +03:00
save = False
if save:
2020-05-08 23:51:15 +03:00
stop = False
while not stop:
try:
image.save(filepath)
stop = True
except Exception as e:
2020-09-13 16:13:23 +03:00
print("Failed to save wallpaper: {}".format(e))
again = input("Do you want to try again? [Y/n] ")
if again == "n" or again == "N":
2020-05-08 23:51:15 +03:00
stop = True
else:
2020-09-13 16:13:23 +03:00
filepath = input(
"Please enter new path where the wallpaper shall be saved: "
)
2020-05-08 22:03:02 +03:00
2021-07-14 18:08:24 +03:00
if __name__ == "__main__":
2020-05-08 22:03:02 +03:00
global width, height, max_hue, sat_min, sat_max, lum_min, lum_max
2020-09-13 16:13:23 +03:00
parser = argparse.ArgumentParser(
description="Create wallpapers based on color hues."
)
parser.add_argument(
"-s",
"--size",
default="1920x1080",
help="size of huepaper in the form WIDTHxHEIGHT (default: 1920x1080)",
)
parser.add_argument(
"-c",
"--color",
help="color, the huepaper is generated from (uses a random color if not given)",
)
parser.add_argument("-p", "--preview", action="store_true", help="preview huepaper")
parser.add_argument(
"-o", "--output", help="file where to save the huepaper to (default: None)"
)
parser.add_argument(
"-l",
"--lines",
nargs="?",
const=0.3,
type=float,
help="include one to three random lines in base color with given opacity in range [0, 1] (default: 0.3)",
)
parser.add_argument(
"-lb",
"--lines_bright",
nargs="?",
const=0.1,
type=float,
help="include one to three bright random lines with given opacity in range [0, 1] (default: 0.1)",
)
parser.add_argument(
"-ld",
"--lines_dark",
nargs="?",
const=0.1,
type=float,
help="include one to three dark random lines with given opacity in range [0, 1] (default: 0.1)",
)
parser.add_argument(
"-P",
"--pixelate",
nargs="?",
const="16x9",
help="pixelate image with WIDTHxHEIGHT (default: 16x9)",
)
parser.add_argument(
"-e", "--emblem", help="emblem to add in the center of the huepaper"
)
parser.add_argument(
"-hue",
default=0.1,
type=float,
help="maximum hue to differ from given color in range [0, 1] (default: 0.1)",
)
parser.add_argument(
"-smin",
default=0.2,
type=float,
help="minimum satisfaction for colors in range [0, 1] (default: 0.2)",
)
parser.add_argument(
"-smax",
default=1.0,
type=float,
help="maximum satisfaction for colors in range [0, 1] (default: 1.0)",
)
parser.add_argument(
"-lmin",
default=0.2,
type=float,
help="minimum luminance for colors in range [0, 1] (default: 0.2)",
)
parser.add_argument(
"-lmax",
default=0.9,
type=float,
help="maximum luminance for colors in range [0, 1] (default: 0.9)",
)
2020-05-08 22:03:02 +03:00
# Get args
args = parser.parse_args()
2020-09-13 16:13:23 +03:00
size = args.size
2020-05-08 22:03:02 +03:00
color = args.color
preview = args.preview
output = args.output
lines = args.lines
lines_bright = args.lines_bright
lines_dark = args.lines_dark
emblem = args.emblem
pixelate = args.pixelate
max_hue = args.hue
sat_min = args.smin
sat_max = args.smax
lum_min = args.lmin
lum_max = args.lmax
2020-09-13 16:13:23 +03:00
# Get size
try:
values = size.split("x")
width = int(values[0])
height = int(values[1])
except:
parser.error("The size must be given in form: 1920x1080")
2020-05-08 22:03:02 +03:00
# Check preconditions
if not preview and not output:
2020-09-13 16:13:23 +03:00
parser.error("You must either set -p (--preview) or -o (--output)")
2020-05-08 22:03:02 +03:00
if pixelate:
try:
2020-09-13 16:13:23 +03:00
values = pixelate.split("x")
2020-05-08 22:03:02 +03:00
px = int(values[0])
py = int(values[1])
except:
2020-09-13 16:13:23 +03:00
parser.error("Pixelation value must be set in form: 42x42")
2020-05-08 22:03:02 +03:00
2020-05-09 14:26:16 +03:00
print_logo()
2020-05-08 22:03:02 +03:00
base_color = get_base_color(color)
c1, c2, c3, c4 = create_colors(base_color)
image = create_base_image(c1, c2, c3, c4)
if lines:
image = add_lines(image, base_color.rgb + (lines,))
if lines_bright:
image = add_lines(image, (1.0, 1.0, 1.0, lines_bright))
if lines_dark:
image = add_lines(image, (0.0, 0.0, 0.0, lines_dark))
if pixelate:
image = add_pixelation(image, px, py)
2020-09-13 15:28:25 +03:00
2020-05-08 23:51:15 +03:00
if emblem:
image = add_emblem(image, emblem)
2020-05-08 22:03:02 +03:00
2020-09-13 16:13:23 +03:00
image.mode = "RGB"
2020-05-09 14:34:11 +03:00
2020-05-08 22:03:02 +03:00
if preview:
image.show()
if not output:
2020-09-13 16:13:23 +03:00
save = input("Do you want to save the image? [y/N] ")
if save == "y" or save == "Y":
path = input("Enter the path where the wallpaper shall be saved: ")
2020-05-08 22:03:02 +03:00
save_image(path, image)
if output:
save_image(output, image)