Programming Assignment 3 Image Processing – Pointers, Class & Dynamic Data Solution

Please only submit MCS2514Pgm3.cpp
This project is called “Image Processing” which will shrink an input image, convert a color image to gray image, add random noise to an image, add the RGB values of an image , or (as bonus) to denoise the image.
But before we rush into this programming challenge, it is important to be intentional about some basic design considerations and a few techniques for managing and manipulating digital images. In solving this problem we will be using classes, pointers, and structured data in order to organize an efficient solution.
The images you will manipulate are two dimensional images with a width and a height in units of pixels.
Think of an image as a 2D array, indexed by rows (horizontal “scan lines”), which go across the image, and columns (vertical stripes through the image). Any pixel in an image is located by its (row, col) coordinates, or in C++ array terms, myImage[row][col]. Stored at each position in this two dimensional structure are the details of an individual pixel, represented as a point in a 24-bit RGB color space (8 bits for red, 8 bits for green, and 8 bits for blue). This is a common way to store image data, and is straightforward to understand. In this case, each and every pixel in the image consists of three values that are each 8 bits in length. This 8 bit size is no accident (we can store 8 bits in one unsigned char variable).
These three values represent the red, green, and blue components of a single pixel. Since the value has 8 bits, it can hold any value between 0 and 255. A pixel with red, green, and blue all zero will appear black; a pixel with R, G, and B all equal to 255 will appear white. Or put another way, a low value is “dark” and a high value is “bright”. Just like an array, the origin (pixel at 0, 0) is normally viewed by convention as the upper left pixel, with rows horizontal and columns vertical. So first, make sure you understand the structure of an image: a 2D array (with a width, height); scanlines as rows; a coordinate frame that has the origin in the upper left, with positive x along the horizontal from left to right, and positive y along the vertical from top to bottom; and at each location a composite pixel object which contains values for R,G,B – and each of those will require 8 bits. The structure/layout has a very natural visual interpretation.
In this project, we will work on how to find the average color in a larger block of pixels can be thought of as a very simple re-sampling of the bigger image. This technique is often used to generate smaller versions of big images. (think of a thumbnail of the original image). In this assignment we will be writing a program that can:
1.) Allow simple loading and saving of images.
2.) Break a loaded image up into blocks of a given width and height and find the average color in that block.
3.) Create a new image from the process in (2) that will consist of 1 pixel per every block analyzed in the first image.
4.) Apply a red, green, or blue tint to an image by increasing the appropriate RGB values to every pixel in the image.
5.) Randomly add noise pixels into an image.
6.) As bonus function, to denoise the image with noise.
Provided in the code base for this assignment, you will see the following files in addition to the usual:
globals.h

Per usual, you can turn on a console to output debugging information to by modifying the SHOW_DEBUG_CONSOLE variable. You should also note in the file that the type “unsigned char” has a typedef to the easier to write, “uchar”. These types are interchangeable throughout the program.
pixel.h
This file contains the “pixel” class that is used to store the red, green, and blue values for a pixel of data.
Note: that the pixel class consists of 3 public uchars to store this data. The implementation of this class with it's data members public is an example of a POD (Plain Old Data) Class. In almost all cases, you want to keep your data members private and provide getters and setters to access them. However, in some cases (such as this) we require no functions whatsoever (no constructors or any member functions) and just want to aggregate several pieces of data into one object. In these cases, it is considered acceptable to just have the data members publicly accessible, as it allows for a smaller memory footprint and easier notation when referencing the data.
image.h / image.cpp
This class is provided to do the grunt work of loading and saving the images. It also provides a way for the windows GUI to display the images represented in our RGB pixel data. The functions you should be concerned with are:
bool loadImage(string filename)
This function will load an image from the specified file and return true if it loaded successfully, false if it did not.
void saveImage(string filename)
This function will attempt to save the picture stored in it's pixel data to disk at the specified file location.
void createNewImage(int width, int height)
This function will clear all current data in the image object and create a new blank image of the specified
width and height. You should call this or loadImage BEFORE you try to access the pixels of an image.
int getWidth()
This function returns the number of pixels wide that the image is.
int getHeight()
This function returns the number of pixels tall that the image is.
pixel** getPixels()
A multidimensional array of pixel objects makes up any of our images. However, because we don't know the size of the image beforehand, this multidimensional array is created dynamically at run time. Feel free to look at the image.cpp code to see how this is done.
This function will return that multidimensional array. Note the return type of pixel**. Remember that an array is essentially a pointer to the first value of the array block in memory. So pixel[ ] is equivalent to pixel*. After understanding this, it is a logical jump that a 2 dimensional array is actually a pointer to a pointer. Therefore, when calling this function, you should write something like:
pixel** myPixels = myImage.getPixels();
You can then reference the elements of the array normally. Ex:
myPixels[0][0].red = 255;
This would set the red value of the first pixel in the image to its max value. You should utilize these objects as you see fit to implement the specifications for this program. Supplied for you is a GUI interface written in Visual C++.

