Jump to content

Code Review on new Popcorn Fractal


Reptillian

Recommended Posts

A long time ago, @MJWsuggested I should use delegate for Popcorn Fractal. I had done that, but however it is slower than non-lambda delegate version. It was supposed to be faster.

 

Here's the two codes with StopWatch and Debug built-in. The  A is the non-lambda delegate version, and the B is the lambda delegate version. B is newer than A.

 

Popcorn Fractal A

 

Spoiler
// Name: Popcorn Fractal
// Submenu: Render
// Author: Reptorian
// Title: Popcorn Fractal
// Version: 1
// Desc: Render Popcorn Fractal into canvas. 
// Keywords: fractal
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help: 
#region UICode
IntSliderControl pts = 25; // [1,100] Points
DoubleSliderControl var_density = 1; // [0.01,2] Density
DoubleSliderControl H = 0.05; // [-5,5] H
DoubleSliderControl K = 3; // [-75,75] K
DoubleSliderControl var_zoom = 1; // [0.1,4] Zoom
AngleControl var_ang = 0; // [-180,180] Angle
PanSliderControl Origin = Pair.Create(0.000, 0.000); // Origin
ListBoxControl TrigMode = 0; // Trig Mode|Trig-4|Trig-6
ListBoxControl func_x1 = 0; // X-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x2 = 2; // X-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x3 = 1; // {!TrigMode} X-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y1 = 0; // Y-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y2 = 2; // Y-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y3 = 1; // {!TrigMode} Y-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
DoubleSliderControl var_midpoint_shift = 0; // [-1,1] Midpoint Shift
DoubleSliderControl multiplier = 1; // [0.01,10] {!norm} Multiplier
CheckboxControl norm = false; // Normalize
#endregion
#if DEBUG
#endif

double rot_x(double a, double b, double cos_ang,double sin_ang){
    return a * cos_ang - b * sin_ang;
}

double rot_y(double a, double b, double cos_ang,double sin_ang){
    return a * sin_ang + b * cos_ang;
}


double func_v(double v,int c){
    switch(c){
       case 0: return Math.Sin(v);
       case 1: return Math.Cos(v);
       case 2: return Math.Tan(v);
       case 3: return Math.Atan(v);
    }
    return Math.Atan(v);
}

int maxnum;
int [,] Popcorn_Array;

void PreRender(Surface dst, Surface src)
{
    int w = src.Width;
    int h = src.Height;    
    bool sd_cond = w>h;
    double dw = (double)(w);
    double dh = (double)(h);
    if (Popcorn_Array == null){Popcorn_Array = new int [w,h];}
    else {Array.Clear(Popcorn_Array, 0, w*h);}
    bool use_trig6 = TrigMode == 1;
    double density = 1 / var_density;
    double zoom = 1 / var_zoom;
    double ang = (var_ang/180) * Math.PI;
    double origin_x = Origin.First * -1 * zoom;
    double origin_y = Origin.Second * zoom;
    double sd = Math.Max(dw,dh) / Math.Min(dw,dh);
    double sx = sd_cond ? sd : 1  ;
    double sy = sd_cond ? 1  : sd ;
    double cx = dw / 2;
    double cy = dh / 2;
    double osx = origin_x * sx ;
    double osy = origin_y * sy ;
    double cx_zoom = cx / zoom;
    double cy_zoom = cy / zoom;
    double cxsx = cx * sx;
    double cysy = cy * sy;
    double cos_ang = Math.Cos(ang);
    double sin_ang = Math.Sin(ang);
    bool angcondition = (var_ang - 360*Math.Floor(var_ang/360))>0;
    double xx,yy,xnew,ynew,xval,yval;
    int xpos,ypos;
    var timer = new Stopwatch();

    timer.Start();

    for (double ix=0 ; ix < dw ; ix+=density)
    {
        if (IsCancelRequested) return;

        for (double iy=0 ; iy < dh ; iy+=density)
        {
            xx = zoom * (ix-cx) / cx;
            yy = zoom * (iy-cy) / cy;
            xx*=sx;
            yy*=sy;
            xx+=origin_x;
            yy+=origin_y;

            for (int ptn=0 ; ptn < pts ; ptn++)
            {
                if (use_trig6) {
                    xnew  = xx-H*func_v(yy+func_v(K*yy+func_v(K*yy,func_x3),func_x2),func_x1);
                    ynew  = yy-H*func_v(xx+func_v(K*xx+func_v(K*xx,func_y3),func_y2),func_y1);
                }
                else {
                    xnew = xx-H*func_v(yy+func_v(K*yy,func_x2),func_x1);
                    ynew = yy-H*func_v(xx+func_v(K*xx,func_y2),func_y1);
                }

                if (angcondition){
                    xval = ((rot_x(xnew,ynew,cos_ang,sin_ang) - osx)*cx_zoom + cxsx)/sx;
                    yval = ((rot_y(xnew,ynew,cos_ang,sin_ang) - osy)*cy_zoom + cysy)/sy;
                }
                else{
                    xval = ((xnew - osx)*cx_zoom+cxsx)/sx;
                    yval = ((ynew - osy)*cy_zoom+cysy)/sy;
                }

                xpos = (int)(Math.Round(xval));
                ypos = (int)(Math.Round(yval));
                
                if ((xpos>=0&&ypos>=0)&&(xpos<w&&ypos<h)){
                    Popcorn_Array[xpos,ypos]++;
                    if (Popcorn_Array[xpos,ypos]>maxnum) { maxnum = Popcorn_Array[xpos,ypos]; }
                }

                xx = xnew;
                yy = ynew;
            }
        }
    }

    timer.Stop();
    Debug.WriteLine(timer.Elapsed);
}

