Compare commits
No commits in common. "beb81771a8b6e5d067bd8baa3d20c0c1cdae868e" and "dfaef09ccbc27fd67181f8aa731745100bb858d5" have entirely different histories.
beb81771a8
...
dfaef09ccb
@ -65,10 +65,9 @@ class CoordsGenerator:
|
|||||||
x, y = self.noise.shape
|
x, y = self.noise.shape
|
||||||
x = np.random.choice(x, size=n, replace=False)
|
x = np.random.choice(x, size=n, replace=False)
|
||||||
y = [
|
y = [
|
||||||
np.random.choice(y, size=1, p=self.noise_distribution[x_, :], replace=False)
|
np.random.choice(y, size=1, p=self.noise_distribution[x_, :], replace=False) for x_ in x
|
||||||
for x_ in x
|
|
||||||
]
|
]
|
||||||
return x, np.array(y).flatten()
|
return x, y
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
384
speckles.py
384
speckles.py
@ -1,302 +1,114 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import io
|
import json
|
||||||
from fastapi.responses import StreamingResponse
|
import random
|
||||||
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*"
|
|
||||||
|
|
||||||
|
|
||||||
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(
|
def make_wallpaper(
|
||||||
speckle_colours: str,
|
background: str,
|
||||||
density: float = 0.12,
|
speckle_colours: list[str],
|
||||||
size: float = 3,
|
filename: str | Path,
|
||||||
fileformat: str = "svg",
|
density: int = 0.6,
|
||||||
orientation: str = "landscape",
|
dimensions: tuple[float | int, float | int] = (1920, 1080),
|
||||||
filename: str = "",
|
) -> None:
|
||||||
markers: str = ".",
|
x, y = dimensions
|
||||||
perlin: bool = True,
|
speckles_per_colour = x / 100 * y / 100 * density
|
||||||
):
|
|
||||||
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))
|
|
||||||
|
|
||||||
if perlin:
|
fig, ax = plt.subplots(figsize=(x / 100, y / 100), facecolor=background)
|
||||||
gen = CoordsGenerator(x, y)
|
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)]
|
for color, size in itertools.product(
|
||||||
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,
|
||||||
markers,
|
np.logspace(1, 6, 8, base=2),
|
||||||
np.logspace(0, size, 10, base=np.exp(2)),
|
|
||||||
):
|
):
|
||||||
if perlin:
|
plt.scatter(
|
||||||
xs, ys = gen.pick(speckles_per_colour)
|
[random.random() * x / 8 for _ in range(speckles_per_colour)],
|
||||||
else:
|
[random.random() * y / 8 for _ in range(speckles_per_colour)],
|
||||||
xs, ys = zip(
|
c=color,
|
||||||
RGen.random(speckles_per_colour) * x,
|
s=size,
|
||||||
RGen.random(speckles_per_colour) * y,
|
|
||||||
)
|
|
||||||
elements.extend(
|
|
||||||
[markertoSVG(marker, x, y, colour, size) for x, y in zip(xs, ys)]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
content = svg.SVG(width=x, height=y, elements=elements)
|
plt.tight_layout()
|
||||||
if filename:
|
# plt.xlim(0, x)
|
||||||
if Path(filename).suffix == ".svg":
|
# plt.ylim(0, y)
|
||||||
with open(filename, "wt") as f:
|
# plt.axis("off")
|
||||||
f.write(content.as_str())
|
plt.savefig(
|
||||||
else:
|
filename,
|
||||||
with pyvips.Image.svgload_buffer(content.as_str().encode("utf-8")) as image:
|
dpi=128,
|
||||||
image.write_to_file(filename)
|
bbox_inches="tight",
|
||||||
return filename
|
pad_inches=0,
|
||||||
elif fileformat:
|
)
|
||||||
buf = io.BytesIO(content.as_str().encode("utf-8"))
|
# plt.show()
|
||||||
buf.seek(0)
|
plt.close()
|
||||||
return StreamingResponse(content=buf, media_type=MEDIA_TYPES[fileformat])
|
|
||||||
|
|
||||||
|
# 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__":
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user