Jump to content

codelab random troubles


leonbloy

Recommended Posts

I'm having problems with Random numbers generation inside CodeLab

For example, this simple code is supposed to generate white noise.

But, as you can see, it doesn't.

Any ideas? I'm using CodeLab 1.6, PaintNet v3.5.8


#region UICode
#endregion

Random rand = new Random();

void Render(Surface dst, Surface src, Rectangle rect)
{
   Random rand = new Random();
   ColorBgra CurrentPixel;
   byte v;
   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {   	
           CurrentPixel = src[x,y];
           v = (byte)rand.Next(256);
           CurrentPixel.R = v;
           CurrentPixel.G = v;
           CurrentPixel.B = v;
           dst[x,y] = CurrentPixel;
       }
   }
}

post-83064-130461378626_thumb.png

Edited by leonbloy
Link to comment
Share on other sites

I had the same problem with random numbers, so I added support right into CodeLab for them.

Try it this way instead:

#region UICode
ColorBgra Amount1 = ColorBgra.FromBgr(255,255,0); // Base Color
byte Amount2 = 0; // [255] Reseed
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
  ColorBgra CurrentPixel;
  for (int y = rect.Top; y < rect.Bottom; y++)
  {
     for (int x = rect.Left; x < rect.Right; x++)
     {
        CurrentPixel = src[x,y];
        if (RandomNumber.Next(255) > 128)
        {
           CurrentPixel = Amount1;
        }
        else
        {
           CurrentPixel.R = (byte)RandomNumber.Next(256);
           CurrentPixel.G = (byte)RandomNumber.Next(256);
           CurrentPixel.B = (byte)RandomNumber.Next(256);
        }
        CurrentPixel.A = (byte)255;

        dst[x,y] = CurrentPixel;
     }
  }
}

This is described at the bottom of this page: http://www.boltbait.com/pdn/CodeLab/help/uielements.asp

Link to comment
Share on other sites

I had the same problem with random numbers, so I added support right into CodeLab for them.

[/code]

...

This is described at the bottom of this page: http://www.boltbait..../uielements.asp

I had already seen that page, but I had understood that it was a convenience feature for dealing with seeds,

I didn't get from it that there was some (unexplained???) problem with the native Random class of .Net

(and that seems very strange... and somewhat scary to me). Some explanation?

Replacing the call to Random by your RandomNumber, I get better results, but still not truly random:

see those vertical strips, they appear always, with varying degrees. I feel something definetely wrong

is happening there.

post-83064-130462147207_thumb.png

Link to comment
Share on other sites

Yeah, using the following code, I'm seeing vertical stripes too:

#region UICode
byte Amount1 = 0; // [255] Reseed
#endregion

void Render(Surface dst, Surface src, Rectangle rect)
{
   ColorBgra CurrentPixel = ColorBgra.Black;
   byte shade;
   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {
           shade = (byte)RandomNumber.Next(256);
           CurrentPixel.R = shade;
           CurrentPixel.G = shade;
           CurrentPixel.B = shade;
           CurrentPixel.A = (byte)255;
           dst[x,y] = CurrentPixel;
       }
   }
}

I'm not sure what's causing them. Building as a DLL, CodeLab is generating the following code:

