graphics.py
2.3 kB · python · 58 lines
1from typing import List, TYPE_CHECKING23import numpy as np45from mrlypy.core.colors import Color6from mrlypy.core.errors import MrlyError78if TYPE_CHECKING:9 from .models import Cell2d1011# SAMPLING1213def get_type(rgb_array: np.ndarray, level: int) -> np.ndarray:14 return (np.mean(rgb_array[:, :, :3], axis=2) < level).astype(np.int8)1516def get_color(rgb_array: np.ndarray, palette: List[Color]) -> np.ndarray:17 if not palette:18 raise MrlyError("Cannot recolor with an empty palette.")19 palette_array = np.array([c.to_rgba() for c in palette], dtype=np.uint8)20 distances = np.sum((rgb_array[:, :, np.newaxis, :] - palette_array[np.newaxis, np.newaxis, :, :]) ** 2, axis=3)21 closest_indices = np.argmin(distances, axis=2)22 return palette_array[closest_indices]2324# FILTERS2526def perforate(types_array: np.ndarray, cell: "Cell2d") -> np.ndarray:27 height, width = types_array.shape28 if width == 0 or height == 0:29 return types_array30 tiled_mask = np.tile(cell.types, (31 (height + cell.height - 1) // cell.height,32 (width + cell.width - 1) // cell.width,33 ))34 return tiled_mask[:height, :width]3536def binarize(colors_array: np.ndarray, level: int = 128) -> np.ndarray:37 if not isinstance(level, int) or not (0 <= level <= 255):38 raise MrlyError(f"Binarize level must be an integer between 0 and 255, got {level}")39 return get_type(colors_array, level)4041def recolor(colors_array: np.ndarray, palette: List[Color]) -> np.ndarray:42 return get_color(colors_array, palette)4344def blur(colors_array: np.ndarray, radius: int = 1) -> np.ndarray:45 if not isinstance(radius, int) or radius < 0:46 raise MrlyError(f"Blur radius must be a non-negative integer, got {radius}")47 if radius == 0:48 return colors_array49 padded = np.pad(colors_array.astype(np.float32), ((radius, radius), (radius, radius), (0, 0)), "edge")50 integral_image = padded.cumsum(axis=0).cumsum(axis=1)51 r = radius52 top_left = integral_image[2 * r:, 2 * r:]53 top_right = integral_image[2 * r:, :-(2 * r)]54 bottom_left = integral_image[:-(2 * r), 2 * r:]55 bottom_right = integral_image[:-(2 * r), :-(2 * r)]56 box_area = (2 * r + 1) ** 257 blurred_float = (top_left - top_right - bottom_left + bottom_right) / box_area58 return np.clip(blurred_float, 0, 255).astype(np.uint8)