void Render(Surface dst, Surface src, Rectangle rect)
{
    double midpoint = 1 - Math.Abs(var_midpoint_shift);
    bool cond_midpoint = var_midpoint_shift>=0;
    double pval;
    int val;

    for (int y = rect.Top; y < rect.Bottom; y++)
    {        
        for (int x = rect.Left; x < rect.Right; x++)
        {
            pval = (double)(Popcorn_Array[x,y])/(double)(maxnum);
            if (cond_midpoint){pval=Math.Pow(pval,midpoint);}
            else {pval=1-(Math.Pow(1-pval,midpoint));}
            if(norm){pval*=255;}
            else {pval*=(double)(maxnum)*multiplier;}
            val=(int)(pval);
            dst[x,y]=ColorBgra.FromBgraClamped(val,val,val,255);
        }
    }
}

 

 

Popcorn Fractal B

 

Spoiler
// Name: Popcorn Fractal
// Submenu: Render
// Author: Reptorian
// Title: Popcorn Fractal
// Version: 1
// Desc: Render Popcorn Fractal into canvas. 
// Keywords: fractal
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help: 
#region UICode
IntSliderControl pts = 25; // [1,100] Points
DoubleSliderControl var_density = 1; // [0.01,2] Density
DoubleSliderControl H = 0.05; // [-5,5] H
DoubleSliderControl K = 3; // [-75,75] K
DoubleSliderControl var_zoom = 1; // [0.1,4] Zoom
AngleControl var_ang = 0; // [-180,180] Angle
PanSliderControl Origin = Pair.Create(0.000, 0.000); // Origin
ListBoxControl TrigMode = 0; // Trig Mode|Trig-4|Trig-6
ListBoxControl func_x1 = 0; // X-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x2 = 2; // X-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x3 = 1; // {!TrigMode} X-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y1 = 0; // Y-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y2 = 2; // Y-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y3 = 1; // {!TrigMode} Y-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
DoubleSliderControl var_midpoint_shift = 0; // [-1,1] Midpoint Shift
DoubleSliderControl multiplier = 1; // [0.01,10] {!norm} Multiplier
CheckboxControl norm = false; // Normalize
#endregion
#if DEBUG
#endif

double rot_x(double a, double b, double cos_ang,double sin_ang){
    return a * cos_ang - b * sin_ang;
}

double rot_y(double a, double b, double cos_ang,double sin_ang){
    return a * sin_ang + b * cos_ang;
}

int maxnum;
int [,] Popcorn_Array;

