Jump to content

Rotate By Torus Map


Recommended Posts

Hi, yes we do have a filter which does exactly the above and this is actually a translation of my gmic filter. The only difference is this is extended with blending option and more modes. That being said, for some reason I am getting a null reference error. Not sure why.

Spoiler



// Name: Rotate By Torus Map
// Submenu: Distortion
// Author: Reptorian
// Title: Rotation by Torus Map
// Version: 1
// Desc: Rotates images using a Torus Map.
// Keywords: distortion
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help:
#region UICode
DoubleSliderControl v_circ_a = 0; // [0,100] Circumference A (%)
DoubleSliderControl v_circ_b = 0; // [0,100] Circumference B (%)
AngleControl angle = 45; // [-180,180] Angle
ListBoxControl distortmode = 0; // {!enable_distortion_blend} Distortion Mode|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
ListBoxControl distortmode_a = 0; // {enable_distortion_blend} Distortion Mode A|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
ListBoxControl distortmode_b = 0; // {enable_distortion_blend} Distortion Mode B|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
DoubleSliderControl v_distort_blend = 0; // [0,100] {enable_distortion_blend} Distortion Mode Blending (%)
CheckboxControl enable_distortion_blend = true; // Enable Distortion Blending Mode?
PanSliderControl distpos = Pair.Create(0.0,0.0); // Distortion Position
CheckboxControl wraparound_mode = true; // Wraparound Distortion?
CheckboxControl remove_background = false; // Remove Background?
#endregion

Surface Surface_Mapped;

double ww,hh,cx,cy,offx,offy,eps,sx,sy,new_min,new_max,pi,distort_blend;
int boundary;

double vallim_x(double v){
    return v - ww * Math.Floor(v/(ww+eps));
}

double vallim_y(double v){
    return v - hh * Math.Floor(v/(hh+eps));
}


double start_x(double v){
    if (wraparound_mode){ return vallim_x(v+offx);}
    else                { return v+offx;}
}

double start_y(double v){
    if (wraparound_mode){ return vallim_y(v+offy);}
    else                { return v+offy;}
}

double nm(double v){
    return (v-new_min)*(1/(new_max - new_min));
}

double limcut(double v){
    return Math.Max(0,Math.Min(1,v));
}

int bndcut(double v){
    return (v > 1 || v >=0 ) ? 1 : 0;
}


double ang2rad(double v){
    return Math.PI * (v/180) ;
}

double rot_x(double a, double b, double c){
    return a * Math.Cos(ang2rad(c)) - b * Math.Sin(ang2rad(c));
}
double rot_y(double a, double b, double c){
    return a * Math.Cos(ang2rad(c)) - b * Math.Sin(ang2rad(c));
}

double sur_x(double v){
    return (v/ww - .5) * 2 * sx;
}


double sur_y(double v){
    return (v/hh - .5) * 2 * sy;
}

double unsur_x(double v){
    return (v/(2*sx) + .5) * ww;
}

double unsur_y(double v){
    return (v/(2*sy) + .5) * hh;
}

double lerp(double a,double b){
    return a * distort_blend + b * (1 - distort_blend);
}

double mode_choice(double v,int mode){
    switch(mode){
        case 0: return (Math.Cos(v*(2*pi)-pi)+1)/2;
        case 1: return Math.Abs(Math.Cos(v*pi+pi/2));
        case 2: return Math.Sqrt(1-Math.Pow(Math.Abs(v-.5)*2,2));
        case 3: return Math.Pow(Math.Pow(1-(Math.Abs(v-.5)*2),2),1/(2+(1-v)));
        case 4: return Math.Cos(v*pi)*boundary;
        case 5: return Math.Cos(v*pi)*boundary*-1;
        case 6: return 1 - Math.Abs(v-.5) * 2;
        case 7: 
            double r = (1-Math.Abs(v-.5)*2)*2;
            return r > 1 ? 1 : r ;
    }
    return 0;
}

double mode(double v){
    if (enable_distortion_blend){
        return lerp(distortmode_a,distortmode_b);
    }
    else{
        return mode_choice(v,distortmode);
    }
}

