pixelizer / main.py
odinhg's picture
upload code and palettes
53b9670 verified
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"
)