Nested for loops

Started by
10 comments, last by Diosces 16 years, 2 months ago
I'm trying to display a form that toggles thru 256 shades in each primary color through nested for loops. I believe it was working right when I had one nested statement but when I added another I just toggle thru a yellow spectrum. I would build in an echo function to see whats wrong but I'm wiped after work, etc.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            while (Visible)
            {
                for (int c = 0; c < 254 && Visible; c++)
                {
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(3);
                    this.BackColor = Color.FromArgb(254 - c, c, c);
                    for (int a = 0; a < 254 && Visible; a++)
                    {
                        Application.DoEvents();
                        System.Threading.Thread.Sleep(3);
                        this.BackColor = Color.FromArgb(254 - c, 254 - a, c);
                        for (int b = 0; b < 254 && Visible; b++)
                        {
                            Application.DoEvents();
                            System.Threading.Thread.Sleep(3);
                            this.BackColor = Color.FromArgb(254 - c, 254 - a, 254-b);
                        }
                    }
                }


            }
        }
    }
}



Hints or answers to what went wrong on adding the second nested statement is greatly appreciated.
Advertisement
this.BackColor = Color.FromArgb(254 - c, c, c);
Try deleting this line, and the one inside the second loop, and only putting it inside the third loop. Also, why are you using c c c as the values? surely it should be a,b,c all the way? I might have misunderstood that part, however.

Also, the blue element is changing pretty rapidly, the green element not so fast, and the red element slowest. If the sleep(3) means 3 milliseconds, then the algorythm is biased towards the red end of the spectrum (since the red values are more static than the other two).
This is because the speed of the inner two loops means you only see the AVERAGE values of red and blue, due to screen response time and the fact that 3ms is too fast for the human eye to detect.

If you want to go through the entire spectrum, you need to use a different system altogether, with some for loops working in series:

START: R=255 G=0 B=0
LOOP 1: green value increases until R=255 G=255 B=0
LOOP 2: red value decreases until R=0 G=255 B=0
LOOP 3: blue value increases until R=0 G=255 B=255
LOOP 4: green value decreases until R=0 G=0 B=255
LOOP 5: red value increases until R=255 G=0 B=255
LOOP 6: blue value decreases until R=255 G=0 B=0

This way you will go through the entire spectrum in the order order red>orange>yellow>greenishyellow>green>cyan>blue>purple>magenta. You can modify the overall saturation of colour by reducing the lower number. (here it is 0)

It may be an idea to pre-calculate these values.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Quote:Original post by Diosces
I'm trying to display a form that toggles thru 256 shades

Funny, your code says 254 instead of 256.

Anyway, here is a solution with only one for loop (untested, but you get the idea):

for (int i = 0xffffff; i >= 0 && Visible; --i){    int r = i & 0xff;    int g = (i >> 8) & 0xff;    int b = i >> 16;    Application.DoEvents();    System.Threading.Thread.Sleep(3);    this.BackColor = Color.FromArgb(r, g, b);}


Let me know if this works for you.
Guys thanks for the replies.

Species:
I hear what you're saying about the unequal delay.
As long as each color change is incremented every 3 ms I feel I might have gotten close to what I initially was shooting for. As it's written do you think I get 1) blue completely cycled through every 254x254 (65,025) times for every increment of red 2) green cycled through every 255 times for every increment of red?

Devfred:
You soultion is simple elegance. Unfortunately as a beginner I can't grasp the logic. It's early morning so maybe with a few cups of coffee+a triple esspresso maybe start to understand.

Thanks again

Could you test if the following, simpler code also works?

for (int i = 0xffffff; i >= 0 && Visible; --i){    Application.DoEvents();    System.Threading.Thread.Sleep(3);    this.BackColor = Color.FromArgb(i);}

Quote:Original post by DevFred
Could you test if the following, simpler code also works?

*** Source Snippet Removed ***


I used DevFred's method for colouring some boxes in a physics simulation, and it works nicely. My method is designed to transform smoothly between the colours, and this DevFred's first method works by turning the channels on and off.

DevFred, I think your simplified method will look the same as the previous one. Your first method works nicely, however.


Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
First, note the repetitiveness of the original code. The repetition comes from treating one iteration of each outer loop as somehow "special". Also, we can fix the bounds of the loops. All we want is for each value to range from 0 to 255, inclusive. That means we logically should start counting from 0, while < 256 (notice, one more than the last value, because of the strict inequality). Then, the colour values will be 255 - whatever: 255 - 0 = 255, and 255 - 255 = 0, so this will produce the whole valid range of values - from 255 to 0, inclusive.

private void button1_Click(object sender, EventArgs e){    while (Visible)    {        for (int c = 0; c < 255 && Visible; c++)        {            for (int a = 0; a < 255 && Visible; a++)            {                for (int b = 0; b < 255 && Visible; b++)                {                    Application.DoEvents();                    System.Threading.Thread.Sleep(3);                    this.BackColor = Color.FromArgb(255 - c, 255 - a, 255 - b);                }            }        }    }}


Next, we note that a simple 'return' can eliminate the repeated checks for 'Visible'ity:


private void button1_Click(object sender, EventArgs e){    while (true)    {        for (int c = 0; c < 255; c++)        {            for (int a = 0; a < 255; a++)            {                for (int b = 0; b < 255; b++)                {                    Application.DoEvents();                    System.Threading.Thread.Sleep(3);                    this.BackColor = Color.FromArgb(255 - c, 255 - a, 255 - b);                    if (!Visible) return;                }            }        }    }}