void PreRender(Surface dst, Surface src)
{
    int w = src.Width;
    int h = src.Height;    
    bool sd_cond = w>h;
    double dw = (double)(w);
    double dh = (double)(h);
    if (Popcorn_Array == null){Popcorn_Array = new int [w,h];}
    else {Array.Clear(Popcorn_Array, 0, w*h);}
    bool use_trig6 = TrigMode == 1;
    double density = 1 / var_density;
    double zoom = 1 / var_zoom;
    double ang = (var_ang/180) * Math.PI;
    double origin_x = Origin.First * -1 * zoom;
    double origin_y = Origin.Second * -1 * zoom;
    double sd = Math.Max(dw,dh) / Math.Min(dw,dh);
    double sx = sd_cond ? sd : 1  ;
    double sy = sd_cond ? 1  : sd ;
    double cx = dw / 2;
    double cy = dh / 2;
    double osx = origin_x * sx ;
    double osy = origin_y * sy ;
    double cx_zoom = cx / zoom;
    double cy_zoom = cy / zoom;
    double cxsx = cx * sx;
    double cysy = cy * sy;
    double cos_ang = Math.Cos(ang);
    double sin_ang = Math.Sin(ang);
    bool angcondition = (var_ang - 360*Math.Floor(var_ang/360))>0;
    double xx,yy,xnew,ynew,xval,yval;
    int xpos,ypos;

    Func<double,double,double> eval_x;
    Func<double,double,double> eval_y;
    Func<double,double> fun_x0 = v => 0;
    Func<double,double> fun_x1 = v => 0;   
    Func<double,double> fun_y0 = v => 0;
    Func<double,double> fun_y1 = v => 0;   
    Func<double,double,double> popcorn_x;
    Func<double,double,double> popcorn_y;

    switch(func_x1){
            case 0: 
                fun_x0 = v => Math.Sin(v);
                break;
            case 1:
                fun_x0 = v => Math.Cos(v);
                break;
            case 2: 
                fun_x0 = v => Math.Tan(v);
                break;
            case 3:
                fun_x0 = v => Math.Atan(v);
                break;
    }

    switch(func_x2){
            case 0: 
                fun_x1 = v => Math.Sin(v);
                break;
            case 1:
                fun_x1 = v => Math.Cos(v);
                break;
            case 2: 
                fun_x1 = v => Math.Tan(v);
                break;
            case 3:
                fun_x1 = v => Math.Atan(v);
                break;
    }

    switch(func_y1){
            case 0: 
                fun_y0 = v => Math.Sin(v);
                break;
            case 1:
                fun_y0 = v => Math.Cos(v);
                break;
            case 2: 
                fun_y0 = v => Math.Tan(v);
                break;
            case 3:
                fun_y0 = v => Math.Atan(v);
                break;
    }

    switch(func_y2){
            case 0: 
                fun_y1 = v => Math.Sin(v);
                break;
            case 1:
                fun_y1 = v => Math.Cos(v);
                break;
            case 2: 
                fun_y1 = v => Math.Tan(v);
                break;
            case 3:
                fun_y1 = v => Math.Atan(v);
                break;
    }

    if (use_trig6){
        Func<double,double> fun_x2 = v => 0;
        Func<double,double> fun_y2 = v => 0;

        switch(func_x3){
            case 0: 
                fun_x2 = v => Math.Sin(v);
                break;
            case 1:
                fun_x2 = v => Math.Cos(v);
                break;
            case 2: 
                fun_x2 = v => Math.Tan(v);
                break;
            case 3:
                fun_x2 = v => Math.Atan(v);
                break;
        }
        switch(func_y3){
            case 0: 
                fun_y2 = v => Math.Sin(v);
                break;
            case 1:
                fun_y2 = v => Math.Cos(v);
                break;
            case 2: 
                fun_y2 = v => Math.Tan(v);
                break;
            case 3:
                fun_y2 = v => Math.Atan(v);
                break;
        }
        popcorn_x = (a,b) => a - H * fun_x0(b+fun_x1(K*b + fun_x2(K*b)));
        popcorn_y = (a,b) => b - H * fun_y0(a+fun_y1(K*a + fun_y2(K*a)));
    }
    else {
        popcorn_x = (a,b) => a - H * fun_x0(b + fun_x1(K * b));
        popcorn_y = (a,b) => b - H * fun_y0(a + fun_y1(K * a));
    }

    if (angcondition){
        eval_x = ( x,y )  => ((rot_x(x,y,cos_ang,sin_ang) - osx)*cx_zoom + cxsx)/sx;
        eval_y = ( x,y )  => ((rot_y(x,y,cos_ang,sin_ang) - osy)*cy_zoom + cysy)/sy;
    }
    else{
        eval_x = ( x,y ) => ((x - osx)*cx_zoom+cxsx)/sx;
        eval_y = ( x,y ) => ((y - osy)*cy_zoom+cysy)/sy;
    }

    var timer = new Stopwatch();

    timer.Start();

    for (double ix=0 ; ix < dw ; ix+=density)
    {
        if (IsCancelRequested) return;

        for (double iy=0 ; iy < dh ; iy+=density)
        {
            xx = zoom * (ix-cx) / cx;
            yy = zoom * (iy-cy) / cy;
            xx*=sx;
            yy*=sy;
            xx+=origin_x;
            yy+=origin_y;

            for (int ptn=0 ; ptn < pts ; ptn++)
            {

                xnew = popcorn_x(xx,yy);
                ynew = popcorn_y(xx,yy);

                xval = eval_x(xnew,ynew);
                yval = eval_y(xnew,ynew);

                xpos = (int)(Math.Round(xval));
                ypos = (int)(Math.Round(yval));
                
                if ((xpos>=0&&ypos>=0)&&(xpos<w&&ypos<h)){
                    Popcorn_Array[xpos,ypos]++;
                    if (Popcorn_Array[xpos,ypos]>maxnum) { maxnum = Popcorn_Array[xpos,ypos]; }
                }

                xx = xnew;
                yy = ynew;
            }
        }
    }

    timer.Stop();
    Debug.WriteLine(timer.Elapsed);
}

