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)