File size: 2,564 Bytes
f67bbda
 
 
 
 
 
 
bb8764b
 
f67bbda
 
 
 
 
 
 
 
bb8764b
f67bbda
bb8764b
 
 
f67bbda
 
bb8764b
f67bbda
 
 
 
 
 
bb8764b
 
 
f67bbda
bb8764b
 
 
 
f67bbda
bb8764b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f67bbda
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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