Created
January 30, 2022 20:10
-
-
Save BenWiederhake/dda788be63bedbfb5e31338c1cfd9366 to your computer and use it in GitHub Desktop.
Annoying pulser that pulses annoyingly
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from PIL import Image | |
import math | |
import time | |
SIZE = (400, 300) | |
TOTAL_TIME = 40 | |
OVERVIEW_TIMESTEPS = 300 | |
BASE_RADIUS = 100 | |
# Maximum reach of the "pulses". | |
PULSE_RADIUS = 150 | |
# Higher power means busier "pulse" towards the outside. 1 means linear. | |
PULSE_FREQUENCY_POWER = 4 | |
# The total phase range visible in a single picture. 2 * pi means that we always see one full wave. | |
# Higher value means that we see many waves, and each individual wave becomes smaller/tight. | |
PULSE_VISIBLE_DELTA = 2 * math.pi * 10 | |
GAMMA = 2.2 | |
PART_BASE = 0.55 | |
def compute_state(distance, time): | |
distance_frac_base = min(distance, BASE_RADIUS) / BASE_RADIUS | |
# Invert and half it, to add it more easily to intensity_pulse. | |
# (I know, applying the gamma before adding doesn't work like this, but whatever.) | |
if distance_frac_base < 0.5: | |
intensity_base = (1 - 2 * distance_frac_base ** 2) | |
else: | |
intensity_base = 2 * (1 - distance_frac_base) ** 2 | |
distance_frac_pulse = min(distance, PULSE_RADIUS) / PULSE_RADIUS | |
amplitude_pulse = 1 - distance_frac_pulse | |
phase_pulse = -time * 2 * math.pi + PULSE_VISIBLE_DELTA * (distance_frac_pulse ** PULSE_FREQUENCY_POWER) | |
intensity_pulse = (math.cos(phase_pulse) + 1) * 0.5 | |
intensity_pulse = amplitude_pulse * intensity_pulse | |
# print(f'{intensity_pulse} ({amplitude_pulse}, {phase_pulse}, {distance_frac_pulse}), {intensity_base} ({distance_frac_base})') | |
intensity = PART_BASE * intensity_base + (1 - PART_BASE) * intensity_pulse | |
#intensity = intensity_pulse * 2 | |
#intensity = intensity_base * 2 | |
intensity = intensity ** GAMMA | |
# Normalize | |
if intensity > 1: | |
print(f'intensity capped from {intensity}') | |
intensity = 1 | |
if intensity < 0: | |
print(f'intensity capped from {intensity}') | |
intensity = 0 | |
return round(255 - 255 * intensity) | |
def make_frame(time_frac): | |
img = Image.new('L', SIZE) | |
data = [compute_state(math.sqrt((x - SIZE[0] / 2) ** 2 + (y - SIZE[1] / 2) ** 2), time_frac) | |
for y in range(SIZE[1]) | |
for x in range(SIZE[0])] | |
img.putdata(data) | |
return img | |
def make_pulser_frames(): | |
frames = [] | |
for i in range(TOTAL_TIME): | |
new_frame = make_frame(i / TOTAL_TIME) | |
frames.append(new_frame) | |
if i % 10 == 0: | |
print(f'Computed {i + 1}/{TOTAL_TIME} frames …') | |
print(f'Computed all {TOTAL_TIME} frames.') | |
return frames | |
def make_overview(): | |
max_distance = round(math.sqrt((SIZE[0] / 2) ** 2 + (SIZE[1] / 2) ** 2)) + 5 | |
img = Image.new('L', (max_distance, OVERVIEW_TIMESTEPS)) | |
data = [compute_state(distance, time / OVERVIEW_TIMESTEPS) | |
for time in range(OVERVIEW_TIMESTEPS) | |
for distance in range(max_distance)] | |
img.putdata(data) | |
return img | |
def save_gif(frames, filename, duration): | |
assert len(frames) > 1 | |
frames[0].save( | |
filename, | |
format='GIF', | |
append_images=frames[1:], | |
save_all=True, duration=duration, loop=0) # "loop=0" is "forever" | |
def run(): | |
filename_gif = time.strftime('pulser_%s.gif') | |
filename_overview = time.strftime('pulser_%s.png') | |
frames = make_pulser_frames() | |
save_gif(frames, filename_gif, 20) | |
overview = make_overview() | |
overview.save(filename_overview) | |
print(f'Written to {filename_gif} and {filename_overview}') | |
if __name__ == '__main__': | |
run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment