COP2/COP2_FullImageFilter.C

/*
 * Copyright (c) 2013
 *      Side Effects Software Inc.  All rights reserved.
 *
 * Redistribution and use of Houdini Development Kit samples in source and
 * binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. The name of Side Effects Software may not be used to endorse or
 *    promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------
 * FullImageFilter Op.
 */
#include <UT/UT_DSOVersion.h>

#include <OP/OP_Context.h>
#include <OP/OP_OperatorTable.h>

#include <SYS/SYS_Math.h>
#include <SYS/SYS_Floor.h>

#include <PRM/PRM_Include.h>
#include <PRM/PRM_Parm.h>

#include <TIL/TIL_Region.h>
#include <TIL/TIL_Plane.h>
#include <TIL/TIL_Sequence.h>
#include <TIL/TIL_Tile.h>

#include <COP2/COP2_CookAreaInfo.h>

#include "COP2_FullImageFilter.h"

using namespace HDK_Sample;

COP_MASK_SWITCHER(1, "Sample Full Image Filter");

static PRM_Name names[] =
{
    PRM_Name("size",    "Size"),
};

static PRM_Default sizeDef(10);
static PRM_Range sizeRange(PRM_RANGE_UI, 0, PRM_RANGE_UI, 100);

PRM_Template
COP2_FullImageFilter::myTemplateList[] =
{
    PRM_Template(PRM_SWITCHER,  3, &PRMswitcherName, switcher),

    PRM_Template(PRM_FLT_J,     TOOL_PARM, 1, &names[0], &sizeDef, 0,
                 &sizeRange),
    PRM_Template(),
};

OP_TemplatePair COP2_FullImageFilter::myTemplatePair(
    COP2_FullImageFilter::myTemplateList,
    &COP2_MaskOp::myTemplatePair);

OP_VariablePair COP2_FullImageFilter::myVariablePair(0,
                                          &COP2_MaskOp::myVariablePair);

const char *    COP2_FullImageFilter::myInputLabels[] =
{
    "Image to Filter",
    "Mask Input",
    0
};

OP_Node *
COP2_FullImageFilter::myConstructor(    OP_Network      *net,
                                        const char      *name,
                                        OP_Operator     *op)
{
    return new COP2_FullImageFilter(net, name, op);
}

COP2_FullImageFilter::COP2_FullImageFilter(OP_Network *parent,
                                           const char *name,
                                           OP_Operator *entry)
    : COP2_MaskOp(parent, name, entry)
{
    // sets the default scope to only affect color and alpha. The global
    // default is 'true, true, "*"', which affects color, alpha and all
    // extra planes.
    setDefaultScope(true, true, 0);
}

COP2_FullImageFilter::~COP2_FullImageFilter()
{
    ;
}

// -----------------------------------------------------------------------


COP2_ContextData *
COP2_FullImageFilter::newContextData(const TIL_Plane * /*plane*/,
                                     int /*arrayindex*/,
                                     float t, int xres, int /*yres*/,
                                     int /*thread*/,  int /*maxthreads*/)
{
    // This method evaluates and stashes parms and any other data that
    // needs to be setup. Parms cannot be evaluated concurently in separate
    // threads. This function is guarenteed to be single threaded.
    cop2_FullImageFilterData *sdata = new cop2_FullImageFilterData();
    int index = mySequence.getImageIndex(t);

    // xres may not be the full image res (if cooked at 1/2 or 1/4). Because
    // we're dealing with a size, scale down the size based on our res.
    // getXScaleFactor will return (xres / full_xres). 
    sdata->mySize = SIZE(t) * getXScaleFactor(xres)*getFrameScopeEffect(index);
           
    return sdata;
}


void
COP2_FullImageFilter::computeImageBounds(COP2_Context &context)
{
    // if your algorthim increases the image bounds (like blurring or
    // transforming) you can set the bounds here.

    // if you need to access your context data for some information to
    // compute the bounds (like blur size), you can do it like:
    //   cop2_FullImageFilterData *sdata =
    //                (cop2_FullImageFilterData *) context.data();

    // SAMPLES:
    
    // expands or contracts the bounds to the visible image resolution
    context.setImageBounds(0,0, context.myXres-1, context.myYres-1);

    // just copies the input bounds (ie this node don't modify it)
    //copyInputBounds(0, context);

    // expands the input bounds by 5 pixels in each direction.
    //   copyInputBounds(0, context);
    //   int x1,y1,x2,y2;
    //   context.getImageBounds(x1,y1,x2,y2);
    //   context.setImageBounds(x1-5, y1-5, x2+5, y2+5);
    
}

void
COP2_FullImageFilter::getInputDependenciesForOutputArea(
    COP2_CookAreaInfo           &output_area,
    const COP2_CookAreaList     &input_areas,
    COP2_CookAreaList           &needed_areas)
{
    COP2_CookAreaInfo   *area;

    // for a given output area and plane, set up which input planes and areas
    // it is dependent on. Basically, if you call inputTile or inputRegion in
    // the cook, for each call you need to make a dependency here.

    // this makes a dependency on the input plane corresponding to the output
    // area's plane. 
    area = makeOutputAreaDependOnInputPlane(0,
                                            output_area.getPlane().getName(),
                                            output_area.getArrayIndex(),
                                            output_area.getTime(),
                                            input_areas, needed_areas);
    
    // Always check for null before setting the bounds of the input area.
    // in this case, all of the input area is required.
    if (area)
        area->enlargeNeededAreaToBounds();

    // If the node depends on its input conterpart PLUS another plane,
    // we need to add a dependency on that plane as well. In this case, we
    // add an extra dependency on alpha (same input, same time).

    area = makeOutputAreaDependOnInputPlane(0,
                                            getAlphaPlaneName(), 0,
                                            output_area.getTime(),
                                            input_areas, needed_areas);
    // again, we'll use all of the area.
    if (area)
        area->enlargeNeededAreaToBounds();

    getMaskDependency(output_area, input_areas, needed_areas);
    
}

