| 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("Загрузи изображение!") |
|
|
| |
| image = image.convert("RGBA") |
| tw, th = parse_size(target_size) |
|
|
| |
| 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() |