File size: 3,369 Bytes
53b9670
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
from PIL import Image, ImageEnhance
from io import BytesIO
from pathlib import Path

def load_palette_file(path: Path):
    """Load a .hex palette file into a list of RGB tuples."""
    colors = []
    with open(path, "r") as f:
        for line in f:
            hexcode = line.strip().lstrip("#")
            if len(hexcode) == 6:  # valid hex
                r = int(hexcode[0:2], 16)
                g = int(hexcode[2:4], 16)
                b = int(hexcode[4:6], 16)
                colors.append((r, g, b))
            else:
                st.warning(f"Invalid hex code in palette file: {line}")
    return colors

def apply_custom_palette(image: Image.Image, colors, dithering: bool):
    """Quantize an image to a custom palette with optional dithering."""
    # Build palette (zero pad to 256 colors if needed)
    palette_img = Image.new("P", (1, 1))
    palette_data = []
    for r, g, b in colors[:256]:
        palette_data.extend([r, g, b])
    palette_data.extend([0] * (768 - len(palette_data)))
    palette_img.putpalette(palette_data)

    # Quantize with the custom palette
    dither_mode = Image.FLOYDSTEINBERG if dithering else Image.NONE
    image = image.quantize(palette=palette_img, dither=dither_mode, method=Image.MEDIANCUT)
    return image.convert("RGB")

st.set_page_config(page_title="Pixelizer", layout="wide")
st.sidebar.title("Pixelizer")
st.sidebar.header("Image Controls")
uploaded_file = st.sidebar.file_uploader("Upload image", type=["png", "jpg", "jpeg"])
show_original = st.sidebar.toggle("Show original", value=False)
dithering = st.sidebar.toggle("Enable dithering", value=True)
pixel_size = st.sidebar.number_input("Pixel size", min_value=1, max_value=100, value=4, step=1)
palette_dir = Path("palettes")
palette_files = sorted(palette_dir.glob("*.hex"))
palette_names = [f.stem for f in palette_files]
palette = st.sidebar.selectbox("Palette", palette_names) 

contrast = st.sidebar.slider("Contrast", 0.5, 2.0, 1.0, 0.1)
brightness = st.sidebar.slider("Brightness", 0.5, 2.0, 1.0, 0.1)

if uploaded_file:
    original_image = Image.open(uploaded_file).convert("RGB")

    # Contrast and brightness adjustment 
    image = original_image.copy()
    enhancer_contrast = ImageEnhance.Contrast(image)
    image = enhancer_contrast.enhance(contrast)
    enhancer_brightness = ImageEnhance.Brightness(image)
    image = enhancer_brightness.enhance(brightness)

    # Downscale for pixelization
    processed_image = image.copy()
    w, h = processed_image.size
    processed_image = processed_image.resize(
        (max(1, w // pixel_size), max(1, h // pixel_size)),
        resample=Image.NEAREST
    )

    # Apply palette
    palette_file = palette_dir / f"{palette}.hex"
    processed_image = apply_custom_palette(processed_image, load_palette_file(palette_file), dithering) 
    upscaled_processed_image = processed_image.resize((w, h), Image.NEAREST)

    # Display
    if show_original:
        st.image(original_image, caption="Original", width="content")
    else:
        st.image(upscaled_processed_image, caption="Processed", width="content")

    # Save 
    buf = BytesIO()
    processed_image.save(buf, format="PNG")
    byte_im = buf.getvalue()

    st.sidebar.download_button(
        "Save image",
        data=byte_im,
        file_name="pixelized.png",
        mime="image/png"
    )