void Render(Surface dst, Surface src, Rectangle rect)
{
    double midpoint = 1 - Math.Abs(var_midpoint_shift);
    bool cond_midpoint = var_midpoint_shift>=0;
    double pval;
    int val;

    for (int y = rect.Top; y < rect.Bottom; y++)
    {        
        for (int x = rect.Left; x < rect.Right; x++)
        {
            pval = (double)(Popcorn_Array[x,y])/(double)(maxnum);
            if (cond_midpoint){pval=Math.Pow(pval,midpoint);}
            else {pval=1-(Math.Pow(1-pval,midpoint));}
            if(norm){pval*=255;}
            else {pval*=(double)(maxnum)*multiplier;}
            val=(int)(pval);
            dst[x,y]=ColorBgra.FromBgraClamped(val,val,val,255);
        }
    }
}

 

 

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

  • Reptillian changed the title to Code Review on new Popcorn Fractal

I wouldn't expect lambdas to be faster. The JIT cannot inline those calls. A regular method with a switch, however, can benefit from inlining -- and the CPU's branch predictor may even do a good job washing away the cost of the branches.

 

An enum  of { Sin, Cos, ... } would be much more readable than 0,1,2,3, so I recommend that.

  • Like 1

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

Thoughts for speed...

 

1. Parallelize the for loops in your pre-render if you can. It's worth tracking two copies of xx/yy and each intermediate variable you defined outside the for-loops, if that's necessary to make it work. Speed will definitely improve if it's at all possible.