You should only need to modify the file: MCS2514Pgm3.cpp. You must implement the following functions in MCS2514Pgm3.cpp :
bool loadImageFromFile(string filename)
INPUTS: a string containing a path to a file to open. This value is returned from the user's selection in the open file dialog.
OUTPUTS: a boolean indicating whether or not the image could be opened correctly.
void saveImageToFile(string filename)
INPUTS: a string containing a path to save the current image out to.
OUTPUTS: NONE
image* displayImage()
INPUTS: NONE
OUTPUTS: This function should return a pointer to the image object that is currently being viewed on the screen. If a user has loaded an image correctly, you should return a pointer to an image object containing the base image. If a user has used the shrink button (aka averageRegions function) or performed any of the red/green/blue filters, or invert function you should of course return a pointer to an image object that reflects all these changes.
void averageRegions(int blockWidth, int blockHeight)
INPUTS: Integers indicating the width and height of the “blocks” to be averaged
OUTPUTS: NONE
When this function is called, you should create a new image that will consist of 1 pixel for every block of size blockWidth by blockHeight pixels in the original image, with each pixel being the average color of the pixels in that region in the original image. So for example if we took the image:
(which is 64 x 64 pixels) and called this function with blockWidth = 16 and blockHeight = 16, then the new image resulting from the function should be a 4 x 4 pixel image consisting of:
white, red, green, blue
black, black, black, black
gray, gray, gray, gray
white, red, green, blue
Obviously it is simple to take the average color of the blocks in question because they are each a solid color, but this should suffice for an example. If the image does not divide evenly into the number of blocks specified, just throw away the remaining pixels off of the right and bottom of the image. (So if the above example was 70 x 70, we would just analyze the first 64 bits (4 blocks) of the pixel data and ignore the last 6 on the top and bottom).
Please note that it may be easier if you split this into 2 functions and call your helper function from within this one. The helper function could then just calculate the average value of a block of pixels given to it, and return that to the averageRegions function to be used. However, this implementation is up to you! Complete it as you see fit.
void increaseRedValues(int value)
INPUTS: An integer indicating the amount to increase the red component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the red component of each pixel in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.
void increaseGreenValues(int value)
INPUTS: An integer indicating the amount to increase the green component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the green component of each pixel in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.
void increaseBlueValues(int value)
INPUTS: An integer indicating the amount to increase the blue component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the blue component of each pixel in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.
void grayImage( )
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should convert the current image to gray image. For each pixel, you calculate the average of aver = (R+G+B)/3, then replace the pixel value using (aver,aver,aver)
for R, G, B values.
void addNoise()
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should randomly replace 10% of the pixels with noises in the current image. To create the noise, you should also randomly generate the R,G,B values for the noise pixel.

Bonuse function (30 points)

void deNoise()
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should denoise the image to remove the noise.
In order to remove the noise, you need to travel every single pixel. For every pixel, you calculate the average pixel from its neighbor (you can use a n*n block for its neighbor, n can be changed).
Then replace the pixel by average neighbor pixel.
You also need to declare some global variables (similar to previous projects) that can be used for the above functions.