// Compiler options:  /unsafe /optimize /debug- /target:library /out:"C:\Program Files\Paint.NET\Effects\RandomTest.dll"
using System;
using System.Text;
using System.Reflection;
using PaintDotNet;
using PaintDotNet.Effects;
using PaintDotNet.IndirectUI;
using PaintDotNet.PropertySystem;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("RandomTestPlugin")]
[assembly: AssemblyDescription("RandomTest Plugin for Paint.NET. (Compiled by Code Lab v1.6)")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RandomTestPlugin")]
[assembly: AssemblyCopyright("Copyright © ")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: AssemblyVersion("1.0.*")]

namespace RandomTestEffect
{
   public class PluginSupportInfo : IPluginSupportInfo
   {
       public string Author
       {
           get
           {
               return "";
           }
       }
       public string Copyright
       {
           get
           {
               return ((AssemblyCopyrightAttribute)base.GetType().Assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)[0]).Copyright;
           }
       }

       public string DisplayName
       {
           get
           {
               return ((AssemblyProductAttribute)base.GetType().Assembly.GetCustomAttributes(typeof(AssemblyProductAttribute), false)[0]).Product;
           }
       }

       public Version Version
       {
           get
           {
               return base.GetType().Assembly.GetName().Version;
           }
       }

       public Uri WebsiteUri
       {
           get
           {
               return new Uri("http://www.getpaint.net/redirect/plugins.html");
           }
       }
   }

   [PluginSupportInfo(typeof(PluginSupportInfo), DisplayName = "RandomTest")]
   public class RandomTestEffectPlugin : PropertyBasedEffect
   {
       public static string StaticName
       {
           get
           {
               return "RandomTest";
           }
       }

       public static Image StaticIcon
       {
           get
           {
               return null;
           }
       }

       public RandomTestEffectPlugin()
           : base(StaticName, StaticIcon, null, EffectFlags.Configurable)
       {
           instanceSeed = unchecked((int)DateTime.Now.Ticks);
       }

       public enum PropertyNames
       {
           Amount1
       }

       [ThreadStatic]
       private static Random RandomNumber;

       private int randomSeed;
       private int instanceSeed;


       protected override PropertyCollection OnCreatePropertyCollection()
       {
           List<Property> props = new List<Property>();

           props.Add(new Int32Property(PropertyNames.Amount1, 0, 0, 255));

           return new PropertyCollection(props);
       }

       protected override ControlInfo OnCreateConfigUI(PropertyCollection props)
       {
           ControlInfo configUI = CreateDefaultConfigUI(props);

           configUI.SetPropertyControlValue(PropertyNames.Amount1, ControlInfoPropertyNames.DisplayName, string.Empty);
           configUI.SetPropertyControlType(PropertyNames.Amount1, PropertyControlType.IncrementButton);
           configUI.SetPropertyControlValue(PropertyNames.Amount1, ControlInfoPropertyNames.ButtonText, "Reseed");

           return configUI;
       }

       protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs)
       {
           this.Amount1 = (byte)newToken.GetProperty<Int32Property>(PropertyNames.Amount1).Value;
           randomSeed = Amount1;

           base.OnSetRenderInfo(newToken, dstArgs, srcArgs);
       }

       protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length)
       {
           if (length == 0) return;
           RandomNumber = GetRandomNumberGenerator(rois, startIndex);
           for (int i = startIndex; i < startIndex + length; ++i)
           {
               Render(DstArgs.Surface,SrcArgs.Surface,rois[i]);
           }
       }

       private Random GetRandomNumberGenerator(Rectangle[] rois, int startIndex)
       {
           Rectangle roi = rois[startIndex];
           return new Random(instanceSeed ^ (randomSeed << 16) ^ (roi.X << 8) ^ roi.Y);
       }

       #region User Entered Code
       #region UICode
       byte Amount1 = 0; // [255] Reseed
       #endregion

       void Render(Surface dst, Surface src, Rectangle rect)
       {
           ColorBgra CurrentPixel = ColorBgra.Black;
           byte shade;
           for (int y = rect.Top; y < rect.Bottom; y++)
           {
               for (int x = rect.Left; x < rect.Right; x++)
               {
                   shade = (byte)RandomNumber.Next(256);
                   CurrentPixel.R = shade;
                   CurrentPixel.G = shade;
                   CurrentPixel.B = shade;
                   CurrentPixel.A = (byte)255;
                   dst[x,y] = CurrentPixel;
               }
           }
       }

       #endregion
   }
}

As you can see, only one random number generator is created for the entire canvas. So, I'm not sure why the stripes exist.

I sent a PM to Ed to see if he can spot the issue.

Link to comment
Share on other sites

I think I've nailed it - partially.

I have found out that the -at least in editing mode- plugin calls the Render() method a lot of times, even when one has done some single eidting change and wait for the code to redraw. It seems to start a thread for each line or some a group of lines. See this code (warning: it write on your C:\temp\ dir, adjust it if you need it)

#region UICode
#endregion
bool inLoop;
void Render(Surface dst, Surface src, Rectangle rect)
{
   inLoop=false;
   ColorBgra CurrentPixel;
   byte v=0;

   try {
   System.IO.StreamWriter sr = new System.IO.StreamWriter(@"C:/temp/codelabtest.txt",true);
   sr.Write("inLoop="+inLoop + " Top=" + rect.Top + "\n");
   sr.Close(); 
   } catch(Exception e) {}

   inLoop=true;
   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {   	
           CurrentPixel = src[x,y];
           CurrentPixel.R = v;
           CurrentPixel.G = v;
           CurrentPixel.B = v;
           dst[x,y] = CurrentPixel;
       }
   }
   inLoop=false;

}

