// Grade.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. const char* const HELP = "

Applies a linear ramp followed by a gamma function to each color channel.

" "

A = multiply * (gain-lift)/(whitepoint-blackpoint)
" " B = offset + lift - A*blackpoint
" " output = pow(A*input + B, 1/gamma)

" "The reverse option is also provided so that you can copy-paste this node to " "invert the grade. This will do the opposite gamma correction followed by the " "opposite linear ramp."; #include "DDImage/PixelIop.h" #include "DDImage/Row.h" #include "DDImage/DDMath.h" #include "DDImage/NukeWrapper.h" #include using namespace DD::Image; static const char* const CLASS = "Grade"; class GradeIop : public PixelIop { float blackpoint[4]; float whitepoint[4]; float black[4]; float white[4]; float add[4]; float multiply[4]; float gamma[4]; bool reverse; bool black_clamp; bool white_clamp; public: GradeIop(Node* node) : PixelIop(node) { for (int n = 0; n < 4; n++) { black[n] = blackpoint[n] = add[n] = 0.0f; white[n] = whitepoint[n] = multiply[n] = 1.0f; gamma[n] = 1.0f; } reverse = false; black_clamp = true; white_clamp = false; } // indicate that channels only depend on themselves: void in_channels(int, ChannelSet& channels) const { } void pixel_engine(const Row &in, int y, int x, int r, ChannelMask, Row &); virtual void knobs(Knob_Callback); const char* Class() const { return CLASS; } const char* node_help() const { return HELP; } static const Iop::Description d; void _validate(bool for_real); }; void GradeIop::_validate(bool for_real) { bool change_any = black_clamp | white_clamp; bool change_zero = false; for (int z = 0; z < 4; z++) { float A = whitepoint[z] - blackpoint[z]; A = A ? (white[z] - black[z]) / A : 10000.0f; A *= multiply[z]; float B = add[z] + black[z] - blackpoint[z] * A; if (A != 1 || B || gamma[z] != 1.0f) { change_any = true; if (B) change_zero = true; } } set_out_channels(change_any ? Mask_All : Mask_None); PixelIop::_validate(for_real); if (change_zero) info_.black_outside(false); } void GradeIop::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) { foreach (n, channels) { unsigned z = colourIndex(n); if (z > 3) { out.copy(in, n, x, r); continue; } float A = whitepoint[z] - blackpoint[z]; A = A ? (white[z] - black[z]) / A : 10000.0f; A *= multiply[z]; float B = add[z] + black[z] - blackpoint[z] * A; if (!B && in.is_zero(n)) { out.erase(n); continue; } float G = gamma[z]; // patch for linux alphas because the pow function behaves badly // for very large or very small exponent values. #ifdef __alpha if (G < 0.008f) G = 0.0f; if (G > 125.0f) G = 125.0f; #endif const float* inptr = in[n] + x; float* OUTBUF = out.writable(n) + x; float* END = OUTBUF + (r - x); if (!reverse) { // do the linear interpolation: if (A != 1 || B) { for (float* outptr = OUTBUF; outptr < END;) *outptr++ = *inptr++ *A + B; inptr = OUTBUF; } // clamp if (white_clamp || black_clamp) { for (float* outptr = OUTBUF; outptr < END;) { float a = *inptr++; if (a < 0.0f && black_clamp) a = 0.0f; else if (a > 1.0f && white_clamp) a = 1.0f; *outptr++ = a; } inptr = OUTBUF; } // do the gamma: if (G <= 0) { for (float* outptr = OUTBUF; outptr < END;) { float V = *inptr++; if (V < 1.0f) V = 0.0f; else if (V > 1.0f) V = INFINITY; *outptr++ = V; } } else if (G != 1.0f) { G = 1.0f / G; for (float* outptr = OUTBUF; outptr < END;) { float V = *inptr++; if (V <= 0.0f) ; //V = 0.0f; #ifdef __alpha else if (V <= 1e-6f && G > 1.0f) V = 0.0f; #endif else if (V < 1) V = powf(V, G); else V = 1.0f + (V - 1.0f) * G; *outptr++ = V; } } else if (inptr != OUTBUF) { memcpy(OUTBUF, inptr, (END - OUTBUF) * sizeof(*OUTBUF)); } } else { // Reverse gamma: if (G <= 0) { for (float* outptr = OUTBUF; outptr < END;) *outptr++ = *inptr++ > 0.0f ? 1.0f : 0.0f; inptr = OUTBUF; } else if (G != 1.0f) { for (float* outptr = OUTBUF; outptr < END;) { float V = *inptr++; if (V <= 0.0f) ; //V = 0.0f; #ifdef __alpha else if (V <= 1e-6f && G > 1.0f) V = 0.0f; #endif else if (V < 1.0f) V = powf(V, G); else V = 1.0f + (V - 1.0f) * G; *outptr++ = V; } inptr = OUTBUF; } // Reverse the linear part: if (A != 1.0f || B) { if (A) A = 1 / A; else A = 1.0f; B = -B * A; for (float* outptr = OUTBUF; outptr < END;) *outptr++ = *inptr++ *A + B; inptr = OUTBUF; } // clamp if (white_clamp || black_clamp) { for (float* outptr = OUTBUF; outptr < END;) { float a = *inptr++; if (a < 0.0f && black_clamp) a = 0.0f; else if (a > 1.0f && white_clamp) a = 1.0f; *outptr++ = a; } inptr = OUTBUF; } else if (inptr != OUTBUF) { memcpy(OUTBUF, inptr, (END - OUTBUF) * sizeof(*OUTBUF)); } } } } #include "DDImage/Knobs.h" void GradeIop::knobs(Knob_Callback f) { AColor_knob(f, blackpoint, IRange(-1, 1), "blackpoint"); Tooltip(f, "This color is turned into black"); AColor_knob(f, whitepoint, IRange(0, 4), "whitepoint"); Tooltip(f, "This color is turned into white"); AColor_knob(f, black, IRange(-1, 1), "black", "lift"); Tooltip(f, "Black is turned into this color"); AColor_knob(f, white, IRange(0, 4), "white", "gain"); Tooltip(f, "White is turned into this color"); AColor_knob(f, multiply, IRange(0, 4), "multiply"); Tooltip(f, "Constant to multiply result by"); AColor_knob(f, add, IRange(-1, 1), "add", "offset"); Tooltip(f, "Constant to add to result (raises both black & white, unlike lift)"); AColor_knob(f, gamma, IRange(.2, 5), "gamma"); Tooltip(f, "Gamma correction applied to final result"); Newline(f, " "); Bool_knob(f, &reverse, "reverse"); Tooltip(f, "Invert the math to undo the correction"); Bool_knob(f, &black_clamp, "black_clamp", "black clamp"); Tooltip(f, "Output that is less than zero is changed to zero"); Bool_knob(f, &white_clamp, "white_clamp", "white clamp"); Tooltip(f, "Output that is greater than 1 is changed to 1"); } static Iop* build(Node* node) { return (new NukeWrapper(new GradeIop(node)))->channelsRGBoptionalAlpha(); } const Iop::Description GradeIop::d(CLASS, "Color/Correct/Grade", build);