Redoing the blur filters for composit;
authorTon Roosendaal <ton@blender.org>
Fri, 3 Feb 2006 20:39:36 +0000 (20:39 +0000)
committerTon Roosendaal <ton@blender.org>
Fri, 3 Feb 2006 20:39:36 +0000 (20:39 +0000)
http://www.blender.org/bf/filters/

I found out current blur actually doesn't do gauss, but more did regular
quadratic. Now you can choose common filter types, but more specifically;

- set gamma on, to emphasize bright parts in blur more than darker parts
- use the bokeh option for (current circlular only) blur based on true
  area filters (meaning, for each pixel it samples the entire surrounding).
  This enables more effects, but is also much slower. Have to check on
  optimization for this still... use with care!

source/blender/blenkernel/bad_level_call_stubs/stubs.c
source/blender/blenkernel/intern/node.c
source/blender/blenkernel/intern/node_composite.c
source/blender/blenloader/intern/readfile.c
source/blender/makesdna/DNA_node_types.h
source/blender/render/extern/include/RE_pipeline.h
source/blender/render/intern/source/initrender.c
source/blender/src/drawnode.c

index e15b308e8b3e3c2cc8c6fe685b29788ca916bfb7..bd802215bfde6a388a16a77da58c5f3c363e5af1 100644 (file)
@@ -218,4 +218,4 @@ int         multitex(struct Tex *tex, float *texvec, float *dxt, float *dyt, int osatex
 struct Render *RE_GetRender(const char *name) {return (struct Render *)NULL;}
 struct RenderResult *RE_GetResult(Render *re) {return (struct RenderResult *)NULL;}
 float *RE_RenderLayerGetPass(RenderLayer *rl, int passtype) {return NULL;}
-
+float RE_filter_value(int type, float x) {return 0.0f;}
index e2dc623859ebef8a3a5488601829d672e97737e3..80393db65c02fd4aee9cf591b0755953a24bc7e8 100644 (file)
@@ -771,6 +771,8 @@ bNode *nodeAddNodeType(bNodeTree *ntree, int type, bNodeTree *ngroup)
                        node->storage= curvemapping_add(4, 0.0f, 0.0f, 1.0f, 1.0f);
                else if(type==CMP_NODE_MAP_VALUE)
                        node->storage= add_mapping();
+               else if(type==CMP_NODE_BLUR)
+                       node->storage= MEM_callocN(sizeof(NodeBlurData), "node blur data");
        }
        
        return node;
index 8157f56c6e6caed0694dd3929ef1cc0c3996a0c5..60cf84c7f929470fd7b4cf877ada23f397c84325 100644 (file)
@@ -85,9 +85,9 @@ static CompBuf *alloc_compbuf(int sizex, int sizey, int type, int alloc)
                if(cbuf->type==CB_RGBA)
                        cbuf->rect= MEM_mallocT(4*sizeof(float)*sizex*sizey, "compbuf RGBA rect");
                else if(cbuf->type==CB_VEC3)
-                       cbuf->rect= MEM_mallocT(4*sizeof(float)*sizex*sizey, "compbuf Vector3 rect");
+                       cbuf->rect= MEM_mallocT(3*sizeof(float)*sizex*sizey, "compbuf Vector3 rect");
                else if(cbuf->type==CB_VEC2)
-                       cbuf->rect= MEM_mallocT(4*sizeof(float)*sizex*sizey, "compbuf Vector2 rect");
+                       cbuf->rect= MEM_mallocT(2*sizeof(float)*sizex*sizey, "compbuf Vector2 rect");
                else
                        cbuf->rect= MEM_mallocT(sizeof(float)*sizex*sizey, "compbuf Fac rect");
                cbuf->malloc= 1;
@@ -100,6 +100,13 @@ static CompBuf *alloc_compbuf(int sizex, int sizey, int type, int alloc)
        return cbuf;
 }
 
