The base System.Drawing.Color class returning only one color after a number of iterations

457 Views Asked by At

Disclaimer: I have probably included too much information, so don't be scared off by the amount of info included.

I want to make a simple 2D game with graphics for the c# console application. To learn how I will handle the basics of the graphics I made a small project where I am playing around with the base Color class (and the library Colorful.Console, more info at: http://colorfulconsole.com/). Just trying to understand the basics of color representation and mixing.

However, I have run into an issue when it comes to painting multiple colors. Since the console only can keep 16 colors by default, it will return custom rgb colors until the 15th iteration (I assume one color is reserved for black) of the program, and then only return the 15th over and over. Is there any way of changing the value of one of the "KnownColor"s over and over again instead of filling all of them and then not being able to overwrite the new values?

My code (simplified) looks something like:


class Object{
    Color color; 
    //for simplicity, let's say that I previously had byte a, r, g, b defined here;
    public Object()
    {
        byte[] rgba = new byte[4];
        Random random = new Random();
        random.NextBytes(rgba);
        color = Color.FromArgb(rgba[0], rgba[1], rgba[2], rgba[3]);
        //previously I assigned a = rbga[0] and so on.
    }
}

class Program
{
    List<Object> objects = new List<Object>();
    static void Main(string[] args)
    {
        while(true)
        {
        objects.Add(new Object()); //I have some user input here, but essentially.
            foreach(Object o in objects) 
            {
                PrintColor(o.color);
                PaintColor(o.color);
            }
        }
    }

    void PrintColor(Color c)
    {
        Console.Write("R:" + c.R + ", G:" + c.G +", B:"+ c.B + ", A:" + c.A);
    }

    static void PaintColor(Color c)
    {
        Console.BackgroundColor = Color.FromArgb(c.R, c.G, c.B);
        Console.Write(" ");
        Console.BackgroundColor = Color.Black;
    }

}

I also tried to make a program that is supposed to draw a diagonal gray gradient (and later on a full-rgb gradient), but in vain.

class Program
{
    static void Main(string[] args)
    {
        GraphicsEngine.GetWindowSize();
        while (true)
        {
            GraphicsEngine.FillWindowGradient();
            Console.Clear(); //I am going to replace this with moving the cursor to 0,0

            if (Console.KeyAvailable)
            {
                GraphicsEngine.GetWindowSize();
            }
        }
    }

    public static void FillWindowGradient()
    {
        int yMax = windowSize.GetLength(0);
        int xMax = windowSize.GetLength(1);
        for (int y = 0; y< yMax; y++)
            for (int x = 0; x < xMax; x++)
            {
                PaintGray((byte)((y/yMax + x/xMax)*127));
            }
    }

    public static void PaintGray(byte gray)
    {
        Console.BackgroundColor = Color.FromArgb(gray, gray, gray);
        Console.Write(" ");
        Console.BackgroundColor = Color.Black;
    }
}

This example worked with any one color it started with, but couldn't handle the gradient, something I assume have something to do with the same issue as in the first example.

PS: it would also be nice to know if there's a "cheaper"/faster way of coloring a cell than:

Console.BackgroundColor = Color.Argb(a, r, g, b);

Console.Write(" ");

1

There are 1 best solutions below

4
On

Your problem is that your are creating a new Random object every time you generate a new object of type Object.

From MSDN:

On the .NET Framework only, because the clock has finite resolution, using the parameterless constructor to create different Random objects in close succession creates random number generators that produce identical sequences of random numbers.

Possible solution:

  1. Create a single Random object in your Program class and use initialize it using the current timestamp.

    Random _random = new Random((int)DateTime.UtcNow.Ticks);
    
  2. Pass the instance of the random to the Object class.

  3. OR, much cleaner -> Pass the actual random color to the Object in constructor.

    class Object
    {
        private Color color; 
    
        public Object(Color c)
        {        
            color = c;   
        }
    }
    
    var rgba = new byte[4];
    random.NextBytes(rgba);
    var obj = new Object(Color.FromArgb(rgba[0], rgba[1], rgba[2], rgba[3]));
    

Edit:

Following up on your comments. I've used your example to generate Object instances in a loop, generating a random Color. At the end, checking for duplicates using ToArgb as a key. Running 1000 times in a loop, generated no duplicates. Running 1M times, ~110 duplicates on average.

class Program
{
    static List<Object> objects = new List<Object>();

    static Random _random = new Random((int) DateTime.UtcNow.Ticks);

    static void Main(string[] args)
    {
        var i = 0;
        while (i<1000)
        {
            var rgba = new byte[4];
            _random.NextBytes(rgba);
            objects.Add(new Object(rgba));
            i++;
        }

        foreach (Object o in objects)
        {
            PrintColor(o.color);
        }

        var query = objects.GroupBy(x => x.color.ToArgb())
            .Where(g => g.Count() > 1)
            .Select(y => y.Key)
            .ToList();

        Console.WriteLine($"Duplicates {query.Count}");
    }

    static void PrintColor(Color c)
    {
        Console.WriteLine("R:" + c.R + ", G:" + c.G + ", B:" + c.B + ", A:" + c.A);
    }
}