MP_COLLAGE: Classes, Inheritance, and Operators¶
Due date: 2024-05-09 23:59.
Description¶
Welcome to the final assignment of our image manipulation series. In this last installment, we will utilize the classes you are already familiar with from previous labs and MPs, including
RGBAPixel
, PNG
, and Drawable
, to create a versatile new class called Canvas
. The purpose of this class is to enable blending of various shapes and images. To accomplish this, this new
Canvas
class will inherit from Drawable
. Another new CanvasItem
class will inherit from PNG
and will allow you to blend scaled, colorized and repositioned PNGs on top of each other. By the end of this assignment, you will have the means to create fun collages, such as the one on top of this page, using the Canvas
class.Download The Provided Files¶
Start with downloading and unzipping the provided files from here: mp_collage.zip.
In your provided mp_collage directory you will find many files, however, to complete your assignment you will only need to modify the following files:
- canvasitem.{cpp,h}
- canvas.{cpp,h}
- drawable.h
Compiling and Running the Provided Code¶
We provided you with the set of files that properly compiles with:
make
The result is two executable files, collage and collage-asan. Try running
./collage
and you will find two new output files, out.png and collage.png, in your current directory. Check how they look right now (open the files in Windows or any other method).
The provided executable isn't functioning correctly at the moment, so your task is to fix the files to get the correct output images. Right now, collage.png only shows the background aurora image and a white rectangle, but once you make the necessary corrections, it should look just like the collage at the top of the page. You'll need to overlay four new images on the background, with a frame around three of them and a partially transparent University of Oulu logo.
Now, let's talk about out.png. The current code only renders the final layer, in which the text “This text is in the foreground” appears on the black background. Instead, the image is supposed to have some text, lines, and triangles all blended together using the alpha channel. Take a look at the animation below for a visual representation of all of the layers, and the final desired result:
The solution images are also provided to you in soln_out.png and soln_collage.png. Now, let's begin resolving the problems by breaking down the tasks into separate exercises.
You can consider the assignment solved once the
diff
command applied to the solution images produces no output or differences between them.diff out.png soln_out.png diff collage.png soln_collage.png
Understanding The Provided Code¶
The goal of this assignment is to fix definitions of two new classes,
Canvas
and CanvasItem
located in their appropriate files canvas.{cpp,h} and canvasitem.{cpp,h}. A
CanvasItem
object is a subclass of the PNG
class. This means it inherits all the member functions of the PNG
class; so anything you could do with a PNG
, you can also do with a CanvasItem
. The CanvasItem
class represents an individual item that can be added to a Canvas
. It will hold information such as the position, size, and appearance of the item.Now, the
Canvas
class is responsible for managing a collection of CanvasItem
s. It allows you to add, remove, and manipulate items within the canvas. We set it up so that the Canvas
class inherits from Drawable
, which allows it to implement the draw()
function to render all of the CanvasItem
s on the Canvas
.Exercise 1: Data Structure to Hold CanvasItems in a Canvas¶
You first task is to establish a suitable way to store the
CanvasItem
s within the Canvas
class. In canvas.h, they are currently stored in a variable called items
as a pointer to only a single CanvasItem
:CanvasItem* items;
Your task is to redefine the storage structure for
items
in any way you like. You can choose any data structure we have already covered in the course. The new structure should have the capability to hold a minimum of 10 CanvasItem
s and ensure that they retain the order in which they are added.Exercise 2: Constructor, Destructor, Add, and draw Functions¶
Once you have modified the declaration of the variable
items
in the previous exercise, it is likely that the code has become broken and does not compile anymore. To ensure that it compiles again, you need to fix the following functions in the canvas.cpp file: the constructor, the destructor, the Add
, and the draw
functions.Here are the requirements for each function:
Canvas::Canvas()
constructor: Initialize theitems
variable appropriately if needed.Canvas::~Canvas()
destructor: Make sure any manually reserved memory gets freed.void Canvas::Add(CanvasItem* item)
function: Add theCanvasItem* item
to theitems
structure. When writing this function, make sure you maintain the correct order of the addeditem
s on theCanvas
since they are rendered on top of each other based on that order. This will ensure that the intended rendering of theitem
s is correct.void Canvas::draw(PNG* canvas) const
function: Modify theDraw
function to iterate through theitems
storage structure according to the order in which they were added, starting from the firstCanvasItem
ever added to the last one. The current implementation only works for one itemCanvasItem* item = items;
and it won’t compile if left like this. You need to update this line to handle multipleitems
correctly.
After you fix the above functions your code should compile. Your
Canvas
class can now manage multiple items
:make clean make ./collage
If you check the out.png and collage.png, you can see that the
items
are not yet rendered properly. Although rendering indeed happens in the correct order, you won't be able to see it yet because the CanvasItem
s are stacked on top of each other while transparency is not yet implemented. To fix this, you'll need to do some more work in canvas.cpp and canvasitem.cpp. You will also need to know a bit about alpha blending, methods in computer graphics for handling layers and transparency.Exercise 3: CanvasItem Constructor¶
The constructor of the
CanvasItem
class needs to be properly configured to enable rendering multiple CanvasItem
s blended together on a Canvas
while handling transparency correctly.In the canvasitem.cpp file, modify the constructor to set the default
alpha
values of every pixel to 0, indicating transparency. Although at 0 alpha it won’t show, it’s best to be thorough and set the default values of all other color channels (red
, green
, and blue
) to 255, which matches the PNG constructor and indicates full intensity. This ensures that newly created CanvasItems
have transparent pixels by default. When drawing shapes, pixels outside of the shape’s area are unaffected and will therefore remain transparent. This way, drawing and blending a triangle, for example, will add just the triangle to the image, instead of a white rectangle containing a triangle.Tip: Normally, when accessing individual pixels in a
PNG image
object, we use the function call operator ()
defined in the PNG
class, like this:RGBAPixel* pixel = image(x, y);
However, in the context of the
CanvasItem
class, which inherits from PNG
, you can directly access this operator by dereferencing this
, as in:RGBAPixel* pixel = (*this)(x, y);
Alternatively, you could call the operator
()
explicitly: RGBAPixel* pixel = operator()(x, y);
Exercise 4: getBlendedPixel Function¶
We need to have a proper way to access the color of individual pixels of each
CanvasItem
. As CanvasItem
inherits from PNG
, it seems tempting to access the RGBA values of an individual pixel of the CanvasItem
, by simply calling the PNG
s function call operator ()
:RGBAPixel * PNG::operator()(size_t x, size_t y);
However,
CanvasItem
has a private variable called color_
, defined in canvasitem.h that should be blended with each pixel without modifying the source image. In this case, we want to multiply each pixel with color_
component-wise. In other words, we need a function that gets the pixel in given coordinates, creates a copy of it, then multiplies it with color_
and returns it.Your task is to finish writing the following function responsible for returning the values of blended pixels of the
CanvasItem
:RGBAPixel getBlendedPixel(size_t x, size_t y);
To achieve this, use the following blending algorithm: multiply each RGBA channel of every
CanvasItem
pixel with the corresponding RGBA channel of color_
, and then divide the result by 255. This division ensures that the resulting color values are normalized within the range of 0 to 255. Note: if our color values were defined as 0-1 instead of 0-255, that division would be unnecessary.After fixing this, you will see the change in colors of the out.png file, as the text will appear red. That is because the foreground.png normally has white text, but now the white pixels are being multiplied with red, which results in red.
Exercise 5: draw Function¶
As you can see in the comments of the
Canvas::draw
function in canvas.cpp, there are a few more things that need to be fixed. You already made sure that the first loop iterates through the items
in the correct order, however, there are three more aspects to fix about the function. You can tackle these issues in any order you like, but we listed them in the order of their location in the code of the Canvas::draw
function:Properly Arranging CanvasItems on Canvas¶
Currently, each
item
is placed onto the canvas at a default position and scale. However, we need to place them on the canvas according to the values of their item->scale()
and item->position()
Start with uncommenting the following line:
// You should uncomment the below line and use in the section below
Vector2 pos = item->position();
As you are iterating through each pixel (x,y) of each item, you need to update these pixels to the new values (x1, y1) according to their
sc
and pos
. Do this by updating the following lines:// Modify the two lines below
int x1 = x;
int y1 = y;
Once you scale up a
CanvasItem
, it is possible for gaps to appear between adjacent pixels in the pixel structure. This means that after the item
is scaled up, pixels that were originally adjacent may no longer be adjacent, resulting in empty spaces between them.To address this issue, there are two loops in the code that iterate through the
xs
and ys
coordinates of the adjacent pixels, filling in the gaps. To ensure that your (x1, y1) coordinates include these adjacent pixels, add xs
to x1
and ys
to y1
:// Add these lines to your code
x1 += xs;
y1 += ys;
Once this is fixed, you should see multiple boxes positioned around the image in collage.png!
Alpha Blending of CanvasItems¶
Alpha blending has not been implemented. Alpha blending is a technique that combines the colors of overlapping pixels based on their transparency, resulting in smooth and transparent composite images. The idea is to interpolate between the new and the old pixel based on the new pixel’s alpha value. This is called straight alpha blending. The formula looks like this:
result = (source.RGB * source.A) + (dest.RGB * (1 - source.A))
In other words when drawing a pixel with 0 alpha, nothing should be drawn. Alpha 255 means the previous pixel is completely replaced by the new pixel, and with alpha 1-254, the two colors are interpolated smoothly.
Note, however, that you must handle each RGB channel separately and of course follow the syntax and naming in our code, so don’t just copy that line!
Once this is fixed, you should see other layers below the foreground text in out.png!
Note, however, that you must handle each RGB channel separately and of course follow the syntax and naming in our code, so don’t just copy that line!
Once this is fixed, you should see other layers below the foreground text in out.png!
After solving all of these, the structure of these images should start to become apparent, but some issues still remain to be fixed.
Exercise 6: Remove and Swap Function¶
Although out.png should show the correct output now, collage.png has some issues, which we can resolve by fixing some functions in canvas.cpp.
- The reason you see squares on top of the background image instead of the images is that the squares are meant to be the frames, but it only works if they are drawn before the images. To fix the order of the layers, the
Swap
function must be implemented. It takes two pointers toCanvasItem
s as arguments, locates them in the list and swaps their positions. The implementation of this function may depend on how the list is implemented.
- The
Remove
function should locate the given pointer in the list and remove it such that it is not drawn anymore, but the memory should not be freed here. The list should otherwise retain its order, so if something in the middle of the list is removed, make sure the continuity of the list is not broken. This should get rid of the frame for the partly transparent logo since it doesn’t look very good there!
Finally, collage.png should match soln_collage.png! I also encourage experimenting with changing things around in
main.cpp
or adding your own images, since there are more things that you could do here, such as applying color to the collage images or scaling them. That might also reveal some flaws in our implementations. After all, image manipulation can get quite tricky!Exercise 7: One More Thing¶
While you have corrected the output .png files, there is still one issue remaining: there are memory leaks in your program, which our autograder will detect. To fix the memory leaks, go through drawable.h and canvas.h files. Are all of the principles of inheritance discussed in lectures implemented properly? Once you fix any bugs, you will be done with this assignment.
Acknowledgments¶
We would like to express our gratitude to prof. Cinda Heeren and the student staff of the UIUC CS Data Structures course for creating and sharing the programming exercise materials that have served as the basis for the labs used in this course.
Revised by: Elmeri Uotila and Anna LaValle
Give feedback on this content
Comments about the task?