// Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. static const char* const CLASS = "TemporalMedian"; static const char* const HELP = "Removes grain by selecting, for each pixel, the median of this frame, " "the frame before, and the frame after."; /*! \class TemporalMedian TemporalMedian.C This file implements a NUKE3 plugin that performs a time-based filtering functioning for removing grain from images. This is based on a NUKE3 widget written by Jonathan Egstad. The Calculations in this plugin are as follows. Note that I use single variables to represent intermediate values, but mean that the computation is performed for all RGBA input channels. For all RGBA input channels z: A = in[z] at Time T B = in[z] at Time T-1 C = in[z] at Time T+1 Take pairwise maximums: D = max(A,C) E = max(B,C) F = max(A,B) And then find the smallest of these maximums: G = min(D,E) H = min(F,G) Now we convert this into a difference from the current frame: I = H - A And now we do some math: We use three values specified by the user: rcore, gcore, bcore, acore (referred to here as Xcore, meaning take the correct one for the channel you are looking at) J = (I > Xcore) ? max(2 * Xcore-I,0):I K = (J < -Xcore) ? min(-2 * Xcore - J, 0):J Finally, we add back in the current value: L = K + A And we are done. \author Daniel Maskit \date September 26th, 2001 File creation. */ // Standard plug-in include files. #include "DDImage/Iop.h" #include "DDImage/NukeWrapper.h" using namespace DD::Image; #include "DDImage/Row.h" #include "DDImage/Tile.h" #include "DDImage/Knobs.h" #include "DDImage/Convolve.h" #include "DDImage/DDMath.h" using namespace std; class TemporalMedian : public Iop { public: // // These are necessary to make it possible to multiplex an input and // get multiple frames worth of input. // int maximum_inputs() const override { return 1; } int minimum_inputs() const override { return 1; } // Tell it that the single input is now 3 inputs int split_input(int n) const override { return 3; } // Routine to return the frame attached to each input const OutputContext& inputContext(int, int, OutputContext&) const override; //! Constructor. Initialize user controls to their default values. TemporalMedian (Node* node) : Iop (node) { core[0] = core[1] = core[2] = core[3] = 0.05f; } // TemporalMedian //! Destructor. ~TemporalMedian () override { } // ~TemporalMedian // The default _validate() and _request work good for this //! This function does all the work. Blur the color channels using other //! channels as your vector data. void engine ( int y, int x, int r, ChannelMask channels, Row& out ) override; //! Describe each user control so the user interface can be built. In this //! class we'll have two sets of inputs. Typically, the user will specify //! the two channels that will have the vector information. But, if they are //! (none), the values in the two double inputs will be used. void knobs ( Knob_Callback f ) override { AColor_knob(f, core, "core"); Tooltip(f, "Differences greater than this are left unchanged, as they " "probably indicate something other than film grain."); Obsolete_knob(f, "Red Core", "knob core.r $value"); Obsolete_knob(f, "Green Core", "knob core.g $value"); Obsolete_knob(f, "Blue Core", "knob core.b $value"); Obsolete_knob(f, "Alpha Core", "knob core.a $value"); } // knobs //! Return the name of the class. const char* Class() const override { return CLASS; } const char* node_help() const override { return HELP; } //! Information to the plug-in manager of DDNewImage/Nuke. static const Iop::Description description; protected: // Variables that are attached to knobs. // float core[4]; }; // class TemporalMedian // The time for image input n :- const OutputContext& TemporalMedian::inputContext(int i, int n, OutputContext& context) const { context = outputContext(); switch (n) { case 0: break; case 1: context.setFrame(context.frame() - 1); break; case 2: context.setFrame(context.frame() + 1); break; } return context; } /*! This is a function that creates an instance of the operator, and is needed for the Iop::Description to work. */ static Iop* TemporalMedianCreate(Node* node) { return new NukeWrapper (new TemporalMedian(node)); } /*! The Iop::Description is how NUKE knows what the name of the operator is, how to create one, and the menu item to show the user. The menu item may be 0 if you do not want the operator to be visible. */ const Iop::Description TemporalMedian::description ( CLASS, "Filter/TemporalMedian", TemporalMedianCreate ); /*! For each line in the area passed to request(), this will be called. It must calculate the image data for a region at vertical position y, and between horizontal positions x and r, and write it to the passed row structure. Usually this works by asking the input for data, and modifying it. */ void TemporalMedian::engine ( int y, int x, int r, ChannelMask channels, Row& row ) { row.get(input0(), y, x, r, channels); Row prevrow(x, r); Row nextrow(x, r); prevrow.get(input1(), y, x, r, channels); nextrow.get(*input(2), y, x, r, channels); foreach ( z, channels ) { const float* PREV = prevrow[z] + x; const float* CUR = row[z] + x; const float* NEXT = nextrow[z] + x; float* outptr = row.writable(z) + x; const float* END = outptr + (r - x); const float core = this->core[z <= Chan_Alpha ? z - 1 : 0]; while (outptr < END) { // We use single letter variable names here to correspond with the // text description of the algorithm above. float A = *CUR++; float B = *PREV++; float C = *NEXT++; float D = MAX(A, C); float E = MAX(B, C); float F = MAX(A, B); float G = MIN(D, E); float H = MIN(F, G); float I = H - A; float J = (I > core) ? MAX(2 * core - I, 0.0f) : I; float K = (J < -core) ? MIN(-2 * core - J, 0.0f) : J; *outptr++ = K + A; } } } // TemporalMedian::engine