void PreRender(Surface dst,Surface src)
{
    int w= src.Width;
    int h = src.Height;
    double d_w=(double)(w);
    double d_h=(double)(h);
    double sd = Math.Max(d_w,d_h)/Math.Min(d_w,d_h);
    if (Surface_Mapped == null)
    {
        Surface_Mapped = new Surface(src.Size);
    }
    double circ_a = v_circ_a / 100;
    double circ_b = v_circ_b / 100;
    double maxang = angle * -1;
    sx = w > h ? sd : 1  ;
    sy = w > h ? 1  : sd ;
    ww = d_w - 1;
    hh = d_h - 1;
    cx = ww/2;
    cy = hh/2;
    offx = cx * distpos.First * -1;
    offy = cy * distpos.Second;
    eps = Math.Pow(10,-10);
    new_min=Math.Min(circ_a,circ_b);
    new_max=Math.Max(circ_a,circ_b);
    distort_blend = v_distort_blend / 100;
    pi = Math.PI;
    double xx,yy,radial_gradient,norm_gradient,z_depth,XX,YY,diff_x,diff_y;
    int Alpha;
    ColorBgra tempcolor;
    for (int y = 0; y < h ; y++){
        if (IsCancelRequested) return;
        for (int x = 0 ; x < w ; x++){
            xx=start_x((double)(x));
            yy=start_y((double)(y));
            xx=sur_x(xx);
            yy=sur_y(yy);
            radial_gradient=nm(Math.Sqrt(xx*xx+yy*yy));
            norm_gradient=limcut(radial_gradient);
            boundary=bndcut(radial_gradient);
            z_depth=mode(norm_gradient)*maxang;
            XX=rot_x(xx,yy,z_depth);
            YY=rot_y(xx,yy,z_depth);
            diff_x=XX-xx;
            diff_y=YY-yy;
            tempcolor=src.GetBilinearSample(x,y);
            if (remove_background){
                Alpha = tempcolor.A * boundary;
                Surface_Mapped[x,y]=ColorBgra.FromBgra(tempcolor.B,tempcolor.G,tempcolor.R,(byte)(Alpha));
            }
            else{
               Surface_Mapped[x,y] = tempcolor;
            }
        }
    }

}
void Render(Surface dst, Surface src, Rectangle rect)
{
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            dst[x,y] = Surface_Mapped[x,y];
        }
    }
}

 

 

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

Thank you, I was able to finish up almost. Now, I definitely have a problem here. Mirror Boundary Code here does not work. I will ask @MJW for suggestions.

 

EDIT: I found my solution. I had to change one part of a small code from hh to ww instead. It works.

 

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

One change I believe you could make is to write directly to the destination surface in PreRender() instead of creating an auxiliary surface, and use a do-nothing Render() routine. I understand from some recent discussions that writing to the dst surface in PreRender(), even outside the clip bounds, is allowed.

 

If you create an auxiliary service, it would probably be best to dispose it in an OnDispose() routine, though Rick Brewster has indicated that doesn't really need to be done.

 

 

Link to comment
Share on other sites

7 minutes ago, MJW said:

One change I believe you could make is to write directly to the destination surface in PreRender() instead of creating an auxiliary surface, and use a do-nothing Render routine. I understand from some recent discussions that writing to the dst surface in PreRender(), even outside the clip bounds, is allowed.

 

If you create an auxiliary service, it would probably be best to dispose it in an OnDispose() routine, though Rick Brewster has indicated that's doesn't really need to be done.

 

I'm concerned about performance and the bug I experienced with the creation of Axis-Based Shift. It is something I will look into. I'm already done with the functionality part of the plugin.

 

Spoiler


// Name: Rotate By Torus Map
// Submenu: Distortion
// Author: Reptorian
// Title: Rotation by Torus Map
// Version: 1
// Desc: Rotates images using a Torus Map.
// Keywords: distortion
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help:
#region UICode
DoubleSliderControl v_circ_a = 50; // [0,100] Circumference A (%)
DoubleSliderControl v_circ_b = 100; // [0,100] Circumference B (%)
AngleControl angle = 45; // [-180,180] Angle
ListBoxControl distortmode = 3; // {!enable_distortion_blend} Distortion Mode|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
ListBoxControl distortmode_a = 0; // {enable_distortion_blend} Distortion Mode A|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
ListBoxControl distortmode_b = 3; // {enable_distortion_blend} Distortion Mode B|Soft|Medium|Hard|Alternative Hard|Distroy|Inverse-Distroy|Quad Extrude|Hexagonal
DoubleSliderControl v_distort_blend = 50; // [0,100] {enable_distortion_blend} Distortion Mode Blending (%)
CheckboxControl enable_distortion_blend = true; // Enable Distortion Blending Mode?
PanSliderControl distpos = Pair.Create(0.000, 0.000); // Distortion Position
CheckboxControl wraparound_mode = true; // Wraparound Distortion?
CheckboxControl remove_background = false; // Remove Background?
ListBoxControl outofrange_boundary_choice = 0; // Boundary|None|Neumann|Periodic|Mirror
#endregion

