leonbloy Posted May 5, 2011 Share Posted May 5, 2011 (edited) 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; } } } Edited May 5, 2011 by leonbloy Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted May 5, 2011 Share Posted May 5, 2011 Moved to Plugin Developer's Center Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
BoltBait Posted May 5, 2011 Share Posted May 5, 2011 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 Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
leonbloy Posted May 5, 2011 Author Share Posted May 5, 2011 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. Quote Link to comment Share on other sites More sharing options...
BoltBait Posted May 5, 2011 Share Posted May 5, 2011 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. Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
leonbloy Posted May 5, 2011 Author Share Posted May 5, 2011 (edited) 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 May 5, 2011 by BoltBait Quote Link to comment Share on other sites More sharing options...
BoltBait Posted May 5, 2011 Share Posted May 5, 2011 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. Quote Download: BoltBait's Plugin Pack | CodeLab | and a Free Computer Dominos Game Link to comment Share on other sites More sharing options...
MadJik Posted May 6, 2011 Share Posted May 6, 2011 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; } } } Quote My DeviantArt | My Pictorium | My Plugins | Donate via Paypal Link to comment Share on other sites More sharing options...
leonbloy Posted May 6, 2011 Author Share Posted May 6, 2011 And here's a doc regarding these kind of issues with Random, and how to deal with concurrency http://csharpindepth.com/Articles/Chapter12/Random.aspx Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted May 6, 2011 Share Posted May 6, 2011 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. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
leonbloy Posted May 6, 2011 Author Share Posted May 6, 2011 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 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.