import io import datetime import gradio as gr from PIL import Image, ImageFilter, ImageDraw, ImageFont import numpy as np def modern_to_nokia_pil( img, jpeg_quality=15, noise_std=12, color_levels=32, blur_radius=0.6, gamma=1.3, add_date_stamp=True, ): img = img.convert("RGB") # --- Crop to 4:3 --- w, h = img.size target_ratio = 4 / 3 current_ratio = w / h if current_ratio > target_ratio: new_w = int(h * target_ratio) left = (w - new_w) // 2 img = img.crop((left, 0, left + new_w, h)) else: new_h = int(w / target_ratio) top = (h - new_h) // 2 img = img.crop((0, top, w, top + new_h)) # --- Resize to 640×480 --- img = img.resize((640, 480), resample=Image.BILINEAR) # --- Blur --- img = img.filter(ImageFilter.GaussianBlur(radius=blur_radius)) # --- Posterize (reduce color depth) --- arr = np.array(img, dtype=np.uint8) step = max(1, 256 // int(color_levels)) arr = (arr // step) * step img = Image.fromarray(arr, mode="RGB") # --- Noise --- arr = np.array(img, dtype=np.int16) noise = np.random.normal(0, noise_std, arr.shape) arr = np.clip(arr + noise, 0, 255).astype(np.uint8) img = Image.fromarray(arr, mode="RGB") # --- Gamma curve --- arr = np.array(img, dtype=np.float32) / 255.0 arr = np.power(arr, gamma) arr = np.clip(arr * 255, 0, 255).astype(np.uint8) img = Image.fromarray(arr, mode="RGB") # --- Date stamp (fixed for Pillow >= 10) --- if add_date_stamp: draw = ImageDraw.Draw(img) font = ImageFont.load_default() text = datetime.datetime.now().strftime("%d-%m-%y %H:%M") # NEW SAFE SIZE METHOD bbox = draw.textbbox((0, 0), text, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] margin = 4 x = img.width - text_w - margin y = img.height - text_h - margin draw.rectangle( [x - 2, y - 2, x + text_w + 2, y + text_h + 2], fill=(0, 0, 0), ) draw.text((x, y), text, font=font, fill=(255, 255, 0)) # --- JPEG compression --- buffer = io.BytesIO() img.save(buffer, format="JPEG", quality=int(jpeg_quality), optimize=False, progressive=False) buffer.seek(0) img = Image.open(buffer) return img def convert_fn(image, jpeg_quality, noise_std, color_levels, blur_radius, gamma, add_date_stamp): if image is None: return None return modern_to_nokia_pil( image, jpeg_quality=jpeg_quality, noise_std=noise_std, color_levels=color_levels, blur_radius=blur_radius, gamma=gamma, add_date_stamp=add_date_stamp, ) demo = gr.Interface( fn=convert_fn, inputs=[ gr.Image(type="pil", label="Input image"), gr.Slider(5, 40, value=15, step=1, label="JPEG quality (lower = more Nokia)"), gr.Slider(0, 40, value=12, step=1, label="Noise amount"), gr.Slider(2, 64, value=32, step=2, label="Color levels per channel"), gr.Slider(0.0, 3.0, value=0.6, step=0.1, label="Blur radius"), gr.Slider(0.5, 2.5, value=1.3, step=0.1, label="Gamma (contrast curve)"), gr.Checkbox(True, label="Add date stamp"), ], outputs=gr.Image(type="pil", label="Nokia-style image"), title="0.3 MP Nokia Camera Filter", description="Upload a modern photo and turn it into an old-school 0.3 MP Nokia-style photo.", ) if __name__ == "__main__": demo.launch()