from __future__ import annotations from PIL import Image, ImageDraw from .text_renderer import TextOptions, render_text DIAGNOSTIC_MODES = { "corners_lines", "row_bands", "address_check", "address_bit_0", "address_bit_1", "address_bit_2", "address_bit_3", "address_bit_4", "text_ok123", "clear", } def render_diagnostic(mode: str, size: tuple[int, int] = (64, 64)) -> Image.Image: if mode not in DIAGNOSTIC_MODES: allowed = ", ".join(sorted(DIAGNOSTIC_MODES)) raise ValueError(f"diagnostic mode must be one of: {allowed}") if mode == "clear": return Image.new("RGB", size, (0, 0, 0)) if mode == "corners_lines": return _render_corners_lines(size) if mode == "row_bands": return _render_row_bands(size) if mode == "address_check": return _render_address_check(size) if mode.startswith("address_bit_"): return _render_address_bit(int(mode.rsplit("_", 1)[1]), size) return render_text(TextOptions(text="OK123", size=12, x=4, y=24), size=size) def _render_corners_lines(size: tuple[int, int]) -> Image.Image: width, height = size image = Image.new("RGB", size, (0, 0, 0)) draw = ImageDraw.Draw(image) for y, color in ((0, (80, 80, 80)), (31, (255, 180, 0)), (32, (0, 180, 255)), (63, (80, 80, 80))): if y < height: draw.line((0, y, width - 1, y), fill=color) for x in (0, width - 1): draw.line((x, 0, x, height - 1), fill=(80, 80, 80)) marker = 4 draw.rectangle((0, 0, marker, marker), fill=(255, 0, 0)) draw.rectangle((width - marker - 1, 0, width - 1, marker), fill=(0, 255, 0)) draw.rectangle((0, height - marker - 1, marker, height - 1), fill=(0, 0, 255)) draw.rectangle((width - marker - 1, height - marker - 1, width - 1, height - 1), fill=(255, 255, 255)) return image def _render_row_bands(size: tuple[int, int]) -> Image.Image: width, height = size image = Image.new("RGB", size, (0, 0, 0)) draw = ImageDraw.Draw(image) colors = ( (255, 0, 0), (255, 120, 0), (255, 255, 0), (0, 255, 0), (0, 255, 255), (0, 0, 255), (180, 0, 255), (255, 255, 255), ) band_height = max(1, height // len(colors)) for index, color in enumerate(colors): y0 = index * band_height y1 = height - 1 if index == len(colors) - 1 else min(height - 1, y0 + band_height - 1) draw.rectangle((0, y0, width - 1, y1), fill=color) draw.line((0, y0, width - 1, y0), fill=(0, 0, 0)) return image def _render_address_check(size: tuple[int, int]) -> Image.Image: width, height = size image = Image.new("RGB", size, (0, 0, 0)) draw = ImageDraw.Draw(image) bit_colors = ( (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), ) segment_width = max(1, width // len(bit_colors)) for y in range(height): for bit, color in enumerate(bit_colors): if y & (1 << bit): x0 = bit * segment_width x1 = width - 1 if bit == len(bit_colors) - 1 else min(width - 1, x0 + segment_width - 2) draw.line((x0, y, x1, y), fill=color) diagonal_x = min(width - 1, y) draw.point((diagonal_x, y), fill=(255, 255, 255)) draw.point((width - 1 - diagonal_x, y), fill=(80, 80, 80)) for y in (0, 31, 32, 63): if y < height: draw.line((0, y, width - 1, y), fill=(255, 255, 255)) return image def _render_address_bit(bit: int, size: tuple[int, int]) -> Image.Image: width, height = size image = Image.new("RGB", size, (0, 0, 0)) draw = ImageDraw.Draw(image) colors = ( (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255), ) color = colors[bit] for y in range(height): if y & (1 << bit): draw.line((0, y, width - 1, y), fill=color) for y in (0, 31, 32, 63): if y < height: draw.line((0, y, width - 1, y), fill=(255, 255, 255)) return image