Computer Graphics – Homework Assignment 1 – Airbrush
Goals:
-
Understand raster images and simple compositing.
-
Become familiar with C++.
-
Successfully install the Qt development environment.
Getting Started & Handing In:
-
This is a programming assignment. You will need to download the codeframework from Piazza.
-
The code will be written in C++, but nothing fancy. It will look like amix of C and Java.
-
The code is written using the open source Qt GUI framework. You willonly need to understand a few functions, described below.
-
The assignments make use of the cross-platform, open source Qt development environment,which comes with a great IDE. Install the open source versionof the Qt environment: https://www.qt.io/download-open-source(At the time of writing, version 5.11 is the newest version. Any 5.xversion should work.) This installer, by default, includes all versions ofQt. This is unnecessary and takes a huge amount of space.Install only the most recent version (e.g. Qt 5.11) and the Qt Creator IDE.On Windows, also install the MingW compiler.
-
Mac users can alternatively install with Homebrew:
brew install qt
andbrew cask install qt-creator
. -
Ubuntu Linux users can alternatively install via
apt-get install qtbase5-dev qtcreator
.
-
-
Download the assignment. This will create a folder named
airbrush
. Open thefile namedairbrush.pro
. This should launch the Qt Creatordevelopment environment (IDE). -
Build and run the code. The code should compile and run, but clickingand dragging in the window won’t have any effect.
-
Add your code to
airbrush.cpp
. -
Build and run and test that it is working correctly. Qt Creator has agreat debugger.
-
Create a painting and save it into the folder.
-
When done, zip your entire
airbrush
directory along with aNotes.txt file and your painting as hw01_lastname_firstname.zip andupload your solution to Blackboard before the deadline. Your Notes.txtshould describe any known issues or extra features. Your Notes.txtshould also note the names of people in the class who deserve a star forhelping you (not by giving your their code!). -
The framework and libraries provide all the support code that you need.Feel free to enhance the interface if you so desire.
-
**THIS IS AN INDIVIDUAL, NOT A GROUP ASSIGNMENT. That means all codewritten for this assignment should be original! Although you arepermitted to consult with each other while working on this assignment,code that is substantially the same will be considered cheating.** In your
Notes.txt
, please note who deserves a star (who helped you with theassignment).
Overview:
In this assignment, you will be implementing a digital painting toolsimilar to the airbrush in Photoshop (and better than the painting toolsin MS Paint). You will be able to create stunning artwork likethis:
Although it looks fancy, it’s pretty simple if we break it down into twoparts: (1) creating a translucent RGBA image for a spray of the airbrushand (2) depositing the paint by compositing the airbrush RGBA image ontothe background image centered at the mouse location.
Rubric:
-
(50 points) Creating a translucent RGBA image for a spray of theairbrush. An airbrush is defined by its color, radius, and shape.Here are some examples:
-
Shape: Quadratic Radius: 25 pixels Color: (0, 0, 255, 64) aka blue at 25% opacity
-
Shape: Linear Radius: 25 pixels Color: (0, 0, 255, 64) aka blue at 25% opacity
-
Shape: Constant Radius: 25 pixels Color: (0, 0, 255, 64) aka blue at 25% opacity
-
The algorithm to generate any of these airbrush images is very similar.
We are given an RGBA color, which means it has red, green, blue, andalpha components. In our code, an RGBA color takes up 32-bits. Each componentwill be stored as one of its four bytes. That means that each component will bean 8-bit integer whose value lies in the range 0 to 255. It is sometimesconvenient to think of components as lying in the continuous range[0,1], in which case you would divide the 8-bit component values by 255.An airbrush image is a square RGBA image with dimensions2*radius+1
by 2*radius+1
. Each pixel has the same RGB values takendirectly from the color but varying A (alpha) or opacity. In the brushesabove, alpha is at its maximum at the center pixel and falls off to 0for pixels whose distance is radius or greater from the center. Thebrushes above differ only in how the opacity falls off. To compute theopacity for a pixel, let’s set a variable t to the distance from thecenter pixel as a fraction of the radius:
*t* = sqrt( ( x - x₀)² + ( y - y₀ )² ) )/radius ![](docs/images/t.png) $t = \frac{ \sqrt{ ( x - x_0 )^2 + ( y - y_0 )^2 } }{ \textit{radius} }$ Then all we need is a *falloff* function of *t* to create interesting
alpha value is then conveniently expressed as color’s alpha * f(t).
* **(25 points)** Writing the code that iterates over every x,y pixel of
the brush image and sets its RGBA components appropriately. Thatfunction signature is:
void create_airbrush( QImage& airbrush_out, AirBrushShape shape, int radius, QRgb color ) `QImage` is Qt's image class that wraps an array of RGBA values with
convenience methods. The &
is C++’s way of passing by reference (it’slike a dereferenced pointer in C). AirBrushShape
is an enum
. You willassign every x,y pixel of the output parameter airbrush_out
. x rangesfrom 0 to airbrush_out.width()
and y ranges from 0 toairbrush_out.height()
. You can assign pixels either by callingairbrush_out.setPixel()
or, if you want to work closer to the metal, byaccessing the pointer to the array of pixels directly viaairbrush_out.scanLine()
(see below). To access the appropriate falloff function (theones you write, see below), call the helper function falloff( shape, t )
.
* **(25 points)** Implement *falloff* functions for the following three
shapes.
![plot of falloff functions](docs/images/falloffs.png) * **(5 of the 25 points)** A constant function `falloff_constant()`. It just
makes a circle. (These equations are shown 3 ways because GitHub’s Markdown processing doesn’t handle equations well.)
![constant function](docs/images/constant.png) $$ f_\textit{constant}(t) = \begin{cases} 1 & \text{if} \quad t < 1 \\ 0 & \text{otherwise} \end{cases} $$ falloff_constant(t) = { 1 if t < 1 { 0 otherwise * **(10 of the 25 points)** A linear function `falloff_linear()`.
It makes a “cone”.
![linear function](docs/images/linear.png) $$ f_\textit{linear}(t) = \begin{cases} 1-t & \text{if} \quad t < 1 \\ 0 & \text{otherwise} \end{cases} $$ falloff_linear(t) = { 1-t if t < 1 { 0 otherwise * **(10 of the 25 points)** A quadratic function `falloff_quadratic()` that
looks like a Gaussian (very aesthetically pleasing) but without aninfinite tail.
![quadratic function](docs/images/quadratic.png) $$ f_\textit{quadratic}(t) = \begin{cases} 1-3t^2 & \text{if\ \ \ } t < \frac{1}{3} \\ 1.5t^2 - 3t + 1.5 & \text{if\ \ \ } \frac{1}{3} \leq t < 1 \\ 0 & \text{otherwise} \end{cases} $$ falloff_quadratic(t) = { 1-3t^2 if t < 1/3 { 1.5t^2 - 3t + 1.5 if 1/3 <= t < 1 { 0 otherwise * **(up to 5 bonus points)** A special function `falloff_special()` that does
anything you want!
-
(50 points) Depositing the paint by compositing the airbrush RGBAimage onto the background image centered at the mouse location. Thefunction signature for this is:
QRect paint_at( QImage& image_to_modify, const QImage& airbrush_image, const QPoint& center )
You will modify a subset of the pixels of the output parameter
image_to_modify
by compositing the pixels ofairbrush_image
overthem.airbrush_image
should be composited with its center at thecenter.x()
,center.y()
pixel ofimage_to_modify
. Note that dependingon the size ofairbrush_image
and the location of its center, only partof it will fit onimage_to_modify
. Figuring out which pixels ofairbrush_image
andimage_to_modify
to iterate over—withoutaccessing out-of-bounds memory—is the first step of this function.You will return the rectangle corresponding to the pixels ofimage_to_modify
that you actually modified.-
(25 of the 50 points) Correct iteration bounds. Note that you mustreturn the iteration bounds related to
image_to_modify
as aQRect
.This is so that the GUI knows which part of the window to redraw. -
(25 of the 50 points) Compositing the corresponding RGBA pixel of
airbrush_image
onto the RGB pixel ofimage_to_modify
. To help youwith this, I have created a helper function signature (you fill it in):QRgb composite( const QRgb& foreground, const QRgb& background );
Recall from class or the textbook or Wikipedia that the formula tocomposite a foreground color “over” a background color is:
*C.red = F.alpha * F.red + (1 – F.alpha) * B.red*
where C is the output composited pixel, F is the foreground pixel,and B is the background pixel. The formula is the same for the greenand blue channels. In this formula, alpha values range from [0,1], not[0,255], so don’t forget to convert. (Note that if the backgroundimage were also transparent, the formula would be slightly different.)The formula
(1 - t)*a + t*b
appears so frequently in math and computerscience that it has a shorthand name: lerp, short for linearinterpolation. You may find it useful to fill in and use a helperfunction. The signature is provided:int lerp( int a, int b, real t )
real
is a typedef for a floating point number. It is a requirement thatyou fill in and make use ofcomposite()
. It is not a requirement thatyou fill in and uselerp()
. -
(5 bonus points) Write composite (and lerp) without using floatingpoint numbers. Floating point operations have traditionally been slower than integer operations. You will have to modify the function signature for lerp to
int lerp( int a, int b, int t )
to accomplish this.
-
Qt functions you need for this assignment
QRect. The constructor of QRect
is QRect( x, y, width, height )
.
QPoint. The methods p.x()
and p.y()
return the x and y components ofa QPoint p
.
QImage:
-
image.pixel(x,y)
returns theQRgb
color for pixel x,y of aQImage image
. -
image.setPixel(x,y,c)
sets the pixel to aQRgb
colorc
. -
image.width()
andimage.height()
return the width and height of theimage. -
(Optional)
image.scanLine(y)
returns a pointer to the array ofQRgb
pixel data forrow y. You do not need to do this but may wish to get closer to the metal forperformance (avoiding function calls) or the experience. If you wish to attempt this,first make your code work using.pixel()
and.setPixel(x,y,c)
.The pointer returned byimage.scanLine(y)
points to the pixel (0,y).If you have a pointer to a pixelQRgb* pix
, the next pixel in the row ispix+1
and the next pixel in the column ispix+image.bytesPerLine()/4
.
sqrt(x)
, std::min(a,b)
, std::max(a,b)
. These are not Qt functions.They are part of C’s math.h
and C++’s <algorithm>. Nevertheless, youwill find them useful. Note that std::min
and std::max
require bothparameters to have the exact same type. If not, you will get a very longcompiler error since they are generic functions written using C++templates.
QRgb. To get the red, green, blue, and alpha components of a QRgbcolor c
as 8-bit values, use qRed(c)
, qGreen(c)
, qBlue(c)
, andqAlpha(c)
. In this assignment, we are ignoring alpha. To create an RGBQRgb
color, use qRgb( red, green, blue )
with 8-bit parameters. Notethat QRgb
is not a class or a struct. It is a typedef
for an `unsignedint`, and those functions are just getting and setting the appropriatebytes. The header qrgb.h
is very short and readable. Here is most ofit:
typedef unsigned int QRgb; // RGB triplet
inline int qRed(QRgb rgb) // get red part of RGB
{ return ((rgb >> 16) & 0xff); }
inline int qGreen(QRgb rgb) // get green part of RGB
{ return ((rgb >> 8) & 0xff); }
inline int qBlue(QRgb rgb) // get blue part of RGB
{ return (rgb & 0xff); }
inline int qAlpha(QRgb rgb) // get alpha part of RGBA
{ return rgb >> 24; }
inline QRgb qRgb(int r, int g, int b)// set RGB value
{ return (0xffu << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu); }
inline QRgb qRgba(int r, int g, int b, int a)// set RGBA value
{ return ((a & 0xffu) << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu); }