Compare commits

..

No commits in common. "beb81771a8b6e5d067bd8baa3d20c0c1cdae868e" and "dfaef09ccbc27fd67181f8aa731745100bb858d5" have entirely different histories.

2 changed files with 100 additions and 289 deletions

View File

@ -65,10 +65,9 @@ class CoordsGenerator:
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
np.random.choice(y, size=1, p=self.noise_distribution[x_, :], replace=False) for x_ in x
]
return x, np.array(y).flatten()
return x, y
if __name__ == "__main__":

View File

@ -1,302 +1,114 @@
import itertools
import io
from fastapi.responses import StreamingResponse
import json
import random
from pathlib import Path
import pyvips
import logging
from perlin import CoordsGenerator
import svg
import matplotlib.pyplot as plt
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*"
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)
]
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(
speckle_colours: str,
density: float = 0.12,
size: float = 3,
fileformat: str = "svg",
orientation: str = "landscape",
filename: str = "",
markers: str = ".",
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))
background: str,
speckle_colours: list[str],
filename: str | Path,
density: int = 0.6,
dimensions: tuple[float | int, float | int] = (1920, 1080),
) -> None:
x, y = dimensions
speckles_per_colour = x / 100 * y / 100 * density
if perlin:
gen = CoordsGenerator(x, y)
fig, ax = plt.subplots(figsize=(x / 100, y / 100), facecolor=background)
ax.set_facecolor(background)
[spine.set_color(background) for spine in ax.spines.values()]
plt.xticks([])
plt.yticks([])
plt.margins(0, 0)
elements = [svg.Rect(width=x, height=y, color=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(
for color, size in itertools.product(
speckle_colours,
markers,
np.logspace(0, size, 10, base=np.exp(2)),
np.logspace(1, 6, 8, base=2),
):
if perlin:
xs, ys = gen.pick(speckles_per_colour)
else:
xs, ys = zip(
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.scatter(
[random.random() * x / 8 for _ in range(speckles_per_colour)],
[random.random() * y / 8 for _ in range(speckles_per_colour)],
c=color,
s=size,
)
content = svg.SVG(width=x, height=y, elements=elements)
if filename:
if Path(filename).suffix == ".svg":
with open(filename, "wt") as f:
f.write(content.as_str())
else:
with pyvips.Image.svgload_buffer(content.as_str().encode("utf-8")) as image:
image.write_to_file(filename)
return filename
elif fileformat:
buf = io.BytesIO(content.as_str().encode("utf-8"))
buf.seek(0)
return StreamingResponse(content=buf, media_type=MEDIA_TYPES[fileformat])
plt.tight_layout()
# plt.xlim(0, x)
# plt.ylim(0, y)
# plt.axis("off")
plt.savefig(
filename,
dpi=128,
bbox_inches="tight",
pad_inches=0,
)
# plt.show()
plt.close()
# palette = random.choice(palettes)
def speckles(palette):
for i, background in enumerate(palette):
speckle_colours = palette[:i] + palette[i + 1 :]
_id = "_".join(speckle_colours).replace("#", "")
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__":
uvicorn.run("speckles:app", workers=3, port=8099)
palettes = [
{"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)