Surface Surface_Mapped;

double ww,hh,cx,cy,offx,offy,eps,sx,sy,new_min,new_max,pi,distort_blend;
float f_w,f_h,f_ww,f_hh;
int boundary;

double vallim_x(double v){
    return v - ww * Math.Floor(v/(ww+eps));
}

double vallim_y(double v){
    return v - hh * Math.Floor(v/(hh+eps));
}


double start_x(double v){
    if (wraparound_mode){ return vallim_x(v+offx);}
    else                { return v+offx;}

}

double start_y(double v){
    if (wraparound_mode){ return vallim_y(v+offy);}
    else                { return v+offy;}
}

double nm(double v){
    return (v-new_min)*(1/(new_max - new_min));
}

double limcut(double v){
    return Math.Max(0,Math.Min(1,v));
}

int bndcut(double v){
    return v>1||v<0?0:1;
}


double ang2rad(double v){
    return Math.PI * (v/180) ;
}

double rot_x(double a, double b, double c){
    return a * Math.Cos(ang2rad(c)) - b * Math.Sin(ang2rad(c));
}
double rot_y(double a, double b, double c){
    return a * Math.Sin(ang2rad(c)) + b * Math.Cos(ang2rad(c));
}

double sur_x(double v){
    return (v/ww - .5) * 2 * sx;
}


double sur_y(double v){
    return (v/hh - .5) * 2 * sy;
}

double unsur_x(double v){
    return (v/(2*sx) + .5) * ww;
}

double unsur_y(double v){
    return (v/(2*sy) + .5) * hh;
}

double lerp(double a,double b){
    return a * distort_blend + b * (1 - distort_blend);
}

float mod(float a,float b){
    return a - b * (float)(Math.Floor(a/b));
}

float neumann_x(float x){
    return Math.Min(f_ww,Math.Max(x,0));
}

float neumann_y(float y){
    return Math.Min(f_hh,Math.Max(y,0));
}

float periodic_x(float x){
    return x-f_ww*(float)(Math.Floor(x/f_ww));
}

float periodic_y(float y){
    return y-f_hh*(float)(Math.Floor(y/f_hh));
}

float mirror_x(float x){
    float mx = mod(x,f_w*2);
    if (mx>=f_ww) {return 2*f_w - mx - 1;}
    else {return mx;}
}

float mirror_y(float y){
    float my = mod(y,f_h*2);
    if (my>=f_hh) {return 2*f_h - my - 1;}
    else {return my;}
}

float out_posx(float posx){
    switch(outofrange_boundary_choice){
        case 0: return posx;
        case 1: return neumann_x(posx);
        case 2: return periodic_x(posx);
        case 3: return mirror_x(posx);
    }

    return 0;
}

float out_posy(float posy){
    switch(outofrange_boundary_choice){
        case 0: return posy;
        case 1: return neumann_y(posy);
        case 2: return periodic_y(posy);
        case 3: return mirror_y(posy);
    }

    return 0;
}

double mode_choice(double v,int mode){
    switch(mode){
        case 0: return (Math.Cos(v*(2*pi)-pi)+1)/2;
        case 1: return Math.Abs(Math.Cos(v*pi+pi/2));
        case 2: return Math.Sqrt(1-Math.Pow(Math.Abs(v-.5)*2,2));
        case 3: return Math.Pow(1-Math.Pow(Math.Abs(v-.5)*2,2),1/(2+(1-v)));
        case 4: return Math.Cos(v*pi)*boundary;
        case 5: return Math.Cos(v*pi)*boundary*-1;
        case 6: return 1 - Math.Abs(v-.5) * 2;
        case 7: 
            double r = (1-Math.Abs(v-.5)*2)*2;
            return r > 1 ? 1 : r ;
    }
    return 0;
}

