import gradio as gr from PIL import Image # ─── Доступные размеры для даунскейла ─── SIZES = ["8×8", "16×16", "32×32", "64×64", "128×128"] def parse_size(label: str) -> tuple[int, int]: """'16×16' → (16, 16)""" w, h = label.split("×") return int(w), int(h) def downscale( image: Image.Image, target_size: str, preview_scale: int, ) -> tuple[Image.Image, Image.Image, str]: """ 1) Даунскейлит входное изображение до target_size методом NEAREST. 2) Апскейлит результат обратно (preview) чтобы пиксели были видны. Возвращает (маленькое, превью, инфо-строку). """ if image is None: raise gr.Error("Загрузи изображение!") # Принудительно RGBA (текстуры Minecraft часто с альфой) image = image.convert("RGBA") tw, th = parse_size(target_size) # ── Nearest-Neighbor даунскейл ── small = image.resize((tw, th), resample=Image.NEAREST) # ── Превью: масштабируем обратно, чтобы пиксели были чётко видны ── preview_w = tw * preview_scale preview_h = th * preview_scale preview = small.resize((preview_w, preview_h), resample=Image.NEAREST) info = ( f"Оригинал: {image.size[0]}×{image.size[1]}\n" f"Результат: {tw}×{th}\n" f"Превью: {preview_w}×{preview_h} (×{preview_scale})" ) print(info) return small, preview, info # ─── Интерфейс ─────────────────────────────────────────────── with gr.Blocks( title="Minecraft Texture Downscaler", theme=gr.themes.Soft(), css=""" /* чёткие пиксели при зуме в браузере */ .pixelated img { image-rendering: pixelated !important; image-rendering: -moz-crisp-edges !important; image-rendering: crisp-edges !important; } """, ) as demo: gr.Markdown( """ # 🟩 Minecraft Texture Downscaler Загрузи текстуру любого размера — получи чёткий **Nearest Neighbor** даунскейл без размытия (без bilinear / bicubic). > Идеально для конвертации HD-текстур (128×, 256×, 512×) > в ванильный формат 16×16. """ ) with gr.Row(): # ── Левая колонка: входные параметры ── with gr.Column(scale=1): img_input = gr.Image( label="📥 Входная текстура", type="pil", image_mode="RGBA", sources=["upload", "clipboard"], ) target_size = gr.Dropdown( label="🎯 Целевой размер", choices=SIZES, value="16×16", ) preview_scale = gr.Slider( label="🔍 Множитель превью", minimum=1, maximum=32, step=1, value=16, info="Во сколько раз увеличить для наглядности", ) btn = gr.Button("⚡ Даунскейлить", variant="primary", size="lg") # ── Правая колонка: результаты ── with gr.Column(scale=2): with gr.Row(equal_height=True): img_small = gr.Image( label="📤 Результат (реальный размер)", type="pil", image_mode="RGBA", format="png", show_download_button=True, elem_classes=["pixelated"], ) img_preview = gr.Image( label="🔎 Превью (увеличенное)", type="pil", image_mode="RGBA", format="png", elem_classes=["pixelated"], ) info_box = gr.Textbox( label="ℹ️ Информация", interactive=False, lines=3, ) # ── Обработчик ── btn.click( fn=downscale, inputs=[img_input, target_size, preview_scale], outputs=[img_small, img_preview, info_box], ) gr.Markdown( """ --- ### Как использовать 1. Загрузи PNG-текстуру (например `diamond_ore.png` 128×128). 2. Выбери целевой размер — по умолчанию **16×16**. 3. Нажми **⚡ Даунскейлить**. 4. Скачай результат (кнопка ⬇️ на картинке). **Nearest Neighbor** сохраняет резкие границы пикселей — никакого мыла, как при Bilinear/Lanczos. """ ) # Запуск if __name__ == "__main__": demo.launch()