Compare commits

..

20 Commits

Author SHA1 Message Date
b97737f670 add blobs 2024-10-27 21:43:34 +01:00
651473975f new scheme 2024-10-27 20:46:54 +01:00
050c1da950 fix: background colour 2024-10-27 20:46:39 +01:00
3220b6aa64 jpg and png works 2024-10-27 20:21:22 +01:00
b4e70c2245 fix: dot needed before suffix 2024-10-27 20:12:44 +01:00
94489b5da2 enable other image formats API 2024-10-27 20:09:57 +01:00
beb81771a8 as API 2024-10-27 19:46:19 +01:00
0ee3e39c3c save as different image formats (pyvips) 2024-10-27 19:28:02 +01:00
9f5bb724cb add posibility for including emojis 2024-10-27 16:42:02 +01:00
62e7aa0e07 working with some emojis and card game characters 2024-10-25 22:54:46 +02:00
dfaef09ccb add filename option 2024-10-21 13:32:10 +02:00
dc3503a407 add Perlin noise as distribution 2024-08-22 15:52:32 +02:00
ba0834d729 add custom markers 2024-08-14 14:19:48 +00:00
93e4e9453f format 2024-08-14 15:28:49 +02:00
06cf912490 add more marker symbols 2024-08-14 13:27:35 +00:00
cbaf9bc071 add custom symbols 2024-08-14 14:14:41 +02:00
eaa2541596 feat: markers + adjusted density 2024-08-14 12:55:22 +02:00
5941ed7909 feat: choose marker 2024-08-13 16:14:29 +02:00
juvilius
01fb94c9eb add some popular colourschemes 2023-07-24 16:22:55 +02:00
juvilius
bfd1ecabeb background local helper script 2023-07-24 16:18:41 +02:00
6 changed files with 572 additions and 121 deletions

48
background.py Normal file
View File

@@ -0,0 +1,48 @@
import json
import random
from speckles import make_wallpaper
palettes = [
{"colours": ["#DE4A21", "#6F04F2", "#490B1C", "#4a9c9e"], "votes": 1000000},
{"colours": ["#eee4ab", "#e5cb9f", "#99c4c8", "#68a7ad"], "votes": 100000},
{"colours": ["#e4d192", "#cba0ae", "#af7ab3", "#80558c"], "votes": 100000},
{"colours": ["#eeeeee", "#e1d4bb", "#cbb279", "#537188"], "votes": 100000},
{"colours": ["#c0dbea", "#ba90c6", "#e8a0bf", "#fdf4f5"], "votes": 100000},
{"colours": ["#f5ffc9", "#b3e5be", "#a86464", "#804674"], "votes": 100000},
{"colours": ["#ffde7d", "#f6416c", "#f8f3d4", "#00b8a9"], "votes": 100000},
{"colours": ["#53354a", "#903749", "#e84545", "#2b2e4a"], "votes": 100000},
{"colours": ["#967e76", "#d7c0ae", "#eee3cb", "#b7c4cf"], "votes": 100000},
{"colours": ["#fc5185", "#f5f5f5", "#3fc1c9", "#364f6b"], "votes": 100000},
{"colours": ["#eaeaea", "#ff2e63", "#252a34", "#08d9d6"], "votes": 100000},
{"colours": ["#eeeeee", "#00adb5", "#393e46", "#222831"], "votes": 100000},
{"colours": ["#2cd3e1", "#a459d1", "#f266ab", "#ffb84c"], "votes": 100000},
{"colours": ["#ffe194", "#e8f6ef", "#1b9c85", "#4c4c6d"], "votes": 100000},
{"colours": ["#146c94", "#19a7ce", "#b0daff", "#feff86"], "votes": 100000},
{"colours": ["#4c3d3d", "#c07f00", "#ffd95a", "#fff7d4"], "votes": 100000},
{"colours": ["#8bacaa", "#b04759", "#e76161", "#f99b7d"], "votes": 100000},
{"colours": ["#146c94", "#19a7ce", "#afd3e2", "#f6f1f1"], "votes": 100000},
{"colours": ["#9ba4b5", "#212a3e", "#394867", "#f1f6f9"], "votes": 100000},
{"colours": ["#00ffca", "#05bfdb", "#088395", "#0a4d68"], "votes": 100000},
{"colours": ["#7c9070", "#9ca777", "#fee8b0", "#f97b22"], "votes": 100000},
{"colours": ["#002a19", "#000000", "#ffffff", "#e0512f"], "votes": 100000},
{"colours": ["#212248", "#EBAE36", "#C7C9F0", "#11088A"], "votes": 100000},
]
with open("popular.json", "r") as f:
palettes += json.load(f)
palette = random.choice(palettes)
enabled_markers = "otY+xP*b"
markers = random.choices(enabled_markers, k=random.randint(1, len(enabled_markers)))
for i, palette in enumerate(palettes):
colours = ["none"] + palette["colours"]
b = make_wallpaper(
",".join(colours),
fileformat="png",
size=2.1,
density=0.2,
filename=f"wallpapers/speckles{i}_2880x1800.png",
perlin=True,
markers=enabled_markers,
orientation="2880x1800",
)

