CSCI 103 Fall 2017 Introduction to Programming

CSCI 103 Fall 2017: Introduction to Programming

Bitmap Graphics

Administrative

Working with Libraries

For Lab 5 and PA 2, you will be accessing new libraries that we will give you.

Remember that built-in C++ libraries are accessed with:

#include <iostream>

The syntax for a non-built-in library uses quotes:

#include "bmplib.h"

A C++ library includes two main parts:

  1. the "header file," e.g. bmplib.h, which describes the functions provided by the library
  2. the full source code, in this case bmplib.cpp

Let's look at bmplib.h...

After the boilerplate, we have

Earlier, we found out that prototypes are enough to use a function that is defined somewhere else.

But where are the functions actually defined?

Let's peek at bmplib.cpp...

Don't try to understand the full file. The only important thing is that each function declared in the header is actually fully defined here. So in summary,

Compiling with Libraries

The bitmap library comes with a demonstration program, demo.cpp. But it uses the functions defined in the bmplib.cpp library. How can we compile two files at once?

Actually, we have some choices.

Grayscale Bitmap Processing

bmplib deals with 256-by-256 pixel (SIZE-by-SIZE) images:

 
 bmplib uses [row][column] indexing. The first index is the row (top 0, bottom 255), and the second is the column (left 0, right 255). Cartesian coordinates

To get started:

mkdir demo-bmplib; cd demo-bmplib
wget ftp://bits.usc.edu/cs103-2/demo-bmplib.tar
tar -xvf demo-bmplib.tar
make                  # note: might ask for password first time.

If asked, type in "developer" (though the screen hides what you type). Then run:

./demo
eog cross.bmp

Using bmplib in Programs

Here is a short example program:

// example.cpp
#include <cstdlib>
#include "bmplib.h"

int main() {
   unsigned char image[SIZE][SIZE];
   for (int i=0; i<SIZE; i++) {
      for (int j=0; j<SIZE; j++) {
         image[i][j] = rand() % 256;
      }
   }
   showGSBMP(image); // from bmplib
}

What do we expect this program to display?

To compile and run, we use

compile example.cpp bmplib.cpp -o example
./example

Use this pattern for compiling everything else today.

Simple fill exercises. Try re-creating the following images in C++ by using a nested loop and putting NON-random values in the 2D array:

Vertical gradient Reverse horizontal gradient Diagonal gradient

Here is a link to a solution for the first of the three.

Plotting

In lab 5, you will create a program capable of plotting rectangles and ellipses. When you plot a shape, you are selectively changing a set of pixels. For example, here is code to plot a diagonal line:

#include "bmplib.h"

int main() {
   unsigned char image[SIZE][SIZE];
   // black background
   for (int i=0; i<SIZE; i++) {
      for (int j=0; j<SIZE; j++) {
         image[i][j] = 0;
      }
   }
   // plot white diagonal line
   for (int t=0; t<SIZE; t++) {
      image[t][t] = 255;
   }
   showGSBMP(image);
}

This is similar to demo.cpp. Here are three more plotting exercises for practice:

x
X marks the spot The function y = x^2.
Hint: to get all pixels,
think of it as a square root.
A sine wave

Transformations

The bmplib library also lets us load existing files. Once loaded, we can transform them, like the following example demonstrates. You can get the image from

wget http://bits.usc.edu/files/cs103/graphics/elephant.bmp
#include "bmplib.h"

int main() {
   unsigned char image[SIZE][SIZE];
   readGSBMP("elephant.bmp", image);
   for (int i=0; i<SIZE; i++) {
      for (int j=0; j<SIZE; j++) {
         image[i][j] = 255-image[i][j]; // invert color
      }
   }
   showGSBMP(image);
}


This looks like:

Original file Displayed image

So this example inverted the colors (white vs black).

Can we instead invert the rows (top vs bottom)? Here's some code that tried to accomplish this:

#include "bmplib.h"

int main() {
   unsigned char image[SIZE][SIZE];
   readGSBMP("elephant.bmp", image);
   for (int i=0; i<SIZE; i++) {
      for (int j=0; j<SIZE; j++) {
         image[i][j] = image[255-i][j]; // flip top and bottom?
      }
   }
   showGSBMP(image);
}

