Paint.NET - Perlin Noise for 2-Dimensional Clouds

Upon browsing the Paint.NET Forums, I ran across someone looking to have a Photoshop tutorial converted to a Paint.NET tutorial. The first thing this tutorial showed was to use Filter -> Render -> Clouds. Currently, there is no “Cloud” effect within Paint.NET, so I set out to figure out how to mathematically generate clouds. After searching the Google Groups, I ran across something called “Perlin Noise,” created by Ken Perlin to generate textures for Tron. More searching led me to a few web sites explaining Perlin Noise and its features. From there I converted a few of the functions to work within the Paint.NET Code Lab, and I present that to you today.

There are five functions in the Paint.NET Code Lab implementation of Perlin Noise: Render, PerlinNoise2d, Smooth, Noise, and Interpolate.

The Render function looks as follows:

void Render(Surface dst, Surface src, Rectangle rect)
{
  for(int y = rect.Top; y < rect.Bottom; y++)
  {
    for (int x = rect.Left; x < rect.Right; x++)
    {
      ColorBgra srcBgra = src[x, y];
      byte c = (byte)(PerlinNoise2d(x, y) * 255);
      dst[x, y] = ColorBgra.FromBgra(srcBgra.B, srcBgra.G, srcBgra.R, c);
    }
  }
}

This is the entry point, from the Code Lab, to the drawing surface in Paint.NET. The Render function takes three parameters, the destination Surface, the source Surface, and the drawing area (or invalid) Rectangle. The function does not return a parameter.

What this function does is loop through each pixel in the drawing area, retrieves the source pixel information (Red, Green, Blue, Alpha), sets a byte (0 – 255) based on the result of the PerlinNoise2d function on the given pixel location, and sets the destination pixel by adjusting the alpha level for a “cloud”-like effect while keeping the source color.

This function can be adjusted depending on what you would like to do. For example, to only draw clouds in a transparent space, you can add an if-statement to check the transparency on the source pixel before setting the destination pixel. Then, line 9 from above (the last line in the for-loop) would become:

dst[x, y] = (0 == srcBgra.A) ? ColorBgra.FromBgra(srcBgra.B, srcBgra.G, srcBgra.R, c) : src[x, y];

Next, we look inside the PerlinNoise2d function. This is where all the magic happens:

double PerlinNoise2d(int x, int y)
{
  double total = 0.0;
  
  double frequency   = .015; // USER ADJUSTABLE
  double persistence = .65;  // USER ADJUSTABLE
  double octaves     = 8;    // USER ADJUSTABLE
  double amplitude   = 1;    // USER ADJUSTABLE
  
  for(int lcv = 0; lcv < octaves; lcv++)
  {
    total = total + Smooth(x * frequency, y * frequency) * amplitude;
    frequency = frequency * 2;
    amplitude = amplitude * persistence;
  }
  
  double cloudCoverage = 0; // USER ADJUSTABLE
  double cloudDensity  = 1; // USER ADJUSTABLE
  
  total = (total + cloudCoverage) * cloudDensity;
  
  if(total < 0) total = 0.0;
  if(total > 1) total = 1.0;
  
  return total;
}

As you can see, there is a lot going on within this function. Simply put, PerlinNoise2d takes in an (x, y) coordinate and returns a double value between 0 and 1, which gets expanded in our Render function to the range of 0 – 255. But, there are a lot of user adjustable variables which can vary the output.

Frequency gives you number of noise values defined between each 2-dimensional point.

Persistence is a constant multiplier adjusting our amplitude in each iteration.

Octaves define the number of iterations over the noise function for a particular point. With each iteration, the frequency is doubled and the amplitude is multiplied by the persistence.

Amplitude is the maximum value added to the total noise value.

Those variables act within the for-loop to generate a cumulative total. There are two other user adjustable variables that act upon the total after the for-loop that help in defining the finished look of your scene.

Cloud Coverage is a constant that gets added (or subtracted) to each total. The default value is zero, meaning that the total should not be altered. Adding values will increase the size of the clouds, while subtracting will reduce them.

Cloud Density is a constant that gets multiplied with the total to increase or decrease the apparent thickness of the clouds. The default value is 1, meaning the totalshould not be altered. Any number between 0 and 1 will reduce the apparent density (making the clouds appear more like fog), while any number greater than 1 will increase the density, and -1 will invert the clouds.

The actual call to the Noise function is buried within the Smooth function called within the for-loop. Since we have double values for our x and y coordinates, our Smoothfunction interpolates the noise value by using the four corners as known data points.

double Smooth(double x, double y)
{
  double n1 = Noise((int)x, (int)y);
  double n2 = Noise((int)x + 1, (int)y);
  double n3 = Noise((int)x, (int)y + 1);
  double n4 = Noise((int)x + 1, (int)y + 1);
  
  double i1 = Interpolate(n1, n2, x - (int)x);
  double i2 = Interpolate(n3, n4, x - (int)x);
  
  return Interpolate(i1, i2, y - (int)y);
}

Say we are given an (x, y) coordinate as shown in the graph. Noise values are only known for integer locations per our Noisefunction below. So, the Smooth function needs to approximate a value for the coordinate we were given based on the known values of the corner points (labeled v1, v2, v3, and v4 in the graph). Once we get our noise values for the corners, we have to interpolate the values to approximate (x, y). In this case, interpolating the values in the x-direction (done by passing the fractional value of x as the third parameter to our Interpolate function), we receive a value that is closer to the right side of the graph. And interpolating in the y-direction (done by passing the fractional value of y as the third parameter to ourInterpolate function), we receive a value that is closer to the top of the graph. After interpolating in the x- and y-directions, we have a noise value for (x, y) that we can return.

