|
|
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") |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
img = img.resize((640, 480), resample=Image.BILINEAR) |
|
|
|
|
|
|
|
|
img = img.filter(ImageFilter.GaussianBlur(radius=blur_radius)) |
|
|
|
|
|
|
|
|
arr = np.array(img, dtype=np.uint8) |
|
|
step = max(1, 256 // int(color_levels)) |
|
|
arr = (arr // step) * step |
|
|
img = Image.fromarray(arr, mode="RGB") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
if add_date_stamp: |
|
|
draw = ImageDraw.Draw(img) |
|
|
font = ImageFont.load_default() |
|
|
text = datetime.datetime.now().strftime("%d-%m-%y %H:%M") |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
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() |
|
|
|