As far as the customer is concerned, the interface is the product --Jef Raskin
The goal for this lab assignment are to
By this point in the semester, everyone should have a github account. Most people are in teams of two for this assignment, so your repository should have the form
$ cd cs71
$ cd 03-photobooth-USER1-USER2.git
$ git pull
If you are working in a team, be professional. As part of your homework submission, you will be asked to fill out a performance evaluation of your teammate.
In this assignment, you will implement a feature that allows the user to apply a post-processing filter to the composite images they build with Photobooth.
Your implementation should be based on the design documentation of the following sections. Please skim through the document before starting.
You will add a new menu to the menu bar, called "Filters". This menu should have options for
Each user story represents a feature that the application should support. Implementing a user story may require modifying several classes. You should test each user story before implementing a new one.
The user can apply a filter to the current image by clicking a filter type from the menu's Filter menu. This will modify the current compoiste image based on the filter. If the user edits layers after applying a filter, the filter will be removed and the user will have to reapply it again.
The filter architecture uses a Factory pattern to create filters of different types. Furthermore, our architecture will use static creator methods to allow new filters to be added to application without changing existing code!
You will implement the following new classes:
src/Filter.cs
) - Subclass of Filter.cs
which converts a given Gdl.Pixbuf
to black and whitesrc/Filter.cs
) - Subclass of Filter.cs
which lightens the colors in a given Gdk.Pixbuf
src/Filter.cs
) - Subclass of Filter.cs
which darkens the colors in a given Gdk.Pixbuf
src/Filter.cs
) - Subclass of Filter.cs
which lightens or darkens the pixel colors randomlysrc/FilterFactory.cs
) - Implements an API for adding and removing different Filters from the application. The menu of Mainwindow
should correspond to the options available from the FilterFactory
. CompositeModel
should use the FilterFactory
to create the filters it uses to edit the CompositeModel
.Containership Diagram
You should extend the API of CompositeModel
to encapsulate the FilterFactory
API. In other words, the MainWindow
does not need to know that CompositeModel
uses FilterFactory
to manage its filters. MainWindow
should only use the methods in CompositeModel
to implement the filter menu.
// New private data members
FilterFactory _filters = new FilterFactory();
// property: get only
// Return the names of the filters contained in the filter factory
public List<string> FilterNames
// requires: nothing
// effects: Applies the filter corresponding to name to the composite image
// and invokes callbacks to notify that the composite image has changed
// Does nothing if name does not correspond to a valid filter
public void RunFilter(string name)
// Wrapper function for FilterFactory.RegisterFilter
public void RegisterFilter(string name, Filter.CreateFn fn)
// Wrapper function for FilterFactory.DeregisterFilter
public void DeregisterFilter(string name)
The API for FilterFactory
should look like
// private variables
Dictionary<string, Filter.CreateFn> _filters;
// default constructor
public FilterFactory()
// effects: associates the filter creator function with name
// replaces the existing creator if one exists
public void RegisterFilter(string name, Filter.CreateFn fn)
// effects: removes the CreatorFn corresponding to name, if one exists;
// otherwise, does nothing
public void DeregisterFilter(string name)
// effects: returns a new filter corresponding to name or null if none exists
public Filter Create(string name)
// effects: Returns the list of names corresponding to each filter
public List<string> GetFilterNames()
The API for Filter
is below. See Filter.cs
for the implementation as well as an example child class called NoneFilter
.
public abstract class Filter
{
// A struct representing the color of a pixel
// Pixels consist of red, green, blue color components
protected struct RGB
// static creator method for creating filters
// subclasses should define their own CreateFn which returns a
// new instance of the Filter
public delegate Filter CreateFn();
// requires: valid buffer
// effects: creates a new Pixbuf modified according to the filter
public abstract Gdk.Pixbuf Run(Gdk.Pixbuf buffer);
// requires: 0 <= row < Width; 0 <= col < Height
// effects: returns RGB with the color of the pixel
protected static RGB GetPixel(Gdk.Pixbuf buffer, int row, int col)
// requires: 0 <= row < Width; 0 <= col < Height
// effects: sets the RGB value of buffer to the color of the given pixel
protected static void SetPixel(Gdk.Pixbuf buffer, int row, int col, RGB color)
}
// Subclass of Filter; implements a NoOp
public class NoneFilter : Filter
{
public static Filter Create()
{
return new NoneFilter();
}
public override Gdk.Pixbuf Run(Gdk.Pixbuf buffer)
{
Gdk.Pixbuf result = buffer.Copy();
return result;
}
}
When you initialize the CompositeModel, you should call CompositeModel.RegisterFilter
to register a filter with the application. For example, to register the NoneFilter
, you would call
_compositeModel.RegisterFilter("None", NoneFilter.Create);
The above call should register the filter's creator with composite model's filter factory. When you initialize the menu in MainWindow
, you should use CompositeModel.FilterNames
to create options for all the registered filters. This type of architecture is an example of a plugin API and can be extended to load new types of filters without recompiling the Photobooth application!
You are asked to implement four types of filters for this assignment
This section describes the algorithms needed to compute each filter
Grayscale Filter
for each row from 0 to Height
for each col from 0 to Width
get the pixel (row, col) # Call base class function
gray = (pixel.red + pixel.green + pixel.blue)/3
set the pixel (row, col) to RGB(gray,gray,gray) # Call base class function
Lighten Filter
for each row from 0 to Height
for each col from 0 to Width
get the pixel (row, col) # Call base class function
red = Clamp(pixel.red + 100, 0, 255)
green = Clamp(pixel.green + 100, 0, 255)
blue = Clamp(pixel.blue + 100, 0, 255)
set the pixel (row, col) to RGB(red,green,blue) # Call base class function
Darken Filter
for each row from 0 to Height
for each col from 0 to Width
get the pixel (row, col) # Call base class function
red = Clamp(pixel.red - 100, 0, 255)
green = Clamp(pixel.green - 100, 0, 255)
blue = Clamp(pixel.blue - 100, 0, 255)
set the pixel (row, col) to RGB(red,green,blue) # Call base class function
Jitter Filter
for each row from 0 to Height
for each col from 0 to Width
get the pixel (row, col) # Call base class function
red = Clamp(pixel.red + random number between -100 and 100, 0, 255)
green = Clamp(pixel.green + random number between - 100 and 100, 0, 255)
blue = Clamp(pixel.blue + random number between - 100 and 100, 0, 255)
set the pixel (row, col) to RGB(red,green,blue) # Call base class function
You should register the filter creators with CompositeModel
after you create it. For most of you, this will be in the constructor of MainWindow
. You should register the creators before you set up the menu because you will need to registered names to set the menu options.
For this design, all callbacks should be in your class MainWindow
.
CompositeModel
(-> may trigger CompositeModel
callbacks)The API documentation is here. The class examples repository has several example programs under /gui to get you started with small programs.
Below are links to some widgets you'll likely need for your assignment.
Byte only fits values between 0 and 255, so if have the potential overflow if you have values out of that range. When you add and subtract byte
types, it is converted to an integer. You should Clamp the results between 0 and 255 before converting back to byte
again.
The simplest version of a factory hard-codes the constructors needed to istatiate a type within a function (AnimalFactory1.cs in the course examples).
For this assignment, you should implement a factory that manages static creator methods. This technique allows a user to implement new filters without changing the implementation of
photobooth. In your class examples repository, this approach is implemented in AnimalFactory2.cs).
CompositeModel
Your implementation of CompositeModel and FilterFactory will be tested against a new subclass of Filter.
Update the README.md file with any feedback on the assignment, how long it took you to do, and what you found most challenging.
Please fill out a performance evaluation of your teammate.
Use git
to hand-in your assignment.
$ git status
$ git add .
$ git status
$ git commit -m "description of my awesome work"
$ git push
NOTES
status
: lists the changes you've madeadd
: specify which files to stage for commit. Dot adds everything recursively starting at the current directory.commit
: Everytime you commit
, you save a snapshot of your work, so you can go back to an old version if necessary. Specify good comments to help you remember what each commit contains.push
: Everytime you push
, you save a backup of your work on Swarthmore's servers. It's good practice to both commit and push your assignments often.