double mode(double v){
    if (enable_distortion_blend){
        return lerp(mode_choice(v,distortmode_a),mode_choice(v,distortmode_b));
    }
    else{
        return mode_choice(v,distortmode);
    }
}

void PreRender(Surface dst, Surface src)
{
    int w= src.Width;
    int h = src.Height;
    f_w=(float)(w);
    f_h=(float)(h);
    f_ww=(float)(w)-1;
    f_hh=(float)(h)-1;
    double d_w=(double)(w);
    double d_h=(double)(h);
    double sd = Math.Max(d_w,d_h)/Math.Min(d_w,d_h);
    if (Surface_Mapped == null)
    {
        Surface_Mapped = new Surface(src.Size);
    }
    double circ_a = v_circ_a / 100;
    double circ_b = v_circ_b / 100;
    double maxang = angle;
    sx = w > h ? sd : 1  ;
    sy = w > h ? 1  : sd ;
    ww = d_w - 1;
    hh = d_h - 1;
    cx = ww/2;
    cy = hh/2;
    offx = cx * distpos.First * -1;
    offy = cy * distpos.Second;
    eps = Math.Pow(10,-10);
    new_min=Math.Min(circ_a,circ_b);
    new_max=Math.Max(circ_a,circ_b);
    distort_blend = v_distort_blend / 100;
    pi = Math.PI;
    double xx,yy,radial_gradient,norm_gradient,z_depth,XX,YY,diff_x,diff_y;
    float posx,posy;
    int Alpha;
    ColorBgra tempcolor;
    for (int y = 0; y < h ; y++){
        if (IsCancelRequested) return;
        for (int x = 0 ; x < w ; x++){
            xx=start_x((double)(x));
            yy=start_y((double)(y));
            xx=sur_x(xx);
            yy=sur_y(yy);
            radial_gradient=nm(Math.Sqrt(xx*xx+yy*yy));
            norm_gradient=limcut(radial_gradient);
            boundary=bndcut(radial_gradient);
            z_depth=mode(norm_gradient)*maxang;
            XX=rot_x(xx,yy,z_depth);
            YY=rot_y(xx,yy,z_depth);
            XX=unsur_x(XX);
            YY=unsur_y(YY);
            xx=unsur_x(xx);
            yy=unsur_y(yy);
            diff_x=XX-xx;
            diff_y=YY-yy;
            posx=(float)(x)+(float)(diff_x);
            posy=(float)(y)+(float)(diff_y);
            posx=out_posx(posx);
            posy=out_posy(posy);
            tempcolor=src.GetBilinearSample(posx,posy);
            if (remove_background){
                Alpha = tempcolor.A * boundary;
                Surface_Mapped[x,y]=ColorBgra.FromBgra(tempcolor.B,tempcolor.G,tempcolor.R,(byte)(Alpha));
            }
            else{
               Surface_Mapped[x,y] = tempcolor;
            }
        }
    }
}

void Render(Surface dst, Surface src, Rectangle rect)
{
    for (int y = rect.Top; y < rect.Bottom; y++)
    {
        if (IsCancelRequested) return;
        for (int x = rect.Left; x < rect.Right; x++)
        {
            dst[x,y] = Surface_Mapped[x,y];
        }
    }
}

 

 

EDIT: I think I will call it done after @BoltBait warning.

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

6 minutes ago, Reptillian said:

Hmm, it seems that I am writing outside selection. I actually don't know how to fix that issue with my plugins.

 

