|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import cairo
|
|
|
|
import math
|
|
|
|
from datetime import datetime
|
|
|
|
import calendar
|
|
|
|
import random
|
|
|
|
import colorsys
|
|
|
|
import argparse
|
|
|
|
|
|
|
|
|
|
|
|
# precision of the calculation
|
|
|
|
PRECISION = 10000
|
|
|
|
|
|
|
|
|
|
|
|
def get_color_from_date(date: datetime) -> tuple[float, float, float]:
|
|
|
|
"""Return a color based on the progress through the year."""
|
|
|
|
# a day between 1 and 365 (inclusive)
|
|
|
|
today = date.timetuple().tm_yday
|
|
|
|
days_in_year = 365 + calendar.isleap(date.year)
|
|
|
|
# between 0 and 1, how far through the year are we?
|
|
|
|
progress = today / days_in_year
|
|
|
|
return colorsys.hsv_to_rgb(progress, 1, 0.9)
|
|
|
|
|
|
|
|
|
|
|
|
def get_amplitude_from_date(date: datetime, waves) -> float:
|
|
|
|
"""Return the amplitude of waves, based on progress through the month."""
|
|
|
|
days_in_month = calendar.monthrange(date.year, date.month)[1]
|
|
|
|
max_amp = 1 / waves / 2
|
|
|
|
return date.day / days_in_month * max_amp
|
|
|
|
|
|
|
|
|
|
|
|
def create_wpotd(
|
|
|
|
width: int,
|
|
|
|
height: int,
|
|
|
|
frequency: int,
|
|
|
|
wave_offset: float,
|
|
|
|
date: datetime = datetime.now(),
|
|
|
|
dark: bool = True,
|
|
|
|
) -> cairo.ImageSurface:
|
|
|
|
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
|
|
|
|
ctx = cairo.Context(surface)
|
|
|
|
|
|
|
|
ctx.scale(width, height) # Normalizing the canvas
|
|
|
|
# ctx.set_antialias(cairo.Antialias.BEST)
|
|
|
|
|
|
|
|
lastpoints = [(x / PRECISION, 0) for x in range(PRECISION + 1)]
|
|
|
|
|
|
|
|
waves = date.month
|
|
|
|
amplitude = get_amplitude_from_date(date, waves)
|
|
|
|
|
|
|
|
r, g, b = get_color_from_date(date)
|
|
|
|
alpha_step = 1 / waves
|
|
|
|
|
|
|
|
# background color
|
|
|
|
if dark:
|
|
|
|
ctx.set_source_rgb(0, 0, 0)
|
|
|
|
else:
|
|
|
|
ctx.set_source_rgb(255, 255, 255)
|
|
|
|
ctx.rectangle(0, 0, 1, 1)
|
|
|
|
ctx.fill()
|
|
|
|
|
|
|
|
wave_height = 1 / waves
|
|
|
|
step_size = 1 / PRECISION
|
|
|
|
|
|
|
|
for wave_index in range(waves + 1):
|
|
|
|
points = []
|
|
|
|
x = 0
|
|
|
|
while x < 1:
|
|
|
|
# step along, create points along the wave
|
|
|
|
y = amplitude * math.sin(frequency * x + (wave_index * wave_offset))
|
|
|
|
points.append((x, (y + (0.5 + wave_index) * wave_height)))
|
|
|
|
x += step_size
|
|
|
|
# print(f'Draw {len(points)} points for curve {num}')
|
|
|
|
else:
|
|
|
|
# make more transparent toward bottom
|
|
|
|
ctx.set_source_rgba(r, g, b, 1 - (alpha_step * wave_index))
|
|
|
|
# draw waves
|
|
|
|
ctx.move_to(*points[0])
|
|
|
|
for p in points[1:]:
|
|
|
|
ctx.line_to(*p)
|
|
|
|
ctx.set_line_width(0.0004)
|
|
|
|
ctx.stroke_preserve()
|
|
|
|
for p in reversed(lastpoints):
|
|
|
|
ctx.line_to(*p)
|
|
|
|
ctx.line_to(*points[0])
|
|
|
|
ctx.fill()
|
|
|
|
lastpoints = points
|
|
|
|
return surface
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument("width", type=int, help="Width of the image")
|
|
|
|
parser.add_argument("height", type=int, help="Height of the image")
|
|
|
|
parser
|
|
|
|
parser.add_argument("--dark", help="Draw on dark background", action="store_true")
|
|
|
|
parser.add_argument(
|
|
|
|
"-o",
|
|
|
|
"--offset",
|
|
|
|
type=float,
|
|
|
|
help="How much the waves should be offset to each other",
|
|
|
|
)
|
|
|
|
parser.add_argument("-f", "--frequency", type=int, help="Frequency of the waves")
|
|
|
|
parser.add_argument(
|
|
|
|
"--stdout", help="Write output image to stdout", action="store_true"
|
|
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
print(args)
|
|
|
|
if not args.offset:
|
|
|
|
args.offset = math.pi / random.choice([1, 2, 4])
|
|
|
|
if not args.frequency:
|
|
|
|
args.frequency = random.randint(10, 40)
|
|
|
|
output = create_wpotd(
|
|
|
|
args.width,
|
|
|
|
args.height,
|
|
|
|
dark=args.dark,
|
|
|
|
wave_offset=args.offset,
|
|
|
|
frequency=args.frequency,
|
|
|
|
)
|
|
|
|
if args.stdout:
|
|
|
|
import sys
|
|
|
|
|
|
|
|
output.write_to_png(sys.stdout.buffer)
|
|
|
|
else:
|
|
|
|
output.write_to_png("out/waves.png")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|