Week 4 -- Modularity
back to syllabusFunctions
"Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Appropriate functions hide details of operation from parts of the program that don't need to know about them, thus clarifying the whole, and easing the pain of making changes." -- Kernigen & RitchieIn processing, we've been using functions all along. When we say "line(0,0,200,200)" we are calling the function "line", a "built-in" function of the processing environment. When we say "void setup()", we are declaring the "setup" function, which is a special processing function that is called when the program first starts.
This week, we are going to learn how to break our small programs down into even smaller parts, allowing for "modularity" and "re-usability."
Declaring a Function
A function declaration/definition has three parts:It looks like this:
ReturnType FunctionName ( Parameters ) { code body of function }
Here's a simple example:
void sayingHello() { println("I'm saying hello!") }This is a simple function that performs one basic task -- printing the phrase "I'm saying hello!"). It has no return type, and therefore we say "void". It has a function name: sayingHello. It has no parameters (and therefore nothing is placed in between the parentheses). We can call the function (and therefore have the instructions of the function executed) like this:
sayingHello();Things to remember
Return Type, Parameter Passing
Ok, now let's look at an example that makes use of a return type and parameters.int add(int a, int b) { int sum; sum = a + b; return sum; }In the first line above, we first have "int" -- which declares the "return" type of the function. Second, we have the word "add" -- this is our name for the function; much like declaring a variable name, we can use any word we want, staying away from already defined keywords (such as primitive data types, built-in processing function names, etc.) Finally, we have some stuff in between paratheses. These are called "parameters" and we can have as many of them as we like.
So, what are all these things doing? Examine the following code:
int x = 5; int y = 7; int total = 0; total = add(x,y); println(total);In the above code, the function "add" is being called, with two parameters (x and y) and the result of that function is then assigned to the variable "total."
Pass by Value, Pass by Reference
In Java, when talking about functions, all primitive variables are "passed by value." What does this mean?Assume the following:
int x = 7;A variable, x, is declared and assigned the value '7'. In other words, the bit value for 7 is stored *inside* the variable x. Now, let's say we have declare function called "blah", with an int parameter named y:
void blah (int y) { //some code }When we call the function blah, passing the variable as the argument. . .
blah(x);. . .the bits in x are copied, and the copy lands in a new storage location, that of y. What's crucial to note is that x and y are not linked in any way -- the copy is a one-time operation. If the value of y is changed during the the function "blah", the value of x in the calling area remains the same. Run the following code and see what happens:
int x = 0; void setup() { println("When the program starts, the value of x is:" + x); change(x); println("A copy of x was passed to the function so the value of x remains: " + x); } void change(int y) { println("Inside the function, y receives the value of x and is: " + y); y = 5; println("Inside the function, y is assigned the value: " + y); }What if x, however, were an array? In this case, java passes the parameter by value, but instead of passing a copy of the data inside a variable, it passes a copy of the array's reference. And so in this case, the values of the elements of the array can be changed. Run the following code, try to follow the logic, and see the difference:
int[] x = {0,0}; void setup() { println("When the program starts, the reference address for the array x is: " + x); for (int i = 0; i < x.length; i++) { println("When the program starts, the value of element # " + i + " of the array x is: " + x[i]); } change(x); println("After the function is called, the reference address for the array x remains: " + x); for (int i = 0; i < x.length; i++) { println("However, the values of x have been changed, element # " + i + ": " + x[i]); } } void change(int[] y) { println("Inside the function, y receives the reference address to the array x: " + y); for (int i = 0; i < y.length; i++) { println("Y has the same values as x, element #" + i + ": " + y[i]); y[i] = 5; println("The value of element #" + i + " of y is changed to: " + y[i]); } }
Using Functions
Now that we've learned all about functions, let's take one of our previous examples and organize it with functions. Having our code well-organized will also allow us to more easily re-use and add features.//a variable to store the maximum # of elements in the array int MAX = 250; //declare array names and allocate the space to store them float[] x = new float[MAX]; float[] y = new float[MAX]; float[] yspeed = new float[MAX]; float[] xspeed = new float[MAX]; color[] c = new color[MAX]; void setup() { size(400,200); colorMode(RGB,255,255,255,100); initArray(x,0,width); initArray(y,0,height); initArray(xspeed,-3,3); initArray(yspeed,-3,3); fillColors(); } void loop() { //set background stroke and fill moveStuff(x,xspeed); moveStuff(y,yspeed); boundary(x,-20,width+20); boundary(y,-20,height+20); drawStuff(); } //fill array with random values void initArray(float[] a, int min, int max) { for (int i = 0; i < a.length; i++) { a[i] = random(min,max); } } //fill color array void fillColors() { for (int i = 0; i < MAX; i++) { c[i] = color(200, i % 255,0,(i) % 100); } } //adjust values of one array by another void moveStuff(float[] a, float[] speed) { for (int i = 0; i < a.length; i++) { a[i] += speed[i]; } } //check to see if element reaches boundary void boundary(float[] a, int min, int max) { for (int i = 0; i < a.length; i++) { if (a[i] > max) { a[i] = min; } if (a[i] < min) { a[i] = max; } } } void drawStuff() { //background(0); fill(0,0,0,30); rect(0,0,width,height); noStroke(); smooth(); for (int i = 0; i < MAX; i++) { fill(c[i]); ellipse(x[i],y[i],i/10,i/10); } }
Related Examples from the Processing Web Site
Draw TargetsRollover Functions
Button
Interpolate
Displaying Images
In processing, we can store an image in a variable of type BImage. This data type will also allow us access to the pixels of the image (which we will use later in the semester) as well as the width and height of the image. Following is how we initialize an image. To display an image, we call the "image" command, followed by our image variable, and the coordinates for displaying the image. We can also adjust the width and height of the image, using two more parameters.BImage b; b = loadImage("filename.jpg"); image(b, 0, 0); // w/ default width and height image(b, 0, 0, 100, 50); // w/ height and width definedFinally, we can tint the image with an RGB color, as well as give it an "alpha" value. To do so, we call the tint function with 4 parameters, R,G,B, and alpha. See an example below. Note that white (255,255,255) gives the image no tint, black (0,0,0) makes the image invisible.
BImage a,b; void setup() { size(200,200); a = loadImage("kerry.jpg"); b = loadImage("bush.jpg"); } void loop() { colorMode(RGB,255,255,255,width); tint(255,255,255,width-mouseX); image(a, 0, 0); tint(255,255,255,mouseX); image(b,0,0); }. . . and here it is w/ rotation, just 'cause we can. . .
BImage a,b; float x; void setup() { size(200,200); x = 0.0f; a = loadImage("kerry.jpg"); b = loadImage("bush.jpg"); } void loop() { background(0); translate(width/2,height/2); imageMode(CENTER_DIAMETER); colorMode(RGB,255,height,height,width); rotate(x); x += 0.5f - float(mouseX) / float(width); tint(255,height-mouseY,mouseY,width-mouseX); image(a, 0, 0); tint(255,mouseY,height-mouseY,mouseX); image(b,0,0); }
Related Examples from the Processing Web Site
display imageimage transparency
background image
back to syllabus