V 2.0.0 - Rasterix 10
This commit is contained in:
+18
-1
@@ -80,6 +80,9 @@ from ..components.images import img_film_grain as img_film_grain
|
||||
from ..components.images import img_blur as img_blur
|
||||
from ..components.images import img_selective_tone as img_selective_tone
|
||||
from ..components.images import img_smart_lighting as img_smart_lighting
|
||||
from ..components.images import img_white_balance as img_white_balance
|
||||
from ..components.images import img_film_rendering as img_film_rendering
|
||||
from ..components.images.img_film_rendering import FILM_PRESETS
|
||||
from ..components.images import img_lens_effects as img_lens_effects
|
||||
|
||||
class PrimereSamplersSteps:
|
||||
@@ -2174,6 +2177,10 @@ class PrimereRasterix:
|
||||
"auto_normalize": ("BOOLEAN", {"default": False, "label_off": "No auto levels", "label_on": "Apply auto levels"}),
|
||||
"auto_levels_threshold": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 10.0, "step": 0.1}),
|
||||
|
||||
"use_white_balance": ("BOOLEAN", {"default": False, "label_off": "Ignore white balance", "label_on": "Apply white balance"}),
|
||||
"wb_temperature": ("FLOAT", {"default": 6500, "min": 2000, "max": 12000, "step": 100}),
|
||||
"wb_tint": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 1}),
|
||||
|
||||
"use_smart_lighting": ("BOOLEAN", {"default": False, "label_off": "Ignore smart lightning", "label_on": "Apply smart lightning"}),
|
||||
"smart_lighting": ("FLOAT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
|
||||
@@ -2191,6 +2198,10 @@ class PrimereRasterix:
|
||||
"contrast": ("FLOAT", {"default": 0, "min": -50, "max": 100, "step": 1}),
|
||||
"use_legacy": ("BOOLEAN", {"default": False, "label_off": "Use non-linear shift", "label_on": "Use adaptive offset"}),
|
||||
|
||||
"use_film_rendering": ("BOOLEAN", {"default": False, "label_off": "Ignore film rendering", "label_on": "Apply film rendering"}),
|
||||
"film_rendering": (list(FILM_PRESETS.keys()), {"default": "kodak_kodachrome_64_CF"}),
|
||||
"film_rendering_intensity": ("FLOAT", {"default": 100, "min": 0, "max": 100, "step": 1}),
|
||||
|
||||
"use_selective_tone": ("BOOLEAN", {"default": False, "label_off": "Ignore selective tone", "label_on": "Apply selective tone"}),
|
||||
"selective_tone_value": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 1}),
|
||||
"selective_tone_zone": (["highlights", "midtones", "shadows", "blacks"], {"default": "midtones"}),
|
||||
@@ -2232,7 +2243,7 @@ class PrimereRasterix:
|
||||
}
|
||||
}
|
||||
|
||||
def primere_rasterix(self, concepts, models, image, auto_normalize, auto_levels_threshold, use_blur, blur_type, blur_intensity, blur_radius, angle, bilateral_edge_sensitivity, blur_edge_only, edge_threshold, use_smart_lighting, smart_lighting, use_brightness_contrast, brightness, contrast, use_legacy, use_selective_tone, selective_tone_value, selective_tone_zone, selective_tone_separation, selective_tone_strength, use_color_balance, color_balance_cyan_red, color_balance_magenta_green, color_balance_yellow_blue, color_balance_tone, color_balance_preserve_luminosity, color_balance_separation, use_hsl, hsl_hue, hsl_saturation, hsl_lightness, hsl_vibrance, hsl_channel, hsl_channel_width, hsl_skin_protection, use_shade_detailer, shade_level, shade_radius, detail_mode, shade_strength, use_ai_detection_bypasser, adb_freq_strength, adb_variance_strength, adb_unsharp_percent, adb_jpeg_cycles, model_concept=None, model_name=None):
|
||||
def primere_rasterix(self, concepts, models, image, auto_normalize, auto_levels_threshold, use_white_balance, wb_temperature, wb_tint, use_blur, blur_type, blur_intensity, blur_radius, angle, bilateral_edge_sensitivity, blur_edge_only, edge_threshold, use_smart_lighting, smart_lighting, use_brightness_contrast, brightness, contrast, use_legacy, use_film_rendering, film_rendering, film_rendering_intensity, use_selective_tone, selective_tone_value, selective_tone_zone, selective_tone_separation, selective_tone_strength, use_color_balance, color_balance_cyan_red, color_balance_magenta_green, color_balance_yellow_blue, color_balance_tone, color_balance_preserve_luminosity, color_balance_separation, use_hsl, hsl_hue, hsl_saturation, hsl_lightness, hsl_vibrance, hsl_channel, hsl_channel_width, hsl_skin_protection, use_shade_detailer, shade_level, shade_radius, detail_mode, shade_strength, use_ai_detection_bypasser, adb_freq_strength, adb_variance_strength, adb_unsharp_percent, adb_jpeg_cycles, model_concept=None, model_name=None):
|
||||
pil_img = utility.tensor_to_image(image)
|
||||
|
||||
rasterix_json_path = os.path.join(PRIMERE_ROOT, 'front_end', 'rasterix.json')
|
||||
@@ -2241,6 +2252,9 @@ class PrimereRasterix:
|
||||
if auto_normalize:
|
||||
pil_img = img_levels_auto.img_levels_auto(image=pil_img, auto_normalize=auto_normalize, threshold=auto_levels_threshold)
|
||||
|
||||
if use_white_balance and (wb_temperature != 6500 or wb_tint != 0):
|
||||
pil_img = img_white_balance.img_white_balance(image=pil_img, temperature=wb_temperature, tint=wb_tint)
|
||||
|
||||
if use_blur and blur_intensity != 0:
|
||||
pil_img = img_blur.img_blur(image=pil_img, blur_type=blur_type, intensity=blur_intensity, radius=blur_radius, angle=angle, edge_only=blur_edge_only, bilateral_edge_sensitivity=bilateral_edge_sensitivity, edge_threshold=edge_threshold)
|
||||
|
||||
@@ -2250,6 +2264,9 @@ class PrimereRasterix:
|
||||
if use_brightness_contrast and (brightness != 0 or contrast != 0):
|
||||
pil_img = img_brightness_contrast.img_brightness_contrast(image=pil_img, brightness=brightness, contrast=contrast, use_legacy=use_legacy)
|
||||
|
||||
if use_film_rendering and film_rendering_intensity != 0:
|
||||
pil_img = img_film_rendering.img_film_rendering(image=pil_img, rendering=film_rendering, intensity=film_rendering_intensity)
|
||||
|
||||
st_data = rasterix_data.get('selective_tone', {})
|
||||
if use_selective_tone and st_data:
|
||||
pil_img = img_selective_tone.img_selective_tone(image=pil_img, channels_data=st_data, separation=selective_tone_separation, strength=selective_tone_strength)
|
||||
|
||||
@@ -0,0 +1,874 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# ── Film / Sensor preset library ─────────────────────────────────────────────
|
||||
# Colour film (_CF):
|
||||
# "bw" : False
|
||||
# "type" : "CF"
|
||||
# "bias" : (R, G, B) multiplicative gain
|
||||
# "curves" : {"r","g","b"} each list of (in, out) control points 0–1
|
||||
#
|
||||
# B&W film (_BWF):
|
||||
# "bw" : True
|
||||
# "type" : "BWF"
|
||||
# "mix" : (R, G, B) spectral sensitivity weights, sum to 1.0
|
||||
# "tint" : (R, G, B) chemical toning colour bias
|
||||
# "bias" : (1,1,1) unused, kept for consistency
|
||||
# "curves" : {"r","g","b"} identical — applied to the grey channel
|
||||
#
|
||||
# Digital sensor (_CCD):
|
||||
# "bw" : False
|
||||
# "type" : "CCD"
|
||||
# "bias" : (R, G, B) sensor colour filter bias
|
||||
# "matrix" : 3×3 colour science matrix (applied in linear light)
|
||||
# "curves" : {"r","g","b"} camera tone curve / picture profile
|
||||
# "shadow_lift" : (R, G, B) black point lift — sensor noise floor colour
|
||||
# "highlight_rolloff" : float 0–1, controls highlight compression strength
|
||||
|
||||
FILM_PRESETS = {
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# COLOUR FILMS (_CF)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
"fuji_astia_100_CF": {
|
||||
"desc": "Fuji Astia 100 — soft, low contrast, neutral skin tones, subtle colours",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.00, 1.00, 0.97),
|
||||
"curves": {
|
||||
"r": [(0.0,0.05),(0.25,0.27),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"g": [(0.0,0.04),(0.25,0.27),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.04),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.96)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_provia_100_CF": {
|
||||
"desc": "Fuji Provia 100F — standard/neutral, accurate colour, moderate contrast",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.00, 1.01, 1.02),
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.26),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"g": [(0.0,0.02),(0.25,0.27),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.03),(0.25,0.27),(0.5,0.53),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_velvia_100_CF": {
|
||||
"desc": "Fuji Velvia 100 — punchy, very saturated, high contrast, vivid greens and blues",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.00, 1.03, 1.06),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.22),(0.5,0.50),(0.75,0.78),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.23),(0.5,0.52),(0.75,0.80),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.24),(0.5,0.54),(0.75,0.82),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_superia_400_CF": {
|
||||
"desc": "Fuji Superia 400 — consumer negative, warm greens, slight grain character",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.02, 1.03, 0.96),
|
||||
"curves": {
|
||||
"r": [(0.0,0.04),(0.25,0.28),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.04),(0.25,0.29),(0.5,0.54),(0.75,0.78),(1.0,0.97)],
|
||||
"b": [(0.0,0.03),(0.25,0.25),(0.5,0.49),(0.75,0.73),(1.0,0.94)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_400h_CF": {
|
||||
"desc": "Fuji 400H — soft highlights, cool shadows, popular portrait film",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (0.99, 1.01, 1.04),
|
||||
"curves": {
|
||||
"r": [(0.0,0.05),(0.25,0.27),(0.5,0.51),(0.75,0.75),(1.0,0.96)],
|
||||
"g": [(0.0,0.05),(0.25,0.28),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.06),(0.25,0.29),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_kodachrome_64_CF": {
|
||||
"desc": "Kodak Kodachrome 64 — iconic warm reds, deep blues, high contrast, rich shadows",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.06, 0.98, 0.94),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.24),(0.5,0.52),(0.75,0.80),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.22),(0.5,0.49),(0.75,0.76),(1.0,0.98)],
|
||||
"b": [(0.0,0.00),(0.25,0.21),(0.5,0.48),(0.75,0.75),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_ektachrome_100vs_CF": {
|
||||
"desc": "Kodak Ektachrome 100VS — very saturated, cool blues, strong greens",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (0.98, 1.02, 1.05),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.23),(0.5,0.50),(0.75,0.78),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.24),(0.5,0.52),(0.75,0.80),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.25),(0.5,0.54),(0.75,0.82),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_portra_160_CF": {
|
||||
"desc": "Kodak Portra 160 — warm skin tones, soft highlights, low contrast, fine grain",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.04, 1.01, 0.95),
|
||||
"curves": {
|
||||
"r": [(0.0,0.04),(0.25,0.28),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.03),(0.25,0.27),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.03),(0.25,0.25),(0.5,0.49),(0.75,0.73),(1.0,0.94)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_portra_400_CF": {
|
||||
"desc": "Kodak Portra 400 — versatile portrait film, warm, slightly lifted shadows",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.03, 1.00, 0.96),
|
||||
"curves": {
|
||||
"r": [(0.0,0.05),(0.25,0.28),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.04),(0.25,0.27),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.04),(0.25,0.25),(0.5,0.50),(0.75,0.74),(1.0,0.95)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_gold_200_CF": {
|
||||
"desc": "Kodak Gold 200 — consumer film, warm golden tone, boosted yellows and reds",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.05, 1.02, 0.92),
|
||||
"curves": {
|
||||
"r": [(0.0,0.03),(0.25,0.28),(0.5,0.54),(0.75,0.79),(1.0,0.98)],
|
||||
"g": [(0.0,0.03),(0.25,0.27),(0.5,0.53),(0.75,0.78),(1.0,0.97)],
|
||||
"b": [(0.0,0.02),(0.25,0.23),(0.5,0.47),(0.75,0.71),(1.0,0.92)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_ultramax_400_CF": {
|
||||
"desc": "Kodak Ultramax 400 — vivid warm colours, punchy contrast, popular street film",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.04, 1.01, 0.93),
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.26),(0.5,0.52),(0.75,0.79),(1.0,0.99)],
|
||||
"g": [(0.0,0.02),(0.25,0.26),(0.5,0.52),(0.75,0.78),(1.0,0.98)],
|
||||
"b": [(0.0,0.01),(0.25,0.22),(0.5,0.47),(0.75,0.73),(1.0,0.94)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_tri_x_400_CF": {
|
||||
"desc": "Kodak Tri-X 400 — B&W look in colour, strong contrast, warm shadow tint",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.02, 1.00, 0.97),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.21),(0.5,0.50),(0.75,0.79),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.21),(0.5,0.50),(0.75,0.79),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.20),(0.5,0.49),(0.75,0.78),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"agfa_vista_200_CF": {
|
||||
"desc": "Agfa Vista 200 — cool shadows, slight blue-green tint, soft contrast",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (0.97, 1.00, 1.04),
|
||||
"curves": {
|
||||
"r": [(0.0,0.04),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.96)],
|
||||
"g": [(0.0,0.04),(0.25,0.27),(0.5,0.52),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.05),(0.25,0.28),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"lomography_lomo_100_CF": {
|
||||
"desc": "Lomography 100 — high contrast, cross-process look, boosted saturation",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.05, 0.97, 1.02),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.24),(0.5,0.53),(0.75,0.82),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.20),(0.5,0.48),(0.75,0.77),(1.0,0.98)],
|
||||
"b": [(0.0,0.00),(0.25,0.23),(0.5,0.52),(0.75,0.80),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"ilford_xp2_400_CF": {
|
||||
"desc": "Ilford XP2 Super 400 — chromogenic B&W, neutral, clean shadows",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_vision3_500t_CF": {
|
||||
"desc": "Kodak Vision3 500T — cinema negative, tungsten balanced, warm shadows, teal highlights",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (1.03, 0.99, 0.96),
|
||||
"curves": {
|
||||
"r": [(0.0,0.05),(0.25,0.28),(0.5,0.52),(0.75,0.76),(1.0,0.96)],
|
||||
"g": [(0.0,0.04),(0.25,0.27),(0.5,0.51),(0.75,0.75),(1.0,0.96)],
|
||||
"b": [(0.0,0.06),(0.25,0.27),(0.5,0.51),(0.75,0.76),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_eterna_250d_CF": {
|
||||
"desc": "Fuji Eterna 250D — cinema film, daylight balanced, soft contrast, desaturated highlights",
|
||||
"bw": False, "type": "CF",
|
||||
"bias": (0.99, 1.01, 1.03),
|
||||
"curves": {
|
||||
"r": [(0.0,0.05),(0.25,0.27),(0.5,0.51),(0.75,0.75),(1.0,0.95)],
|
||||
"g": [(0.0,0.05),(0.25,0.28),(0.5,0.52),(0.75,0.76),(1.0,0.96)],
|
||||
"b": [(0.0,0.05),(0.25,0.28),(0.5,0.52),(0.75,0.76),(1.0,0.96)],
|
||||
},
|
||||
},
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# B&W FILMS (_BWF)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
"ilford_hp5_400_BWF": {
|
||||
"desc": "Ilford HP5 Plus 400 — classic panchromatic, neutral grey, forgiving latitude",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.24),(0.5,0.51),(0.75,0.77),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.24),(0.5,0.51),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.24),(0.5,0.51),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"ilford_delta_100_BWF": {
|
||||
"desc": "Ilford Delta 100 — fine grain, cool neutral tone, excellent shadow detail",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.28, 0.60, 0.12),
|
||||
"tint": (0.98, 0.99, 1.01),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"ilford_delta_3200_BWF": {
|
||||
"desc": "Ilford Delta 3200 — very high ISO, lifted shadows, compressed highlights",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.30, 0.59, 0.11),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.08),(0.25,0.28),(0.5,0.52),(0.75,0.75),(1.0,0.94)],
|
||||
"g": [(0.0,0.08),(0.25,0.28),(0.5,0.52),(0.75,0.75),(1.0,0.94)],
|
||||
"b": [(0.0,0.08),(0.25,0.28),(0.5,0.52),(0.75,0.75),(1.0,0.94)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_tmax_100_BWF": {
|
||||
"desc": "Kodak T-Max 100 — ultra-fine grain, high contrast, deep clean blacks",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.27, 0.62, 0.11),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.22),(0.5,0.51),(0.75,0.80),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.22),(0.5,0.51),(0.75,0.80),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.22),(0.5,0.51),(0.75,0.80),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_tmax_400_BWF": {
|
||||
"desc": "Kodak T-Max 400 — fine grain for ISO 400, excellent tonal range",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.28, 0.61, 0.11),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"kodak_tri_x_400_BWF": {
|
||||
"desc": "Kodak Tri-X 400 B&W — iconic, punchy, deep blacks, photojournalism classic",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.32, 0.58, 0.10),
|
||||
"tint": (1.01, 1.00, 0.99),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.21),(0.5,0.50),(0.75,0.80),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.21),(0.5,0.50),(0.75,0.80),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.21),(0.5,0.50),(0.75,0.80),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"agfa_apx_100_BWF": {
|
||||
"desc": "Agfa APX 100 — smooth midtones, slightly warm neutral, soft shadow gradation",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.30, 0.59, 0.11),
|
||||
"tint": (1.01, 1.00, 0.99),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
"g": [(0.0,0.02),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
"b": [(0.0,0.02),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
},
|
||||
},
|
||||
|
||||
"agfa_apx_400_BWF": {
|
||||
"desc": "Agfa APX 400 — medium grain, contrasty midtones, green-sensitive",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.29, 0.61, 0.10),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.79),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.79),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.23),(0.5,0.51),(0.75,0.79),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"rollei_rpx_400_BWF": {
|
||||
"desc": "Rollei RPX 400 — very deep blacks, punchy street photography look",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.30, 0.59, 0.11),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.20),(0.5,0.50),(0.75,0.81),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.20),(0.5,0.50),(0.75,0.81),(1.0,1.00)],
|
||||
"b": [(0.0,0.00),(0.25,0.20),(0.5,0.50),(0.75,0.81),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"fomapan_100_BWF": {
|
||||
"desc": "Fomapan 100 — orthochromatic character, blue-sensitive, soft contrast, vintage look",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.22, 0.55, 0.23),
|
||||
"tint": (1.00, 1.00, 1.00),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
"g": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
"b": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"selenium_tone_BWF": {
|
||||
"desc": "Selenium toning — cool blue-purple shadow tone, archival darkroom process",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (0.96, 0.97, 1.04),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
"g": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.23),(0.5,0.50),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"sepia_tone_BWF": {
|
||||
"desc": "Sepia toning — warm brown throughout, classic Victorian / vintage look",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (1.08, 1.00, 0.82),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.04),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.04),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"b": [(0.0,0.04),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"gold_tone_BWF": {
|
||||
"desc": "Gold toning — warm golden highlights, cooler shadows, elegant darkroom effect",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (1.05, 1.02, 0.88),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.24),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"g": [(0.0,0.02),(0.25,0.24),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"b": [(0.0,0.02),(0.25,0.24),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"cyanotype_BWF": {
|
||||
"desc": "Cyanotype — deep cyan-blue alternative process print look",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (0.78, 0.90, 1.15),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.23),(0.5,0.50),(0.75,0.76),(1.0,0.97)],
|
||||
"g": [(0.0,0.02),(0.25,0.23),(0.5,0.50),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.02),(0.25,0.23),(0.5,0.50),(0.75,0.76),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
"platinum_palladium_BWF": {
|
||||
"desc": "Platinum/Palladium print — long tonal scale, warm neutral, rich shadow detail",
|
||||
"bw": True, "type": "BWF",
|
||||
"mix": (0.299, 0.587, 0.114),
|
||||
"tint": (1.02, 1.01, 0.99),
|
||||
"bias": (1.00, 1.00, 1.00),
|
||||
"curves": {
|
||||
"r": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
"g": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
"b": [(0.0,0.03),(0.25,0.26),(0.5,0.51),(0.75,0.75),(1.0,0.97)],
|
||||
},
|
||||
},
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# DIGITAL CAMERA SENSORS (_CCD)
|
||||
#
|
||||
# "matrix" : 3×3 colour science matrix applied in linear light.
|
||||
# Rows = output R, G, B. Columns = input R, G, B.
|
||||
# Identity = [[1,0,0],[0,1,0],[0,0,1]]
|
||||
# "shadow_lift" : (R, G, B) black point offset — sensor noise floor colour
|
||||
# "highlight_rolloff" : 0–1, how aggressively highlights are compressed
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
"canon_5d_mark2_CCD": {
|
||||
"desc": "Canon 5D Mark II — warm romantic colour, gentle highlight rolloff, "
|
||||
"smooth skin tones, classic full-frame DSLR",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.04, 1.00, 0.97),
|
||||
"matrix": [
|
||||
[ 1.06, -0.04, -0.02],
|
||||
[-0.03, 1.04, -0.01],
|
||||
[-0.02, -0.06, 1.08],
|
||||
],
|
||||
"shadow_lift": (0.008, 0.006, 0.005),
|
||||
"highlight_rolloff": 0.35,
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.01),(0.25,0.26),(0.5,0.51),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.01),(0.25,0.25),(0.5,0.50),(0.75,0.75),(1.0,0.96)],
|
||||
},
|
||||
},
|
||||
|
||||
"canon_5d_mark1_CCD": {
|
||||
"desc": "Canon 5D Mark I — original full-frame, warm CCD character, "
|
||||
"lower dynamic range but very pleasing colour rendering",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.05, 1.00, 0.95),
|
||||
"matrix": [
|
||||
[ 1.08, -0.05, -0.03],
|
||||
[-0.03, 1.05, -0.02],
|
||||
[-0.03, -0.07, 1.10],
|
||||
],
|
||||
"shadow_lift": (0.012, 0.009, 0.007),
|
||||
"highlight_rolloff": 0.45,
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.27),(0.5,0.53),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.76),(1.0,0.96)],
|
||||
"b": [(0.0,0.01),(0.25,0.24),(0.5,0.50),(0.75,0.74),(1.0,0.95)],
|
||||
},
|
||||
},
|
||||
|
||||
"canon_1dx_CCD": {
|
||||
"desc": "Canon 1Dx — professional sports/press, accurate neutral colour, "
|
||||
"punchy contrast, excellent highlight detail",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.02, 1.00, 0.99),
|
||||
"matrix": [
|
||||
[ 1.04, -0.02, -0.02],
|
||||
[-0.02, 1.03, -0.01],
|
||||
[-0.01, -0.04, 1.05],
|
||||
],
|
||||
"shadow_lift": (0.006, 0.005, 0.005),
|
||||
"highlight_rolloff": 0.25,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"g": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.78),(1.0,0.99)],
|
||||
"b": [(0.0,0.00),(0.25,0.24),(0.5,0.50),(0.75,0.77),(1.0,0.98)],
|
||||
},
|
||||
},
|
||||
|
||||
"sony_a7iii_CCD": {
|
||||
"desc": "Sony A7 III — neutral accurate colour, cool shadow character, "
|
||||
"high dynamic range, clinical modern look",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.00, 1.01, 1.02),
|
||||
"matrix": [
|
||||
[ 1.02, -0.01, -0.01],
|
||||
[-0.01, 1.03, 0.00],
|
||||
[ 0.00, -0.02, 1.02],
|
||||
],
|
||||
"shadow_lift": (0.004, 0.004, 0.006),
|
||||
"highlight_rolloff": 0.20,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"g": [(0.0,0.00),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,1.00)],
|
||||
"b": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"sony_a7rii_CCD": {
|
||||
"desc": "Sony A7R II — very high resolution, neutral-cool, extremely detailed, "
|
||||
"wide colour gamut rendering",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (0.99, 1.01, 1.03),
|
||||
"matrix": [
|
||||
[ 1.01, -0.01, 0.00],
|
||||
[-0.01, 1.03, 0.00],
|
||||
[ 0.00, -0.02, 1.02],
|
||||
],
|
||||
"shadow_lift": (0.003, 0.003, 0.005),
|
||||
"highlight_rolloff": 0.18,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"g": [(0.0,0.00),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,1.00)],
|
||||
"b": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.78),(1.0,1.00)],
|
||||
},
|
||||
},
|
||||
|
||||
"nikon_d800_CCD": {
|
||||
"desc": "Nikon D800 — neutral accurate, slightly cool shadows, excellent detail, "
|
||||
"professional landscape/studio camera",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.00, 1.01, 1.01),
|
||||
"matrix": [
|
||||
[ 1.03, -0.02, -0.01],
|
||||
[-0.01, 1.03, 0.00],
|
||||
[ 0.00, -0.03, 1.03],
|
||||
],
|
||||
"shadow_lift": (0.005, 0.005, 0.007),
|
||||
"highlight_rolloff": 0.22,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.77),(1.0,0.99)],
|
||||
"g": [(0.0,0.00),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"nikon_d3_CCD": {
|
||||
"desc": "Nikon D3 — warm classic DSLR rendering, photojournalism standard, "
|
||||
"smooth pleasing colour, good shadow detail",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.03, 1.01, 0.97),
|
||||
"matrix": [
|
||||
[ 1.05, -0.03, -0.02],
|
||||
[-0.02, 1.04, -0.01],
|
||||
[-0.01, -0.05, 1.06],
|
||||
],
|
||||
"shadow_lift": (0.008, 0.007, 0.006),
|
||||
"highlight_rolloff": 0.30,
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.01),(0.25,0.26),(0.5,0.51),(0.75,0.76),(1.0,0.97)],
|
||||
"b": [(0.0,0.01),(0.25,0.25),(0.5,0.50),(0.75,0.75),(1.0,0.96)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_xt3_CCD": {
|
||||
"desc": "Fuji X-T3 — film-simulation-inspired colour science, warm midtones, "
|
||||
"distinctive X-Trans rendering, vivid but natural",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.03, 1.01, 0.98),
|
||||
"matrix": [
|
||||
[ 1.05, -0.03, -0.02],
|
||||
[-0.02, 1.05, -0.02],
|
||||
[-0.01, -0.04, 1.05],
|
||||
],
|
||||
"shadow_lift": (0.007, 0.006, 0.005),
|
||||
"highlight_rolloff": 0.40,
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.27),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"g": [(0.0,0.02),(0.25,0.27),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"b": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.75),(1.0,0.96)],
|
||||
},
|
||||
},
|
||||
|
||||
"fuji_gfx_CCD": {
|
||||
"desc": "Fuji GFX 100 — medium format digital, very neutral and clean, "
|
||||
"exceptional tonal gradation, subtle warm-neutral",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.01, 1.01, 1.00),
|
||||
"matrix": [
|
||||
[ 1.02, -0.01, -0.01],
|
||||
[-0.01, 1.03, -0.01],
|
||||
[ 0.00, -0.02, 1.02],
|
||||
],
|
||||
"shadow_lift": (0.005, 0.005, 0.004),
|
||||
"highlight_rolloff": 0.28,
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.26),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
"g": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.26),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
},
|
||||
},
|
||||
|
||||
"leica_m9_CCD": {
|
||||
"desc": "Leica M9 CCD — iconic true CCD sensor, warm romantic colour, "
|
||||
"beautiful highlight glow, beloved by photographers for its "
|
||||
"three-dimensional rendering",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.05, 1.01, 0.94),
|
||||
"matrix": [
|
||||
[ 1.07, -0.04, -0.03],
|
||||
[-0.03, 1.05, -0.02],
|
||||
[-0.02, -0.08, 1.10],
|
||||
],
|
||||
"shadow_lift": (0.010, 0.008, 0.007),
|
||||
"highlight_rolloff": 0.50,
|
||||
"curves": {
|
||||
"r": [(0.0,0.02),(0.25,0.27),(0.5,0.53),(0.75,0.78),(1.0,0.97)],
|
||||
"g": [(0.0,0.02),(0.25,0.27),(0.5,0.52),(0.75,0.77),(1.0,0.97)],
|
||||
"b": [(0.0,0.01),(0.25,0.24),(0.5,0.50),(0.75,0.74),(1.0,0.95)],
|
||||
},
|
||||
},
|
||||
|
||||
"leica_m11_CCD": {
|
||||
"desc": "Leica M11 CMOS — modern Leica, very neutral and clinical, "
|
||||
"60MP resolution character, faithful colour science",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.01, 1.01, 1.00),
|
||||
"matrix": [
|
||||
[ 1.02, -0.01, -0.01],
|
||||
[-0.01, 1.02, 0.00],
|
||||
[ 0.00, -0.02, 1.02],
|
||||
],
|
||||
"shadow_lift": (0.004, 0.004, 0.004),
|
||||
"highlight_rolloff": 0.22,
|
||||
"curves": {
|
||||
"r": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
"g": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.77),(1.0,0.99)],
|
||||
"b": [(0.0,0.01),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.98)],
|
||||
},
|
||||
},
|
||||
|
||||
"hasselblad_x2d_CCD": {
|
||||
"desc": "Hasselblad X2D — 100MP medium format, extremely neutral and clean, "
|
||||
"very wide tonal range, clinical precision colour",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.00, 1.01, 1.01),
|
||||
"matrix": [
|
||||
[ 1.01, 0.00, -0.01],
|
||||
[ 0.00, 1.02, 0.00],
|
||||
[ 0.00, -0.01, 1.01],
|
||||
],
|
||||
"shadow_lift": (0.003, 0.003, 0.003),
|
||||
"highlight_rolloff": 0.15,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"g": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
"b": [(0.0,0.00),(0.25,0.25),(0.5,0.51),(0.75,0.76),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
|
||||
"olympus_omd_CCD": {
|
||||
"desc": "Olympus OM-D E-M1 — punchy vivid colour, slightly cool, "
|
||||
"Micro Four Thirds sensor, sharp contrasty rendering",
|
||||
"bw": False, "type": "CCD",
|
||||
"bias": (1.01, 1.02, 1.02),
|
||||
"matrix": [
|
||||
[ 1.03, -0.01, -0.02],
|
||||
[-0.01, 1.04, -0.01],
|
||||
[-0.01, -0.02, 1.03],
|
||||
],
|
||||
"shadow_lift": (0.005, 0.005, 0.006),
|
||||
"highlight_rolloff": 0.20,
|
||||
"curves": {
|
||||
"r": [(0.0,0.00),(0.25,0.25),(0.5,0.52),(0.75,0.78),(1.0,1.00)],
|
||||
"g": [(0.0,0.00),(0.25,0.26),(0.5,0.52),(0.75,0.79),(1.0,1.00)],
|
||||
"b": [(0.0,0.01),(0.25,0.26),(0.5,0.52),(0.75,0.78),(1.0,0.99)],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Main function
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def img_film_rendering(
|
||||
image: Image.Image,
|
||||
rendering: str = "kodak_kodachrome_64_CF",
|
||||
intensity: float = 100,
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Film and digital sensor rendering simulation.
|
||||
Supports colour film (_CF), B&W film (_BWF), and digital camera sensors (_CCD).
|
||||
|
||||
Args:
|
||||
image : PIL Image (RGB)
|
||||
|
||||
rendering : Preset name — suffix indicates type:
|
||||
_CF = colour film
|
||||
_BWF = black and white film
|
||||
_CCD = digital camera sensor
|
||||
|
||||
── Colour film (_CF) ─────────────────────────────────────
|
||||
"fuji_astia_100_CF" Soft, neutral skin, low contrast
|
||||
"fuji_provia_100_CF" Standard, accurate, moderate contrast
|
||||
"fuji_velvia_100_CF" Vivid, high contrast, punchy colours
|
||||
"fuji_superia_400_CF" Warm greens, consumer negative
|
||||
"fuji_400h_CF" Soft highlights, cool shadows, portrait
|
||||
"kodak_kodachrome_64_CF" Warm reds, deep blues, high contrast
|
||||
"kodak_ektachrome_100vs_CF" Very saturated, cool blues
|
||||
"kodak_portra_160_CF" Warm skin, soft highlights, fine grain
|
||||
"kodak_portra_400_CF" Warm, lifted shadows, versatile portrait
|
||||
"kodak_gold_200_CF" Warm golden, boosted yellows
|
||||
"kodak_ultramax_400_CF" Vivid warm, punchy, street film
|
||||
"kodak_tri_x_400_CF" Strong contrast, warm shadow tint
|
||||
"agfa_vista_200_CF" Cool shadows, blue-green tint
|
||||
"lomography_lomo_100_CF" High contrast, cross-process
|
||||
"ilford_xp2_400_CF" Neutral, clean, minimal colour shift
|
||||
"kodak_vision3_500t_CF" Cinema negative, warm shadows, teal hi
|
||||
"fuji_eterna_250d_CF" Soft cinema, desaturated highlights
|
||||
|
||||
── B&W film (_BWF) ───────────────────────────────────────
|
||||
"ilford_hp5_400_BWF" Neutral panchromatic, forgiving
|
||||
"ilford_delta_100_BWF" Fine grain, cool tone, sharp
|
||||
"ilford_delta_3200_BWF" High ISO, lifted shadows, pushed look
|
||||
"kodak_tmax_100_BWF" Ultra-fine grain, high contrast, sharp
|
||||
"kodak_tmax_400_BWF" Fine grain, excellent tonal range
|
||||
"kodak_tri_x_400_BWF" Iconic, punchy, photojournalism classic
|
||||
"agfa_apx_100_BWF" Smooth midtones, slightly warm neutral
|
||||
"agfa_apx_400_BWF" Medium grain, contrasty midtones
|
||||
"rollei_rpx_400_BWF" Very deep blacks, punchy street look
|
||||
"fomapan_100_BWF" Soft, orthochromatic character, vintage
|
||||
"selenium_tone_BWF" Cool blue-purple shadows, archival
|
||||
"sepia_tone_BWF" Warm brown throughout, Victorian vintage
|
||||
"gold_tone_BWF" Warm golden highlights, cooler shadows
|
||||
"cyanotype_BWF" Deep cyan-blue, alternative process
|
||||
"platinum_palladium_BWF" Long tonal scale, subtle warm neutral
|
||||
|
||||
── Digital sensors (_CCD) ────────────────────────────────
|
||||
"canon_5d_mark2_CCD" Warm romantic, gentle rolloff, skin tones
|
||||
"canon_5d_mark1_CCD" Original full-frame CCD, warm character
|
||||
"canon_1dx_CCD" Neutral accurate, punchy, professional
|
||||
"sony_a7iii_CCD" Neutral, cool shadows, high dynamic range
|
||||
"sony_a7rii_CCD" Very neutral-cool, extremely detailed
|
||||
"nikon_d800_CCD" Neutral accurate, slightly cool, landscape
|
||||
"nikon_d3_CCD" Warm classic, photojournalism standard
|
||||
"fuji_xt3_CCD" Film-simulation-inspired, vivid natural
|
||||
"fuji_gfx_CCD" Medium format, neutral, exceptional tonal
|
||||
"leica_m9_CCD" Iconic true CCD, warm, highlight glow
|
||||
"leica_m11_CCD" Modern Leica, very neutral and clinical
|
||||
"hasselblad_x2d_CCD" 100MP MF, clinical precision, wide tonal
|
||||
"olympus_omd_CCD" Punchy vivid, slightly cool, contrasty
|
||||
|
||||
intensity : 0 … 100. Blend between original and full rendering.
|
||||
0 = passthrough.
|
||||
50 = 50/50 blend.
|
||||
100 = full rendering (default).
|
||||
|
||||
Returns:
|
||||
PIL Image (RGB)
|
||||
"""
|
||||
if intensity == 0:
|
||||
return image.convert("RGB")
|
||||
|
||||
if rendering not in FILM_PRESETS:
|
||||
valid = ", ".join(sorted(FILM_PRESETS.keys()))
|
||||
raise ValueError(f"Unknown rendering '{rendering}'. Valid: {valid}")
|
||||
|
||||
if not (0 <= intensity <= 100):
|
||||
raise ValueError(f"intensity must be 0–100, got {intensity}")
|
||||
|
||||
preset = FILM_PRESETS[rendering]
|
||||
img = image.convert("RGB")
|
||||
arr = np.array(img, dtype=np.float32) / 255.0
|
||||
orig = arr.copy()
|
||||
|
||||
if preset["type"] == "BWF":
|
||||
arr_out = _apply_bw(arr, preset)
|
||||
elif preset["type"] == "CCD":
|
||||
arr_out = _apply_sensor(arr, preset)
|
||||
else:
|
||||
arr_out = _apply_colour(arr, preset)
|
||||
|
||||
blend = intensity / 100.0
|
||||
result = orig * (1.0 - blend) + arr_out * blend
|
||||
return Image.fromarray(np.clip(result * 255, 0, 255).astype(np.uint8), mode="RGB")
|
||||
|
||||
|
||||
def list_film_presets() -> dict:
|
||||
"""Returns {preset_name: description} for all presets. Use for ComfyUI combo."""
|
||||
return {k: v["desc"] for k, v in FILM_PRESETS.items()}
|
||||
|
||||
|
||||
def list_presets_by_type() -> dict:
|
||||
"""Returns presets grouped by type: {"CF": [...], "BWF": [...], "CCD": [...]}"""
|
||||
result = {"CF": [], "BWF": [], "CCD": []}
|
||||
for k, v in FILM_PRESETS.items():
|
||||
result[v["type"]].append(k)
|
||||
return result
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Internal rendering paths
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _apply_lut(channel: np.ndarray, pts: list) -> np.ndarray:
|
||||
pts_arr = np.array(pts, dtype=np.float64)
|
||||
lut = np.interp(np.linspace(0, 1, 256), pts_arr[:,0], pts_arr[:,1]).astype(np.float32)
|
||||
indices = np.clip((channel * 255).astype(np.int32), 0, 255)
|
||||
return lut[indices]
|
||||
|
||||
|
||||
def _apply_colour(arr: np.ndarray, preset: dict) -> np.ndarray:
|
||||
bias = np.array(preset["bias"], dtype=np.float32)
|
||||
arr = np.clip(arr * bias, 0, 1)
|
||||
curves = preset["curves"]
|
||||
return np.clip(np.stack([
|
||||
_apply_lut(arr[..., 0], curves["r"]),
|
||||
_apply_lut(arr[..., 1], curves["g"]),
|
||||
_apply_lut(arr[..., 2], curves["b"]),
|
||||
], axis=-1), 0, 1)
|
||||
|
||||
|
||||
def _apply_bw(arr: np.ndarray, preset: dict) -> np.ndarray:
|
||||
mix = preset["mix"]
|
||||
grey = np.clip(arr[...,0]*mix[0] + arr[...,1]*mix[1] + arr[...,2]*mix[2], 0, 1)
|
||||
grey = _apply_lut(grey, preset["curves"]["r"])
|
||||
tint = np.array(preset["tint"], dtype=np.float32)
|
||||
return np.clip(np.stack([grey*tint[0], grey*tint[1], grey*tint[2]], axis=-1), 0, 1)
|
||||
|
||||
|
||||
def _apply_sensor(arr: np.ndarray, preset: dict) -> np.ndarray:
|
||||
# ── 1. Linearise sRGB (remove gamma) ──────────────────────────────────────
|
||||
linear = np.where(arr <= 0.04045, arr/12.92,
|
||||
((arr + 0.055)/1.055)**2.4)
|
||||
|
||||
# ── 2. Apply sensor colour bias ────────────────────────────────────────────
|
||||
bias = np.array(preset["bias"], dtype=np.float32)
|
||||
linear = np.clip(linear * bias, 0, 1)
|
||||
|
||||
# ── 3. Apply 3×3 colour science matrix ────────────────────────────────────
|
||||
M = np.array(preset["matrix"], dtype=np.float32)
|
||||
H, W = linear.shape[:2]
|
||||
flat = linear.reshape(-1, 3)
|
||||
flat = np.clip(flat @ M.T, 0, 1)
|
||||
linear = flat.reshape(H, W, 3)
|
||||
|
||||
# ── 4. Apply highlight rolloff in linear light ─────────────────────────────
|
||||
# Compress values above rolloff_start toward 1.0 with a smooth curve
|
||||
rolloff = preset["highlight_rolloff"]
|
||||
if rolloff > 0:
|
||||
hi_start = 1.0 - rolloff
|
||||
hi_mask = np.clip((linear - hi_start) / rolloff, 0, 1)
|
||||
# Smooth S-curve compression in the highlight zone
|
||||
hi_compressed = hi_start + rolloff * (1.0 - (1.0 - hi_mask)**2)
|
||||
linear = np.where(linear > hi_start, hi_compressed, linear)
|
||||
|
||||
# ── 5. Re-apply sRGB gamma ─────────────────────────────────────────────────
|
||||
srgb = np.where(linear <= 0.0031308, linear*12.92,
|
||||
1.055 * np.power(np.clip(linear, 0, None), 1.0/2.4) - 0.055)
|
||||
srgb = np.clip(srgb, 0, 1)
|
||||
|
||||
# ── 6. Apply camera tone curve (picture profile) ───────────────────────────
|
||||
curves = preset["curves"]
|
||||
srgb = np.clip(np.stack([
|
||||
_apply_lut(srgb[..., 0], curves["r"]),
|
||||
_apply_lut(srgb[..., 1], curves["g"]),
|
||||
_apply_lut(srgb[..., 2], curves["b"]),
|
||||
], axis=-1), 0, 1)
|
||||
|
||||
# ── 7. Apply shadow lift / sensor noise floor colour ──────────────────────
|
||||
lift = np.array(preset["shadow_lift"], dtype=np.float32)
|
||||
srgb = np.clip(srgb + lift * (1.0 - srgb), 0, 1)
|
||||
|
||||
return srgb
|
||||
@@ -0,0 +1,98 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def img_white_balance(
|
||||
image: Image.Image,
|
||||
temperature: float = 6500,
|
||||
tint: float = 0,
|
||||
) -> Image.Image:
|
||||
"""
|
||||
RGB white balance adjustment using colour temperature in Kelvin.
|
||||
Approximates the DxO-style cool/warm slider with real Kelvin values.
|
||||
|
||||
Works on JPEG/PNG by applying per-channel RGB gain corrections derived
|
||||
from the difference between the target temperature and neutral (6500K).
|
||||
This is an approximation — true white balance requires RAW data — but
|
||||
produces visually correct and useful results on rendered images.
|
||||
|
||||
Args:
|
||||
image : PIL Image (RGB)
|
||||
temperature : 2000 … 12000. Colour temperature in Kelvin.
|
||||
2000K = very warm / candlelight (strong orange)
|
||||
3200K = tungsten / indoor lamp (warm)
|
||||
5500K = daylight / flash (neutral-warm)
|
||||
6500K = sRGB standard white point (no change)
|
||||
7500K = overcast / shade (cool-blue)
|
||||
10000K = deep shade / very blue sky (very cool)
|
||||
12000K = extreme cool (strong blue)
|
||||
tint : -100 … +100. Green/magenta tint correction.
|
||||
Negative = shift toward magenta.
|
||||
Positive = shift toward green.
|
||||
0 = no tint (default).
|
||||
|
||||
Returns:
|
||||
PIL Image (RGB)
|
||||
"""
|
||||
if temperature == 6500 and tint == 0:
|
||||
return image.convert("RGB")
|
||||
|
||||
if not (2000 <= temperature <= 12000):
|
||||
raise ValueError(f"temperature must be 2000–12000K, got {temperature}")
|
||||
if not (-100 <= tint <= 100):
|
||||
raise ValueError(f"tint must be -100 … +100, got {tint}")
|
||||
|
||||
img = image.convert("RGB")
|
||||
arr = np.array(img, dtype=np.float32) / 255.0
|
||||
|
||||
# ── Kelvin → RGB gain ─────────────────────────────────────────────────────
|
||||
# Derived from Tanner Helland's empirical Kelvin→RGB formula, then
|
||||
# normalised so 6500K = (1.0, 1.0, 1.0) — no change at neutral point.
|
||||
# We compute gain for the target temp and divide by the 6500K gain to
|
||||
# get the relative correction needed on a sRGB-origin image.
|
||||
|
||||
def kelvin_to_rgb(K):
|
||||
K = K / 100.0
|
||||
# Red
|
||||
if K <= 66:
|
||||
R = 255.0
|
||||
else:
|
||||
R = 329.698727446 * ((K - 60) ** -0.1332047592)
|
||||
R = np.clip(R, 0, 255)
|
||||
|
||||
# Green
|
||||
if K <= 66:
|
||||
G = 99.4708025861 * np.log(K) - 161.1195681661
|
||||
else:
|
||||
G = 288.1221695283 * ((K - 60) ** -0.0755148492)
|
||||
G = np.clip(G, 0, 255)
|
||||
|
||||
# Blue
|
||||
if K >= 66:
|
||||
B = 255.0
|
||||
elif K <= 19:
|
||||
B = 0.0
|
||||
else:
|
||||
B = 138.5177312231 * np.log(K - 10) - 305.0447927307
|
||||
B = np.clip(B, 0, 255)
|
||||
|
||||
return np.array([R, G, B]) / 255.0
|
||||
|
||||
# RGB at target temperature and at neutral 6500K
|
||||
rgb_target = kelvin_to_rgb(temperature)
|
||||
rgb_neutral = kelvin_to_rgb(6500)
|
||||
|
||||
# Per-channel gain relative to neutral
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
gain = np.where(rgb_neutral > 0, rgb_target / rgb_neutral, 1.0)
|
||||
|
||||
# ── Apply temperature gain ─────────────────────────────────────────────────
|
||||
arr = arr * gain
|
||||
|
||||
# ── Apply tint (green ↔ magenta on green channel) ─────────────────────────
|
||||
if tint != 0:
|
||||
tint_gain = 1.0 + (tint / 100.0) * 0.3
|
||||
arr[..., 1] = arr[..., 1] * tint_gain
|
||||
|
||||
arr = np.clip(arr, 0, 1)
|
||||
return Image.fromarray((arr * 255).astype(np.uint8), mode="RGB")
|
||||
Reference in New Issue
Block a user