#!/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()