Thursday, 7 May 2009

sine in action - part 5

Making adjustments

When we first plotted our sine function we added ½ the screen height to the value of the function. This is because the sine function oscillates above and below zero, and to plot the entire function on screen we need to adjust the values so that it is centred.

Instead of merely adding a constant to the sin function, we could make more complex adjustments to vary the result.

This is the reason we have chosen to factor out the sine function as plotSineWave() and the adjustment() function as separate functions making it easier to change or entirely swap one or the other. In the final version of the program we have bundled several different adjustment functions into one, and the function to use can be set in the initial options. This is similar to the way we set up the choice of colours. It is designed to make it simple to change the program, as we merely have to set one variable instead of rewriting an entire section of our code.

There are three options for the choice of adjustment to make. The choice is set by the variable adjustmentChoice. If a valid adjustment choice isn’t made then the adjustment function returns 0, and the sine function is plotted without any modification.

The first choice is the one we have already discussed, that of a constant added to our sine function. The second choice of adjustment is based on a linear function. As we saw in section 24 the general form of a linear function is:

g(x) = intersect + gradient * x

In the function the variable intersect is where the line intersects the y axis and the gradient is the gradient of the line.

Our second adjustment function is based on this form of equation; however our gradient is varied based on the frame count.

The variable count takes the values 1 and -1. Each time the modulus of the frameCount reaches our variable range, count is set to the opposite value. This is done with the following code in the draw() method.

if ( frameCount % range == 0){
count = -count;
}


The variable count is tested in our adjustment function to determine whether we should be increasing or decreasing the gradient. In either case the gradient is varied from the minimum and the maximum each time. In our example the minimum is 0 and the maximum is 1, since we have set range to be equal to the image height. If range was set larger or smaller then the extreme values of the gradient would also be changed.

With the colour type set to greyscale this adjustment function creates an image similar to that in Figure 8.

Figure 8 greyscale sine function with linear adjustment function

Our third choice is somewhat more complex and the end result looks less like our simple sine function.
Option three combines a linear function of the form g(x) above with a sine function. The linear function has a variable gradient although this time the variance in the gradient is based on a sine function with a period equal to the width of our image. This means that the gradient varies smoothly between plus and minus the amplitude of this sine function (In our example we have set this to be 0.25).

This time we have based the value of the intersect variable on our variable count. If count is positive then we increase the intersect until it reaches the value of our variable range. At this point count will become negative, and we decrease the intersect until it reaches the minimum (in our example this is zero, but if range is greater than the height of the image then this could be below zero). In this way the entire sine wave is moved up and down the screen.


The final part of this function includes slightly more variation, by adding another sine wave to the result. This sine wave has a low amplitude and fairly high frequency so it contributes a fairly subtle variance to the overall function and causes it to move in a more interesting manner.


The impact of making these adjustments can be seen when they are added to a constant value. This is shown in Figure 9.


Figure 9 adjustment function three plotted with a constant

Final Program


Putting all of these functions together gives us our final program included below. The result of which can be seen in Figure 10
Figure 10 coloured sine function with adjustment function three

code for the final program can be found in the earlier post below.


Sine in action - part 4

The getColourValue() function

In our main program instead of setting the point (x,f(x)) to a particular colour, we are choosing to set every other point (x,y) based on the proximity of y to f(x); where f(x) is defined as above.

In order to set the colour of pixel y based on its proximity to
f(x) we need a function based on y and f(x) to calculate this. We have chosen to do this by creating another function getColourValue() that calculates how the range of colours should be distributed around f(x) and returns a value that represents the position of y within this colour range.

We pass y as the argument value, and
f(x) as the argument maxValue to getColourValue(). The function then returns a value between 0 and 255 depending on how close the argument value is to the argument maxValue. The function scaleValue() we discussed above is used to scale the return values appropriately so that they are in this range.

At value = maxValue the function returns 255. As value moves further from maxValue then the return value is decreased. The rate of the decrease is set so that at least one of value = 0 or value = height will return 0; which of these returns 0 is dictated by which is furthest from maxValue. So that if maxValue is centred the decrease will be even on both sides. If maxValue is offset, either above or below the centre then only one side will reach zero, and the minimum colour. The other side will decrease at the same rate but won’t reach the minimum. This is shown in Figure 5.


Figure 5 Centred and offset colour distribution graphs

For

float h = img.height;

then the line

minValue = min(0, 2* maxValue - h);

is used to determine whether the minimum will be reached at 0 or at h.

If maxValue is above the centre of the screen then 0 will be the minimum point, minValue. The colours will be plotted with red at maxValue and blue at 0, with the colours changing at the same rate from maxValue to h so that the minimum colour isn’t reached before h.

If maxValue is below the centre of the screen, then the minimum point, minValue, will be below 0 at 2* maxValue – h. This means that if we continued to plot colours below 0 then the minimum colour blue would be reached at minValue. Our choice of minimum here ensures that as we approach h then getColorVal() approaches zero and we plot the full range of colours from red at maxValue and through to blue at h.

The variable float rangeColor is defined as
rangeColor = max(maxValue, h - maxValue);

This is the distance that colours will range over, it is the maximum of the distance from zero to maxValue (or f(x)) and the distance from h to maxValue (or f(x)). In common with to finding the minimum, if maxValue is above the centre of the screen then rangeColor = maxValue, otherwise rangeColor = h- maxValue, this is illustrated in Figure 6.

Figure 6 Plotting the colour range

The variable rangeColor is used to scale the return value so that it returns values from 0 to 255 over the distance rangeColor. This is done using the scaleValue() function we described earlier with the minimum as 0 and the maximum as rangeColor.

temp = scaleValue(temp, 0, rangeColor);

When combined with the function getColor(), the output of getColorValue() can be plotted as shown Figure 7 with f(x) as a constant.

Figure 7 Colour distribution plotted around a constant

Sine in action - part 3

Drawing with pixels

In section 21 we saw that individual pixels could be referred to by using their position in Cartesian coordinates. This technique can be used to set the colour of individual pixels in an image; which is how we colour points in relation to our sine function in our final program.

The steps needed are:
  1. Create a PImage object img.
  2. PImage img = createImage(300,200,RGB);

  3. Set the colour of one or more pixels. The pixel at the point (x,y)of the image object can be set using the line:
    img.set(x,y,aColor);

  4. Display the image after all points have been set using the line:
    image(img,0,0,width,height);
For example we use this method to plot a sine function by setting the pixel at point (x, f(x)) to be a particular colour; where f(x) is our sine function.

As we saw earlier the basic form of the sine function is

f(θ) = amplitude * sin( frequency * θ)

We have chosen to scale f(θ) so that it is calculated between 0 and 2 π over the width of the image, such that θ at x is defined as follows.

θ = ( 2 π /img.width ) * x

This gives us

f(x) = amplitude * sin( frequency * ( 2 π /img.width ) * x )


We can draw this function by colouring the pixels at white, as follows

size(600, 300);
//create the image
PImage img;
img = createImage(600,300,RGB);

//set variables for sine function
int y = 0;
float theta = 0;
float dTheta = TWO_PI / img.width;
float amplitude = img.height/2 ;
float damping = 0.994;
float position = img.height/2;
float frequency = 99;

//set colour of points
for (int x=0; x <= img.width; x++){
y = round(position + amplitude*sin(frequency*theta));
color c = color(255);
img.set(x,y,c);
theta += dTheta;
amplitude *= damping;
}
// display the results
image(img,0,0,width,height);



Figure 4 sine function plotted with pixels

In this example we have chosen a high frequency of 99. It is this choice of frequency that gives us the rather intricate distribution we can see in Figure 4. Although in this image it appears we are plotting four different strands, in fact there is only one and the distance between the plotted points at out chosen frequency gives us this effect.

Sine in action - Part 2

Colouring and the getColor() function

In our main program, instead of plotting the sine function we are going to colour the surrounding pixels in relation to how close they are to the calculated sine function.
The first step towards this is to define how we might colour a point in relation to a value calculated for that point.

We will begin by defining a function getColor(int colorVal, int choice) that will allow us to plot a smooth gradient of colour. Values of the argument colorVal should be in the range 0 to 255, any values above or below this range will return the maximum or minimum colour defined by the function.

Two colouring options have been specified for this function. Which of these is used is determined by the value of the argument colorChoice - valid options for which are 1 and 2. Setting anything else will return 0, and the colour black, for all other values of the argument colorVal. In our main program colorChoice is set in the initial setup options, which can be found in the function setOptions().

Setting colorChoice to 1 gives us our simpler option - greyscale. This is easier to define since the argument colorVal is already in the range 0 to 255, we can simply set the red, green and blue values all to be equal to colorVal.

Setting colorChoice to 2 gives us our second option - a smooth colour shift from blue to red through green. To achieve this, the input range, 0 to 255, was divided into four equal sections. In each section two of the RGB values were kept constant whilst the other was increased or decreased to give a smooth transition.

For example, in the first section - colorVal between 0 and 63 - to get from blue to cyan:
  • Keep the red component of the colour as 0 and the blue value constant at 255
  • Then increase the amount of green proportional to the argument colorVal. We need to ensure the green value increases from 0 to 255 (approximately) based on colorVal, which ranges from 0 to 63; hence we multiply colorVal by 4.
Similarly in the other sections we subtract the start of the range from colorVal before multiplying by 4 to give a value between 0 and 255 that can be used to vary one of the colours red, green or blue.

//set everything to 0, will return black if colorVal outside range
int tempColor;
int redVal = 0;
int greenVal =0;
int blueVal =0;

//first section of colour scale
if ( colorVal >= 0 && colorVal <= 63){ tempColor = 4 * colorVal; redVal = 0; greenVal = tempColor; blueVal = 255; } //The other three sections are defined similarly, //they complete the shift to red return color(redVal,greenVal,blueVal);


With all four sections this gives us the colour scale in Figure 2.





Figure 2 colour scale

Next we define the function scaleValue(float val, float minVal, float maxVal) which converts the argument val to a float in the range 0 to 255 proportional to the arguments maxVal and minVal; such that val = maxVal returns 255 and val = minVal returns 0. We will use this result later in the function getColor().

To scale val we first need to ensure that the minimum value returned will be 0. This is achieved by subtracting the argument minVal - the minimum possible value that that val could take. Hence if val is greater than 0 it is reduced to 0, and conversely if val is less than 0 then it is increased to 0. This new adjusted value is adjVal.

Next we need to ensure that the maximum value returned is 255. In order to this we create a scale factor which we will multiply by adjVal to give us the required results.

We begin by adjusting the maximum value in the same way as before - subtracting the minimum value minVal. Then we divide 255 by this new adjusted maximum value adjMax to create a scale factor. This scale factor is multiplied by adjVal to produce a number in the range 0 to 255 proportional to the maximum and minimum values passed to scaleValue().

We can apply these two functions scaleValue() and getColorValue() to our original sine function to colour each point. The maximum at any given point will be red and the minimum will be blue. This is shown in Figure 3.

The additions to our original code are shown below

//set up initial variables as before

//add variables to hold color and scaled value used to set color
float colorValue;
color aColor;

//add three lines to change color of points as function is plotted
for(int x=0; x < y =" position" colorvalue =" scaleValue(y," acolor =" getColor(round(colorValue),">












Figure 3 colourful sine function with damping

Note that the minimum and maximum values change as the function is plotted, so that each peak and trough is plotted in the same colour. This is because the amplitude is passed to the scaleValue() function after each iteration so the damping is taken into account when determining the maximum and plotting each point.

Sine in action part 1

Posting the final draft is proving a little problematic, so I will be splitting it into several sections.

Chunk 54 – Sine in action

In this section we are going to develop a program that uses the sine function. Our program is designed in a modular way, with fairly self contained functions each handling different aspects of the program operation. Where functions could be reused elsewhere they are kept independent of the rest of the program by using arguments passed from other functions instead of relying on global variables.

Although this approach leads to some additional complexity, it is designed to allow code reuse and make future alterations simpler.

We will begin by discussing the individual functions that make up our program. At the end of this section we will combine these to give our main program. The full code is listed at the end; although where it clarifies explanations small excerpts have been used in the discussion.

The sine function

As we saw in section 53 the sine function is a trigonometric function which generates a periodic repeating wave.

The basic function takes the form

f(θ) = amplitude * sin( frequency * θ)

The maximum value of the sine function
f(θ) is equal to the amplitude, and the minimum value is equal to minus the amplitude. The function f(θ) varies smoothly between these values during the period of the wave.

The frequency affects the number of repetitions of the wave within the period 2 π radians; such that if frequency = 1 then the wave repeats once over the period and if
frequency = n then the wave repeats n times.

A damping variable can be used to decrease the amplitude over time such that the maximum and minimum values of the sine function tend towards to zero.

In the following example, the amplitude is decreased with each point plotted by multiplying the amplitude by the variable damping. The angle theta is increased from 0 to TWO_PI as x increases from 0 to width. To do this we define theta as a function of and width. We initialise theta to 0 and then with each iteration we add dTheta, where

dTheta = TWO_PI/width;

Additionally the variable frequency is set to 4 so the wave repeats four times within the period. This is shown in Figure 1.

size(600, 300);
background(0);
stroke(255);
strokeWeight(5);

float y = 0;
float theta = 0.0;
float amplitude = height/2 ;
float damping = 0.994;
float dTheta= TWO_PI/width;
float position = height/2;
float frequency = 4;
smooth();

for(int x=0; x <>
y = position + amplitude*sin(frequency*theta);
point (x, y);
amplitude *= damping;
theta += dTheta;
}













Figure 1 Sine function with damping

Monday, 27 April 2009

Chunk 54 sine in action

I'm writing chunk 54 Sine in action. I've not really kept up with posting regular updates, but thought I'd post the program that I will be writing about.

This program plots a sine function, by creating an image and then colouring the pixels of the image based on their distance from the plotted function.

The sine function is adjusted using different functions to produce varied output, depending on the parameters selected.

The output looks something like this




The full write up will follow shortly, but for the moment here is the program:






/*
// Chunk 54 - Sine in Action
// Plots a sine function by colouring surrounding pixels
// in relation to their proximity to the sine wave
*/

float theta0 ; //Start angle
float dTheta0; // Increment start angle 'angular velocity'
float dTheta; // Value for incrementing theta, a function of period

float amplitude; // maximum height of wave
float amplitude0; //intial undamped amplitude
float damping; //decrase in height over period of wave
float frequency; //repetitions of wave within 2Pi radians

int count = 1;//use to reverse gradient
int range; //range for vertical movement of function
int colorChoice; //colour type to plot
int adjustmentChoice; // choice of funtion to adjust postion of sine wave

PImage img;
void setup() {
size(500, 500);
frameRate(20);
img = createImage(250,250,RGB);

noSmooth();
setOptions();
}

void draw() {
background(255);
amplitude = amplitude0;
theta0 += dTheta0; //increment initial angle

if ( frameCount % range == 0){
count = -count;
}

//set colours of image
float theta = theta0; //set initial angle for calculating sin wave
int intColor; // value of colour to plot at x,y
color c; //colour of plotted point x,y based on intColor
float adjSinWave; // y value to plot colour around

//plot sin function by colouring surrounding points
for (int x = 0; x < img.width; x++) {
adjSinWave = adjustment(x, adjustmentChoice) + plotSinWave(theta);

for ( int y= 0; y < img.height; y++){
intColor = getColorValue(adjSinWave, y);
c= getColor(intColor,colorChoice);
img.set(x,y,c);
}
theta += dTheta;
}

// display the results
image(img,0,0,width,height);
}

void setOptions(){
float period = 450.0; // How many pixels before the wave repeats
//values for plotting sine wave
theta0 = 0;
dTheta0 = 0.01;
dTheta = TWO_PI / period; //change in angle plotted between x and x+1
amplitude0 = img.height/2; //amplitude looks best close to height/2
damping = 0.987; //keep close to 1, or sine function becomes quickly flat
frequency = 6;
range = img.height;
colorChoice = 2; // 1- grayscale, 2 - colour shift,
adjustmentChoice = 3; //1 - constant, 2 - linear, 3 - sin & linear
}

float plotSinWave(float angle){ //main sine wave to plot everything else round
// float result = 0; //for testing colour distibution
float result = amplitude * sin (frequency * angle);
amplitude = amplitude * damping;
return result;
}

float adjustment(float xPos, int choice){ //adjustment to position of sine wave
float result = 0;

if (choice == 1){ //constant

result = img.height/2;

}
else if (choice ==2) { //simple linear function

int intersect = 0;
float gradient ;
float gradientChange =0;

if (count > 0 ){ //count switches from positive to negative when framecount % range is reached
//increase intersect up to range
gradientChange = frameCount % (range);
}
else {//decrease intersect from range to zero
gradientChange = range - frameCount % (range);
}

gradient = (img.height - gradientChange) / img.width;
result = intersect + gradient * xPos ;

}
else if (choice == 3){//combination of sin and linear functions

float amp1 = 0.25;
int freq1 = 1;
int period1 = img.width;
float amp2 = 5;
int freq2= 2;
int period2 = img.width/3;
int intersect = 0;
float gradient;

gradient = amp1 * sin(freq1 * (xPos * TWO_PI/period1));
if (count > 0 ){ //count switches from positive to negative when framecount % range is reached
//increase intersect up to range
intersect = frameCount % (range);
}
else {//decrease intersect from range to zero
intersect = range - frameCount % (range);
}

//linear function with variable intersect and gradient
float result1 = intersect + gradient * xPos;
float result2 = amp2 * sin(freq2 * (xPos * TWO_PI/period2));

result = result1 + result2;

}
return result;

}

int getColorValue(float maxValue, float value){
//colors plotted around sine wave
// returns int value between 0 & 255 based on distance from maxValue

float h = img.height;
float temp = 0;
int result;
float minValue;
float rangeColor;

// if maxValue > h/2 then minValue is 0 else minValue 2 * maxValue - h
minValue = min(0, 2* maxValue - h);
//color increasing to maxValue
if ( value <= maxValue){
temp = value - minValue ;
}
else { //color decreasing from maxValue
temp = 2 * maxValue - (value + minValue );
}

rangeColor = max(maxValue, h - maxValue); //range over which colour varies between 0 and 255
temp = scaleValue(temp, 0, rangeColor); //scale value to be between 0 and 255
result = round(temp); //convert scaled value to int

return result;
}

float scaleValue(float z, float minVal, float maxVal){

//adjust values to ensure minimum equals 0
float adjZ = z - minVal;
//Scale values to ensure new max equals 255
float adjMaxZ = maxVal - minVal;
float scaledZ = adjZ * (255/adjMaxZ);

return scaledZ;
}

color getColor(int colorVal, int choice){

int tempColor;
int redVal = 0;
int greenVal =0;
int blueVal =0;

if (choice == 1){ //grayscale

redVal = greenVal = blueVal = colorVal;

}
else if (choice == 2 ){// Smooth transition of color from blue to green to yellow to red
//subdivide colour selection based on range of values

if ( colorVal >= 0 && colorVal <= 63){
tempColor = 4 * colorVal;
redVal = 0;
greenVal = tempColor;
blueVal = 255;
}
else if ( colorVal > 63 && colorVal <= 127){
tempColor = 4 * (colorVal - 64);
redVal = 0;
greenVal = 255;
blueVal = 255 - tempColor;
}
else if ( colorVal > 127 && colorVal <= 191){
tempColor = 4 * (colorVal - 128);
redVal = tempColor;
greenVal = 255;
blueVal = 0;
}
else if ( colorVal > 191 && colorVal <= 255){
tempColor = 4 * (colorVal - 192);
redVal = 255;
greenVal = 255 - tempColor;
blueVal = 0;
}
}

return color(redVal,greenVal,blueVal);

}


Java2html