Even if writing outside the selection is a problem (which the comment I linked to says it isn't), you aren't doing so. You are only writing to dst in Render(), and the Rectangle it is passed only includes selected pixels. Writing outside the selection in the aux surface could never be a problem.

Link to comment
Share on other sites

6 minutes ago, MJW said:

 

Even if writing outside the selection is a problem (which the comment I linked to says it isn't), you aren't doing so. You are only writing to dst in Render(), and the Rectangle it is passed only includes selected pixels. Writing outside the selection in the aux surface could never be a problem.

Got it. One last thing, do you have any suggestion on speeding up plugin? It is a bit slow. 

G'MIC Filter Developer

Link to comment
Share on other sites

I'll take a look at it later an see if I can see any improvements.

 

I've only glanced at it so far, but I'm somewhat surprised it's slow. Seemed like it was just a loop through all the pixels in PreRender(), with some straightforward computations, and a trivial Render(). I must have overlooked something.

Link to comment
Share on other sites

6 minutes ago, Reptillian said:

Removing the if canceled on render helps and creating tyy for start_y(y) helps. I think that's all that can be done from my side of things.

 

That seems strange to me. I would expect those changes to have a negligible effect on the run time. Perhaps IsCancelRequested is for some reason inefficient in PreRender(), though I've used it there and never noticed a problem. It's only executed once per row.

Link to comment
Share on other sites

I could be missing something, but I don't see why the aux surface and the PreRender() computations using it can't be eliminated, and the computations moved to Render(). The same values computed for the aux surface are the ones needed for rendering in Render().

Link to comment
Share on other sites

34 minutes ago, MJW said:

I could be missing something, but I don't see why the aux surface and the PreRender() computations using it can't be eliminated, and the computations moved to Render(). The same values computed for the aux surface are the ones needed for rendering in Render().

I tested it, even worse.

G'MIC Filter Developer

Link to comment
Share on other sites

@Reptillian When you publish this plugin, can you change the menu to Distort rather than creating a new sub-menu called Distortion?

Link to comment
Share on other sites

@MJW  I found the solution. It's a strange solution, but it took me a while to find the best code for it just like how it took me time to finish the Thorn Fractal. I took out xx=unsur(xx), and used nxx, and nyy to define that instead. It trimmed off the time it takes to calculate by more than half. I think I will publish it.

 

            nxx=start_x((double)(x));
            nyy=tyy;
            xx=sur_x(nxx);
            yy=sur_y(nyy);
            radial_gradient=nm(Math.Sqrt(xx*xx+yy*yy));
            norm_gradient=limcut(radial_gradient);
            boundary=bndcut(radial_gradient);
            z_depth=mode(norm_gradient)*maxang;
            XX=rot_x(xx,yy,z_depth);
            YY=rot_y(xx,yy,z_depth);
            XX=unsur_x(XX);
            YY=unsur_y(YY);
            diff_x=XX-nxx;
            diff_y=YY-nyy;
            posx=(float)(x)+(float)(diff_x);
            posy=(float)(y)+(float)(diff_y);
            posx=out_posx(posx);
            posy=out_posy(posy);
            tempcolor=src.GetBilinearSample(posx,posy);
            if (remove_background){
                Alpha = tempcolor.A * boundary;
                Surface_Mapped[x,y]=ColorBgra.FromBgra(tempcolor.B,tempcolor.G,tempcolor.R,(byte)(Alpha));
            }
            else{
               Surface_Mapped[x,y] = tempcolor;
            }

 

G'MIC Filter Developer

Link to comment
Share on other sites

Are you testing performance outside of CodeLab? Those performance issues strike me as the kind of thing that might happen in non-optimized debug code, but not in optimized code. I don't know how CodeLab handles optimization, but I wonder if the optimization is different when it's run under CodeLab then when the code is compiled and run as a separate plugin.

Link to comment
Share on other sites

1 hour ago, Reptillian said:

I tested it, even worse.

 

Even if it is worse, that's how it should be done. If it's done in PreRender(), it can't take advantage of multiprocessing. Also, the progress bar isn't updated correctly. Computing per-pixel data in PreRender() should only be done when the algorithm can't be implemented any other way.  I believe (though I'm not certain) that Render() code executed within CodeLab will only run on one core. CodeLab is meant to develop the code, not run it in the most efficient manner.

Link to comment
Share on other sites

I just published the plugin.

 

I'm not sure how it could take advantage of multiprocessing. Usually when I do image processing outside of CodeLab like g'mic or c++ with a established code with its own functions, I know that what I'm doing involves multiple calculation in different areas on different threads. I will try to change the code back to render, compile it and report back.

G'MIC Filter Developer

Link to comment
Share on other sites

3 minutes ago, Reptillian said:

I'm not sure how it could take advantage of multiprocessing.

 

Different ROIs are processed by different cores, simultaneously.  You don't have to do anything besides include the pixel-processing code in Render() for that to happen.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...