Let's try it out and see what happens...

What we wanted instead was this:

What was the cause of this bug?

By the time we are reading the bottom half of the image, we've already copied the bottom into the top half.

Let's try it again, but using two separate 2D arrays for the original image and the transformed image.

#include "bmplib.h"

int main() {
   unsigned char src[SIZE][SIZE];
   unsigned char dest[SIZE][SIZE];
   readGSBMP("elephant.bmp", src);
   for (int i=0; i<SIZE; i++) {
      for (int j=0; j<SIZE; j++) {
         dest[i][j] = src[255-i][j];
      }
   }
   showGSBMP(dest);
}

Transformation exercises. Try to re-create the following effects:

Diagonal flip Tile Zoom

You can access all of the examples at: http://bits.usc.edu/files/cs103/graphics/

 

Parallel Arrays and Redirecting Input

As part of the next programming assignment (N-Body), you need to be able to do two things.

Let's talk about storing information first.

Suppose we want a program that keeps track of the name, age, and weight of several cats. Perhaps it reads input in this format:

3
Bella        9    4.5
Meowthuselah 2073 0.003
Garfield     57   30.0

where the first line is the number of cats to follow, and the numbers per line are a name, age, and weight.

How can we store this information?

Here's a sample program:

/*
catstats: accepts input in the format

3
Bella        9    4.5
Meowthuselah 2073 0.003
Garfield     57   30.0

First line is # of cats.
Each line contains name, age, and weight of a cat.

Program computes the maximum cat age, and
lists all the cats with age below half the maximum.

We assume # cats <= 100 and each name is <= 29 chars long
*/

#include <iostream>
using namespace std;

int main() {
   // variables to store input
   int numcats;
   char name[100][30]; // 100 names, each <= 30 bytes
   int age[100];
   double weight[100];

   // read the input
   cin >> numcats;
   for (int i=0; i<numcats; i++) {
      cin >> name[i];
      cin >> age[i];
      cin >> weight[i];
   }

   // compute maximum age
   int max_age = 0;
   for (int i=0; i<numcats; i++)
      if (age[i] > max_age)
         max_age = age[i];

   // print output
   cout << "The maximum age is " << max_age << endl;
   cout << "These cats have age less than half the maximum: " << endl;
   for (int i=0; i<numcats; i++) {
      if (age[i] < max_age / 2.0) { // avoid int division
         cout << name[i] << endl;
      }
   }

}

We need to use arrays: one for the names, one for the ages, and one for the weights. This parallel arrays idiom is useful whenever we have to store identical types of information about a sequence of objects.

Let's try running this program on our VM on the sample input.

cd (whereever you like)
wget ftp://bits.usc.edu/cs103-2/catstats.tar
tar xvf catstats.tar
compile catstats

Input Redirection

If we have a lot of cats, it gets quite inconvenient to re-type in the input every time we run the program. The shell therefore makes it possible to redirect the input from a file. (This is possible not only in Linux, but also in the OSX Terminal and the Windows Command Prompt.)

For example, the file mycats.txt has information about my cats (we'll open it up in gedit to check). Instead of typing it in manually, I will execute

./catstats < mycats.txt

which will execute the program, but taking input from the mycats.txt file instead of from the keyboard. Try it out! It should print

The maximum age is 2073
These cats have age less than half the maximum:
Bella
Garfield

What happens when we run the following?  ./catstats < longcats.txt

Exercise

Modify catstats.cpp to create a program called fatcats that prints out the average weight and all the cats with weight above the average. You should get the following outputs on these test cases:

$ ./fatcats < mycats.txt
The average weight is 11.501
Here are the cats with above-average weight:
Meowthuselah
Garfield
$ ./fatcats < longcats.txt
The average weight is 8.19767
Here are the cats with above-average weight:
Bella
Max
Kitty
... some cats omitted
Jasmine
Tucker
Skittles

Output Redirection

You can also redirect the output of a program. This can be quite convenient so that you can save the output and look at it in a text editor instead of always scrolling up your terminal. Look at how the > symbol is used:

./hello_world > hello.txt
./fatcats < mycats.txt > myfatcats.txt