Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions cogs/randcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from discord.ext import commands

from fractal import fractal
from spirograph import spirograph
from brainfuck import process_brainfuck
from util import is_staff, baseconvert, reply

Expand Down Expand Up @@ -260,8 +261,13 @@ async def fractal(self, ctx, seed: str):
max_iter = self.bot.config["fractalDeets"]["maxIterations"]
messiness = self.bot.config["fractalDeets"]["messiness"]
zoom = self.bot.config["fractalDeets"]["zoom"]

frac = await to_thread(fractal, seed, size, size, max_iter, messiness, zoom)
try:
frac = await asyncio.wait_for(
to_thread(fractal, seed, size, size, max_iter, messiness, zoom),
timeout=15.0
)
except asyncio.TimeoutError:
return await reply(ctx, "Fractal generation took too long, terminating.")

with BytesIO() as image_binary:
frac.save(image_binary, "PNG")
Expand All @@ -276,6 +282,28 @@ async def fractal(self, ctx, seed: str):
f"Fractal generation took {end - start:.2f} seconds for seed '{seed}'"
)

@commands.command(help="Generate a spirograph image using a given seed.")
async def spirograph(self, ctx, seed: str):
start = perf_counter()
width = self.bot.config["spirographDeets"]["width"]
height = self.bot.config["spirographDeets"]["height"]
length = self.bot.config["spirographDeets"]["length"]

img = await to_thread(spirograph, seed, width, height, length)

with BytesIO() as image_binary:
img.save(image_binary, "PNG")
image_binary.seek(0)
file = discord.File(fp=image_binary, filename="spirograph.png")
embed = discord.Embed()
embed.set_image(url="attachment://spirograph.png")
await reply(ctx, seed, file=file, embed=embed)

end = perf_counter()
self.bot.logger.info(
f"Spirograph generation took {end - start:.2f} seconds for seed '{seed}'"
)

@commands.command(help="Be mean to someone. >:D")
async def insult(self, ctx, target: str = None):
if target is None:
Expand Down
4 changes: 4 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ fractalDeets:
maxIterations: 10000
messiness: 30
zoom: 3.5
spirographDeets:
height: 2000
width: 2000
length: 1000
automod_regexes:
- "^test(ing)?$"
- "f[0o]+"
Expand Down
82 changes: 82 additions & 0 deletions spirograph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import hashlib
from random import Random
from math import sin, cos, ceil

from PIL import Image
from numpy import array

def generate_spirograph_points(R, r, p, length):
a = 0.0 # angle parameter (aka steps)
points = []
while a < length:
x = (R - r) * cos(a) + p * cos(((R - r) / r) * a) # x coordinate
y = (R - r) * sin(a) - p * sin(((R - r) / r) * a) # y coordinate
points.append((x, y))
a += 0.01 # This can be adjusted for a more or less detailed spirograph. Smaller values yield more points.
return points

def scale_points(points, width, height):
# Calculate min and max for x and y
min_x = min(point[0] for point in points)
max_x = max(point[0] for point in points)
min_y = min(point[1] for point in points)
max_y = max(point[1] for point in points)

# Calculate scaling factors based on the image dimensions and the range of x and y
# This is done by taking the available width and height (minus some padding) and dividing by the range
scale_x = (width - 20) / (max_x - min_x)
scale_y = (height - 20) / (max_y - min_y)

# Center of the image. Used for distance calculation.
middle = (width//2, height//2)
scaled_points = []
for point in points:
x = int((point[0] - min_x) * scale_x) + 10 # Take X value, padding correction, apply scale and readd padding
y = int((point[1] - min_y) * scale_y) + 10 # Take Y value, padding correction, apply scale and readd padding
distance = ((x - middle[0])**2 + (y - middle[1])**2)**0.5 # pythagorean distance from center
if 2 <= x < width-2 and 2 <= y < height-2: # Ensure point is within bounds for drawing
scaled_points.append((x, y, distance))
return scaled_points

def sha256_lower_long(str):
acc = 0
for byte in hashlib.sha256(bytes(str, "utf-8")).digest():
acc = (acc << 8) | (byte & 0xFF)
return acc

def spirograph(seed: str, width: int, height: int, length: int) -> Image.Image:
rng = Random(sha256_lower_long(seed))
line_color_start = (
rng.randint(0, 255),
rng.randint(0, 255),
rng.randint(0, 255),
)
line_color_end = (
rng.randint(0, 255),
rng.randint(0, 255),
rng.randint(0, 255),
)
colors = []
for i in range(100):
ratio = i / 99
r = int(line_color_start[0] * (1 - ratio) + line_color_end[0] * ratio)
g = int(line_color_start[1] * (1 - ratio) + line_color_end[1] * ratio)
b = int(line_color_start[2] * (1 - ratio) + line_color_end[2] * ratio)
colors.append((r, g, b))
img_array = array(
[[(0, 0, 0) for _ in range(height)] for _ in range(width)], dtype="uint8"
)
R = rng.randint(50, 150)
r = rng.randint(10, 50)
p = rng.randint(10, 50)
points = generate_spirograph_points(R, r, p, length)
scaled_points = scale_points(points, width, height)
min_distance = min(point[2] for point in scaled_points)
max_distance = max(point[2] for point in scaled_points)
scale_distance = 100 / (max_distance - min_distance) # Scaling between 0 and 99 for color indexing
for point in scaled_points:
x = point[0]
y = point[1]
color = ceil((point[2] - min_distance) * scale_distance) - 1 # -1 to convert to 0-99 index. Ceil and then -1 ensures max_distance maps to 99
img_array[x-2:x+2, y-2:y+2] = colors[color]
return Image.fromarray(img_array, "RGB")