colour-science / colour-hdri

HDRI / Radiance image processing algorithms for Python
https://www.colour-science.org
BSD 3-Clause "New" or "Revised" License
135 stars 18 forks source link

Implement support for "Piece-wise Power Curves" tonemapping operator. #2

Open KelSolaar opened 7 years ago

KelSolaar commented 7 years ago

References

KelSolaar commented 5 years ago

We should create a new colour_hdri.tonemapping_operator_piecewise_power_curves global tonemapping operator in the colour.tonemapping.global_operators.operators module.

It would be great to update the Jupyter Notebook example if possible.

The implementation should be straightforward given the code from John Habble:

void FilmicToneCurve::CalcDirectParamsFromUser(CurveParamsDirect & dstParams, const CurveParamsUser & srcParams)
{
    dstParams = CurveParamsDirect();

    float toeStrength = srcParams.m_toeStrength;
    float toeLength = srcParams.m_toeLength;
    float shoulderStrength = srcParams.m_shoulderStrength;
    float shoulderLength = srcParams.m_shoulderLength;

    float shoulderAngle = srcParams.m_shoulderAngle;
    float gamma = srcParams.m_gamma;

    // This is not actually the display gamma. It's just a UI space to avoid having to 
    // enter small numbers for the input.
    float perceptualGamma = 2.2f;

    // constraints
    {
        toeLength = Saturate(toeLength);
        toeStrength = Saturate(toeStrength);
        shoulderAngle = Saturate(shoulderAngle);
        shoulderLength = Saturate(shoulderLength);

        shoulderStrength = MaxFloat(0.0f,shoulderStrength);
    }

    // apply base params
    {
        // toe goes from 0 to 0.5
        float x0 = toeLength * .5f;
        float y0 = (1.0f - toeStrength) * x0; // lerp from 0 to x0

        float remainingY = 1.0f - y0;

        float initialW = x0 + remainingY;

        float y1_offset = (1.0f - shoulderLength) * remainingY;
        float x1 = x0 + y1_offset;
        float y1 = y0 + y1_offset;

        // filmic shoulder strength is in F stops
        float extraW = exp2f(shoulderStrength)-1.0f;

        float W = initialW + extraW;

        // to adjust the perceptual gamma space, apply power
        dstParams.m_x0 = powf(x0,perceptualGamma);
        dstParams.m_y0 = powf(y0,perceptualGamma);
        dstParams.m_x1 = powf(x1,perceptualGamma);
        dstParams.m_y1 = powf(y1,perceptualGamma);
        dstParams.m_W = W;

        // bake the linear to gamma space conversion
        dstParams.m_gamma = gamma;
    }

    dstParams.m_overshootX = (dstParams.m_W * 2.0f) * shoulderAngle * shoulderStrength;
    dstParams.m_overshootY = 0.5f * shoulderAngle * shoulderStrength;
}