add Perlin noise as distribution
This commit is contained in:
		
							
								
								
									
										27
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								main.py
									
									
									
									
									
								
							@@ -1,13 +1,11 @@
 | 
			
		||||
import io
 | 
			
		||||
import os
 | 
			
		||||
import matplotlib
 | 
			
		||||
from perlin import CoordsGenerator
 | 
			
		||||
from typing import Literal
 | 
			
		||||
import itertools
 | 
			
		||||
import logging
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
import matplotlib.pyplot as plt
 | 
			
		||||
import matplotlib.font_manager as font_manager
 | 
			
		||||
import numpy as np
 | 
			
		||||
import uvicorn
 | 
			
		||||
from fastapi import FastAPI
 | 
			
		||||
@@ -90,12 +88,13 @@ marker_subs = {
 | 
			
		||||
@app.get("/speckles/")
 | 
			
		||||
def make_wallpaper(
 | 
			
		||||
    speckle_colours: str,
 | 
			
		||||
    density: float | None = 0.12,
 | 
			
		||||
    size: float | None = 3,
 | 
			
		||||
    density: float = 0.12,
 | 
			
		||||
    size: float = 3,
 | 
			
		||||
    fileformat: str = "svg",
 | 
			
		||||
    orientation: str | None = "landscape",
 | 
			
		||||
    orientation: str = "landscape",
 | 
			
		||||
    local: bool = False,
 | 
			
		||||
    markers: str | None = ".",
 | 
			
		||||
    markers: str = ".",
 | 
			
		||||
    perlin: bool = True,
 | 
			
		||||
):
 | 
			
		||||
    if fileformat not in MEDIA_TYPES:
 | 
			
		||||
        return
 | 
			
		||||
@@ -126,15 +125,25 @@ def make_wallpaper(
 | 
			
		||||
    ax.set_yticks([])
 | 
			
		||||
    ax.margins(0, 0)
 | 
			
		||||
 | 
			
		||||
    if perlin:
 | 
			
		||||
        gen = CoordsGenerator(y, x)
 | 
			
		||||
 | 
			
		||||
    for color, marker, size in itertools.product(
 | 
			
		||||
        speckle_colours,
 | 
			
		||||
        markers,
 | 
			
		||||
        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(
 | 
			
		||||
            [random.random() * x / 8 for _ in range(speckles_per_colour)],
 | 
			
		||||
            [random.random() * y / 8 for _ in range(speckles_per_colour)],
 | 
			
		||||
            x_coords,
 | 
			
		||||
            y_coords,
 | 
			
		||||
            c=color,
 | 
			
		||||
            s=size,
 | 
			
		||||
            marker=marker,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										84
									
								
								perlin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								perlin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
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, y
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
		Reference in New Issue
	
	Block a user