Click here to Skip to main content
Click here to Skip to main content
Go to top

The Art of Noise

, 15 Mar 2004
Rate this:
Please Sign up or sign in to vote.
An image synthesis algorithm using noise and interpolation.

Introduction

Do you want to know how the above image was created? Then read on...

First, let's take the simple approach: we assign to each pixel in the 256x256 bitmap, a random value. We only assign a value to the blue component:

for each row in the bitmap
    for each column in the bitmap
        N = a random value in the range [0,1]
        Color = (Red = 0, Green = 0, Blue = N * 255)
    end for
end for

Here's the result:

As you can see, it's not that bad, but it's not what we want either. In fact, what do we want? We don't want a bitmap with random colors, we want a bitmap with more or less random colors. But what does that mean? It means that if we take, for example, pixel (0,1), the color of pixel (0,2) will be more or less the same color of pixel (0,1). So, if the color of pixel (0,1) is (0,0,255), the color of pixel (0,2) cannot be (0,0,0). No, it should be, let's say, (0,0,250).

Ok, how do we do that? Instead of assigning a random color to each and every pixel in the bitmap, we assign a random color to only some of the pixels in the bitmap. We could, for example, assign a random color to every 10 pixels. So, the pixels (0,0), (0,10), (0,20), ... (10,0), (10,10), (10,20), ... (20,0), (20,10), (20,20), ... will have a random color. All other pixels will be calculated based on the color of the neighboring pixels.

Here's some code:

...
void Ran()
{
    Random r = new Random();
    _Random = new Double[_Width * _Height];
    for (Int32 i = 0; i < _Width * _Height; i++)
        _Random[i] = r.NextDouble();
}
 
public void Create(Bitmap bmp)
{
    _Width = bmp.Width;
    _Height = bmp.Height;
    Ran();
    for (Int32 y = 0; y < bmp.Height; y++) {
        for (Int32 x = 0; x < bmp.Width; x++) {
            Double N = 0.0;
            N += GetColor(x, y, 40);
            Byte b = (Byte)(N * 255.0);
            Color c = Color.FromArgb(255, 0, 0, b);
            bmp.SetPixel(x, y, c);
        }
    }
}
...

The method Create() will fill a bitmap with the desired pattern. Create() first calls Ran() which creates an array with random values in the range [0,1]. The array contains as much cells as there are pixels in the bitmap, just for the sake of simplicity. Then, we assign a color to each pixel in the bitmap. The method GetColor() will assign a random color to the pixels (0,0), (0,40), (0,80), ..., (40,0), (40,40), (40,80), ... and so on. GetColor() returns a number in the range [0,1].

Here's the result:

But what does GetColor() do? For example, how do we calculate the color for pixel (0,12)? What do we know? We know the values for pixel (0,0) and pixel (0,40): we can look up their values in the _Random array, remember? Here's an illustration to make it clear:

There are several ways to calculate a value for (0,12). We can draw a straight line between the points, or maybe a curved line:

A curved line is more natural, so this is what we're going to use. We have to write a function which takes as input two points x0 and x1 with values y0 and y1 and a variable x. That function will return the value for x. Here's the code:

...
Double f(Double x0, Double y0, Double x1, Double y1, Double x)
{
    return (1.0 + Math.Cos(Math.PI + 
        (Math.PI / (x1-x0)) * (x-x0))) / 2.0 * (y1-y0) + y0;
}
...

Whoa, I hear you cry! Where does that formula come from? I'll explain. Remember, we want to put a smooth, curved line between two points, right? Take a look at the following picture:

Interesting, isn't? This picture is a plot of the function cos(x). Now, take a closer look at the function in the green rectangle. That's the line we want to put between our points (x0,y0) and (x1,y1). How do we put that line there? Well, we know our x is in the range [x0,x1]. We want to take the cosine of x. But as you can see in the plot of cos(x), x must be in the range [pi,2pi]:

x                         -> [x0,x1]
x / (x1 - x0)             -> 1
x / (x1 - x0) * pi        -> pi
(x / (x1 - x0) * pi) + pi -> [pi,2pi]

We have converted x from the range [x0,x1] to the range [pi,2pi]. Now we can take the cosine of x: cos(x). cos(x) will have a value in the range [-1,1]. But we want a value in the range [y0,y1]. We have to convert our value y = cos(x) again:

y                              -> [-1,1]
y + 1                          -> [0,2]
(y + 1) / 2                    -> [0,1]
(y + 1) / 2 * (y1 - y0)        -> (y1 - y0)
((y + 1) / 2 * (y1 - y0)) + y0 -> [y0, y1]

If you replace y with cos((x / (x1 - x0) * pi) + pi), you can see that we get the formula:

...
Double f(Double x0, Double y0, Double x1, Double y1, Double x)
{
    return (1.0 + Math.Cos(Math.PI + 
      (Math.PI / (x1-x0)) * (x-x0))) / 2.0 * (y1-y0) + y0;
}
...