A couple sample images have been included for your convenience. Feel free to test your program with these or any other images. Please note however, that these images can be in jpg, tif, png, bmp, etc format, but MUST be saved as a 24 bpp (aka 8 bits per RGB value) image. We are limiting our work to this color space. There are many other colorspaces that images can be saved in (Grayscale, Palette, CMYK, etc) but these are beyond the scope of this assignment. If an image can not be loaded for this reason or one similar, a message will be printed out to the console (if it is turned on) and false will be returned from the load function.
When submitting your program you should submit the following files:
1. MCS2514pgm3.cpp
Example Screens from the program:
Open an new image:
Shirnk it by block width 3, block height 3
After adding noise

This file should interface with the supplied windows GUI interface to allow images to be resampled to smaller sizes and be tinted.
Please read the included Project Specification for more detailed descriptions of the project.

In this assignment, we will be laying the foundation for a larger photo-mosiac project.
We will be finding the average color in a larger block of pixels (aka re-sampling).
This technique is often used to generate smaller versions of big images. (think of a thumbnail of the original image)

In this assignment we will be writing a program that can:

1.)Allow simple loading and saving of images.
2.)Break a loaded image up into blocks of a given width and height and find the average color in that block.
3.)Create a new image from the process in 2 that will consist of 1 pixel per every block analyzed in the first image.
4.)Apply a red, green, or blue tint to an image by increasing the appropriate RGB values to every pixel in the image.
5.)Invert the individual RGB color components of an image.


The functions in this file should be completed as follows:

bool loadImageFromFile(string filename)
INPUTS: a string containing a path to a file to open. This value is returned from the
user's selection in the open file dialog.
OUTPUTS: a boolean indicating whether or not the image could be opened correctly.

void saveImageToFile(string filename)
INPUTS: a string containing a path to save the current image out to.
OUTPUTS: NONE

image* displayImage()
INPUTS: NONE
OUTPUTS: This function should return a pointer to the image object that is currently being viewed on the screen.
If a user has loaded an image correctly, you should return a pointer to an image object containing the base image.
If a user has used the shrink button (aka averageRegions function) or performed any of the red/green/blue filters,
you should of course return a pointer to an image object that reflects these changes.

void averageRegions(int blockWidth, int blockHeight)
INPUTS: Integers indicating the width and height of the blocks?to be averaged
OUTPUTS: NONE
When this function is called, you should create a new image that will consist of 1 pixel for every block of size
blockWidth by blockHeight pixels in the original image, with each pixel being the average color of the pixels in that
region in the original image.
Please note that it may be easier if you split this into 2 functions and call your helper function from within this one.
The second function could then just calculate the average value of a block of pixels given to it, and return that
to the original function to be used. However, this implementation is up to you! Complete it as you see fit.

void increaseRedValues(int value)
INPUTS: An integer indicating the amount to increase the red component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the red component of each
pixel in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.

void increaseGreenValues(int value)
INPUTS: An integer indicating the amount to increase the green component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the green component of each pixel
in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.

void increaseBlueValues(int value)
INPUTS: An integer indicating the amount to increase the blue component of each pixel.
OUTPUTS: NONE
When this function is called, you should take the current image and increase the blue component of each pixel
in the image by the amount specified. Please note that an RGB value has a maximum of 255 and a minimum of 0.

void grayImage( )
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should convert the current image to gray image.
For each pixel, you calculate the average of aver = (R+G+B)/3, then replace the
pixel value using (aver,aver,aver) for R, G, B values.

void addNoise()
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should randomly replace 10% of the pixels with noises in the current image. To create
the noise, you should also randomly generate the R,G,B values for the noise pixel.

Bonuse function (30 points)
void deNoise()
INPUTS: NONE
OUTPUTS: NONE
When this function is called you should denoise the image to remove the noise.
In order to remove the noise, you need to travel every single pixel. For every pixel,
you calculate the average pixel from its neighbor (you can use a n*n block for its
neighbor, n can be changed). Then replace the pixel by average neighbor pixel.
Powered by