OP_ERROR
COP2_FullImageFilter::doCookMyTile(COP2_Context &context, TIL_TileList *tiles)
{
    // normally, this is where you would process your tile. However,
    // cookFullImage() is a convenience function which assembles a full image
    // and does all the proper locking for you, then calls your filter
    // function.
    
    cop2_FullImageFilterData *sdata = 
        static_cast<cop2_FullImageFilterData *>(context.data());

    return cookFullImage(context, tiles, &COP2_FullImageFilter::filter,
                         sdata->myLock, true);
}

OP_ERROR
COP2_FullImageFilter::filter(COP2_Context &context,
                             const TIL_Region *input,
                             TIL_Region *output,
                             COP2_Node  *me)
{
    // since I don't like typing me-> constantly, just call a member function
    // from this static function. 
    return ((COP2_FullImageFilter*)me)->filterImage(context, input, output);

}

OP_ERROR
COP2_FullImageFilter::filterImage(COP2_Context &context,
                                  const TIL_Region *input,
                                  TIL_Region *output)
{
    // retrive my context data information (built in newContextData).
    cop2_FullImageFilterData *sdata =
        (cop2_FullImageFilterData *) context.data();

    // currently we have a blank output region, and an input region filled with
    // whatever plane we've been told to cook. Both are in the same format, as
    // this node didn't alter the data formats of any planes.

    
    // we need the alpha plane, so grab it (generally, you'd want to check if
    // context.myPlane->isAlphaPlane() first, and then just use the 'input'
    // region if we were cooking alpha, but for simplicity's sake we won't
    // bother). Oh, and we'll grab it as floating point.

    // make a copy of the alpha plane & set it to FP format.
    TIL_Plane alphaplane(*mySequence.getPlane(getAlphaPlaneName()));
    alphaplane.setFormat(TILE_FLOAT32);
    alphaplane.setScoped(1);
    
    TIL_Region *alpha = inputRegion(0, context,    // input 0
                                    &alphaplane,0, // FP alpha plane.
                                    context.myTime, // at current cook time
                                    0, 0, // lower left corner
                                    context.myXsize-1, context.myYsize-1); //UR
    if(!alpha)
    {
        // something bad happened, possibly error, possibly user interruption.
        return UT_ERROR_ABORT;
    }

    int comp;
    int x,y;
    char *idata, *odata;
    float *adata;

    // my silly algorithm is as follows: it will take the value of the alpha
    // plane multiplied by the user defined size and move the source point
    // up to that distance away from its original location. It just adds the
    // pixel over any pixel at that location, for simplicities sake.

    adata = (float *) alpha->getImageData(0);
        
    // go component by component. PLANE_MAX_VECTOR_SIZE = 4.
    for(comp = 0; comp < PLANE_MAX_VECTOR_SIZE; comp++)
    {
        idata = (char *) input->getImageData(comp);
        odata = (char *) output->getImageData(comp);
        
        if(odata)
        {
            // since we aren't guarenteed to write to every pixel with this
            // 'algorithm', the output data array needs to be zeroed. 
            memset(odata, 0, context.myXsize*context.myYsize * sizeof(float));
        }

        if(idata && odata)
        {
            // myXsize & myYsize are the actual sizes of the large canvas,
            // which may be different from the resolution (myXres, myYres).
            for(y=0; y<context.myYsize; y++)
                for(x=0; x<context.myXsize; x++)
                {
                    float *pix = (float *) idata;
                    float *out = (float *) odata;

                    unsigned seed = x * context.myYsize + y;
                    float dx = SYSrandomZero(seed);
                    float dy = SYSrandomZero(seed);
                    int idx, idy;
                    int nx, ny;

                    dx *= adata[x + y * context.myXsize] * sdata->mySize;
                    dy *= adata[x + y * context.myXsize] * sdata->mySize;

                    idx = (int) SYSrint(dx);
                    idy = (int) SYSrint(dy);

                    nx = x+idx;
                    ny = y+idy;

                    if(nx < 0 || nx >= context.myXsize ||
                       ny < 0 || ny >= context.myYsize)
                        continue;

                    pix += (x+y*context.myXsize);
                    out += (nx+ny*context.myXsize);
                        
                    *out = *out + *pix;
                }
        }
    }
    

    // It is important to release regions and tiles you request with
    // inputRegion & inputTile, otherwise they will just sit around until the
    // end of the cook taking up memory. If someone puts down many of your
    // nodes in a network, this could be problematic.
    releaseRegion(alpha);

    // input and output are allocated & released by cookFullImage, so don't
    // release them.
    
    return error();
}


void
newCop2Operator(OP_OperatorTable *table)
{
    table->addOperator(new OP_Operator("hdk_fullfilter",
                                       "HDK Full Image Filter",
                                       COP2_FullImageFilter::myConstructor,
                                       &COP2_FullImageFilter::myTemplatePair,
                                       1, 
                                       2, // optional mask input.
                                       &COP2_FullImageFilter::myVariablePair,
                                       0, // not generator
                                       COP2_FullImageFilter::myInputLabels));
}


Generated on Mon Jan 28 00:26:18 2013 for HDK by  doxygen 1.5.9