diff --git a/perlin.py b/perlin.py index 4d47bd9..1e97901 100644 --- a/perlin.py +++ b/perlin.py @@ -65,9 +65,10 @@ 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, y + return x, np.array(y).flatten() if __name__ == "__main__": diff --git a/speckles.py b/speckles.py index 878e577..8995019 100644 --- a/speckles.py +++ b/speckles.py @@ -1,79 +1,374 @@ import itertools -import json -import random from pathlib import Path +import logging +from perlin import CoordsGenerator +import svg +import json -import matplotlib.pyplot as plt import numpy as np +RGen = np.random.default_rng() + +heartshape = [ + svg.M(1, 3), + svg.Arc(2, 2, 0, False, True, x=5, y=3), + svg.Arc(2, 2, 0, False, True, x=9, y=3), + svg.Q(9, 6, 5, 9), + svg.Q(1, 6, 1, 3), + svg.Z(), +] +heartshape2 = [ + svg.M(-4.913, -10.043), + svg.Q(-1.968, -10.043, -0.404, -6.332), + svg.Q(-0.024, -5.252, -0.024, -4.929), + svg.Q(0.054, -4.929, 0.231, -5.874), + svg.Q(1.743, -10.043, 4.913, -10.043), + svg.Q(7.734, -10.043, 9.384, -7.196), + svg.Q(9.894, -6.142, 9.894, -5.062), + svg.L(9.894, -4.885), + svg.Q(9.894, -1.404, 6.913, 1.715), + svg.Q(0.823, 9.153, 0.015, 10.043), + svg.L(-0.067, 10.043), + svg.L(-8.274, -0.069), + svg.Q(-9.894, -2.471, -9.894, -4.929), + svg.Q(-9.894, -7.762, -7.207, -9.438), + svg.Q(-6.114, -10.043, -4.913, -10.043), + svg.Z(), + svg.M(-9.203, -5.062), + svg.L(-9.203, -4.846), + svg.Q(-9.203, -1.892, -6.395, 1.188), + svg.L(0.015, 9.015), + svg.L(0.054, 9.015), + svg.L(7.803, -0.419), + svg.Q(9.207, -2.592, 9.207, -4.63), + svg.L(9.207, -5.144), + svg.Q(9.207, -7.464, 6.654, -8.976), + svg.Q(5.7, -9.356, 4.913, -9.356), + svg.Q(1.35, -9.356, 0.192, -3.322), + svg.Q(0.054, -2.808, 0.054, -2.458), + svg.L(-0.024, -2.458), + svg.Q(-0.024, -3.512, -0.996, -6.332), + svg.Q(-2.361, -9.356, -4.913, -9.356), + svg.Q(-7.259, -9.356, -8.706, -6.967), + svg.Q(-9.203, -6.021, -9.203, -5.062), + svg.Z(), +] + +clubs = [ + svg.M(0.005, -2.647), + svg.Q(0.765, -2.647, 1.122, -1.841), + svg.Q(1.169, -1.657, 1.179, -1.458), + svg.Q(1.179, -1.043, 0.753, -0.475), + svg.L(0.541, -0.263), + svg.L(0.551, -0.263), + svg.Q(0.814, -0.362, 1.112, -0.362), + svg.L(1.179, -0.362), + svg.Q(2.024, -0.362, 2.365, 0.355), + svg.Q(2.446, 0.589, 2.446, 0.802), + svg.Q(2.446, 1.53, 1.705, 1.899), + svg.Q(1.563, 1.941, 1.311, 1.963), + svg.Q(0.452, 1.963, 0.094, 0.991), + svg.L(0.083, 0.991), + svg.L(0.083, 1.022), + svg.Q(0.083, 2.24, 0.374, 2.626), + svg.L(0.374, 2.647), + svg.L(-0.364, 2.647), + svg.L(-0.385, 2.626), + svg.Q(-0.083, 2.172, -0.083, 0.991), + svg.L(-0.083, 0.979), + svg.L(-0.095, 0.979), + svg.Q(-0.449, 1.963, -1.319, 1.963), + svg.Q(-1.997, 1.963, -2.358, 1.25), + svg.Q(-2.446, 1.008, -2.446, 0.802), + svg.Q(-2.446, -0.024, -1.574, -0.32), + svg.Q(-1.339, -0.362, -1.159, -0.362), + svg.L(-1.127, -0.362), + svg.Q(-0.821, -0.362, -0.544, -0.263), + svg.L(-0.534, -0.263), + svg.L(-0.534, -0.273), + svg.Q(-0.833, -0.525, -0.935, -0.769), + svg.Q(-1.148, -1.16, -1.148, -1.447), + svg.L(-1.148, -1.497), + svg.Q(-1.148, -2.21, -0.385, -2.579), + svg.Q(-0.133, -2.647, 0.005, -2.647), + svg.Z(), +] + +spades = [] + +diamond = [ + svg.M(0.01, -7.2), + svg.L(4.86, -0.9), + svg.Q(5.53, -0.08, 5.53, 0.05), + svg.L(0.01, 7.2), + svg.Q(-0.11, 7.12, -5.53, 0.05), + svg.Q(-5.53, -0.08, -4.46, -1.42), + svg.L(0.01, -7.2), + svg.Z(), +] + +"♠" "♣" "♥" + + +def openmoji(marker: str, x, y, s, c): + if len(marker) != 1: + return + file = hex(ord(marker))[2:] + file = Path(f"openmoji/{file}.svg") + if file.is_file(): + return svg.Text(text=marker, x=x, y=y, class_=["c"+c[1:], hex(int(s*100))[2:]]) + # return svg.Image(href=str(file), transform=[svg.Translate(x=x, y=y), svg.Scale(s)]) + + +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 == "♦": + s = np.sqrt(s) / 8 + return svg.Path( + d=diamond, + fill=c, + transform=[svg.Translate(x=x, y=y), svg.Scale(s)], + ) + elif marker == "♠": + return openmoji(marker, x, y, s, c) + elif marker == "♣": + s = np.sqrt(s) / 2 + return svg.Path( + d=clubs, + fill=c, + transform=[svg.Translate(x=x, y=y), svg.Scale(s)], + ) + elif marker == "♦": + s = np.sqrt(s) / 8 + return svg.Path( + d=diamond, + fill=c, + transform=[svg.Translate(x=x, y=y), svg.Scale(s)], + ) + elif marker == "♡": + s = np.sqrt(s) / 4 + return svg.Path( + d=heartshape2, + fill="none", + stroke=c, + stroke_width=s / 4, + transform=[svg.Translate(x=x, y=y), svg.Scale(s / 2)], + ) + elif marker == "♥": + s = np.sqrt(s) / 4 + return svg.Path( + d=heartshape, + fill=c, + transform=[svg.Translate(x=x - 5, y=y - 9), svg.Scale(s)], + ) + 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) + ] + def make_wallpaper( - 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 + 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)) - 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) + if perlin: + gen = CoordsGenerator(x, y) - for color, size in itertools.product( + 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".{hex(int(size*100))[2:]} {{ font-size: {int(np.sqrt(size)*16)}px; }}" + for size in np.logspace(0, size, 10, base=np.exp(2)) + ] + ) + ) + elements.append(style) + + for colour, marker, size in itertools.product( speckle_colours, - np.logspace(1, 6, 8, base=2), + markers, + np.logspace(0, size, 10, base=np.exp(2)), ): - 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, + 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.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", - ) + content = svg.SVG(width=x, height=y, elements=elements) + with open(filename, "wt") as f: + f.write(content.as_str()) if __name__ == "__main__": 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}, @@ -96,6 +391,13 @@ if __name__ == "__main__": {"colours": ["#7c9070", "#9ca777", "#fee8b0", "#f97b22"], "votes": 100000}, {"colours": ["#002a19", "#000000", "#ffffff", "#e0512f"], "votes": 100000}, ] + make_wallpaper( + ",".join(["#000000"] + palettes[0]["colours"]), + orientation="2880x1920", + filename="testing.svg", + markers="Yt♠", + ) + exit() palette_files = ["colorhunt.json"] for palette_file in palette_files: