Jump to content


Photo

codelab random troubles


10 replies to this topic

#1 leonbloy

leonbloy
  • Newbies
  • 6 posts
  • Reputation:0

Posted 05 May 2011 - 04:43 PM

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;
        }
    }
}

Attached Thumbnails

  • Clipboard-1.png

Edited by leonbloy, 05 May 2011 - 04:43 PM.

  • 0

#2 Rick Brewster

Rick Brewster

    Paint.NET Author and Developer

  • Administrators
  • 13,554 posts
  • LocationKirkland, WA
  • Reputation:325

Posted 05 May 2011 - 04:54 PM

Moved to Plugin Developer's Center
  • 0
The Paint.NET Blog: http://blog.getpaint.net/
Donations are always appreciated! http://www.getpaint.net/donate.html

Posted Image

#3 BoltBait

BoltBait

    2013 Movie Guru Award Winner

  • Administrators
  • 9,591 posts
  • LocationCalifornia, USA
  • Reputation:299

Posted 05 May 2011 - 06:31 PM

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..../uielements.asp
  • 0
Click to play:
Posted ImagePosted ImagePosted ImagePosted ImagePosted Image
Download: BoltBait's Plugin Pack | CodeLab | More... and how about a Computer Dominos Game

#4 leonbloy

leonbloy
  • Newbies
  • 6 posts
  • Reputation:0

Posted 05 May 2011 - 06:52 PM

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.

Attached Thumbnails

  • Clipboard-2.png

  • 0

#5 BoltBait

BoltBait

    2013 Movie Guru Award Winner

  • Administrators
  • 9,591 posts
  • LocationCalifornia, USA
  • Reputation:299

Posted 05 May 2011 - 08:41 PM

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.
  • 0
Click to play:
Posted ImagePosted ImagePosted ImagePosted ImagePosted Image
Download: BoltBait's Plugin Pack | CodeLab | More... and how about a Computer Dominos Game

#6 leonbloy

leonbloy
  • Newbies
  • 6 posts
  • Reputation:0

Posted 05 May 2011 - 09:32 PM

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, 05 May 2011 - 10:38 PM.

  • 0

#7 BoltBait

BoltBait

    2013 Movie Guru Award Winner

  • Administrators
  • 9,591 posts
  • LocationCalifornia, USA
  • Reputation:299

Posted 05 May 2011 - 10:40 PM

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....lp/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. ;)
  • 0
Click to play:
Posted ImagePosted ImagePosted ImagePosted ImagePosted Image
Download: BoltBait's Plugin Pack | CodeLab | More... and how about a Computer Dominos Game

#8 MadJik

MadJik
  • Members
  • 2,428 posts
  • LocationLille;France
  • Reputation:21

Posted 06 May 2011 - 03:20 AM

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;
    }
  }
}

  • 0

#9 leonbloy

leonbloy
  • Newbies
  • 6 posts
  • Reputation:0

Posted 06 May 2011 - 06:24 PM

And here's a doc regarding these kind of issues with Random, and how to deal with concurrency

http://csharpindepth...r12/Random.aspx
  • 0

#10 Rick Brewster

Rick Brewster

    Paint.NET Author and Developer

  • Administrators
  • 13,554 posts
  • LocationKirkland, WA
  • Reputation:325

Posted 06 May 2011 - 07:01 PM

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.
  • 0
The Paint.NET Blog: http://blog.getpaint.net/
Donations are always appreciated! http://www.getpaint.net/donate.html

Posted Image

#11 leonbloy

leonbloy
  • Newbies
  • 6 posts
  • Reputation:0

Posted 06 May 2011 - 07:50 PM

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 Posted Image


  • 0