Getting Started
  • Read the lab writeup: https://profmarz.com/index.php/cs102/labs/labJ
  • There is no debug for Lab J. This gives you (a little) more time to work on your lab.
  • PPM is a file format I have written documentation for. Dr. Marz even included a link to it on the official lab writeup. You must understand how PPM files work before you write a program to read it in. Check it out here: http://utk.claranguyen.me/guide/PPM.html
  • I wrote a powerpoint for my lab session... for once. It includes a visualisation of vectors of vectors which you may want to check out here.
If you missed lab or failed to copy my code from the projector during lab session, the files I typed/utilised during lab sections 1 and 2 are available here: [LINK]
Synopsis
This is where things get a bit rough. No worries, it is the introduction to real problem solving. PPM is a very easy format to memorise and comes in many flavours. For this lab, you will be reading in text-based PPM files (With the P3 header) and then manipulating it and outputting it to a file. It is like how you use Microsoft Paint, but you are writing it. For the lowdown of the lab, check out the bottom of the page, like usual.
Handling Command Line Arguments

Demonstration: Print out all arguments passed into program

If you actually go to my lab lectures, you will notice I do everything by command line. Yes, I like to expose you to it, but also it is very easy to execute programs in certain ways. Let's say I had a program, argtest.cpp, and it had this code:
#include <iostream>
using namespace std;

int main(int argc, char* argv[]) {
	for (int i = 1; i < argc; i++) {
		cout << argv[i] << endl;
	}
}
Let's run this program like I normally would:
./argtest
...And it prints out nothing. Fantastic. Now let's load in some arguments.
./argtest This is a test
It will output the following:
This
is
a
test
Okay okay, wait a minute. Let's break down what is going on here.

It should be common knowledge at this point that main() is a function, just like every other function you write. It just happens to be that main is executed after the program starts (well, that is putting it simply). Because of this, "main" can take arguments just like other functions. But you will almost always use int argc, char* argv[] as your arguments (You might also see the C equal, "int argc, char** argv". Don't use it in C++). So what do these variables mean?
  • argc - ARGument Count. It has the number of arguments passed into a program.
  • argv - ARGument Vector. It is a "c-string" that contains values passed into the program. For your sake, this is essentially an array of strings.

"Wait Clara, your for loop starts at 1!"

Good eye. It starts at 1 and goes up to argc. You want to know what would happen if we started it at 0? It would print this:
./argtest
This
is
a
test
The path to the program being executed is the 0th element in your argv array. We don't usually use this (I've had a few uses) so the first useful argument to you is argv[1].

Example: Require certain number of arguments to run program

As you make more and more complicated programs, you will want specific input from your users. What if you made an image editor (HINT HINT) but it required an input file to read from, and an output file to write to? That means your program will require 2 arguments passed into it. Here is how you can check for that:
int main(int argc, char* argv[]) {
	//Return if more or less than 2 arguments are passed into program
	if (argc != 3) {
		cout << "Error: Requested 2 arguments, got " << (argc - 1) << endl;
		return -1;
	}

	//Carry on...
}
That "3" is no typo. Remember earlier when I said about how the 0th value of argv is the program path? To ignore that, we have to increment the requested arguments by 1. So if the program requires 2 arguments, we need to say that it takes 3. Hence "./imgedit imageA.ppm out.ppm" being 3 arguments, but the first is the program name.

Example: Optional Arguments

I feel like I am "spoon-feeding" at this point, but what if your program wanted to have 2 required arguments but also an optional argument? Sure.
int main(int argc, char* argv[]) {
	//Either 2 or 3 arguments please
	if (argc < 3 || argc > 4) {
		cout << "You failed" << endl;
		return -1;
	}

	if (argc == 4) {
		//Do something with the optional argument
	}

	//Carry on
}
Class Constructors
Constructors are an important part of a class/struct. Think of it as a function that automatically fires the moment you initialise a class. Let's go straight into an example since that is the best way to show it.

Example: Setting private variables to 0

I want a struct called "point_3D" which stores coordinates x, y, and z as doubles.
struct point_3D {
	double x, y, z; //Variables
};
When I initialise the struct, the values are not declared. So let's make a constructor that will set all three axis points to 0.
struct point_3D {
	point_3D();     //Constructor
	double x, y, z; //Variables
};
Congratulations, you prototyped it. Notice, it doesn't return anything. In fact, a return type isn't even there. If you try to compile it now, it will fail. Wonderful, let's actually define this constructor.
point_3D::point_3D() {
	x = 0;
	y = 0;
	z = 0;
}
Now try compiling the code. It will work! And furthermore, it will set all of the values to 0 when an instance of "point_3D" is initialised.