57
blob.py Normal file
View File

@@ -0,0 +1,57 @@
import svg
from typing import Iterator, NamedTuple
import numpy as np
RGen = np.random.default_rng()
class Point(NamedTuple):
x: float
y: float
def iter_body_points(size: int) -> Iterator[Point]:
n_points = RGen.integers(3, 12) # how many points do we want?
angle_step = (
np.pi * 2 / n_points
) # step used to place each point at equal distances
for point_number in range(1, n_points + 1):
pull = 0.75 * RGen.random() * 0.25
angle = point_number * angle_step
x = size + np.cos(angle) * size * pull
y = size + np.sin(angle) * size * pull
yield Point(x, y)
def spline(points: list[Point], body_tension: int) -> Iterator[svg.PathData]:
"""
https://github.com/georgedoescode/splinejs
"""
yield svg.MoveTo(*points[-1])
first_point = points[0]
second_point = points[1]
points.insert(0, points[-1])
points.insert(0, points[-2])
points.append(first_point)
points.append(second_point)
for p0, p1, p2, p3 in zip(points, points[1:], points[2:], points[3:]):
yield svg.CubicBezier(
x1=p1.x + (p2.x - p0.x) / 6 * body_tension,
y1=p1.y + (p2.y - p0.y) / 6 * body_tension,
x2=p2.x - (p3.x - p1.x) / 6 * body_tension,
y2=p2.y - (p3.y - p1.y) / 6 * body_tension,
x=p2.x,
y=p2.y,
)
def blob(x, y, c, s, body_tension: int = 1) -> svg.Path:
points = list(iter_body_points(s))
return svg.Path(
d=list(spline(points, body_tension)),
fill=c,
stroke="#000000",
stroke_width=2,
transform=[svg.Translate(x-s, y-s)],
)

107
main.py
View File