2. func_v gets called all the time. If replacing the trig functions with dummy code like return v; or return v + 1; gives you a useful speed boost, you should look into building a dynamic table of values, because that means the trig functions are bottlenecking your speed somewhat. The table works like this: when func_v is called, if the input is a key in the table, retrieve its value. If not, perform the computation and store the result using the input as the key and result as the value. What this does is allow repetitive input to skip the computation, since you can just retrieve the previously-calculated answer. To make this possible, you'll want to start passing in xx % 2pi and yy % 2pi so that the domain is always within 0 to 2pi, or you'll hardly see repetition in the domain (modulo doesn't actually work with floats like 2pi, but you can try other ways of keeping them to the smallest factor that produces the same results in func_v). If that's not enough, you can hazard rounding the input values when you first get them to 3 or 2 decimal points -- this will make it an imperfect calculation, but probably the results won't be too different that it won't matter. You can experiment with this fuzziness if it helps you get the program faster. But please verify that the trig calls are really a slowdown to speed first.

Edited by NinthDesertDude
Link to comment
Share on other sites

6 hours ago, NinthDesertDude said:

Thoughts for speed...

 

1. Parallelize the for loops in your pre-render if you can. It's worth tracking two copies of xx/yy and each intermediate variable you defined outside the for-loops, if that's necessary to make it work. Speed will definitely improve if it's at all possible.

 

This is something I'm interested into doing. How to parallelize it. It's not possible to put it in render.

 

On 2. That might take some time to be doing. Doable, but takes a bit of time. I doubt that they do affect performance that much. My test with dummy code shows it is at least 2 times faster. Half as fast as the multi-threaded version coded in G'MIC. I will see if the table suggestion works.

G'MIC Filter Developer

Link to comment
Share on other sites

I made the table version of it. There's a error about it being out of range. :/

 

Table is generated from this python code

Spoiler
import math

length=50
precision=4

init_sin_table=[round(math.sin(x/length*2*math.pi),precision) for x in range(length)]
init_cos_table=[round(math.cos(x/length*2*math.pi),precision) for x in range(length)]
init_tan_table=[round(math.tan(x/length*2*math.pi),precision) for x in range(length)]
init_atan_table=[round(math.atan(x/length*2*math.pi),precision) for x in range(length)]

print(len(init_sin_table))

sin_table="double[] sin_table={"+",".join(map(str,init_sin_table))+"};"
cos_table="double[] cos_table={"+",".join(map(str,init_cos_table))+"};"
tan_table="double[] tan_table={"+",".join(map(str,init_tan_table))+"};"
atan_table="double[] atan_table={"+",".join(map(str,init_atan_table))+"};"

print(sin_table)
print(cos_table)
print(tan_table)
print(atan_table)

 

 

C#

Spoiler
// Name: Popcorn Fractal
// Submenu: Render
// Author: Reptorian
// Title: Popcorn Fractal
// Version: 1
// Desc: Render Popcorn Fractal into canvas. 
// Keywords: fractal
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help: 
#region UICode
IntSliderControl pts = 25; // [1,100] Points
DoubleSliderControl var_density = 1; // [0.01,2] Density
DoubleSliderControl H = 0.05; // [-5,5] H
DoubleSliderControl K = 3; // [-75,75] K
DoubleSliderControl var_zoom = 1; // [0.1,4] Zoom
AngleControl var_ang = 0; // [-180,180] Angle
PanSliderControl Origin = Pair.Create(0.000, 0.000); // Origin
ListBoxControl TrigMode = 0; // Trig Mode|Trig-4|Trig-6
ListBoxControl func_x1 = 0; // X-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x2 = 2; // X-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x3 = 1; // {!TrigMode} X-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y1 = 0; // Y-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y2 = 2; // Y-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y3 = 1; // {!TrigMode} Y-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
DoubleSliderControl var_midpoint_shift = 0; // [-1,1] Midpoint Shift
DoubleSliderControl multiplier = 1; // [0.01,10] {!norm} Multiplier
CheckboxControl norm = false; // Normalize
#endregion
#if DEBUG
#endif

double length=50;
double[] sin_table={0.0,0.1253,0.2487,0.3681,0.4818,0.5878,0.6845,0.7705,0.8443,0.9048,0.9511,0.9823,0.998,0.998,0.9823,0.9511,0.9048,0.8443,0.7705,0.6845,0.5878,0.4818,0.3681,0.2487,0.1253,0.0,-0.1253,-0.2487,-0.3681,-0.4818,-0.5878,-0.6845,-0.7705,-0.8443,-0.9048,-0.9511,-0.9823,-0.998,-0.998,-0.9823,-0.9511,-0.9048,-0.8443,-0.7705,-0.6845,-0.5878,-0.4818,-0.3681,-0.2487,-0.1253};
double[] cos_table={1.0,0.9921,0.9686,0.9298,0.8763,0.809,0.729,0.6374,0.5358,0.4258,0.309,0.1874,0.0628,-0.0628,-0.1874,-0.309,-0.4258,-0.5358,-0.6374,-0.729,-0.809,-0.8763,-0.9298,-0.9686,-0.9921,-1.0,-0.9921,-0.9686,-0.9298,-0.8763,-0.809,-0.729,-0.6374,-0.5358,-0.4258,-0.309,-0.1874,-0.0628,0.0628,0.1874,0.309,0.4258,0.5358,0.6374,0.729,0.809,0.8763,0.9298,0.9686,0.9921};
double[] tan_table={0.0,0.1263,0.2568,0.3959,0.5498,0.7265,0.9391,1.2088,1.5757,2.1251,3.0777,5.2422,15.8945,-15.8945,-5.2422,-3.0777,-2.1251,-1.5757,-1.2088,-0.9391,-0.7265,-0.5498,-0.3959,-0.2568,-0.1263,-0.0,0.1263,0.2568,0.3959,0.5498,0.7265,0.9391,1.2088,1.5757,2.1251,3.0777,5.2422,15.8945,-15.8945,-5.2422,-3.0777,-2.1251,-1.5757,-1.2088,-0.9391,-0.7265,-0.5498,-0.3959,-0.2568,-0.1263};

double rot_x(double a, double b, double cos_ang,double sin_ang){
    return a * cos_ang - b * sin_ang;
}

double rot_y(double a, double b, double cos_ang,double sin_ang){
    return a * sin_ang + b * cos_ang;
}

double rescale_factor=2*Math.PI/50;

double table_sin(double v){
    int index=(int)(v/rescale_factor);
    return sin_table[index%50];
}

double table_cos(double v){
    int index=(int)(v/rescale_factor);
    return cos_table[index%50];
}

double table_tan(double v){
    int index=(int)(v/rescale_factor);
    return tan_table[index%50];
}

double func_v(double v,int c){
    switch(c){
       case 0: return table_sin(v);
       case 1: return Math.Cos(v);
       case 2: return Math.Tan(v);
       case 3: return Math.Atan(v);
    }
    return Math.Atan(v);
}

int maxnum;
int [,] Popcorn_Array;

void PreRender(Surface dst, Surface src)
{
    int w = src.Width;
    int h = src.Height;    
    bool sd_cond = w>h;
    double dw = (double)(w);
    double dh = (double)(h);
    if (Popcorn_Array == null){Popcorn_Array = new int [w,h];}
    else {Array.Clear(Popcorn_Array, 0, w*h);}
    bool use_trig6 = TrigMode == 1;
    double density = 1 / var_density;
    double zoom = 1 / var_zoom;
    double ang = (var_ang/180) * Math.PI;
    double origin_x = Origin.First * -1 * zoom;
    double origin_y = Origin.Second * zoom;
    double sd = Math.Max(dw,dh) / Math.Min(dw,dh);
    double sx = sd_cond ? sd : 1  ;
    double sy = sd_cond ? 1  : sd ;
    double cx = dw / 2;
    double cy = dh / 2;
    double osx = origin_x * sx ;
    double osy = origin_y * sy ;
    double cx_zoom = cx / zoom;
    double cy_zoom = cy / zoom;
    double cxsx = cx * sx;
    double cysy = cy * sy;
    double cos_ang = Math.Cos(ang);
    double sin_ang = Math.Sin(ang);
    bool angcondition = (var_ang - 360*Math.Floor(var_ang/360))>0;
    double xx,yy,xnew,ynew,xval,yval;
    int xpos,ypos;
    var timer = new Stopwatch();

    timer.Start();

    for (double ix=0 ; ix < dw ; ix+=density)
    {
        if (IsCancelRequested) return;

        for (double iy=0 ; iy < dh ; iy+=density)
        {
            xx = zoom * (ix-cx) / cx;
            yy = zoom * (iy-cy) / cy;
            xx*=sx;
            yy*=sy;
            xx+=origin_x;
            yy+=origin_y;

            for (int ptn=0 ; ptn < pts ; ptn++)
            {
                if (use_trig6) {
                    xnew  = xx-H*func_v(yy+func_v(K*yy+func_v(K*yy,func_x3),func_x2),func_x1);
                    ynew  = yy-H*func_v(xx+func_v(K*xx+func_v(K*xx,func_y3),func_y2),func_y1);
                }
                else {
                    xnew = xx-H*func_v(yy+func_v(K*yy,func_x2),func_x1);
                    ynew = yy-H*func_v(xx+func_v(K*xx,func_y2),func_y1);
                }

                if (angcondition){
                    xval = ((rot_x(xnew,ynew,cos_ang,sin_ang) - osx)*cx_zoom + cxsx)/sx;
                    yval = ((rot_y(xnew,ynew,cos_ang,sin_ang) - osy)*cy_zoom + cysy)/sy;
                }
                else{
                    xval = ((xnew - osx)*cx_zoom+cxsx)/sx;
                    yval = ((ynew - osy)*cy_zoom+cysy)/sy;
                }

                xpos = (int)(Math.Round(xval));
                ypos = (int)(Math.Round(yval));
                
                if ((xpos>=0&&ypos>=0)&&(xpos<w&&ypos<h)){
                    Popcorn_Array[xpos,ypos]++;
                    if (Popcorn_Array[xpos,ypos]>maxnum) { maxnum = Popcorn_Array[xpos,ypos]; }
                }

                xx = xnew;
                yy = ynew;
            }
        }
    }

    timer.Stop();
    Debug.WriteLine(timer.Elapsed);
}

void Render(Surface dst, Surface src, Rectangle rect)
{
    double midpoint = 1 - Math.Abs(var_midpoint_shift);
    bool cond_midpoint = var_midpoint_shift>=0;
    double pval;
    int val;

    for (int y = rect.Top; y < rect.Bottom; y++)
    {        
        for (int x = rect.Left; x < rect.Right; x++)
        {
            pval = (double)(Popcorn_Array[x,y])/(double)(maxnum);
            if (cond_midpoint){pval=Math.Pow(pval,midpoint);}
            else {pval=1-(Math.Pow(1-pval,midpoint));}
            if(norm){pval*=255;}
            else {pval*=(double)(maxnum)*multiplier;}
            val=(int)(pval);
            dst[x,y]=ColorBgra.FromBgraClamped(val,val,val,255);
        }
    }
}

 

 

G'MIC Filter Developer

Link to comment
Share on other sites

index%50 sometimes gives negative result. 

 

In C#, % operator is not the modulus operator, it's remainder operator. So it behaves differently for negative numbers. In Python -100 % 3 = 2,in C# -100 % 3 = -1

 

I use this method in C#:

public static int Mod(int a, int n)
{
    return a >= n ? a % n : a >= 0 ? a : n - 1 - (-1 - a) % n;
}
  • Like 1
  • You're a Smart Cookie! 1
Link to comment
Share on other sites

Thanks, I have been able to get my code working now. The table idea actually slowed down the generation of Popcorn Fractal. I still want to know how to parallelize the code within PreRender though.

G'MIC Filter Developer

Link to comment
Share on other sites

5 hours ago, Reptillian said:

Thanks, I have been able to get my code working now. The table idea actually slowed down the generation of Popcorn Fractal. I still want to know how to parallelize the code within PreRender though.

 

I tried to parallelize. It runs faster now.

 

Spoiler
// Name: Popcorn Fractal
// Submenu: Render
// Author: Reptorian
// Title: Popcorn Fractal
// Version: 1
// Desc: Render Popcorn Fractal into canvas. 
// Keywords: fractal
// URL: https://forums.getpaint.net/profile/85868-reptillian/
// Help: 
#region UICode
IntSliderControl pts = 25; // [1,100] Points
DoubleSliderControl var_density = 1; // [0.01,2] Density
DoubleSliderControl H = 0.05; // [-5,5] H
DoubleSliderControl K = 3; // [-75,75] K
DoubleSliderControl var_zoom = 1; // [0.1,4] Zoom
AngleControl var_ang = 0; // [-180,180] Angle
PanSliderControl Origin = Pair.Create(0.000, 0.000); // Origin
ListBoxControl TrigMode = 0; // Trig Mode|Trig-4|Trig-6
ListBoxControl func_x1 = 0; // X-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x2 = 2; // X-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_x3 = 1; // {!TrigMode} X-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y1 = 0; // Y-S|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y2 = 2; // Y-T|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
ListBoxControl func_y3 = 1; // {!TrigMode} Y-U|Sinusoidal|Cosinusoidal|Tangent|Arc-Tangent
DoubleSliderControl var_midpoint_shift = 0; // [-1,1] Midpoint Shift
DoubleSliderControl multiplier = 1; // [0.01,10] {!norm} Multiplier
CheckboxControl norm = false; // Normalize
#endregion
#if DEBUG
#endif

double rot_x(double a, double b, double cos_ang,double sin_ang){
    return a * cos_ang - b * sin_ang;
}

double rot_y(double a, double b, double cos_ang,double sin_ang){
    return a * sin_ang + b * cos_ang;
}


double func_v(double v,int c){
    switch(c){
       case 0: return Math.Sin(v);
       case 1: return Math.Cos(v);
       case 2: return Math.Tan(v);
       case 3: return Math.Atan(v);
    }
    return Math.Atan(v);
}

int maxnum;
int [,] Popcorn_Array;

IEnumerable<double> iter(double fromInclusive, double toExclusive, double step)
{
    for (double v = fromInclusive; v < toExclusive; v += step)
    {
        yield return v;
    }
}

void PreRender(Surface dst, Surface src)
{
    int w = src.Width;
    int h = src.Height;    
    bool sd_cond = w>h;
    double dw = (double)(w);
    double dh = (double)(h);
    if (Popcorn_Array == null){Popcorn_Array = new int [w,h];}
    else {Array.Clear(Popcorn_Array, 0, w*h);}
    bool use_trig6 = TrigMode == 1;
    double density = 1 / var_density;
    double zoom = 1 / var_zoom;
    double ang = (var_ang/180) * Math.PI;
    double origin_x = Origin.First * -1 * zoom;
    double origin_y = Origin.Second * zoom;
    double sd = Math.Max(dw,dh) / Math.Min(dw,dh);
    double sx = sd_cond ? sd : 1  ;
    double sy = sd_cond ? 1  : sd ;
    double cx = dw / 2;
    double cy = dh / 2;
    double osx = origin_x * sx ;
    double osy = origin_y * sy ;
    double cx_zoom = cx / zoom;
    double cy_zoom = cy / zoom;
    double cxsx = cx * sx;
    double cysy = cy * sy;
    double cos_ang = Math.Cos(ang);
    double sin_ang = Math.Sin(ang);
    bool angcondition = (var_ang - 360*Math.Floor(var_ang/360))>0;
    var timer = new Stopwatch();

    timer.Start();

    System.Threading.Tasks.Parallel.ForEach(iter(0, dw, density), (ix, loopState) =>
    {
        if (IsCancelRequested)
        {
            loopState.Stop();
            return;
        }

        for (double iy = 0; iy < dh; iy += density)
        {
            double xx = zoom * (ix - cx) / cx;
            double yy = zoom * (iy - cy) / cy;
            double xval, yval, xnew, ynew;
            xx *= sx;
            yy *= sy;
            xx += origin_x;
            yy += origin_y;

            for (int ptn = 0; ptn < pts; ptn++)
            {
                if (use_trig6)
                {
                    xnew = xx - H * func_v(yy + func_v(K * yy + func_v(K * yy, func_x3), func_x2), func_x1);
                    ynew = yy - H * func_v(xx + func_v(K * xx + func_v(K * xx, func_y3), func_y2), func_y1);
                }
                else
                {
                    xnew = xx - H * func_v(yy + func_v(K * yy, func_x2), func_x1);
                    ynew = yy - H * func_v(xx + func_v(K * xx, func_y2), func_y1);
                }

                if (angcondition)
                {
                    xval = ((rot_x(xnew, ynew, cos_ang, sin_ang) - osx) * cx_zoom + cxsx) / sx;
                    yval = ((rot_y(xnew, ynew, cos_ang, sin_ang) - osy) * cy_zoom + cysy) / sy;
                }
                else
                {
                    xval = ((xnew - osx) * cx_zoom + cxsx) / sx;
                    yval = ((ynew - osy) * cy_zoom + cysy) / sy;
                }

                int xpos = (int)(Math.Round(xval));
                int ypos = (int)(Math.Round(yval));

                if ((xpos >= 0 && ypos >= 0) && (xpos < w && ypos < h))
                {
                    int val = Interlocked.Increment(ref Popcorn_Array[xpos, ypos]);
                    if (val > maxnum)
                    {
                        Interlocked.Exchange(ref maxnum, val);
                    }
                }

                xx = xnew;
                yy = ynew;
            }
        }
    });

    timer.Stop();
    Debug.WriteLine(timer.Elapsed);
}

void Render(Surface dst, Surface src, Rectangle rect)
{
    double midpoint = 1 - Math.Abs(var_midpoint_shift);
    bool cond_midpoint = var_midpoint_shift>=0;
    double pval;
    int val;

    for (int y = rect.Top; y < rect.Bottom; y++)
    {        
        for (int x = rect.Left; x < rect.Right; x++)
        {
            pval = (double)(Popcorn_Array[x,y])/(double)(maxnum);
            if (cond_midpoint){pval=Math.Pow(pval,midpoint);}
            else {pval=1-(Math.Pow(1-pval,midpoint));}
            if(norm){pval*=255;}
            else {pval*=(double)(maxnum)*multiplier;}
            val=(int)(pval);
            dst[x,y]=ColorBgra.FromBgraClamped(val,val,val,255);
        }
    }
}
 

 

 

Edited by otuncelli
  • Like 1
Link to comment
Share on other sites

@otuncelli Sorry to ask, but I am finding that threading seem unstable. No remedy here? GUI locks up for a bit for large images, and I have to wait for it to finish after clicking ok.

 

Other than that, works.

 

Edited by Reptillian

G'MIC Filter Developer

Link to comment
Share on other sites

Why is your inner loop inside of Parallel.ForEach() walking the y values? You're trashing cache locality

 

Always walk pixels left-to-right, top-to-bottom.

 

Also it's a very bad idea to do for-loops with floating point values. You can easily get into an infinite loop with for (double x = initial; x < max; x += step) if step ends up being less than x's epsilon (in other words, x+step == x).

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

And once you've parallelized your PreRender(), you really ought to move all of that into Render(). That will give you the best performance and early cancellation support.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

45 minutes ago, Rick Brewster said:

Why is your inner loop inside of Parallel.ForEach() walking the y values? You're trashing cache locality

 

Because it's not a Surface object it's a multi-dimensional array. Surface object is not used in this loop. Memory arrangement for multidimensional array is like this: [0, 0] [0, 1] [0, 2] [1, 0] [1, 1] [1, 2]. So in this case, it's faster walking the y values in inner loop. Also ix, iy variables are not directly used for accessing array items.

 

45 minutes ago, Rick Brewster said:

Also it's a very bad idea to do for-loops with floating point values. You can easily get into an infinite loop with for (double x = initial; x < max; x += step) if step ends up being less than x's epsilon (in other words, x+step == x).

 

I agree, but I never encountered this problem. I always prefer integer loops. This snippet was suggested here:

https://devblogs.microsoft.com/pfxteam/parallel-for-loops-over-non-integral-types/

Edited by otuncelli
Link to comment
Share on other sites

4 hours ago, Reptillian said:

@otuncelli Sorry to ask, but I am finding that threading seem unstable. No remedy here? GUI locks up for a bit for large images, and I have to wait for it to finish after clicking ok.

 

Other than that, works.

 

 

It's not optimal but maybe slow down a little to keep GUI responsive? I don't know much about Paint.NET's effect engine.

 

    System.Threading.Tasks.Parallel.ForEach(iter(0, dw, density), (ix, loopState) =>
    {
        if (IsCancelRequested)
        {
            loopState.Stop();
            return;
        }
       System.Threading.Thread.Sleep(1); // <-------
        for (double iy = 0; iy < dh; iy += density)
        {

 

Edited by otuncelli
Link to comment
Share on other sites

42 minutes ago, otuncelli said:

It's not optimal but maybe slow down a little to keep GUI responsive? I don't know much about Paint.NET's effect engine.

 

It doesn't work, it seems. I guess, I will have to add a warning to my plugin thread about that issue.

G'MIC Filter Developer

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...