Skip to content

Instantly share code, notes, and snippets.

@BenWiederhake
Created January 30, 2022 20:10
Show Gist options
  • Save BenWiederhake/dda788be63bedbfb5e31338c1cfd9366 to your computer and use it in GitHub Desktop.
Save BenWiederhake/dda788be63bedbfb5e31338c1cfd9366 to your computer and use it in GitHub Desktop.
Annoying pulser that pulses annoyingly
#!/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