import itertools from pathlib import Path import pyvips import logging from perlin import CoordsGenerator import svg import json import numpy as np RGen = np.random.default_rng() 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)], ) 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)) if perlin: gen = CoordsGenerator(x, y) 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( speckle_colours, markers, np.logspace(0, size, 10, base=np.exp(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)] ) content = svg.SVG(width=x, height=y, elements=elements) 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) 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}, {"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}, ] make_wallpaper( ",".join(["#000000"] + palettes[0]["colours"]), orientation="2880x1920", filename="testing.png", markers="tY.", size=3, ) exit() 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)