Week 4 -- Modularity

back to syllabus

Functions

"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 & Ritchie

In 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:
  • return type
  • function name
  • parameters


  • 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
  • The value you pass as an argument to a function can be a literal value (20,5,4.3f, etc.) or a variable of the declared parameter type.
  • A function must always have a return type. If it doesn't return anything, you should declare the return type as "void"
  • If the return type is not void, your function must return a value compatible with the declared return type
  • 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."

  • Return -- The function add has a return type "int." This means that at the end of the function, we will have a statement "return x" where x is some integer value to be sent back to the calling area. This resulting value can be assigned to a variable of type "int" (in this case "total") or used in an expression, i.e. line(0,0,200,add(100,50)).
  • Parameter Passing -- x and y, which hold the values 5 and 7, respectively are "passed" into the function "add." They are called the "arguments." Arguments that are passed to a function arrive in the same order as they were passed. The first argument lands in the first parameter, second in the second, and so on. Variables passed as arguments must have the same type as the parameters in the function definition.
  • Local Variables -- it should be noted that the above function "add" declares a local variable called "sum" which is uses to compute the value of a plus b. Functions can have local variables, and these variables are allocated and initalized when a function is invoked. When the function returns, they are then destroyed. The parameters also act as local variables -- but how does this work?

    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 Targets
    Rollover 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 defined
    
    Finally, 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 image
    image transparency
    background image


    back to syllabus