The writing to the file sometimes fails (with "file used by another process" error), but sometimes it works, and prints a lot of lines (try with a small image), and sometimes it prints "inLoop=true" (big warning!)

The moral is that a Render() function should be stricyl thread-safe.

My former code, actually, does not have a concurrency problem, as a new Random object is obtained locally in each Render() call. BUT the problem is (I guess) that the several Random() objects that are obtained are internally seeded with some system timestamp that happens to be the same. Because of this, consecutive lines will have -probably- the same random sequence.

An alternative would be to seed the Random object with some combination of timestamp plus rect coordinates.

But a more elegant/secure approach seems to have a single Random objects, as a class attribute.

However, it seems that the Random class does not like to be called simulateously from several threads (no idea why).

I must synchronize the access, to make it work:

#region UICode
#endregion
Random rand = new Random();
void Render(Surface dst, Surface src, Rectangle rect)
{
   ColorBgra CurrentPixel;
   byte v=0;
   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {   	
           CurrentPixel = src[x,y];
           v = getRandByte();
           CurrentPixel.R = v;
           CurrentPixel.G = v;
           CurrentPixel.B = v;
           dst[x,y] = CurrentPixel;
       }
   }
   //inLoop=false;

}


[system.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
public byte getRandByte() {
       return (byte)rand.Next(256);
  }

You can experiment the difference by commenting out the Synchronized annotation.

Edited by BoltBait
Link to comment
Share on other sites

I have found out that the -at least in editing mode- plugin calls the Render() method a lot of times, even when one has done some single eidting change and wait for the code to redraw. It seems to start a thread for each line or some a group of lines.

That behavior is documented here: http://www.boltbait.com/pdn/CodeLab/help/overview.asp

I think I've nailed it - partially.

...it seems that the Random class does not like to be called simulateously from several threads (no idea why).

I must synchronize the access, to make it work

Nice job finding a solution to this "random" issue. ;)

Link to comment
Share on other sites

Hi,

Thanks for the code, that would help me a lot!

To go on with the multiple Render() calls this code shows the facts:

// Count one by one and color in scale per channel the pixels in the usual x,y loops.
// You could think you will create a gradient, but surprise!

//Set the steps per channel
int Amount1=2;  //[-100,100]Red Step
int Amount2=2;  //[-100,100]Green Step
int Amount3=2;  //[-100,100]Blue Step

// Define the counter to be unique regardless the (multi)threads
[ThreadStatic]
public static int threadCount = 0;

void Render(Surface dst, Surface src, Rectangle rect)
{
 // Count + 1
 ++threadCount;

 ColorBgra CurrentPixel;
 for(int y = rect.Top; y < rect.Bottom; y++)
 {
   for (int x = rect.Left; x < rect.Right; x++)
   {
     CurrentPixel = src[x,y];
     CurrentPixel.R = (byte)((threadCount * Amount1) % 255);
     CurrentPixel.G = (byte)((threadCount * Amount2) % 255);
     CurrentPixel.B = (byte)((threadCount * Amount3) % 255);
     dst[x,y] = CurrentPixel;
   }
 }
}

Link to comment
Share on other sites

If you use a single Random instance from multiple threads, it will probably not produce very good results.

The trick is to create a [ThreadStatic] instance of Random, and initialize it using a seed which is a simple hash of the current thread ID and the current time.

[ThreadStatic]
private static Random threadRandom;

...
OnRender(...)
{
...
Random random = threadRandom;
if (random == null)
{
	random = new Random(Thread.CurrentThread.GetHashCode() ^ ((int)DateTime.Now.Ticks));
threadRandom = random;
}
...
... random.Next(...) ...
...
}

This is what the built-in AddNoiseEffect does.

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

If you use a single Random instance from multiple threads, it will probably not produce very good results.

That's kind of an understatement. What I've experienced, when two thread enters concurrently a random.next() method is that the object is screwed, its internal state is not consistent, and it starts returning zero always !

Well, I've learned something new boltbait.roll.png

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