File size: 5,417 Bytes
d03debd
 
 
 
 
 
e5648d6
d03debd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aa112fe
d03debd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570e9e3
d03debd
 
 
 
e5648d6
d03debd
 
 
 
 
 
 
e5648d6
d03debd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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()