"""Tests de comfyui_pixelize_image (offline, sin red ni GPU; PIL/numpy).""" import os import sys import numpy as np from PIL import Image sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from ml.comfyui_pixelize_image import comfyui_pixelize_image # noqa: E402 def _noisy_png(path, w=256, h=256): """PNG ruidoso con cientos de colores (simula crudo borroso de IA).""" rng = np.random.default_rng(7) arr = rng.integers(0, 256, size=(h, w, 3), dtype=np.uint8) Image.fromarray(arr, "RGB").save(path) return path def test_golden_downscale_quantize(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "pixel.png") res = comfyui_pixelize_image(src, dst, downscale=8, colors=16) assert res["ok"] is True, res["error"] assert os.path.isfile(dst) assert res["size"] == [256, 256] # upscale_back=True conserva tamano assert res["n_colors_final"] <= 16 # cuantizado a <=16 colores def test_no_upscale_back_keeps_small(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "small.png") res = comfyui_pixelize_image(src, dst, downscale=8, colors=16, upscale_back=False) assert res["ok"] is True assert res["size"] == [32, 32] # 256//8 def test_edge_fixed_palette_game_boy(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "gb.png") res = comfyui_pixelize_image(src, dst, palette="game-boy") assert res["ok"] is True, res["error"] assert res["n_colors_final"] <= 4 # paleta Game Boy = 4 colores def test_edge_palette_list_hex(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "pal.png") res = comfyui_pixelize_image(src, dst, palette=["#000000", "#ffffff", "#ff0000"]) assert res["ok"] is True assert res["n_colors_final"] <= 3 def test_edge_downscale_1_only_quantizes(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "q.png") res = comfyui_pixelize_image(src, dst, downscale=1, colors=8) assert res["ok"] is True assert res["size"] == [256, 256] assert res["n_colors_final"] <= 8 def test_error_missing_src(tmp_path): res = comfyui_pixelize_image(str(tmp_path / "nope.png"), str(tmp_path / "o.png")) assert res["ok"] is False assert "no existe" in res["error"] def test_error_downscale_zero(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) res = comfyui_pixelize_image(src, str(tmp_path / "o.png"), downscale=0) assert res["ok"] is False assert "downscale" in res["error"] def test_error_bad_palette(tmp_path): src = _noisy_png(str(tmp_path / "raw.png")) res = comfyui_pixelize_image(src, str(tmp_path / "o.png"), palette="not-a-palette") assert res["ok"] is False assert "paleta" in res["error"].lower() # --- alpha-aware (sprites con fondo transparente) --- def _rgba_subject_png(path, canvas=256, box=120): """RGBA: sujeto opaco de colores variados centrado, fondo transparente.""" rng = np.random.default_rng(3) arr = np.zeros((canvas, canvas, 4), dtype=np.uint8) o = (canvas - box) // 2 arr[o:o + box, o:o + box, :3] = rng.integers(0, 256, size=(box, box, 3), dtype=np.uint8) arr[o:o + box, o:o + box, 3] = 255 # sujeto opaco Image.fromarray(arr, "RGBA").save(path) return path def test_alpha_preserved_transparent_corners(tmp_path): """RGBA in -> RGBA out con esquinas transparentes y paleta limitada en lo opaco.""" src = _rgba_subject_png(str(tmp_path / "sprite.png")) dst = str(tmp_path / "px.png") res = comfyui_pixelize_image(src, dst, downscale=4, colors=16, upscale_back=False) assert res["ok"] is True, res["error"] assert res["has_alpha"] is True out = Image.open(dst).convert("RGBA") a = np.asarray(out)[..., 3] w, h = out.size # Las 4 esquinas deben ser transparentes (alpha == 0). assert a[0, 0] == 0 and a[0, w - 1] == 0 assert a[h - 1, 0] == 0 and a[h - 1, w - 1] == 0 # Centro opaco. assert a[h // 2, w // 2] == 255 # Colores limitados en la zona opaca. assert res["n_colors_final"] <= 16 def test_alpha_off_flattens_to_rgb(tmp_path): """keep_alpha=False sobre RGBA -> sale RGB (sin canal alpha).""" src = _rgba_subject_png(str(tmp_path / "sprite.png")) dst = str(tmp_path / "flat.png") res = comfyui_pixelize_image(src, dst, downscale=4, colors=16, keep_alpha=False) assert res["ok"] is True assert res["has_alpha"] is False assert Image.open(dst).mode != "RGBA" def test_rgb_input_unaffected_by_keep_alpha(tmp_path): """Imagen RGB (sin alpha) con keep_alpha=True sigue saliendo RGB, sin romper.""" src = _noisy_png(str(tmp_path / "raw.png")) dst = str(tmp_path / "rgb.png") res = comfyui_pixelize_image(src, dst, downscale=8, colors=16) # keep_alpha default True assert res["ok"] is True assert res["has_alpha"] is False assert res["n_colors_final"] <= 16 def test_error_all_transparent_no_crash(tmp_path): """RGBA toda transparente (rembg sin sujeto): no crashea, 0 colores opacos.""" arr = np.zeros((64, 64, 4), dtype=np.uint8) # alpha 0 en todo src = str(tmp_path / "empty.png") Image.fromarray(arr, "RGBA").save(src) dst = str(tmp_path / "out.png") res = comfyui_pixelize_image(src, dst, downscale=1, colors=16) assert res["ok"] is True, res["error"] assert res["has_alpha"] is True assert res["n_colors_final"] == 0 out = np.asarray(Image.open(dst).convert("RGBA")) assert out[..., 3].max() == 0 # sigue toda transparente