The Noise function acts as a semi-random number generator. Instead of returning a random number each time it is called, it returns a random number based on the input parameters. If you pass the same parameters, you get the same result. TheNoise function is as follows:

static Random r = new Random();
int r1 = r.Next(1000, 10000);
int r2 = r.Next(100000, 1000000);
int r3 = r.Next(1000000000, 2000000000);

double Noise(int x, int y)
{
  int n = x + y * 57;
  n = (n<<13) ^ n;
  
  return ( 1.0 - ( (n * (n * n * r1 + r2) + r3) & 0x7fffffff) / 1073741824.0);
}

The Noise function above will return a value between -1 and 1 for each (x, y) coordinate. Since this is only a semi-random number generator, having set values for r1, r2, and r3 (original values: 15731, 789221, 1376312589) will generate the same design each time. I have modified the function to set random values for r1, r2, and r3 so that we can generate a new cloud pattern each time the code is "built" in the Code Lab. The most important part of the Noise function is the return statement. By performing a bitwise AND operation with the Hexadecimal value 0×7FFFFFFF, the largest value possible is 0×80000000 (or 2147483648). Dividing by 1073741824.0 gives us a maximum value of 2.0 and a minimum value of 0.0, subtracting from 1.0, we return a value between -1 and 1.

In the Interpolate function, we are using a standard cosine interpolation formula:

double Interpolate(double x, double y, double a)
{
  double val = (1 - Math.Cos(a * Math.PI)) * .5;
  return  x * (1 - val) + y * val;
}

That completes the walkthrough of my Perlin Noise function for generating 2-dimensional clouds. Enjoy!

Examples

Download the C# Source Code Segment for Paint.NET’s Code Lab

Perlin Noise Sources

Video Games Live 2006

On Saturday, August 5th, 2006, Stephanie and I went to the Rosemont Theatre to see Video Games Live! The show was a lot of fun. For those of you who don't know,Video Games Live is an orchestra concert playing nothing but music from video games. The concert started around 8 o'clock, but we arrived around 6:30 pm to catch the pre-show entertainment (Space Invaders competition, Guitar Hero 2 competition, Costume Contest, and more). Originally, we were sitting in row HH, the very last row on the main floor, but our tickets got upgraded to row C! The third row behind the orchestra pit! Our seats for the night were great, we were very close to stage and could see and hear everything perfectly. My favorite part of the show would have to be the Final Fantasy piano sequence, and Steph enjoyed the Mario Brothers sequence. After the show (not before, like I thought) was a meet and greet with some people in the video game industry (mostly composers). I got autographs from the co-creators of Video Games Live, along with people responsible for the music in Dance Dance Revolution, Halo, God of War, Smash TV, and more. But, the best part about the meet and greet was meeting four of the men responsible for Mortal Kombat, including Ed Boon (lead programmer)! I had a lot of fun, and would definitely go back next year.

Updated: August 9th, 2006

During the concert, I took some pictures using Stephanie's RAZR phone. I have the images up in a photo album in the pictures directory. Take a look Video Games Live! Photo Album

Minnesota Birthdays

Stephanie and I went up to Minnesota last weekend to celebrate her nephew's 1st birthday and her aunt's 50th birthday.

Happy 1st Birthday Jack! On Friday, June 23rd, we celebrated Jack's first birthday at Stephanie's parents' house. He is one spoiled kid :) It took four of us a couple trips to bring all of his gifts inside!

Happy 50th Birthday Shelley! On Saturday, June 24th, we celebrated Shelley's 50th birthday at Gulden's Bar and Restaurant. This was a surprise party for Shelley where over 100 people showed up to show their love. It was pretty amazing that the secret was kept. The day before, on Jack's birthday, everyone in the family was around and had to keep quiet about Shelley's birthday the next day, but they managed. After the party was over, a few people stuck around and went up to the bar for karaoke night. Sorry, no video of that ;)

Kelly's Wedding

On July 21st, 2006, my sister, Kelly, got married to Laurence Bradley. And their daughter, Shalese (who just turned two), was one of the three flower girls in the wedding. But, when the flower girls came out, there were only two... No Shalese. Apparently, she didn't want to walk in by herself, and when my dad walked my sister down the aisle, he had Kelly on his left arm, and Shalese in his right. It was pretty cute (sorry, no pictures of this as of yet, I was in the wedding). Aside from that little mishap, and the wedding starting about 40 minutes later than expected, everything ran smoothly. Both of our families got along well and there were no major disasters that I can remember. Congratulations Kelly and Laurence! I hope you are enjoying your honeymoon in the Bahamas!

Sailing on Serenity

Steph and I went sailing for the first time today! Steph's research professor, Rafael, has a friend, Arnie, who owns a 42-foot sail boat named Serenity. I got to manage the ropes for the "jib" on the "port" side of the boat. We learned that the ship we were sailing in weighed about 22,000 pounds, 9,000 pounds of which is consumed in the "keel" which is used to counter the force of the wind. Whew. Anyway, we had a good time going out (in rough, choppy water) and it was a great way to get some fresh air and cool down after these past few high-90 degree days. Thanks Rafael for the invite and Arnie for letting us on your boat!