Continued Example: Constructors that take variables

So you initialise "point_3D" and the values are set to 0. That's fine and all, but I would then have to set the values manually by going into the struct and go "point.x = ??;", and so forth. That can get very messy. You can make constructors that take arguments, since they are functions. So let's make a constructor that takes 3 doubles and sets x, y, and z to them.
struct point_3D {
	point_3D();                       //Constructor
	point_3D(double, double, double); //Also a constructor
	double x, y, z;                   //Variables
}
So you probably noticed that there are two functions named "point_3D". Yup, don't freak out. Functions can have the same name as long as the variable types you throw in are different. So I can have 2 functions called "add" where one takes 2 ints, and another takes 2 doubles and have them do two completely different things.

Moving on though, let's define the second constructor.
point_3D::point_3D(double a, double b, double c) {
	x = a;
	y = b;
	z = c;
}
So what do we have now? We have two constructors for the struct "point_3D" which set x, y, and z automatically.

Continued Example: Declare a struct or class with constructor that takes variables

So you have your constructor that takes multiple arguments, but you probably have no idea how to actually use it. Let's look at this main function:
int main() {
	point_3D p; //Initialises p
}
The above code will initialise "p", and set p.x, p.y and p.z all to 0 because it calls the constructor function. No questions asked. Now how about that second one that sets points automatically?
int main() {
	point_3D p(1, 2, 3); //Initialises p with predefined points
}
This is how you call it, and it will initialise "p" to set p.x to "1", p.y to "2", and p.z to "3". Now you probably also see why the constructor has no return value. There is nothing the value can return to.

I bring up constructors right now because the next section talks about Vectors, which is a class with its own constructors to help make your code look much cleaner.
Vectors
I've waited the entire semester to write about this. Vectors are one of your many friends in C++. They act like an array... but turns out, vector is a class that is "armed and dangerous" with functions for you to use. You can resize it at will. You can reverse elements in it. You can sort them. You can store anything you want in them. It will take any data type you can throw at it. Vectors are very useful in C++.

Initialisation

Vectors are not initialised the same way as arrays are. Since they are a class, you have to declare them as a data type and with a constructor. So:
#include <vector>
using namespace std;

int main() {
	vector<int> vec; //Create a vector of "int", size 0.
}
Let's set the size to 4 upon initialisation:
#include <vector>
using namespace std;

int main() {
	vector<int> vec(4); //Create a vector of "int", size 4.
}
How about resizing it to 8?
#include <vector>
using namespace std;

int main() {
	vector<int> vec(4); //Create a vector of "int", size 4.
	vec.resize(8);      //Resize it to 8.
}

Storing and getting values

Okay so we get it, you can resize vectors. But what about storing numbers and getting numbers out of it? Let's say I wanted to store every number from 0 to 99 in a vector of size 100, and then print out the 65th element of that vector.
int main() {
	vector<int> vec(100);            //Create a vector of "int", size 4.
	for (int i = 0; i < 100; i++) {
		vec[i] = i;                  //So vec[0] = 0, vec[1] = 1, etc.
	}
	cout << vec[64] << endl;         //Print 65th element ("64") to the console.
}
See what I mean? Vectors act just like arrays and can be used as such.

push_back(...&)

This function is a "fan-favourite" for the vector class. It will insert an element to the back of the vector and increment the vector's size by 1. So if I had a vector (named "vec"), size 4:
52, 89, 201, 2
If I ran the following code:
vec.push_back(123);
The size will become 5 and the number "123" will be at the end like this:
52, 89, 201, 2, 123
A word of warning: If you know the number of entries you are reading in, it is better to just use resize and edit the elements inside the vector directly as opposed to pushing back multiple times.

Storing a struct (or class) in vector

When I said you can store any data type in a vector that you can throw at it, I meant it. This includes classes and structs. Behold.
struct point_3D {
	double x, y, z;
};

int main() {
	vector<point_3D> vec; //Initialise
	vec.resize(2);        //Set size to "2"

	vec[0].x = 0.123;     //Set first point's coordinates
	vec[0].y = 19.2;
	vec[0].z = -23;
}

push_back on a struct (or class)

When you are in a situation where you do not know the number of instances being read in from a file, you can read into a temporary struct or class and then push_back into the vector. It will work because push_back copies the data.
struct point_3D {
	double x, y, z;
};

