basic-image-editing / global_tone_mapping.py
balthou's picture
update tone mapping curve (3 points Hermite Cubic Spline)
bb8764b
import numpy as np
def apply_s_curve_tone_mapping(
x: np.ndarray,
shadow_boost: float = 0.,
highlight_boost: float = 0.,
contrast: float = 0.,
exposure: float = 0.
) -> np.ndarray:
"""
Apply an S-curve tone mapping to input x with given parameters.
Parameters:
x : array-like
Input values in the range [0, 1].
shadow_boost : float
Shadow boost parameter. Positive values increase the steepness in shadows.
highlight_boost : float
Highlight boost parameter. Positive values increase the steepness in highlights.
contrast : float
Contrast parameter. Positive values increase the steepness at midtones.
exposure : float
Exposure adjustment parameter. When exposure=0, 0.5 maps to 0.5.
Exposure defines the mapping of 0.5 to 0.5 * (2 ** exposure).
Returns:
y : array-like
Tone-mapped output values in the range [0, 1].
"""
# Compute the midtone mapped value based on exposure
midtone_mapped_value = 0.5 * (2 ** exposure)
midtone_mapped_value = np.clip(midtone_mapped_value, 0, 1)
# Define the slopes (derivatives) at the three key points
s0 = 1 + shadow_boost # Slope at x = 0 (shadows)
s1 = 1 + contrast # Slope at x = 0.5 (midtone)
s2 = 1 + highlight_boost # Slope at x = 1 (highlights)
# Initialize the output array
y = np.zeros_like(x)
# Segment 1: x in [0, 0.5]
idx1 = x <= 0.5
x0, x1 = 0.0, 0.5
y0, y1 = 0.0, midtone_mapped_value
m0, m1 = s0, s1
delta_x = x1 - x0
t = (x[idx1] - x0) / delta_x # Normalize x to parameter t in [0, 1]
# Hermite basis functions
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
h10 = t ** 3 - 2 * t ** 2 + t
h01 = -2 * t ** 3 + 3 * t ** 2
h11 = t ** 3 - t ** 2
# Calculate the spline for the first segment
y[idx1] = (h00 * y0 + h10 * m0 * delta_x + h01 * y1 + h11 * m1 * delta_x)
# Segment 2: x in (0.5, 1]
idx2 = x > 0.5
x0, x1 = 0.5, 1.0
y0, y1 = midtone_mapped_value, 1.0
m0, m1 = s1, s2
delta_x = x1 - x0
t = (x[idx2] - x0) / delta_x # Normalize x to parameter t in [0, 1]
# Hermite basis functions for the second segment
h00 = 2 * t ** 3 - 3 * t ** 2 + 1
h10 = t ** 3 - 2 * t ** 2 + t
h01 = -2 * t ** 3 + 3 * t ** 2
h11 = t ** 3 - t ** 2
# Calculate the spline for the second segment
y[idx2] = (h00 * y0 + h10 * m0 * delta_x + h01 * y1 + h11 * m1 * delta_x)
# Ensure the output is within [0, 1]
y = np.clip(y, 0, 1)
return y