@@ -1,17 +1,14 @@
import io import io
import logging from perlin import CoordsGenerator
from typing import Literal
import itertools import itertools
import json import logging
import random import random
from collections import Counter
from copy import deepcopy
from datetime import datetime
from pathlib import Path
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
import uvicorn import uvicorn
from fastapi import FastAPI, Response from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
@@ -41,15 +38,65 @@ MEDIA_TYPES = {
} }
Marker = Literal[
".",
",",
"o",
"v",
"^",
"<",
">",
"1",
"2",
"3",
"4",
"8",
"s",
"p",
"P",
"*",
"h",
"H",
"+",
"x",
"X",
"D",
"d",
"|",
"_",
]
marker_subs = {
"=": "$=$",
"/": "$/$",
"\\": "$\\setminus$",
"«": "$«$",
"»": "$»$",
"~": "$\\sim$",
"": "$♪$",
"": "$♫$",
"": "$\\infty$",
"": "$♡$",
"o": "$\\bigcirc$",
"": "$♠$",
"": "$♣$",
"": "$♥$",
"": "$♦$",
}
@app.get("/speckles/") @app.get("/speckles/")
def make_wallpaper( def make_wallpaper(
speckle_colours: str, speckle_colours: str,
density: float | None = 0.12, density: float = 0.12,
size: float | None = 3, size: float = 3,
fileformat: str = "svg", fileformat: str = "svg",
orientation: str | None = "landscape", orientation: str = "landscape",
filename: str = "",
markers: str = ".",
perlin: bool = True,
): ):
if not fileformat in MEDIA_TYPES: if fileformat not in MEDIA_TYPES:
return return
speckle_colours = speckle_colours.split(",") speckle_colours = speckle_colours.split(",")
background = speckle_colours.pop(0) background = speckle_colours.pop(0)
@@ -59,17 +106,17 @@ def make_wallpaper(
x, y = (1920, 1080) x, y = (1920, 1080)
elif "x" in orientation: elif "x" in orientation:
resolution = orientation.split("x") resolution = orientation.split("x")
if len(resolution) !=2 : if len(resolution) != 2:
logging.critical("input resolution has more or less than 2 dimensions") logging.critical("input resolution has more or less than 2 dimensions")
return return
x,y = resolution x, y = resolution
if all([x.isdigit(), y.isdigit()]): if all([x.isdigit(), y.isdigit()]):
x,y = int(x), int(y) x, y = int(x), int(y)
else: else:
return return
else: else:
x, y = (1920, 1080) x, y = (1920, 1080)
speckles_per_colour = int(x / 128 * y / 128 * density) speckles_per_colour = int(x / 128 * y / 128 * density / len(markers))
fig, ax = plt.subplots(figsize=(x / 120, y / 120), facecolor=background) fig, ax = plt.subplots(figsize=(x / 120, y / 120), facecolor=background)
ax.set_facecolor(background) ax.set_facecolor(background)
@@ -78,15 +125,28 @@ def make_wallpaper(
ax.set_yticks([]) ax.set_yticks([])
ax.margins(0, 0) ax.margins(0, 0)
for color, size in itertools.product( if perlin:
gen = CoordsGenerator(y, x)
for color, marker, size in itertools.product(
speckle_colours, speckle_colours,
markers,
np.logspace(0, size, 10, base=np.exp(2)), np.logspace(0, size, 10, base=np.exp(2)),
): ):
marker = marker_subs.get(marker, marker)
if perlin:
x_coords, y_coords = gen.pick(speckles_per_colour)
else:
x_coords, y_coords = (
[random.random() * x / 8 for _ in range(speckles_per_colour)],
[random.random() * y / 8 for _ in range(speckles_per_colour)],
)
ax.scatter( ax.scatter(
[random.random() * x / 8 for _ in range(speckles_per_colour)], x_coords,
[random.random() * y / 8 for _ in range(speckles_per_colour)], y_coords,
c=color, c=color,
s=size, s=size,
marker=marker,
) )
fig.tight_layout() fig.tight_layout()
@@ -103,9 +163,14 @@ def make_wallpaper(
pad_inches=0, pad_inches=0,
) )
buf.seek(0) buf.seek(0)
return StreamingResponse(content=buf, media_type=MEDIA_TYPES[fileformat]) if filename:
buf.close() with open(filename, "wb") as f:
f.write(buf.getbuffer())
buf.close()
return filename
else:
return StreamingResponse(content=buf, media_type=MEDIA_TYPES[fileformat])
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run("main:app", workers=2, port=8099, reload=False) uvicorn.run("main:app", workers=3, port=8099, reload=True)

85
perlin.py Normal file
View File

@@ -0,0 +1,85 @@
import numpy as np
from scipy.special import softmax
def interpolant(t):
return t * t * t * (t * (t * 6 - 15) + 10)
def generate_perlin_noise_2d(
shape, res, tileable=(False, False), interpolant=interpolant
):
"""Generate a 2D numpy array of perlin noise.
Args:
shape: The shape of the generated array (tuple of two ints).
This must be a multple of res.
res: The number of periods of noise to generate along each
axis (tuple of two ints). Note shape must be a multiple of
res.
tileable: If the noise should be tileable along each axis
(tuple of two bools). Defaults to (False, False).
interpolant: The interpolation function, defaults to
t*t*t*(t*(t*6 - 15) + 10).
Returns:
A numpy array of shape shape with the generated noise.
Raises:
ValueError: If shape is not a multiple of res.
"""
delta = (res[0] / shape[0], res[1] / shape[1])
d = (shape[0] // res[0], shape[1] // res[1])
grid = np.mgrid[0 : res[0] : delta[0], 0 : res[1] : delta[1]].transpose(1, 2, 0) % 1
# Gradients
angles = 2 * np.pi * np.random.rand(res[0] + 1, res[1] + 1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
if tileable[0]:
gradients[-1, :] = gradients[0, :]
if tileable[1]:
gradients[:, -1] = gradients[:, 0]
gradients = gradients.repeat(d[0], 0).repeat(d[1], 1)
g00 = gradients[: -d[0], : -d[1]]
g10 = gradients[d[0] :, : -d[1]]
g01 = gradients[: -d[0], d[1] :]
g11 = gradients[d[0] :, d[1] :]
# Ramps
n00 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1])) * g00, 2)
n10 = np.sum(np.dstack((grid[:, :, 0] - 1, grid[:, :, 1])) * g10, 2)
n01 = np.sum(np.dstack((grid[:, :, 0], grid[:, :, 1] - 1)) * g01, 2)
n11 = np.sum(np.dstack((grid[:, :, 0] - 1, grid[:, :, 1] - 1)) * g11, 2)
# Interpolation
t = interpolant(grid)
n0 = n00 * (1 - t[:, :, 0]) + t[:, :, 0] * n10
n1 = n01 * (1 - t[:, :, 0]) + t[:, :, 0] * n11
return np.sqrt(2) * ((1 - t[:, :, 1]) * n0 + t[:, :, 1] * n1)
class CoordsGenerator:
def __init__(self, x: int, y: int, factor: int = 500):
print(x, y, x // factor, y // factor)
self.noise = generate_perlin_noise_2d((x, y), (x // factor, y // factor))
self.noise_distribution = softmax(self.noise, axis=1)
def pick(self, n):
x, y = self.noise.shape
x = np.random.choice(x, size=n, replace=False)
y = [
np.random.choice(y, size=1, p=self.noise_distribution[x_, :], replace=False)
for x_ in x
]
return x, np.array(y).flatten()
if __name__ == "__main__":
import matplotlib.pyplot as plt
# gen = CoordsGenerator(1920, 1080, threshold=0.85)
# x, y = gen.pick(1000)
# plt.scatter(x, y)
factor = 500
noise = generate_perlin_noise_2d((1080, 1920), (1080 // factor, 1920 // factor))
plt.matshow(noise, cmap="bwr")
plt.colorbar()
plt.show()

1
popular.json Normal file
View File

@@ -0,0 +1 @@
[{"colours": ["#264653", "#2a9d8f", "#e9c46a", "#f4a261", "#e76f51"], "votes": 99400}, {"colours": ["#ffcdb2", "#ffb4a2", "#e5989b", "#b5838d", "#6d6875"], "votes": 64500}, {"colours": ["#e63946", "#f1faee", "#a8dadc", "#457b9d", "#1d3557"], "votes": 64099}, {"colours": ["#cb997e", "#ddbea9", "#ffe8d6", "#b7b7a4", "#a5a58d", "#6b705c"], "votes": 63700}, {"colours": ["#606c38", "#283618", "#fefae0", "#dda15e", "#bc6c25"], "votes": 56400}, {"colours": ["#ccd5ae", "#e9edc9", "#fefae0", "#faedcd", "#d4a373"], "votes": 54200}, {"colours": ["#fec5bb", "#fcd5ce", "#fae1dd", "#f8edeb", "#e8e8e4", "#d8e2dc", "#ece4db", "#ffe5d9", "#ffd7ba", "#fec89a"], "votes": 51800}, {"colours": ["#cdb4db", "#ffc8dd", "#ffafcc", "#bde0fe", "#a2d2ff"], "votes": 50700}, {"colours": ["#ffadad", "#ffd6a5", "#fdffb6", "#caffbf", "#9bf6ff", "#a0c4ff", "#bdb2ff", "#ffc6ff", "#fffffc"], "votes": 42800}, {"colours": ["#006d77", "#83c5be", "#edf6f9", "#ffddd2", "#e29578"], "votes": 40900}, {"colours": ["#000000", "#14213d", "#fca311", "#e5e5e5", "#ffffff"], "votes": 40500}, {"colours": ["#8ecae6", "#219ebc", "#023047", "#ffb703", "#fb8500"], "votes": 39700}, {"colours": ["#f4f1de", "#e07a5f", "#3d405b", "#81b29a", "#f2cc8f"], "votes": 39300}, {"colours": ["#03045e", "#023e8a", "#0077b6", "#0096c7", "#00b4d8", "#48cae4", "#90e0ef", "#ade8f4", "#caf0f8"], "votes": 38300}, {"colours": ["#003049", "#d62828", "#f77f00", "#fcbf49", "#eae2b7"], "votes": 36300}, {"colours": ["#eeeeee", "#00adb5", "#393e46", "#222831"], "votes": 53077}, {"colours": ["#71c9ce", "#a6e3e9", "#cbf1f5", "#e3fdfd"], "votes": 30343}, {"colours": ["#6a2c70", "#b83b5e", "#f08a5d", "#f9ed69"], "votes": 27275}, {"colours": ["#8785a2", "#f6f6f6", "#ffe2e2", "#ffc7c7"], "votes": 27218}, {"colours": ["#95e1d3", "#eaffd0", "#fce38a", "#f38181"], "votes": 26925}, {"colours": ["#eaeaea", "#ff2e63", "#252a34", "#08d9d6"], "votes": 26832}, {"colours": ["#112d4e", "#3f72af", "#dbe2ef", "#f9f7f7"], "votes": 26219}, {"colours": ["#ffffd2", "#fcbad3", "#aa96da", "#a8d8ea"], "votes": 24588}, {"colours": ["#424874", "#a6b1e1", "#dcd6f7", "#f4eeff"], "votes": 23767}, {"colours": ["#61c0bf", "#bbded6", "#fae3d9", "#ffb6b9"], "votes": 23214}, {"colours": ["#fc5185", "#f5f5f5", "#3fc1c9", "#364f6b"], "votes": 22036}, {"colours": ["#bbe1fa", "#3282b8", "#0f4c75", "#1b262c"], "votes": 21806}, {"colours": ["#fffbe9", "#e3caa5", "#ceab93", "#ad8b73"], "votes": 21786}, {"colours": ["#ff9494", "#ffd1d1", "#ffe3e1", "#fff5e4"], "votes": 21497}, {"colours": ["#cca8e9", "#c3bef0", "#cadefc", "#defcf9"], "votes": 21348}]

View File

@@ -1,116 +1,311 @@
import itertools import itertools
import json from blob import blob
import random import io
from collections import Counter from fastapi.responses import StreamingResponse
from datetime import datetime
from pathlib import Path from pathlib import Path
import pyvips
import logging
from perlin import CoordsGenerator
import svg
import matplotlib.pyplot as plt
import numpy as np import numpy as np
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="Speckles API", root_path="/images")
origins = [
"http://localhost",
"http://localhost:3000",
"https://localhost",
"https://0124816.xyz",
"http://0124816.xyz:3001",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
RGen = np.random.default_rng()
MEDIA_TYPES = {
"png": "image/png",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"svg": "image/svg+xml",
"pdf": "application/pdf",
}
def emoji(marker: str, source="twemoji"):
if len(marker) != 1:
return
if source == "openmoji":
scaler = 512
file = hex(ord(marker))[2:].upper()
elif source == "twemoji":
scaler = 768
file = hex(ord(marker))[2:]
file = Path(f"{source}/{file}.svg")
if file.is_file():
return svg.G(
elements=[Include(text=file.read_text())],
id=marker,
transform=[svg.Scale(100 / scaler)],
)
class Include(svg.Element):
element_name = "svg"
transform: list[svg.Transform] | None = None
def __str__(self) -> str:
return self.text
DEFINED = ".o+tYP-|sSex*b"
def markertoSVG(marker, x, y, c, s):
if marker == ".":
return svg.Circle(cx=x, cy=y, fill=c, r=np.sqrt(s))
elif marker == "o":
return svg.Circle(
cx=x,
cy=y,
stroke=c,
fill="none",
stroke_width=np.sqrt(s / 4),
r=np.sqrt(s),
)
elif marker == "+":
shift = np.sqrt(s)
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
stroke_linecap="round",
d=[
svg.M(x - shift, y), # horizontal line
svg.L(x + shift, y),
svg.M(x, y - shift), # vertical line
svg.L(x, y + shift),
],
)
elif marker == "t":
shift = np.sqrt(s)
return svg.Polygon(
stroke=c,
stroke_width=np.sqrt(s) / 4,
stroke_linejoin="round",
points=[
x + shift * np.sqrt(3) / 2,
y - shift / 2,
x - shift * np.sqrt(3) / 2,
y - shift / 2,
x,
y + shift,
],
transform=[svg.Rotate(RGen.random() * 360, x, y)],
)
elif marker == "Y":
shift = np.sqrt(s)
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
stroke_linecap="round",
d=[
svg.M(x + shift * np.sqrt(3) / 2, y - shift / 2), # / line
svg.L(x, y),
svg.M(x - shift * np.sqrt(3) / 2, y - shift / 2), # / line
svg.L(x, y),
svg.M(x, y + shift), # / line
svg.L(x, y),
],
transform=[svg.Rotate(RGen.random() * 360, x, y)],
)
elif marker == "P":
shift = np.sqrt(s)
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) * 2 / 3,
d=[
svg.M(x - shift, y), # horizontal line
svg.L(x + shift, y),
svg.M(x, y - shift), # vertical line
svg.L(x, y + shift),
],
)
elif marker == "-":
shift = np.sqrt(s)
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
d=[
svg.M(x - shift, y), # horizontal line
svg.L(x + shift, y),
],
)
elif marker == "|":
shift = np.sqrt(s)
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
d=[
svg.M(x, y - shift), # vertical line
svg.L(x, y + shift),
],
)
elif marker == "s":
shift = np.sqrt(s) * 2
return svg.Rect(x=x, y=y, width=shift, height=shift, fill=c)
elif marker == "S":
s = np.sqrt(s) * 2
return svg.Rect(x=x, y=y, width=s, height=s, fill=c, rx=s / 3, ry=s / 3)
elif marker == "e":
s = np.sqrt(s * 2)
return svg.Ellipse(
cx=x,
cy=y,
fill=c,
rx=s,
ry=s / 2,
transform=[svg.Rotate(RGen.random() * 360, x, y)],
)
elif marker == "x":
shift = np.sqrt(s)
scale = np.sqrt(2) / 2
return svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
stroke_linecap="round",
d=[
svg.M(x - shift * scale, y - shift * scale), # / line
svg.L(x + shift * scale, y + shift * scale),
svg.M(x - shift * scale, y + shift * scale),
svg.L(x + shift * scale, y - shift * scale), # / line
],
)
elif marker == "*":
shift = np.sqrt(s)
return [
svg.Path(
stroke=c,
stroke_width=np.sqrt(s) / 4,
stroke_linecap="round",
transform=[svg.Rotate(angle, x, y)],
d=[
svg.M(x - shift, y),
svg.L(x + shift, y),
],
)
for angle in (0, 60, -60)
]
elif marker == "b":
return blob(x, y, c, np.sqrt(s)*10)
else:
return svg.Use(
href=f"#{marker}",
transform=[svg.Translate(x=x, y=y), svg.Scale(np.sqrt(s) / 100)],
)
@app.get("/speckles/")
def make_wallpaper( def make_wallpaper(
background: str, speckle_colours: str,
speckle_colours: list[str], density: float = 0.12,
filename: str | Path, size: float = 3,
density: int = 0.6, fileformat: str = "svg",
dimensions: tuple[float | int, float | int] = (1920, 1080), orientation: str = "landscape",
) -> None: filename: str = "",
x, y = dimensions markers: str = ".",
speckles_per_colour = x / 100 * y / 100 * density perlin: bool = True,
):
speckle_colours = speckle_colours.split(",")
background = speckle_colours.pop(0)
if orientation == "portrait":
x, y = (1080, 1920)
elif orientation == "landscape":
x, y = (1920, 1080)
elif "x" in orientation:
resolution = orientation.split("x")
if len(resolution) != 2:
logging.critical("input resolution has more or less than 2 dimensions")
return
x, y = resolution
if all([x.isdigit(), y.isdigit()]):
x, y = int(x), int(y)
else:
return
else:
x, y = (1920, 1080)
speckles_per_colour = int(x / 128 * y / 128 * density / len(markers))
fig, ax = plt.subplots(figsize=(x / 100, y / 100), facecolor=background) if perlin:
ax.set_facecolor(background) gen = CoordsGenerator(x, y)
[spine.set_color(background) for spine in ax.spines.values()]
plt.xticks([])
plt.yticks([])
plt.margins(0, 0)
for color, size in itertools.product( elements = [svg.Rect(width=x, height=y, fill=background)]
style = svg.Style(
text="\n".join(
[f".c{colour[1:]} {{ fill: {colour}; }}" for colour in speckle_colours]
)
)
elements.append(style)
style = svg.Style(
text="\n".join(
[
f'.s{hex(int(size*100))[2:]} {{ font-family: "OpenMoji"; font-size: {int(np.sqrt(size)*16)}px; }}'
for size in np.logspace(0, size, 10, base=np.exp(2))
]
)
)
elements.append(style)
elements.append(
svg.Defs(
elements=[emoji(marker) for marker in markers if marker not in DEFINED]
)
)
for colour, marker, size in itertools.product(
speckle_colours, speckle_colours,
np.logspace(1, 6, 8, base=2), markers,
np.logspace(0, size, 10, base=np.exp(2)),
): ):
plt.scatter( if perlin:
[random.random() * x / 8 for _ in range(speckles_per_colour)], xs, ys = gen.pick(speckles_per_colour)
[random.random() * y / 8 for _ in range(speckles_per_colour)], else:
c=color, xs, ys = zip(
s=size, RGen.random(speckles_per_colour) * x,
RGen.random(speckles_per_colour) * y,
)
elements.extend(
[markertoSVG(marker, x, y, colour, size) for x, y in zip(xs, ys)]
) )
plt.tight_layout() content = svg.SVG(width=x, height=y, elements=elements)
# plt.xlim(0, x) if filename:
# plt.ylim(0, y) if Path(filename).suffix == ".svg":
# plt.axis("off") with open(filename, "wt") as f:
plt.savefig( f.write(content.as_str())
filename, else:
dpi=128, with pyvips.Image.svgload_buffer(content.as_str().encode("utf-8")) as image:
bbox_inches="tight", image.write_to_file(filename)
pad_inches=0, return filename
) elif fileformat:
# plt.show() if fileformat == "svg":
plt.close() buf = io.BytesIO(content.as_str().encode("utf-8"))
buf.seek(0)
elif fileformat in ["jpg", "png"]:
# palette = random.choice(palettes) with pyvips.Image.svgload_buffer(content.as_str().encode("utf-8")) as image:
def speckles(palette): data = image.write_to_buffer("." + fileformat)
for i, background in enumerate(palette): buf = io.BytesIO(data)
speckle_colours = palette[:i] + palette[i + 1 :] buf.seek(0)
_id = "_".join(speckle_colours).replace("#", "") return StreamingResponse(content=buf, media_type=MEDIA_TYPES[fileformat])
speckle_colours += ["#000000", "#000000", "#ffffff"]
make_wallpaper(
background,
speckle_colours,
f"speckles/speckles_{_id}.png",
)
def night_sky(palette):
speckle_colours = palette + ["#ffffff"]
_id = "_".join(palette).replace("#", "")
make_wallpaper(
"#000000",
speckle_colours,
f"speckles/night_sky_{_id}.png",
)
if __name__ == "__main__": if __name__ == "__main__":
palettes = [ uvicorn.run("speckles:app", workers=3, port=8099)
{"colours": ["#eee4ab", "#e5cb9f", "#99c4c8", "#68a7ad"], "votes": 100000},
{"colours": ["#e4d192", "#cba0ae", "#af7ab3", "#80558c"], "votes": 100000},
{"colours": ["#eeeeee", "#e1d4bb", "#cbb279", "#537188"], "votes": 100000},
{"colours": ["#c0dbea", "#ba90c6", "#e8a0bf", "#fdf4f5"], "votes": 100000},
{"colours": ["#f5ffc9", "#b3e5be", "#a86464", "#804674"], "votes": 100000},
{"colours": ["#ffde7d", "#f6416c", "#f8f3d4", "#00b8a9"], "votes": 100000},
{"colours": ["#53354a", "#903749", "#e84545", "#2b2e4a"], "votes": 100000},
{"colours": ["#967e76", "#d7c0ae", "#eee3cb", "#b7c4cf"], "votes": 100000},
{"colours": ["#fc5185", "#f5f5f5", "#3fc1c9", "#364f6b"], "votes": 100000},
{"colours": ["#eaeaea", "#ff2e63", "#252a34", "#08d9d6"], "votes": 100000},
{"colours": ["#eeeeee", "#00adb5", "#393e46", "#222831"], "votes": 100000},
{"colours": ["#2cd3e1", "#a459d1", "#f266ab", "#ffb84c"], "votes": 100000},
{"colours": ["#ffe194", "#e8f6ef", "#1b9c85", "#4c4c6d"], "votes": 100000},
{"colours": ["#146c94", "#19a7ce", "#b0daff", "#feff86"], "votes": 100000},
{"colours": ["#4c3d3d", "#c07f00", "#ffd95a", "#fff7d4"], "votes": 100000},
{"colours": ["#8bacaa", "#b04759", "#e76161", "#f99b7d"], "votes": 100000},
{"colours": ["#146c94", "#19a7ce", "#afd3e2", "#f6f1f1"], "votes": 100000},
{"colours": ["#9ba4b5", "#212a3e", "#394867", "#f1f6f9"], "votes": 100000},
{"colours": ["#00ffca", "#05bfdb", "#088395", "#0a4d68"], "votes": 100000},
{"colours": ["#7c9070", "#9ca777", "#fee8b0", "#f97b22"], "votes": 100000},
{"colours": ["#002a19", "#000000", "#ffffff", "#e0512f"], "votes": 100000},
]
palette_files = ["colorhunt.json"]
for palette_file in palette_files:
with open(palette_file, "rt") as f:
palettes += json.load(f)
print(len(palettes))
palettes = [
palette["colours"]
for palette in palettes
if palette["votes"] > 30000 or len(palette["colours"]) > 5
]
print(len(palettes))
for palette in palettes:
night_sky(palette)
speckles(palette)