int main() {
	vector<point_3D> vec; //Initialise
	ifstream fp;
	fp.open("test.txt");
	point_3D temp;
	while (fp >> temp.x >> temp.y >> temp.z) {
		vec.push_back(temp);
	}
}
The reason I mention that it copies data is because what would happen if we push_back'd on the data, but then change temp? It would be horrible if it also changed the value of the element pushed into the vector. C++ saves you that stress.
Vectors of Vectors
So you now (hopefully) know about the luxury that vectors can give you. Unfortunately, this lab doesn't require you to use one vector. It requires you to have a vector of vectors... or essentially a 2D array. Vectors actually make this easy to understand too. We will step through this one by one.

Initialise a 2D Vector of Vectors of ints

I want to make a 10x5 (width x height) grid of integers. Easy right? Before we actually do this, we need to know how to even initialise this thing. Behold:
vector< vector<int> > grid;
You should ALWAYS include the spaces the way I did it here. If not, the C++ compiler will confuse ">>" with the one you use when using cin. You need spaces there so it knows what you are trying to do (Unless you enable C++11... but it is still good practice).

So let's continue. 10x5. Let's think about the outer vector first. Every entity of that vector contains another vector of ints in it. So "grid[0]" will return a "vector<int>", and so will "grid[1]". But they are unique. So let's set up the outside vector to have "height" number of vectors of ints:
grid.resize(5); //Set size to height of grid
So now we have 5 vectors of ints stored in this vector. Now we want all of those vectors to have the size "width". To do this, we will need to make a for loop and loop around all of the entries and set the size of those vectors one-by-one.
for (int i = 0; i < height; i++) {
	grid[i].resize(10); //Set size to width of grid
}
If you want a visualisation of this process, check out the powerpoint linked at the very top of this page. I visualise how the grid is generated there.

Accessing elements in a 2D Array

So you can access a vector inside a vector with "grid[0]", but it will just return a "vector<int>". So how do we access the elements inside that vector?

...Just call the [] operator twice.
grid[0][0] = 2; //Sets the first element in the first vector to "2"
grid[0][2] = 3; //Sets the third element in the first vector to "3"
Why this works: You can access an element inside of a vector with the [] operator. So if you call grid[0], it will give you a vector of ints. You can then take that vector and access the first element of it by using the [] operator on it as well. So there will be two. [][]. The first one accesses the vector of rows. The second one accesses the cell in that row. The general formula for this example is:
grid[y][x]
The Lab
I hope you have soaked in the knowledge I typed out above, because it will help you, a lot.

I will assume you have mastered vectors by this point since they are like arrays (Which you should have mastered too...). You will be utilising vectors of vectors as if it was a 2D array to store information about a PPM image. PPM images are a really simple format to understand as they simply store their width, height, and pixel information. (Standard) Pixels contain three colours, red, green, and blue. So you will have a struct for that, of three ints. On top of that, you will need to have constructors to set the values accordingly. NO CHEATING AROUND THIS. Constructors are a very important aspect of classes.
struct rgb {
	rgb();              //Prototype for "set r, g and b to 0".
	rgb(int, int, int); //Prototype for "set r, g and b to whatever these 3 are".
	int r, g, b;        //Colour values
};
Define the two constructors. One will set all three values to 0. The other takes 3 ints, and will set r, g and b to whatever these three ints are. Afterwards, you will have to declare your picture class to have the functions needed to actually do the lab.
class picture {
	public:
		//Your functions

	private:
		int width, height, intensity;
		vector< vector<rgb> > pixels;
};
Implement the functions given in the lab writeup, and you will be good to go. I will give some advice:
  • Your class should have an additional 3 ints for width, height, and intensity. Remember, a PPM specifies these at the very beginning of the file.
  • Write your read function first and see if it works on printing out colours of a PPM file. Use my guide's example PPM file and check output if you have to.
  • Write your Invert() function first. It is literally a nested for loop setting the colour values to (intensity - value), for all pixels. So if a pixel had 200 red, 100 green, and 50 blue, inversion would say Red = 255 - 200 = 55, Green = 255 - 100 = 155, Blue = 255 - 50 = 205.
  • Both of your reverse functions should be similar to your old lab where you read in student info and reversed it. Do NOT use the vector.reverse() function. And it isn't because I am mean. You must know about STL Iteration before you can use functions like reverse, insert, erase, etc... and that won't come for a while.