Now, we note that Color.FromArgb() is also overloaded to take one int argument, a single "packed" colour value. We are in effect "counting down" through all the possible colours: in the ARGB representation, the blue byte is least significant. The largest colour value we want to use is the one corresponding to a = 0, r = 255, g = 255, b = 255. If you look at that as a base -256 number, you can see what it equals: the same as FFFFFF in hex (since two hex digits correspond exactly to one base-256 digit). The smallest is zero, of course.

private void button1_Click(object sender, EventArgs e){    while (true)    {        for (int colour = 0xffffff; colour >= 0; --colour)        {            Application.DoEvents();            System.Threading.Thread.Sleep(3);            this.BackColor = Color.FromArgb(colour);            if (!Visible) return;        }    }}


Finally, we can set up the colour to loop around indefinitely, thus making the while loop redundant. We can do that by not having a loop-exit condition in the for loop, and using some modulus arithmetic in the per-loop change. However, now we can simply move our explicit loop exit (the Visible check) back into the for-initializer, since there is only one place we want to check. That's awkward, though: the loop exit has nothing to do with our "counter" variable, so it's a highly nonstandard way to use the for loop construct. Instead, we just use a while loop, initializing the "counter" outside.

private void button1_Click(object sender, EventArgs e){    int colour = 0xffffff;    while (Visible)    {        Application.DoEvents();        System.Threading.Thread.Sleep(3);        this.BackColor = Color.FromArgb(colour);        colour = (colour + 0xffffff) % 0x1000000); // or & 0xffffff, if you prefer    }}
Man, You guys rock! Thanks!

I'm trying to make the leap from simple statements to the more simple, more elegant(efficient) manner of doing something.

Devfred your stuff works

I definitely get most of it~70% and will study the feedback till I'm satisfied I can apply the solution methods to other, similar problems.

I especially dig Zahlman's stepped way of teaching how to 'distill'(removing the repetitiveness) to a final solution.
Quote:Original post by Zahlman
First, note the repetitiveness of the original code. The repetition comes from treating one iteration of each outer loop as somehow "special". Also, we can fix the bounds of the loops. All we want is for each value to range from 0 to 255, inclusive. That means we logically should start counting from 0, while < 256 (notice, one more than the last value, because of the strict inequality). Then, the colour values will be 255 - whatever: 255 - 0 = 255, and 255 - 255 = 0, so this will produce the whole valid range of values - from 255 to 0, inclusive.

private void button1_Click(object sender, EventArgs e){    while (Visible)    {        for (int c = 0; c < 255 && Visible; c++)        {            for (int a = 0; a < 255 && Visible; a++)            {                for (int b = 0; b < 255 && Visible; b++)                {                    Application.DoEvents();                    System.Threading.Thread.Sleep(3);                    this.BackColor = Color.FromArgb(255 - c, 255 - a, 255 - b);                }            }        }    }}


Next, we note that a simple 'return' can eliminate the repeated checks for 'Visible'ity:


private void button1_Click(object sender, EventArgs e){    while (true)    {        for (int c = 0; c < 255; c++)        {            for (int a = 0; a < 255; a++)            {                for (int b = 0; b < 255; b++)                {                    Application.DoEvents();                    System.Threading.Thread.Sleep(3);                    this.BackColor = Color.FromArgb(255 - c, 255 - a, 255 - b);                    if (!Visible) return;                }            }        }    }}


Now, we note that Color.FromArgb() is also overloaded to take one int argument, a single "packed" colour value. We are in effect "counting down" through all the possible colours: in the ARGB representation, the blue byte is least significant. The largest colour value we want to use is the one corresponding to a = 0, r = 255, g = 255, b = 255. If you look at that as a base -256 number, you can see what it equals: the same as FFFFFF in hex (since two hex digits correspond exactly to one base-256 digit). The smallest is zero, of course.

private void button1_Click(object sender, EventArgs e){    while (true)    {        for (int colour = 0xffffff; colour >= 0; --colour)        {            Application.DoEvents();            System.Threading.Thread.Sleep(3);            this.BackColor = Color.FromArgb(colour);            if (!Visible) return;        }    }}


Finally, we can set up the colour to loop around indefinitely, thus making the while loop redundant. We can do that by not having a loop-exit condition in the for loop, and using some modulus arithmetic in the per-loop change. However, now we can simply move our explicit loop exit (the Visible check) back into the for-initializer, since there is only one place we want to check. That's awkward, though: the loop exit has nothing to do with our "counter" variable, so it's a highly nonstandard way to use the for loop construct. Instead, we just use a while loop, initializing the "counter" outside.

private void button1_Click(object sender, EventArgs e){    int colour = 0xffffff;    while (Visible)    {        Application.DoEvents();        System.Threading.Thread.Sleep(3);        this.BackColor = Color.FromArgb(colour);        colour = (colour + 0xffffff) % 0x1000000); // or & 0xffffff, if you prefer    }}


Wow, I certainly learned something from this. Its always good when somebody takes the time to share knowledge. Thanks Zahlman.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Zalman

I think I got all of it can you just explain the math in this part?
I know 0x specifies take the hex string and convert to an int,
how it's incremneting looks like white value is added to white value ffffff is being added to ffffff in each loop..



colour = (colour + 0xffffff) % 0x1000000); // or & 0xffffff, if you prefer

This topic is closed to new replies.

Advertisement