Principal Concept: The function of my project is to create a simple image editor, along the lines of Microsoft Paint. My initial assumption was that I would be able to make something reasonably functional in comparison to the pre-Windows 7 paint. This turned out to be doable in some respects, but not in others resulting primarily from limitations in Tkinter. My 'drawing tools' can be broken up into two major functional areas: free draw shapes (i.e. rectangle) and filters that apply a rule on each pixel in the image (i.e. grayscale). An additional concern was being able to open and save in a variety of formats, otherwise the program would have absolutely no value beyond the scope of this project. PIL: Solving this problem required me to look beyond Tkinter and to PIL for image file support. So, my first objective was to create a skeleton program that could open an image, change some pixels, and save it. A side effect this is that I need to use PIL to draw on the image, worth mentioning but by no means a difficult task. The problem arises from the fact that Tkinter cannot directly display PIL images. Fortunately, there exists a PIL class called ImageTk that converts a PIL image to something that Tkinter can display. Problem apparently solved, except not. Unfortunately, this conversion involves a nontrivial code overhead. Naturally, the first drawing tool I implemented was a freehand brush, initally as an adjustable square pattern that would change the region's pixels with click and drag. However, aforementioned code overhead made these 'lines' a far cry from smooth. In fact, unless you moved slowly, they were disjoint rectangles. The first component of my fix was to draw connecting lines between successive polls of the mouse location. Better, but still not great. After some though and some Googling, a better approach is to draw to both the Tkinter canvas and PIL image at the same time, only redrawing the PIL image at strategic intervals. This issue is still noticeable with quick mouse movements, but is much better than it was. Shapes: The next logical step is to add support for basic shapes, like rectangles, circles, straight lines, and free form polygons, plus the eraser. The biggest issue here is that PIL and Tkinter handle shape borders differently and the PIL drawing is actually an outer shape of border color and an inner shape of inner color. Consequently, the method doesn't translate well to polygons, which cannot have a border (it is also irrelevant with regard to straight lines). To handle the idea that you want to see that shape as you drag it, I use classes to handle the various mouse actions associated with each shape. Some classes are even useful for multiples shapes (e.g. rectangles and circles). Essentially each class has a function for each mouse action, (e.g. leftClick). Since not all classes respond to each action, it is still necessary to define each function for every class, but have it do nothing. Flood Fill: Initially, I thought it would be easy to do flood fill similarly to the way that we did it in class. Unfortunately, real world images overflow the recursion depth quickly. Increasing the default recursion depth (1000) crashes for a different reason around 9950, so is still unusable. My solution was to implement a queue along the lines of how it was discussed in the Honors lecture with Professor Sutner. In effect, this is very similar to what recursion did anyway. As it stands, this method this works, but is slow for large areas. I Googled 'flood fill algorithms' but only found either unusable or very similar approaches. I discussed a possible change with AJ, however after a short attempt to make the disscussed change, the effect was that flood fill became even slower, so it was left as is to focus on other features. Effects: The other major factor is applying a filter pixel by pixel. They read each pixel value and set it to a new value. Two that I include are Sepia and Grayscale, which are based on formulas found online (source in comment). The other one that I created myself is brightness, which is fairly straightforward. I also included several other of the built in filters for added versitility, adding the dialog boxes that control them. This is the other reason the using PIL turned out to be necessary. In Tkinter, all canvas objects are kept separate. Only 'PhotoImage' objects can be edited on a pixel by pixel basis. Shapes and images live in separate worlds. In PIL, the ImageDraw module acts on same image, thus everything works out. Clearly, adding more filters should be easy and even possible for a hypothetical user to create custom effects. Resizing: I have two options for resizing. One is resizing the full image, which heavily relies on a built in function. I added the dialog that allows the user to choose which algorithm to use. The other enlarges the canvas, but anchors the original image at a fixed position. This required some work to decide how to handle the request because the resize can be either larger or smaller. GUI: Despite Professor Kosbie's immense hatred for Tkinter GUIs, a project like mine requires a necessarily heavy GUI. I have fairly standard menu at the top of the window, a top toolbar, and a side toolbar. The features of the top toolbar and 'file' menu are redundant, which is seen in many programs. The rest of the menu bar adds the effects and other image operations such as resizing, plus a 'help' menu. The side toolbar contains all of the mouse controlled draing operations as well as the double color chooser and width selection. By changing the relief of the button, I indicate the active tool. As for the colors, I use the built in color chooser window. While not all tools take advantage of both colors, for example with rectangle the primary color controls the border and the secondary the fill. The width slider controls line widths or shape border widths. Lastly, I decided that scroll bars are important for working with large images or for being able to resize the windows and still work on the full image. I also use dialogs and popups heavily. For example, I made a custom dialog the handles image resizing, but in three different applications. It allows a custom size for a new image, can resize the current image (uses a built in PIL function, but adds options for the four different menthods), and resizing the canvas while keeping image a static size (adding options for which corner to anchor). Another interesting addition is that I keep track of 'unsaved changes' prompting the user upon exit if they want to save (if there are unsaved changes). It is a simple implementation, but effective. I also use the built in file open and save dialogs. I implemented filters in the open dialog so that only supported file types are shown. Unfortunately, using filters in the save dialog does not equal appending the extension of the selected filter to the save path, so I removed the filters from the save dialog and the file extension must be manually added. Lastly, I added a 'help' feature in the help menu that gives brief descriptions for how to use the tools associated with the mouse, as how to use them may not be obvious (especially polygon). Function Dictionaries: Given the variety of functions that get called, especially for the mouse, I use dictionaries that act as function pointers. So, for each mouse action, instead of navigating a mess of if statements, the dictionary points to the right function. I do something similar for the effect filters where instead of having a double for loop in each, I have one double for loop function that calls the appropriate filter for each pixel again based on the dictionary. Modules: Given the size of my project, breaking it up into modules significantly relieves cutter. For example, the GUI is in its own module. Not much to say about this, logical things are grouped together. Since many modules need to access canvas.data, instead of passing in the canvas in as an argument to everything, I effectively make it a global variable where it is needed with a line like 'DrawingTools.canvas = canvas' in the init() of my main.py. Closing Remarks: I was unable to get to some of my more ambitious goals. For example, I wanted to do an undo/redo feature, but it was unclear what the most efficient way to keep track of changes would be. In the end, time did not permit exploration into the matter. Secondly, I wanted to interact with the clipboard, but upon looking into it, Python module support was lacking. Finally, I had many other simpler features planned that there was simply not time for. This includes: rotation, gradients, being able to select a region and move it, etc. Overall, I am pleased with what I was able to accomplish and think that falling short was simply a product of high goals. I feel that what I finished still represents a meaningful and useful program.