V2.0.0 - Rasterix - photo papers
This commit is contained in:
+44
-6
@@ -30,6 +30,8 @@ from ..components.images import img_filmic_curve as img_filmic_curve
|
||||
from ..components.images import img_lut3d as img_lut3d
|
||||
from ..components.images import img_edge_jitter as img_edge_jitter
|
||||
from ..components.images import img_depth_blur as img_depth_blur
|
||||
from ..components.images import img_photo_paper as img_photo_paper
|
||||
from ..components.images.img_photo_paper import PAPER_PRESETS
|
||||
|
||||
from ..components import utility
|
||||
from .Dashboard import PrimereModelConceptSelector as PrimereModelConceptSelector
|
||||
@@ -69,7 +71,8 @@ class PrimereRasterix:
|
||||
{"after": "blend_mode", "name": "rasterix_local_laplacian", "title": "🧱 Tone: Edge-Aware Pyramid", "color": "#405985", "text_color": "#EAF1F8", "label": "Local Laplacian contrast/detail enhancement with strong edge preservation."},
|
||||
|
||||
{"after": "levels", "name": "rasterix_analog_film", "title": "🎞 Creative: Analog Film / CCD", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Stylized film and sensor-era rendering for mood, palette, and texture character. Inspired by DxO."},
|
||||
{"after": "photo_paper", "name": "rasterix_lut_reader", "title": "📷 Creative: LUT .cube Reader", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Load and blend LUT looks for fast creative direction and consistent show style. Inspired by Blackmagic DaVinci Resolve and DxO."},
|
||||
{"after": "expiration_years", "name": "rasterix_photo_paper", "title": "🧪 Creative: Photo Paper Simulation", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Darkroom-inspired paper response with selectable grade, RC/FB base, color/B&W mode, and controlled print intensity."},
|
||||
{"after": "paper_intensity", "name": "rasterix_lut_reader", "title": "📷 Creative: LUT .cube Reader", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Load and blend LUT looks for fast creative direction and consistent show style. Inspired by Blackmagic DaVinci Resolve and DxO."},
|
||||
{"after": "color_space", "name": "rasterix_filmic_camera", "title": "🎥 Creative: Filmic Camera Curve", "color": "#3B5E68", "text_color": "#EAF1F8", "label": "Camera-like highlight roll-off and tonal response for cinematic dynamic range behavior. Inspired by Adobe Camera Raw."},
|
||||
|
||||
{"after": "pivot", "name": "rasterix_selective_tone", "title": "🎛 Color: Selective Tone Zones", "color": "#6A5636", "text_color": "#EAF1F8", "label": "Zone-based tonal pushes for highlights, midtones, shadows, and blacks. Inspired by DxO Photolab"},
|
||||
@@ -176,8 +179,12 @@ class PrimereRasterix:
|
||||
"halation": ("BOOLEAN", {"default": False, "label_off": "Ignore halation", "label_on": "Add halation"}),
|
||||
"expiration_years": ("INT", {"default": 0, "min": 0, "max": 30, "step": 1}),
|
||||
|
||||
"use_photo_paper": ("BOOLEAN", {"default": False, "label_off": "Ignore H/B photo paper", "label_on": "Apply H/B photo paper"}),
|
||||
"photo_paper": ("BOOLEAN", {"default": False, "label_off": "Soft paper", "label_on": "Hard paper"}),
|
||||
"use_photo_paper": ("BOOLEAN", {"default": False, "label_off": "Ignore photo paper", "label_on": "Apply photo paper"}),
|
||||
"photo_paper": (list(PAPER_PRESETS.keys()), {"default": list(PAPER_PRESETS.keys())[0]}),
|
||||
"color_paper": ("BOOLEAN", {"default": False, "label_off": "B&W paper", "label_on": "Color paper"}),
|
||||
"paper_base": (["RC", "FB"], {"default": "RC"}),
|
||||
"paper_expiration_years": ("FLOAT", {"default": 0, "min": 0, "max": 30, "step": 0.1}),
|
||||
"paper_intensity": ("FLOAT", {"default": 100, "min": 0, "max": 200, "step": 1}),
|
||||
|
||||
"use_lut": ("BOOLEAN", {"default": False, "label_off": "Ignore LUT", "label_on": "Apply LUT"}),
|
||||
"lut_file": (cls._list_luts(),),
|
||||
@@ -339,7 +346,11 @@ class PrimereRasterix:
|
||||
halation = kwargs.get('halation', False)
|
||||
expiration_years = kwargs.get('expiration_years', 0)
|
||||
use_photo_paper = kwargs.get('use_photo_paper', False)
|
||||
photo_paper = kwargs.get('photo_paper', False)
|
||||
photo_paper = kwargs.get('photo_paper', "N (ISO R 90, normal)")
|
||||
color_paper = kwargs.get('color_paper', False)
|
||||
paper_base = kwargs.get('paper_base', "RC")
|
||||
paper_expiration_years = kwargs.get('paper_expiration_years', 0)
|
||||
paper_intensity = kwargs.get('paper_intensity', 100)
|
||||
use_filmic = kwargs.get('use_filmic', False)
|
||||
curve_type = kwargs.get('curve_type', "filmic")
|
||||
filmic_contrast = kwargs.get('filmic_contrast', 1.0)
|
||||
@@ -427,8 +438,8 @@ class PrimereRasterix:
|
||||
if use_filmic:
|
||||
pil_img = img_filmic_curve.img_filmic_curve(image=pil_img, curve_type=curve_type, contrast=filmic_contrast, highlight_rolloff=highlight_rolloff, shadow_lift=shadow_lift, pivot=pivot)
|
||||
|
||||
if use_photo_paper:
|
||||
pil_img = img_solarization_bw.img_solarization_bw(image=pil_img, color_mode=False, strength=0.00, pivot=0.00, sigma=0.01, edge_boost=0.00, edge_radius=0.5, contrast=1, precision=precision, hard_paper=photo_paper, grain_modulation=False, grain_strength=0, grain_scale=1, seed=1)
|
||||
if use_photo_paper and paper_intensity != 0:
|
||||
pil_img = img_photo_paper.img_photo_paper(image=pil_img, paper_type=photo_paper, color_paper=color_paper, paper_base=paper_base, paper_intensity=paper_intensity, expiration_years=paper_expiration_years)
|
||||
|
||||
st_data = rasterix_data.get('selective_tone', {})
|
||||
if use_selective_tone and st_data:
|
||||
@@ -1378,4 +1389,31 @@ class PrimereDepthBlur:
|
||||
if use_depth_blur:
|
||||
pil_img = img_depth_blur.img_depth_blur(image=pil_img, focus_depth=focus_depth, depth_range=depth_range, max_blur=max_blur, depth_gamma=depth_gamma, auto_optimize=auto_optimize, use_v3=use_DA_v3)
|
||||
|
||||
return (utility.image_to_tensor(pil_img),)
|
||||
|
||||
class PrimerePhotoPaper:
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE",)
|
||||
FUNCTION = "primere_photo_paper"
|
||||
CATEGORY = TREE_RASTERIX
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", {"forceInput": True}),
|
||||
"use_photo_paper": ("BOOLEAN", {"default": False, "label_off": "Ignore photo paper", "label_on": "Apply photo paper"}),
|
||||
"photo_paper": (list(PAPER_PRESETS.keys()), {"default": list(PAPER_PRESETS.keys())[0]}),
|
||||
"color_paper": ("BOOLEAN", {"default": False, "label_off": "B&W paper", "label_on": "Color paper"}),
|
||||
"paper_base": (["RC", "FB"], {"default": "RC"}),
|
||||
"paper_expiration_years": ("FLOAT", {"default": 0, "min": 0, "max": 30, "step": 0.1}),
|
||||
"paper_intensity": ("FLOAT", {"default": 100, "min": 0, "max": 200, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
def primere_photo_paper(self, image, use_photo_paper, photo_paper, color_paper, paper_base, paper_intensity, paper_expiration_years):
|
||||
pil_img = utility.tensor_to_image(image)
|
||||
if use_photo_paper and paper_intensity != 0:
|
||||
pil_img = img_photo_paper.img_photo_paper(image=pil_img, paper_type=photo_paper, color_paper=color_paper, paper_base=paper_base, paper_intensity=paper_intensity, expiration_years=paper_expiration_years)
|
||||
|
||||
return (utility.image_to_tensor(pil_img),)
|
||||
@@ -67,6 +67,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"PrimereBlur": Rasterix.PrimereBlur,
|
||||
"PrimereBrightnessContrast": Rasterix.PrimereBrightnessContrast,
|
||||
"PrimereFilmRendering": Rasterix.PrimereFilmRendering,
|
||||
"PrimerePhotoPaper": Rasterix.PrimerePhotoPaper,
|
||||
"PrimereSelectiveTone": Rasterix.PrimereSelectiveTone,
|
||||
"PrimereColorBalance": Rasterix.PrimereColorBalance,
|
||||
"PrimereHSL": Rasterix.PrimereHSL,
|
||||
@@ -174,6 +175,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PrimereBlur": "Primere Rasterix (Blur)",
|
||||
"PrimereBrightnessContrast": "Primere Rasterix (Brightness Contrast)",
|
||||
"PrimereFilmRendering": "Primere Rasterix (Film Rendering)",
|
||||
"PrimerePhotoPaper": "Primere Rasterix (Photo Paper)",
|
||||
"PrimereSelectiveTone": "Primere Rasterix (Selective Tone)",
|
||||
"PrimereColorBalance": "Primere Rasterix (Color Balance)",
|
||||
"PrimereHSL": "Primere Rasterix (HSL)",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
PAPER_PRESETS = {
|
||||
"BN (ISO R 130, soft)": {"contrast": 0.88, "toe": 0.10, "shoulder": 0.04},
|
||||
"B (ISO R 110, medium-soft)": {"contrast": 0.96, "toe": 0.08, "shoulder": 0.05},
|
||||
"N (ISO R 90, normal)": {"contrast": 1.04, "toe": 0.06, "shoulder": 0.06},
|
||||
"H (ISO R 70, hard)": {"contrast": 1.18, "toe": 0.05, "shoulder": 0.08},
|
||||
"HD (ISO R 50, extra hard)": {"contrast": 1.32, "toe": 0.03, "shoulder": 0.10},
|
||||
"Grade 00 (very soft)": {"contrast": 0.84, "toe": 0.12, "shoulder": 0.04},
|
||||
"Grade 0 (soft)": {"contrast": 0.92, "toe": 0.09, "shoulder": 0.05},
|
||||
"Grade 2 (normal)": {"contrast": 1.00, "toe": 0.07, "shoulder": 0.06},
|
||||
"Grade 3 (normal-hard)": {"contrast": 1.12, "toe": 0.06, "shoulder": 0.07},
|
||||
"Grade 4 (hard)": {"contrast": 1.24, "toe": 0.05, "shoulder": 0.09},
|
||||
"Grade 5 (ultra hard)": {"contrast": 1.36, "toe": 0.03, "shoulder": 0.12},
|
||||
}
|
||||
|
||||
|
||||
def _apply_tone_curve(x, contrast, toe, shoulder):
|
||||
x = np.clip(0.5 + (x - 0.5) * contrast, 0.0, 1.0)
|
||||
if toe > 0:
|
||||
x = x * (1.0 - toe) + np.power(x, 1.25) * toe
|
||||
if shoulder > 0:
|
||||
inv = 1.0 - x
|
||||
x = 1.0 - (inv * (1.0 - shoulder) + np.power(inv, 1.25) * shoulder)
|
||||
return np.clip(x, 0.0, 1.0)
|
||||
|
||||
|
||||
def _apply_paper_expiration(arr, years):
|
||||
out = arr.copy()
|
||||
|
||||
fog = np.clip(years * 0.006, 0, 0.18)
|
||||
out = out + fog * (1.0 - out)
|
||||
|
||||
blue_fade = np.clip(1.0 - (years * 0.012), 0.65, 1.0)
|
||||
out[..., 2] *= blue_fade
|
||||
|
||||
contrast = np.clip(1.0 - (years * 0.01), 0.7, 1.0)
|
||||
out = np.clip((out - 0.5) * contrast + 0.5, 0.0, 1.0)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def img_photo_paper(
|
||||
image: Image.Image,
|
||||
paper_type: str = "N (ISO R 90, normal)",
|
||||
color_paper: bool = False,
|
||||
paper_base: str = "RC",
|
||||
paper_intensity: float = 100.0,
|
||||
expiration_years: float = 0.0,
|
||||
) -> Image.Image:
|
||||
src = np.asarray(image.convert("RGB"), dtype=np.float32) / 255.0
|
||||
preset = PAPER_PRESETS.get(paper_type, PAPER_PRESETS["N (ISO R 90, normal)"])
|
||||
|
||||
contrast = preset["contrast"]
|
||||
toe = preset["toe"]
|
||||
shoulder = preset["shoulder"]
|
||||
|
||||
if paper_base == "FB":
|
||||
toe += 0.02
|
||||
shoulder += 0.01
|
||||
contrast *= 0.98
|
||||
elif paper_base == "RC":
|
||||
contrast *= 1.02
|
||||
|
||||
intensity = float(np.clip(paper_intensity, 0.0, 200.0))
|
||||
strength = (intensity - 100.0) / 100.0
|
||||
contrast = np.clip(contrast * (1.0 + 0.25 * strength), 0.5, 2.5)
|
||||
toe = np.clip(toe * (1.0 + 0.35 * strength), 0.0, 0.35)
|
||||
shoulder = np.clip(shoulder * (1.0 + 0.35 * strength), 0.0, 0.35)
|
||||
|
||||
if color_paper:
|
||||
out = _apply_tone_curve(src, contrast, toe, shoulder)
|
||||
sat = 1.02 if paper_base == "RC" else 0.98
|
||||
mean = out.mean(axis=2, keepdims=True)
|
||||
out = np.clip(mean + (out - mean) * sat, 0.0, 1.0)
|
||||
else:
|
||||
# In B&W mode, force grayscale source before blending to avoid color casts
|
||||
# at any intensity value (including > 100).
|
||||
luma = 0.299 * src[..., 0] + 0.587 * src[..., 1] + 0.114 * src[..., 2]
|
||||
src = np.stack([luma, luma, luma], axis=-1)
|
||||
tone = _apply_tone_curve(luma, contrast, toe, shoulder)
|
||||
out = np.stack([tone, tone, tone], axis=-1)
|
||||
|
||||
out = _apply_paper_expiration(out, expiration_years)
|
||||
|
||||
if intensity <= 100.0:
|
||||
amount = (intensity / 100.0) * 0.90
|
||||
else:
|
||||
amount = 1.0 + ((intensity - 100.0) / 100.0) * 1.10
|
||||
amount = np.clip(amount, 0.0, 2.0)
|
||||
|
||||
blended = np.clip(src + (out - src) * amount, 0.0, 1.0)
|
||||
return Image.fromarray((blended * 255.0).astype(np.uint8), mode="RGB")
|
||||
Reference in New Issue
Block a user