+static CompBuf *dupalloc_compbuf(CompBuf *cbuf)
+{
+       CompBuf *dupbuf= alloc_compbuf(cbuf->x, cbuf->y, cbuf->type, 1);
+       memcpy(dupbuf->rect, cbuf->rect, cbuf->type*sizeof(float)*cbuf->x*cbuf->y);
+       return dupbuf;
+}
+
 void free_compbuf(CompBuf *cbuf)
 {
        if(cbuf->malloc && cbuf->rect)
@@ -1501,7 +1508,7 @@ static bNodeType cmp_node_map_value= {
        
 };
 
-/* **************** GAUSS BLUR ******************** */
+/* **************** BLUR ******************** */
 static bNodeSocketType cmp_node_blur_in[]= {
        {       SOCK_RGBA, 1, "Image",                  0.8f, 0.8f, 0.8f, 1.0f, 0.0f, 1.0f},
        {       SOCK_VALUE, 1, "Size",                  1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
@@ -1512,7 +1519,7 @@ static bNodeSocketType cmp_node_blur_out[]= {
        {       -1, 0, ""       }
 };
 
-static float *make_gausstab(int rad)
+static float *make_gausstab(int filtertype, int rad)
 {
        float *gausstab, sum, val;
        int i, n;
@@ -1523,7 +1530,7 @@ static float *make_gausstab(int rad)
        
        sum = 0.0f;
        for (i = -rad; i <= rad; i++) {
-               val = exp(-4.0*((float)i*i) / (float) (rad*rad));
+               val= RE_filter_value(filtertype, (float)i/(float)rad);
                sum += val;
                gausstab[i+rad] = val;
        }
@@ -1552,7 +1559,7 @@ static float *make_bloomtab(int rad)
        return bloomtab;
 }
 
-static void blur_single_image(CompBuf *new, CompBuf *img, float blurx, float blury)
+static void blur_single_image(CompBuf *new, CompBuf *img, float scale, NodeBlurData *nbd)
 {
        CompBuf *work;
        register float sum, val;
@@ -1567,13 +1574,13 @@ static void blur_single_image(CompBuf *new, CompBuf *img, float blurx, float blu
        work= alloc_compbuf(imgx, imgy, img->type, 1); // allocs
        
        /* horizontal */
-       rad = ceil(blurx);
+       rad = scale*(float)nbd->sizex;
        if(rad>imgx/2)
                rad= imgx/2;
        else if(rad<1) 
                rad= 1;
 
-       gausstab= make_gausstab(rad);
+       gausstab= make_gausstab(nbd->filtertype, rad);
        gausstabcent= gausstab+rad;
        
        for (y = 0; y < imgy; y++) {
@@ -1611,13 +1618,13 @@ static void blur_single_image(CompBuf *new, CompBuf *img, float blurx, float blu
        /* vertical */
        MEM_freeT(gausstab);
        
-       rad = ceil(blury);
+       rad = scale*(float)nbd->sizey;
        if(rad>imgy/2)
                rad= imgy/2;
        else if(rad<1) 
                rad= 1;
 
-       gausstab= make_gausstab(rad);
+       gausstab= make_gausstab(nbd->filtertype, rad);
        gausstabcent= gausstab+rad;
        
        bigstep = pix*imgx;
@@ -1660,7 +1667,7 @@ static void blur_single_image(CompBuf *new, CompBuf *img, float blurx, float blu
 }
 
 /* reference has to be mapped 0-1, and equal in size */
-static void bloom_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float blurx, float blury)
+static void bloom_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float fac, NodeBlurData *nbd)
 {
        CompBuf *wbuf;
        register float val;
@@ -1677,14 +1684,14 @@ static void bloom_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float
        memset(wbuf->rect, sizeof(float)*imgx*imgy, 0);
        
        /* horizontal */
-       radx = ceil(blurx);
+       radx = (float)nbd->sizex;
        if(radx>imgx/2)
                radx= imgx/2;
        else if(radx<1) 
                radx= 1;
        
        /* vertical */
-       rady = ceil(blury);
+       rady = (float)nbd->sizey;
        if(rady>imgy/2)
                rady= imgy/2;
        else if(rady<1) 
@@ -1780,8 +1787,130 @@ static void bloom_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float
        
 }
 
+static void gamma_correct_compbuf(CompBuf *img, int inversed)
+{
+       float *drect;
+       int x;
+       
+       drect= img->rect;
+       if(inversed) {
+               for(x=img->x*img->y; x>0; x--, drect+=4) {
+                       if(drect[0]>0.0f) drect[0]= sqrt(drect[0]); else drect[0]= 0.0f;
+                       if(drect[1]>0.0f) drect[1]= sqrt(drect[1]); else drect[1]= 0.0f;
+                       if(drect[2]>0.0f) drect[2]= sqrt(drect[2]); else drect[2]= 0.0f;
+               }
+       }
+       else {
+               for(x=img->x*img->y; x>0; x--, drect+=4) {
+                       if(drect[0]>0.0f) drect[0]*= drect[0]; else drect[0]= 0.0f;
+                       if(drect[1]>0.0f) drect[1]*= drect[1]; else drect[1]= 0.0f;
+                       if(drect[2]>0.0f) drect[2]*= drect[2]; else drect[2]= 0.0f;
+               }
+       }
+}
+#if 0
+static float hexagon_filter(float fi, float fj)
+{
+       fi= fabs(fi);
+       fj= fabs(fj);
+       
+       if(fj>0.33f) {
+               fj= (fj-0.33f)/0.66f;
+               if(fi+fj>1.0f)
+                       return 0.0f;
+               else
+                       return 1.0f;
+       }
+       else return 1.0f;
+}
+#endif
+/* uses full filter, no horizontal/vertical optimize possible */
+static void bokeh_single_image(CompBuf *new, CompBuf *img, float fac, NodeBlurData *nbd)
+{
+       register float val;
+       float radxf, radyf;
+       float *gausstab, *dgauss;
+       int radx, rady, imgx= img->x, imgy= img->y;
+       int x, y;
+       int i, j;
+       float *src, *dest;
+       
+       /* horizontal */
+       radx = fac*(float)nbd->sizex;
+       if(radx>imgx/2)
+               radx= imgx/2;
+       else if(radx<1) 
+               radx= 1;
+       
+       /* vertical */
+       rady = fac*(float)nbd->sizey;
+       if(rady>imgy/2)
+               rady= imgy/2;
+       else if(rady<1) 
+               rady= 1;
+       
+       radxf= (float)radx;
+       radyf= (float)rady;
+       
+       /* create a full filter image */
+       gausstab= MEM_mallocT(sizeof(float)*radx*rady*4, "filter tab");
+       dgauss= gausstab;
+       val= 0.0f;
+       for(j=-rady; j<rady; j++) {
+               for(i=-radx; i<radx; i++, dgauss++) {
+                       float fj= (float)j/radyf;
+                       float fi= (float)i/radxf;
+                       float dist= sqrt(fj*fj + fi*fi);
+                       
+//                     *dgauss= hexagon_filter(fi, fj);
+                       *dgauss= RE_filter_value(nbd->filtertype, 2.0f*dist - 1.0f);
+
+                       val+= *dgauss;
+               }
+       }
+       val= 1.0f/val;
+       for(j= 4*radx*rady -1; j>=0; j--)
+               gausstab[j]*= val;
+       
+       
+       /* vars to store before we go */
+       //      refd= ref->rect;
+       src= img->rect;
+       
+       memset(new->rect, 4*imgx*imgy, 0);
+       
+       for (y = 0; y < imgy; y++) {
+               for (x = 0; x < imgx ; x++, src+=4) {//, refd++) {
+                       int minxr= x-radx<0?-x:-radx;
+                       int maxxr= x+radx>imgx?imgx-x:radx;
+                       int minyr= y-rady<0?-y:-rady;
+                       int maxyr= y+rady>imgy?imgy-y:rady;
+                       
+                       float *destd= new->rect + 4*( (y + minyr)*imgx + x + minxr);
+                       
+                       float *dgausd= gausstab + (minyr+rady)*2*radx + minxr+radx;
+                       
+                       for (i= minyr; i < maxyr; i++, destd+= 4*imgx, dgausd+= 2*radx) {
+                               dest= destd;
+                               dgauss= dgausd;
+                               for (j= minxr; j < maxxr; j++, dest+=4, dgauss++) {
+                                       
+                                       val= *dgauss;
+                                       dest[0] += val * src[0];
+                                       dest[1] += val * src[1];
+                                       dest[2] += val * src[2];
+                                       dest[3] += val * src[3];
+                               }
+                       }
+               }
+       }
+       
+       MEM_freeT(gausstab);
+}
+
+
 /* reference has to be mapped 0-1, and equal in size */
-static void blur_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float blurx, float blury)
+static void blur_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, NodeBlurData *nbd)
 {
        CompBuf *blurbuf;
        register float sum, val;
@@ -1804,17 +1933,17 @@ static void blur_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float
                else blurd[0]= refd[0];
        }
        
-       blur_single_image(blurbuf, blurbuf, blurx, blury);
+       blur_single_image(blurbuf, blurbuf, 1.0f, nbd);
        
        /* horizontal */
-       radx = ceil(blurx);
+       radx = (float)nbd->sizex;
        if(radx>imgx/2)
                radx= imgx/2;
        else if(radx<1) 
                radx= 1;
        
        /* vertical */
-       rady = ceil(blury);
+       rady = (float)nbd->sizey;
        if(rady>imgy/2)
                rady= imgy/2;
        else if(rady<1) 
@@ -1823,7 +1952,7 @@ static void blur_with_reference(CompBuf *new, CompBuf *img, CompBuf *ref, float
        x= MAX2(radx, rady);
        maintabs= MEM_mallocT(x*sizeof(void *), "gauss array");
        for(i= 0; i<x; i++)
-               maintabs[i]= make_gausstab(i+1);
+               maintabs[i]= make_gausstab(nbd->filtertype, i+1);
        
        refd= blurbuf->rect;
        dest= new->rect;
@@ -1906,7 +2035,7 @@ static void node_composit_exec_blur(void *data, bNode *node, bNodeStack **in, bN
                /* make output size of input image */
                new= alloc_compbuf(img->x, img->y, CB_RGBA, 1); // allocs
                
-               blur_with_reference(new, img, in[1]->data, (float)node->custom1, (float)node->custom2);
+               blur_with_reference(new, img, in[1]->data, node->storage);
                
                out[0]->data= new;
        }
@@ -1917,19 +2046,35 @@ static void node_composit_exec_blur(void *data, bNode *node, bNodeStack **in, bN
                        new->rect= img->rect;
                }
                else {
+                       NodeBlurData *nbd= node->storage;
+                       CompBuf *gammabuf;
+                       
                        /* make output size of input image */
                        new= alloc_compbuf(img->x, img->y, img->type, 1); // allocs
-                       if(1)
-                               blur_single_image(new, img, in[1]->vec[0]*(float)node->custom1, in[1]->vec[0]*(float)node->custom2);
+                       
+                       if(nbd->gamma) {
+                               gammabuf= dupalloc_compbuf(img);
+                               gamma_correct_compbuf(gammabuf, 0);
+                       }
+                       else gammabuf= img;
+                       
+                       if(nbd->bokeh)
+                               bokeh_single_image(new, gammabuf, in[1]->vec[0], nbd);
+                       else if(1)
+                               blur_single_image(new, gammabuf, in[1]->vec[0], nbd);
                        else    /* bloom experimental... */
-                               bloom_with_reference(new, img, NULL, in[1]->vec[0]*(float)node->custom1, in[1]->vec[0]*(float)node->custom2);
+                               bloom_with_reference(new, gammabuf, NULL, in[1]->vec[0], nbd);
+                       
+                       if(nbd->gamma) {
+                               gamma_correct_compbuf(new, 1);
+                               free_compbuf(gammabuf);
+                       }
                }
                out[0]->data= new;
        }
 }
        
 
-/* custom1 custom2 = blur filter size */
 static bNodeType cmp_node_blur= {
        /* type code   */       CMP_NODE_BLUR,
        /* name        */       "Blur",
@@ -1937,7 +2082,7 @@ static bNodeType cmp_node_blur= {
        /* class+opts  */       NODE_CLASS_OPERATOR, NODE_OPTIONS,
        /* input sock  */       cmp_node_blur_in,
        /* output sock */       cmp_node_blur_out,
-       /* storage     */       "",
+       /* storage     */       "NodeBlurData",
        /* execfunc    */       node_composit_exec_blur
        
 };
@@ -1955,6 +2100,7 @@ static bNodeSocketType cmp_node_vecblur_out[]= {
 
 static void node_composit_exec_vecblur(void *data, bNode *node, bNodeStack **in, bNodeStack **out)
 {
+       NodeBlurData nbd;
        CompBuf *new, *img= in[0]->data, *vecbuf= in[1]->data, *wbuf;
        float *vect, *dest;
        int x, y;
@@ -1984,7 +2130,8 @@ static void node_composit_exec_vecblur(void *data, bNode *node, bNodeStack **in,
        /* make output size of input image */
        new= alloc_compbuf(img->x, img->y, CB_RGBA, 1); // allocs
        
-       blur_with_reference(new, img, wbuf, 100.0f, 100.0f);
+       nbd.sizex= 100; nbd.sizey= 100; nbd.filtertype= R_FILTER_GAUSS;
+       blur_with_reference(new, img, wbuf, &nbd);
        
        free_compbuf(wbuf);
        out[0]->data= new;
index 6342578115ece445481b81f80df59b176674b6c8..96828d0faa46998b72edf680c41d22c984ca7f54 100644 (file)
@@ -3593,6 +3593,25 @@ static void bone_version_239(ListBase *lb)
        }
 }
 
+static void ntree_version_241(bNodeTree *ntree)
+{
+       bNode *node;
+       
+       if(ntree->type==NTREE_COMPOSIT) {
+               for(node= ntree->nodes.first; node; node= node->next) {
+                       if(node->type==CMP_NODE_BLUR) {
+                               if(node->storage==NULL) {
+                                       NodeBlurData *nbd= MEM_callocN(sizeof(NodeBlurData), "node blur patch");
+                                       nbd->sizex= node->custom1;
+                                       nbd->sizey= node->custom2;
+                                       nbd->filtertype= R_FILTER_QUAD;
+                                       node->storage= nbd;
+                               }
+                       }
+               }
+       }
+}
+
 static void do_versions(FileData *fd, Library *lib, Main *main)
 {
        /* WATCH IT!!!: pointers from libdata have not been converted */
@@ -5253,6 +5272,7 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
        if(main->versionfile <= 241) {
                Scene *sce;
                bArmature *arm;
+               bNodeTree *ntree;
                
                /* updating layers still */
                for(arm= main->armature.first; arm; arm= arm->id.next) {
@@ -5265,7 +5285,14 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
                        /* adds default layer */
                        if(sce->r.layers.first==NULL)
                                scene_add_render_layer(sce);
+                       /* node version changes */
+                       if(sce->nodetree)
+                               ntree_version_241(sce->nodetree);
                }
+               
+               for(ntree= main->nodetree.first; ntree; ntree= ntree->id.next)
+                       ntree_version_241(ntree);
+               
                //Object *ob;
                
                /* for empty drawsize and drawtype */
index 05d3a276607eb0a374166c25573fce2771746cec..340f000316a4be4e3d82aea0c30d025ce7ec1653 100644 (file)
@@ -185,5 +185,11 @@ typedef struct NodeImageAnim {
        char cyclic, movie;
 } NodeImageAnim;
 
+typedef struct NodeBlurData {
+       short sizex, sizey;
+       short filtertype;
+       char bokeh, gamma;
+} NodeBlurData;
+
 #endif
 
index 1afce5467918902fa231151251b49d79a9c29a6b..07d0b0a13150f86fd1901800d2ea869b5cb6e492 100644 (file)
@@ -171,8 +171,8 @@ void RE_test_break_cb       (struct Render *re, int (*f)(void));
 void RE_test_return_cb (struct Render *re, int (*f)(void));
 void RE_error_cb               (struct Render *re, void (*f)(const char *str));
 
-
-
+/* should move to kernel once... still unsure on how/where */
+float RE_filter_value(int type, float x);
 
 #endif /* RE_PIPELINE_H */
 
index 9a36cf99e27a9fc18dc1d390bb8b3c38c6944d8e..19d593a9409a1764a83fba36535539ffaa27204f 100644 (file)
@@ -153,6 +153,41 @@ static float filt_mitchell(float x)        /* Mitchell & Netravali's two-param cubic */
        return 0.0;
 }
 
+/* x ranges from -1 to 1 */
+float RE_filter_value(int type, float x)
+{
+       float gaussfac= 1.6f;
+       
+       x= ABS(x);
+       
+       switch(type) {
+               case R_FILTER_BOX:
+                       if(x>1.0) return 0.0f;
+                       return 1.0;
+                       
+               case R_FILTER_TENT:
+                       if(x>1.0) return 0.0f;
+                       return 1.0f-x;
+                       
+               case R_FILTER_GAUSS:
+                       x*= gaussfac;
+                       return (1.0/exp(x*x) - 1.0/exp(gaussfac*gaussfac*2.25));
+                       
+               case R_FILTER_MITCH:
+                       return filt_mitchell(x*gaussfac);
+                       
+               case R_FILTER_QUAD:
+                       return filt_quadratic(x*gaussfac);
+                       
+               case R_FILTER_CUBIC:
+                       return filt_cubic(x*gaussfac);
+                       
+               case R_FILTER_CATROM:
+                       return filt_catrom(x*gaussfac);
+       }
+       return 0.0f;
+}
+
 static float calc_weight(Render *re, float *weight, int i, int j)
 {
        float x, y, dist, totw= 0.0;
index 9dc00c1806571c5fef0635cb1400fee7a0701367..ed8f6d81ba9ea15f8db33a764b850109b37956bb 100644 (file)
@@ -699,17 +699,34 @@ static int node_composit_buts_renderresult(uiBlock *block, bNodeTree *ntree, bNo
 static int node_composit_buts_blur(uiBlock *block, bNodeTree *ntree, bNode *node, rctf *butr)
 {
        if(block) {
+               NodeBlurData *nbd= node->storage;
                uiBut *bt;
+               short dy= butr->ymin+19;
+               short dx= (butr->xmax-butr->xmin)/2;
+               short dx3=(butr->xmax-butr->xmin)/3;
+               char str[256];
                
                uiBlockBeginAlign(block);
+               sprintf(str, "Filter Type%%t|Flat %%x%d|Tent %%x%d|Quad %%x%d|Cubic %%x%d|Gauss %%x%d|CatRom %%x%d|Mitch %%x%d", R_FILTER_BOX, R_FILTER_TENT, R_FILTER_QUAD, R_FILTER_CUBIC, R_FILTER_GAUSS, R_FILTER_CATROM, R_FILTER_MITCH);
+               uiDefButS(block, MENU, B_NODE_EXEC,str,         
+                                 butr->xmin, dy, dx3, 19, 
+                                 &nbd->filtertype, 0, 0, 0, 0, "Set sampling filter for blur");
+               uiDefButC(block, TOG, B_NODE_EXEC, "Bokeh",             
+                                 butr->xmin+dx3, dy, dx3, 19, 
+                                 &nbd->bokeh, 0, 0, 0, 0, "Uses circular filter, warning it's slow!");
+               uiDefButC(block, TOG, B_NODE_EXEC, "Gamma",             
+                                 butr->xmin+2*dx3, dy, dx3, 19, 
+                                 &nbd->gamma, 0, 0, 0, 0, "Applies filter on gamma corrected values");
+               
+               dy-=19;
                bt=uiDefButS(block, NUM, B_NODE_EXEC+node->nr, "X:",
-                                        butr->xmin, butr->ymin, (butr->xmax-butr->xmin)/2, 19, 
-                                        &node->custom1, 0, 256, 0, 0, "");
+                                        butr->xmin, dy, dx, 19, 
+                                        &nbd->sizex, 0, 256, 0, 0, "");
                bt=uiDefButS(block, NUM, B_NODE_EXEC+node->nr, "Y:",
-                                        butr->xmin+(butr->xmax-butr->xmin)/2, butr->ymin, (butr->xmax-butr->xmin)/2, 19, 
-                                        &node->custom2, 0, 256, 0, 0, "");
+                                        butr->xmin+dx, dy, dx, 19, 
+                                        &nbd->sizey, 0, 256, 0, 0, "");
        }
-       return 19;
+       return 38;
 }
 
 static int node_composit_buts_filter(uiBlock *block, bNodeTree *ntree, bNode *node, rctf *butr)