Firstly, let us describe what a 2D array is. Where a 1D array is basically a list of items of a similar type, a 2D array is best described as a table of items of a similar type. In terms of geometry, a 2D array can be thought of as a flat plane with an X and Y axis.
Figure 1 shows a 1D array with which you should already be familiar. This array has five elements, numbered 0 to 4. Figure 2 shows a 2D array. This array has four rows and four columns. The first number in each subscript refers to the row and the second number refers to the column.
Now we will look at the syntax for declaring arrays. We know that a 1D array is declared as follows:
int[] myArray = new int[size];
Similarly, a 2D array is declared as follows:
int[][] myArray = new int[sizeX][sizeY];
The difference here is that the 2D array has two subscripts, i.e. one to control the X coordinate and one to control the Y coordinate. In theory, it dosn’t really matter whether the first or second subscript is considered to be the X or Y coordinate but what is important is that any reference to the subscripts in the code should remain consistent throughout the program.
The example we will develop in this chapter will involve manipulating each element of the 2D array which will then be drawn to the screen using Processing’s point(x,y) syntax. Writing such a program involves thinking of the elements of the array as pixels on the screen. Therefore, it might help to firstly explain what a pixel actually is.
A pixel (picture element) is a small piece of data in an image on a television or computer display. It is not a small square with one colour; rather is is composed of three or four smaller parts for each of the primary colours: cyan, magenta, yellow and black. Any image displayed on a pixellated screen will involve many of these pixels (sample points) glowing at different intensities in order to produce the larger image that we see when we are at a reasonable distance.
So now to move on to developing our program! Here’s the specification:
Write a Processing program that displays a large square containing nine different randomly coloured squares within it.
This program sounds quite simple but when we break it down into stages, we can see how much processing is involved:
1. Define display area width and height
2. Declare array screenArray[][] to represent the pixels in the display area
3. Generate random colours
4. Begin drawing horizontal and vertical pixel lines in screenArray[][]
5. Continue drawing horizontal and vertical pixel lines in screenArray[][]
a. test current x, y coordinates to determine which coloured square is being processed
b. set current element of screenArray[][] to appropriate colour accordingly
6. Repeat step 5 until all nine squares have been drawn in screenArray[][]
7. Loop through screenArray[][] and draw each element to its corresponding pixel on the display
These steps can be broken down into further substeps that get closer to Processing’s syntax. A couple of things to note about the above algorithm follow:
a) Firstly, screenArray[][] is the 2D array that holds values representing the pixels in the display area. The contents of screenArray[][] are not the pixels themselves.
b) Secondly, step 5 is a loop. We will know in advance what the width and height of the display area will be, thus we know the terminating condition for the loop. Therefore, a for loop as seen on page xx will be suitable here.
c) In order to loop successfully through all rows and columns of the array and display area, we need to employ the concept of nested loops. Doing this will allow us to access each array element distinctly, as you will see.
Before we develop the code for our program, we need to describe the concept of outer and nested loops. Looping or iterating through code should be familiar to you from page xx. A loop can be used to populate or process the elements in a one dimensional (1D) array as you will have already seen. When processing a 2D array, we need to move horizontally accross the columns as well as vertically down the rows. This is done by using an outer loop to control the vertical, top-to-bottom processing and a nested loop contained inside the outer loop to control the horizontal, left-to-right processing. For each value of the outer loop, the nested loop does its full range in its entirety. Below is a simple piece of code showing a nested for loop:
for (int x=0; x <=100; x++){
for (int y=0; y <=100; y++){
// do processing here...
}
}
The processing inside the nested loop could be anything from mathematical calculations to graphical manipulation, which will be the case in the programs we develop below for Processing. An easy way to describe what happens in the above code is as follows. When the outer loop sets x to 0, the nested loop runs from 0 to 100. When the outer loop subsequently moves on to 1, the nested loop again runs from 0 to 100. When the outer loop moves on to 2, the nested loop once again runs from 0 to 100. This process of the nested loop running repeatedly through its full range continues until the outer loop reaches the end of its range.
We will now go straight into the programming and show how each of the steps in our program specification above can be implemented, one by one.
Step 1 states that we define the display area width and height. The Processing code for this should be very familiar to you at this stage:
// set up display area 200 by 200 pixels
size(200, 200);
Step 2 involves creating the array with the arbitrary name screenArray[][] to hold the values representing the pixels on the screen:
// create an array called screenArray[][] to hold coloured pixel points representing the display area
color[][] screenArray = new color[200][200];
Step 3 states that we generate random colours. We have chosen to do this using a one dimensional (1D) array with nine elements, one for each randomly coloured square:
// create 1D array to hold random colour for each of the nine squares
color[] colour = new color[9];
// populate colour[] array with nine random colours, one for each square
for (int i=0; i<9; i++){
colour[i] = color(random(255), random(255), random(255));
}
The color function is used here to generate a colour with a random amount of red, green and blue; thus generating any colour in the spectrum. The first parameter taken by color() is the red value, the second is the green and the third is blue. Each parameter is set to a random number between 0 and 255 (where 0 is black and 255 is white).
Step 4 is implemented by setting up control variables to track our current x and y positions in the 2D array:
// declare variables vertical and horizontal to control outer and nested loops
int vertical = 0;
int horizontal = 0;
We then set up our outer and nested loops to allow us to distinctly access each element of the array in turn. For each value of the outer loop, the nested loop runs through all of its values. In other words, each time the outer loop changes to its next value, the nested loop runs through its entire range. So we are effectively moving through the array one row at a time, where the nested loop is controlling the horizontal movement from beginning to end, each time the outer loop changes vertically downwards to the next row; where the nested loop subsequently starts over. This process continues until the end of the array is reached (bottom-right element or screenArray[200][200]). Figure 4 illustrates the direction of movement through an array as the code runs.
/** loop through screenArray[][] and fill it with the appropriate values to produce coloured squares **/
// outer loop to process vertical direction, y axis, top-to-bottom column in the 2D array
for (vertical=0; vertical<200; vertical++){
// nested loop to process horizontal direction, x axis, left-to-right row in the 2D array
for (horizontal=0; horizontal<200; horizontal++){
Step 5 is the most complex part of our program and involves testing (using if() conditions) which of the nine square areas we are currently processing the pixels for. So, we need to choose a random colour from our pre-defined 1D colour[] array when we know that the pixel we are processing is in the area of a particular square. We need to set up range tests for each of the nine squares and select the appropriate colour from colour[0] to colour[8]:
if ((vertical <= 200) && (horizontal <= 200))
pixelColour = colour[8];
if ((vertical <= 200) && (horizontal <= 133))
pixelColour = colour[7];
if ((vertical <= 200) && (horizontal <= 66))
pixelColour = colour[6];
if ((vertical <= 133) && (horizontal <= 200))
pixelColour = colour[5];
if ((vertical <= 133) && (horizontal <= 133))
pixelColour = colour[4];
if ((vertical <= 133) && (horizontal <= 66))
pixelColour = colour[3];
if ((vertical <= 66) && (horizontal <= 200))
pixelColour = colour[2];
if ((vertical <= 66) && (horizontal <= 133))
pixelColour = colour[1];
if ((vertical <= 66) && (horizontal <= 66))
pixelColour = colour[0];
// store previously set pixelColour variable in the current element of our array
screenArray[vertical][horizontal] = pixelColour;
Note that the values 200, 133 and 66 are not arbitrary; they define the boundaries between the edges of our nine squares based on a 200 X 200 display area.
Step 6 is nothing more than a statement that says the code of Step 5 is within the loop set up in Step 4.
Step 7 draws the contents of the array screenArray[][] to the display area:
// use nested loops again to draw coloured squares to the display pixel by pixel
// note how this processing is separate from the array processing above. We can
// effectively manipulate an array in the computer's memory before displaying our
// results on the screen
for (vertical=0; vertical<200; vertical++){
for (horizontal=0; horizontal<200; horizontal++){
stroke(screenArray[vertical][horizontal]); //set colour from current element in our 2D array
point(vertical, horizontal); //draw pixel point at same position on the display area
}
}
Nested loops are used here again; this time to read the values into Processing’s stroke() function that sets the current colour being used. The point() function then draws the coloured point to the current pixel on the display area. The result seen on the screen is the nine coloured squares, which proves that screenArray[][] itself contains a set of correctly coloured elements where each element corresponds to a pixel on the display.
So, our completed program (with a few code optimizations added in) looks like this:
/*
Coloured Squares
This program displays nine coloured squares on the display
It employs a 1D array to hold the list of nine random colours
and a 2D array of the color datatype to represent the screen output
*/
// set up display area 200 by 200 pixels
size(200, 200);
// create an array called screenArray[][] to hold coloured pixel points representing the display area
color[][] screenArray = new color[200][200];
// create 1D array to hold random colour for each of the nine squares
color[] colour = new color[9];
// populate colour[] array with nine random colours, one for each square
for (int i=0; i<9; i++){
colour[i] = color(random(255), random(255), random(255));
}
// declare variable pixelColour to hold current element / pixel colour
// initialise to first random colour value
color pixelColour = colour[0];
// declare variables vertical and horizontal to control outer and nested loops
int vertical = 0;
int horizontal = 0;
/** loop through screenArray[][] and fill it with the appropriate values to produce coloured squares **/
// outer loop to process vertical direction, y axis, top-to-bottom column in the 2D array
for (vertical=0; vertical<200; vertical++){
// nested loop to process horizontal direction, x axis, left-to-right row in the 2D array
for (horizontal=0; horizontal<200; horizontal++){
if ((vertical <= 200) && (horizontal <= 200))
pixelColour = colour[8];
if ((vertical <= 200) && (horizontal <= 133))
pixelColour = colour[7];
if ((vertical <= 200) && (horizontal <= 66))
pixelColour = colour[6];
if ((vertical <= 133) && (horizontal <= 200))
pixelColour = colour[5];
if ((vertical <= 133) && (horizontal <= 133))
pixelColour = colour[4];
if ((vertical <= 133) && (horizontal <= 66))
pixelColour = colour[3];
if ((vertical <= 66) && (horizontal <= 200))
pixelColour = colour[2];
if ((vertical <= 66) && (horizontal <= 133))
pixelColour = colour[1];
if ((vertical <= 66) && (horizontal <= 66))
pixelColour = colour[0];
// store previously set pixelColour variable in the current element of our array
screenArray[vertical][horizontal] = pixelColour;
}
}
// use nested loops again to draw coloured squares to the display pixel by pixel
// note how this processing is separate from the array processing above. We can
// effectively manipulate an array in the computer's memory before displaying our
// results on the screen
for (vertical=0; vertical<200; vertical++){
for (horizontal=0; horizontal<200; horizontal++){
stroke(screenArray[vertical][horizontal]); //set colour from current element in our 2D array
point(vertical, horizontal); //draw pixel point at same position on the display area
}
}
We hope this example illustrates the point that an array is essentially a data structure in memory that holds data for reuse and has no interface in itself. Putting data into an array and retrieving data from an array is a process that takes place in the program. The point being made about 2D arrays is that they are very useful for storing image data corresponding to areas of the screen (in our case, pixels) and have many wide and varied uses in graphics applications. Another point to note is that Processing can easily be programmed to draw directly to the screen without the use of a 2D array at all. So what is the advantage of storing the screen data in an array first and then drawing it out to the screen later on?
Firstly, it creates a faster and cleaner appearance of the image(s) on the display; as the step by step generation of the data is masked from the user until it is completed – then drawn out to the display with two very fast nested loops.
Secondly, it allows for unpredictable processing where a particular point in the image may need to be altered from within the program if a certain condition becomes true. For example, suppose we have an image bitmap loaded into our screen array using Processing’s loadImage() syntax. We then set up an outer and nested loop to scan through each row of pixels as you have seen. If we come to an area of the image that has a colour that is darker than some threshold, we may want to darken the other image areas – perhaps to reduce contrast. If we are using an array, we can do this quite easily by re-initialising our nested loops and applying a formula to darken each of the lighter pixels; but if we had originally started out by drawing each pixel from the image directly to the display, it would then be too late to go back and process any of the previously drawn pixels.
We hope that this section on 2D (two dimensional) arrays helps you to understand an important construct in computer programming in general, as well as inspire you to apply the concept in a graphical context.