Spaces:
Running
Running
"""Generates psychedelic color textures in the spirit of Blender's magic texture shader using Python/Numpy | |
https://github.com/cheind/magic-texture | |
""" | |
from typing import Tuple, Optional | |
import numpy as np | |
def coordinate_grid(shape: Tuple[int, int], dtype=np.float32): | |
"""Returns a three-dimensional coordinate grid of given shape for use in `magic`.""" | |
x = np.linspace(-1, 1, shape[1], endpoint=True, dtype=dtype) | |
y = np.linspace(-1, 1, shape[0], endpoint=True, dtype=dtype) | |
X, Y = np.meshgrid(x, y) | |
XYZ = np.stack((X, Y, np.ones_like(X)), -1) | |
return XYZ | |
def random_transform(coords: np.ndarray, rng: np.random.Generator = None): | |
"""Returns randomly transformed coordinates""" | |
H, W = coords.shape[:2] | |
rng = rng or np.random.default_rng() | |
m = rng.uniform(-1.0, 1.0, size=(3, 3)).astype(coords.dtype) | |
return (coords.reshape(-1, 3) @ m.T).reshape(H, W, 3) | |
def magic( | |
coords: np.ndarray, | |
depth: Optional[int] = None, | |
distortion: Optional[int] = None, | |
rng: np.random.Generator = None, | |
): | |
"""Returns color magic color texture. | |
The implementation is based on Blender's (https://www.blender.org/) magic | |
texture shader. The following adaptions have been made: | |
- we exchange the nested if-cascade by a probabilistic iterative approach | |
Kwargs | |
------ | |
coords: HxWx3 array | |
Coordinates transformed into colors by this method. See | |
`magictex.coordinate_grid` to generate the default. | |
depth: int (optional) | |
Number of transformations applied. Higher numbers lead to more | |
nested patterns. If not specified, randomly sampled. | |
distortion: float (optional) | |
Distortion of patterns. Larger values indicate more distortion, | |
lower values tend to generate smoother patterns. If not specified, | |
randomly sampled. | |
rng: np.random.Generator | |
Optional random generator to draw samples from. | |
Returns | |
------- | |
colors: HxWx3 array | |
Three channel color image in range [0,1] | |
""" | |
rng = rng or np.random.default_rng() | |
if distortion is None: | |
distortion = rng.uniform(1, 4) | |
if depth is None: | |
depth = rng.integers(1, 5) | |
H, W = coords.shape[:2] | |
XYZ = coords | |
x = np.sin((XYZ[..., 0] + XYZ[..., 1] + XYZ[..., 2]) * distortion) | |
y = np.cos((-XYZ[..., 0] + XYZ[..., 1] - XYZ[..., 2]) * distortion) | |
z = -np.cos((-XYZ[..., 0] - XYZ[..., 1] + XYZ[..., 2]) * distortion) | |
if depth > 0: | |
x *= distortion | |
y *= distortion | |
z *= distortion | |
y = -np.cos(x - y + z) | |
y *= distortion | |
xyz = [x, y, z] | |
fns = [np.cos, np.sin] | |
for _ in range(1, depth): | |
axis = rng.choice(3) | |
fn = fns[rng.choice(2)] | |
signs = rng.binomial(n=1, p=0.5, size=4) * 2 - 1 | |
xyz[axis] = signs[-1] * fn( | |
signs[0] * xyz[0] + signs[1] * xyz[1] + signs[2] * xyz[2] | |
) | |
xyz[axis] *= distortion | |
x, y, z = xyz | |
x /= 2 * distortion | |
y /= 2 * distortion | |
z /= 2 * distortion | |
c = 0.5 - np.stack((x, y, z), -1) | |
np.clip(c, 0, 1.0) | |
return c |