Spaces:
Runtime error
Runtime error
import numpy as np | |
from PIL import Image, ImageEnhance, ImageOps, ImageFilter | |
import colour | |
import json | |
import os | |
import psutil | |
from scipy.interpolate import interp1d | |
import piexif | |
import argparse | |
import multiprocessing | |
from functools import partial | |
class FilmProfile: | |
def __init__(self, name, color_curves, contrast, saturation, chromatic_aberration, blur, base_color, grain_amount, grain_size, advanced_curve=None): | |
self.name = name | |
self.color_curves = color_curves | |
self.contrast = contrast | |
self.saturation = saturation | |
self.chromatic_aberration = chromatic_aberration | |
self.blur = blur | |
self.base_color = base_color | |
self.grain_amount = grain_amount | |
self.grain_size = grain_size | |
self.advanced_curve = advanced_curve | |
def create_curve(curve_data): | |
x = np.array(curve_data['x']) | |
y = np.array(curve_data['y']) | |
return interp1d(x, y, kind='cubic', bounds_error=False, fill_value=(y[0], y[-1])) | |
def load_film_profiles_from_json(json_path): | |
with open(json_path, 'r') as f: | |
profiles_data = json.load(f) | |
profiles = {} | |
for name, data in profiles_data.items(): | |
color_curves = { | |
channel: create_curve(curve_data) | |
for channel, curve_data in data['color_curves'].items() | |
} | |
advanced_curve = data.get('advanced_curve', None) | |
profiles[name] = FilmProfile( | |
name, | |
color_curves=color_curves, | |
contrast=data['contrast'], | |
saturation=data['saturation'], | |
chromatic_aberration=data.get('chromatic_aberration', 0), | |
blur=data.get('blur', 0), | |
base_color=tuple(data.get('base_color', (255, 255, 255))), | |
grain_amount=data.get('grain_amount', 0), | |
grain_size=data.get('grain_size', 1), | |
advanced_curve=advanced_curve | |
) | |
return profiles | |
def load_default_profiles(): | |
default_json_path = '12-film-profiles.json' | |
return load_film_profiles_from_json(default_json_path) | |
def apply_color_curves(image, curves): | |
result = np.zeros_like(image) | |
for i, channel in enumerate(['R', 'G', 'B']): | |
result[:,:,i] = curves[channel](image[:,:,i]) | |
return result | |
def interpolate_circular(x, y, new_x): | |
x_extended = np.concatenate((x, x + 360)) | |
y_extended = np.concatenate((y, y)) | |
interp_func = interp1d(x_extended, y_extended, kind='cubic') | |
return interp_func(new_x % 360) | |
def apply_advanced_curve(image, advanced_curve): | |
hsv_image = colour.RGB_to_HSV(image) | |
hue_values = np.array(advanced_curve['hue_values']) | |
saturation_multipliers = np.array(advanced_curve['saturation_multipliers']) | |
hue_shifts = np.array(advanced_curve['hue_shifts']) | |
value_multipliers = np.array(advanced_curve['value_multipliers']) | |
hue = hsv_image[:,:,0] * 360 | |
saturation = hsv_image[:,:,1] | |
value = hsv_image[:,:,2] | |
interp_saturation_multipliers = interpolate_circular(hue_values, saturation_multipliers, hue) | |
max_saturation = 1.0 | |
interp_saturation_multipliers = np.clip(interp_saturation_multipliers, 0, max_saturation / saturation) | |
saturation *= interp_saturation_multipliers | |
interp_hue_shifts = interpolate_circular(hue_values, hue_shifts, hue) | |
hue = (hue + interp_hue_shifts) % 360 | |
interp_value_multipliers = interpolate_circular(hue_values, value_multipliers, hue) | |
max_value = 1.0 | |
interp_value_multipliers = np.clip(interp_value_multipliers, 0, max_value / value) | |
value *= interp_value_multipliers | |
hsv_image[:,:,0] = hue / 360 | |
hsv_image[:,:,1] = saturation | |
hsv_image[:,:,2] = value | |
return colour.HSV_to_RGB(hsv_image) | |
def apply_chromatic_aberration_pil(img, strength): | |
width, height = img.size | |
center_x, center_y = width // 2, height // 2 | |
r, g, b = img.split() | |
def create_displacement(x, y): | |
return int(strength * ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5 / (width + height)) | |
r = r.transform(img.size, Image.AFFINE, (1, 0, create_displacement(0, 0), 0, 1, 0)) | |
b = b.transform(img.size, Image.AFFINE, (1, 0, -create_displacement(0, 0), 0, 1, 0)) | |
return Image.merge("RGB", (r, g, b)) | |
def add_film_grain(image, amount=0.1, size=1): | |
width, height = image.size | |
grain = np.random.normal(0, amount, (height//size + 1, width//size + 1, 3)) | |
grain = np.repeat(np.repeat(grain, size, axis=0), size, axis=1) | |
grain = grain[:height, :width, :] | |
img_array = np.array(image).astype(np.float32) / 255.0 | |
grainy_image = np.clip(img_array + grain, 0, 1) * 255 | |
return Image.fromarray(grainy_image.astype(np.uint8)) | |
def adjust_color_temperature(image, temperature): | |
r_multiplier = 1 + (temperature - 6500) / 100 * 0.01 | |
b_multiplier = 1 - (temperature - 6500) / 100 * 0.01 | |
g_multiplier = 1 | |
r, g, b = image.split() | |
r = r.point(lambda i: min(255, int(i * r_multiplier))) | |
g = g.point(lambda i: min(255, int(i * g_multiplier))) | |
b = b.point(lambda i: min(255, int(i * b_multiplier))) | |
return Image.merge('RGB', (r, g, b)) | |
def apply_base_color(image, base_color): | |
base = Image.new('RGB', image.size, base_color) | |
return Image.blend(image, base, 0.1) | |
def cross_process(image): | |
contrast_enhancer = ImageEnhance.Contrast(image) | |
image = contrast_enhancer.enhance(1.5) | |
r, g, b = image.split() | |
r = r.point(lambda i: min(255, int(i * 1.2))) | |
g = g.point(lambda i: int(i * 0.9)) | |
b = b.point(lambda i: min(255, int(i * 1.1))) | |
image = Image.merge('RGB', (r, g, b)) | |
saturation_enhancer = ImageEnhance.Color(image) | |
image = saturation_enhancer.enhance(1.3) | |
return image | |
def apply_film_profile(img, profile, chroma_override=None, blur_override=None, color_temp=6500, cross_process_flag=False, curve_type="auto"): | |
img_array = np.array(img).astype(np.float32) / 255.0 | |
img_linear = colour.models.eotf_sRGB(img_array) | |
if curve_type == "advanced" or (curve_type == "auto" and profile.advanced_curve): | |
img_color_adjusted = apply_advanced_curve(img_linear, profile.advanced_curve) | |
elif curve_type == "color" or (curve_type == "auto" and not profile.advanced_curve): | |
img_color_adjusted = apply_color_curves(img_linear, profile.color_curves) | |
elif curve_type == "both": | |
if profile.color_curves: | |
img_color_adjusted = apply_color_curves(img_linear, profile.color_curves) | |
if profile.advanced_curve: | |
img_color_adjusted = apply_advanced_curve(img_color_adjusted, profile.advanced_curve) | |
img_srgb = colour.models.eotf_inverse_sRGB(img_color_adjusted) | |
img_pil = Image.fromarray((img_srgb * 255).astype(np.uint8)) | |
enhancer = ImageEnhance.Contrast(img_pil) | |
img_contrast = enhancer.enhance(profile.contrast) | |
enhancer = ImageEnhance.Color(img_contrast) | |
img_saturated = enhancer.enhance(profile.saturation) | |
chroma_strength = chroma_override if chroma_override is not None else profile.chromatic_aberration | |
if chroma_strength > 0: | |
img_saturated = apply_chromatic_aberration_pil(img_saturated, chroma_strength) | |
blur_amount = blur_override if blur_override is not None else profile.blur | |
if blur_amount > 0: | |
img_saturated = img_saturated.filter(ImageFilter.GaussianBlur(radius=blur_amount)) | |
img_saturated = apply_base_color(img_saturated, profile.base_color) | |
img_saturated = add_film_grain(img_saturated, amount=profile.grain_amount, size=profile.grain_size) | |
if color_temp is not None: | |
img_saturated = adjust_color_temperature(img_saturated, color_temp) | |
if cross_process_flag: | |
img_saturated = cross_process(img_saturated) | |
return img_saturated | |
def process_images(image, profiles_json=None, selected_profile=None, chroma_override=None, blur_override=None, color_temp=6500, cross_process_flag=False, curve_type="auto"): | |
if profiles_json: | |
film_profiles = load_film_profiles_from_json(profiles_json) | |
else: | |
film_profiles = load_default_profiles() | |
input_path_base = "output" | |
processed_images = [] | |
for profile_name, profile in film_profiles.items(): | |
if selected_profile and selected_profile != "All" and selected_profile != profile_name: | |
continue | |
processed_image = apply_film_profile(image, profile, chroma_override, blur_override, color_temp, cross_process_flag, curve_type) | |
processed_images.append(processed_image) | |
return processed_images | |