Back to GetColor(). Now that we know how to calculate the value of a point between to other points, we can calculate the value of a pixel in a bitmap. I'm not going to show you how to calculate the value for pixel (0,12) because we're working in 2D. To make things more clear, I'm going to show how to calculate the value for pixel (10,12).

The first thing we have to do is to take the value of the neighboring pixels. Well, these pixels are not really neighbors, they are the pixels which were assigned a random value and lay closest to the pixel we want to calculate. So, we look up the value of pixels (0,0), (0,40), (40,0) and (40,40). Then we have to find the value for x = 10 in row 0 (we'll name it xx0). We also have to find the value for x = 10 in row 40 (xx1). We can find the value for pixel (10,12) by calculating the value for y: f(0, xx0, 40, xx1, y). Here's a picture:

Note that this picture has nothing to do with our data, I only show it to make things clear. So, the first step is to calculate the value for x between the pixels (0,0) and (40,0). So, we ignore one dimension: we have a range of values [0,40], we know the y value for 0 and 40, so we can find the value for x = 10. Think about it. We do the same for y = 40: we have a range, [0,40], and we know the values for 0 and 40: not on row 0 but on row 40. Now the final step: again, we have a range, [0,40], and we know the values for 0 and 40, because we just calculated these values in the two previous steps. Think about it and look at the picture above.

Here's the code for GetColor():

Double GetColor(Int32 x, Int32 y, Int32 M)
{
    Int32 x0 = x - (x % M);
    Int32 x1 = x0 + M;
    Int32 y0 = y - (y % M);
    Int32 y1 = y0 + M;

    Double x0y0 = Noise(x0, y0);
    Double x1y0 = Noise(x1, y0);
    Double x0y1 = Noise(x0, y1);
    Double x1y1 = Noise(x1, y1);

    Double xx0 = Interpolate(x0, x0y0, x1, x1y0, x);
    Double xx1 = Interpolate(x0, x0y1, x1, x1y1, x);

    Double N = Interpolate(y0, xx0, y1, xx1, y);
    return N;
}

Double Noise(Int32 x, Int32 y)
{
    if (x < _Width && y < _Height)
        return _Random[y * _Width + x];
    else
        return 0.0;
}

The method Noise() looks up the value for a pixel (x,y). If the pixel is outside the bitmap, Noise() returns 0. Note that it's not necessary to return 0: any number between 0 and 1 is fine. In fact, Noise() should be able to return a random number for every (x,y)! In our program, we don't do that: we use an array with random values.

We're almost there. Take a look at the following pictures:

These pictures are generated using the same algorithm as before, except in the call to GetColor(), 40 is replaced with 40/2, 40/4, 40/8, 40/16 and 40/32. Now, we're just adding these pictures and what do we get? Right, the picture at the top of this article! Here's the new code for Create():

public void Create(ref Bitmap bmp)
{
    _Width = bmp.Width;
    _Height = bmp.Height;
    Ran();
    for (Int32 y = 0; y < bmp.Height; y++) {
        for (Int32 x = 0; x < bmp.Width; x++) {
            Double N = 0.0;
            N += GetColor(x, y, 40/32);
            N += GetColor(x, y, 40/2);
            N += GetColor(x, y, 40/4);
            N += GetColor(x, y, 40/8);
            N += GetColor(x, y, 40/16);
            N += GetColor(x, y, 40/32);
            N /= 6.0;
            Byte b = (Byte)(N * 255.0);
            Color c = Color.FromArgb(255, 0, 0, b);
            bmp.SetPixel(x, y, c);
        }
    }
}

See? We add the values together and divide the result by 6 to get again a value between 0 and 1.

Well, that's it! You've learned to create art with noise.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Mike Finnegan

Belgium Belgium
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermanoj kumar choubey26-Feb-12 21:24 
Jokeart genetic Pinmembersameabd9-Mar-06 3:33 
Generalunderscores at start of varnames Pinmembertedvg11-Jun-04 5:35 
GeneralRe: underscores at start of varnames PinsussAnonymous11-Jun-04 21:24 
GeneralRe: underscores at start of varnames Pinmembertedvg11-Jun-04 23:53 
GeneralRe: underscores at start of varnames Pinmemberomoshima17-Jun-04 8:56 
GeneralRe: underscores at start of varnames PinmemberDaberElay20-Jul-04 6:38 
GeneralRe: underscores at start of varnames Pinmembergnjunge17-Mar-06 6:12 
GeneralRe: underscores at start of varnames PinmemberNatza Mitzi6-Nov-08 9:21 
GeneralReminds me of some pictures I saw once... PineditorMarc Clifton21-Mar-04 10:55 
QuestionPerlin Npise? PinmemberJoel Holdsworth16-Mar-04 6:29 
AnswerRe: Perlin Npise? PinsussAnonymous19-Mar-04 9:56 
GeneralLayout PinmemberPJ Arends16-Mar-04 5:51 
GeneralRe: Layout Pinmemberrfmobile16-Mar-04 6:09 
GeneralRe: Layout PinsussAnonymous16-Mar-04 6:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 16 Mar 2004
Article Copyright 2004 by Mike Finnegan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid