Skip to content

Rebound370/Pillow

Repository files navigation

Image Manipulation via Pillow

When I first started out, learning the Pillow library seemed like a daunting task. However, through clear and concise documentation, understanding and using the library turned out to be much easier then expected. I'll go over each of the five programs I created here, describing my process of creating the program, the issues I encoutered along the way, and how I overcame them.

Exercise 1: Filters and Operations

This program was the first program I wrote with the Pillow library. As such, most of the difficulty came from understanding the syntax and general formatting of the library. I encountered something strange almost immediately. I was messing around with both the ImageOps and the ImageFilter modules, and the process of calling functions from them was completely different. ImageFilter used newImage = image.filter(ImageFilter.FILTER), while ImageOps used newImage = ImageOps.operation(image). One was called as a function of the image itself, while the other had to call the module directly and pass the image in as a parameter. As we'll see in the next exercise, there's another format for function calls as well. I don't understand why each module calls functions differently instead of having a consistent format. If I were to contribute to this project, the first thing that I would do would be to unify the format of function calls across the library. However, I do realize that just changing all of these to one singular format would break many people's code. Therefore, I would leave the old formatting options in as legacy function calls, while creating a new consistent formatting option. I'd also go through the docs and update the examples to primarily show the new consistent function call format.

Exercise 2: ImageDraw

This program was written as a simple way to start messing around with Pillow's drawing functionality. With this module, we run into yet another different format for function calls. In this case, we instantiate an instance of the ImageDraw module while passing the image we want to draw on as a parameter. We then call functions from this instance rather then the calling from the module directly or calling functions from the image itself. The format is imageDrawer = ImageDraw(image).

Exercise 3: Composite Pictures

In this program I explored the image blending capabilities of the ImageChops module. This module is called similarily to ImageOps at least, so no new weird way to call functions to deal with here. However, that doesn't mean that I did not encounter difficulties. In fact, this was the first program where I encountered a traditional programming error rather then just weird formatting. Initially I tried to draw the secondary image that was going to be added to the first, but then I decided to leave that for later. So I created a black ellipse in an external program and tried to add it on top of another picture. When I tried to add these together, I received a value error, stating the "images do not match". My initial thoughts went to double checking that the images were the same width and height. They were. I did some digging and found a Stack Overflow Post detailing some complexity with the various modes and adding. This made me realize that the black ellipse image I had created was being imported in the "L" mode, as the only color in the image was black. The other image was being imported in "RGB" mode. I then converted the "L" mode image to "RGB" mode, and everything worked well.

Exercise 4: Advanced Composite Pictures

In the last program I initially attempted to create a secondary image through Pillow's ImageDraw module, then add it to the other picture. Due to running into other errors, I shelved that idea for the time. In this program, I revisited it. The process was fairly smooth, with only two minor hiccups along the way. The first was realizing that the way to generate a blank image and then to draw on top of it was, suprisingly, to generate a blank image and draw on top of it. Before I tried importing an image and drawing over that, but once I created a completely transparent image with the np.zeros function it was trivial to manipulate the images as I please. Beyond that, the only other quirk was that I initially attempted to use ImageChops.blend, because I thought that using the transparency blend function would make it look nicer. I thought it would take the second image and blend it over the first with some transparency. What ended up happening is that it gave transparency to both and giving both a really washed out look. ImageChops.add gave me the result I desired.

Exercise 5: Transparency Masking with ImageDrawing

For my last program, I wanted something that would bring together all my knowledge from the last four programs into one; on top of exploring a new function. I settled on learning the Image.composite function as my new function. My goal was to create an image that had an inner zone filtered differently from the outer zone. For the filtering, I used both ImageFilter and ImageOps to achieve my desired effect. The transparency mask was created with ImageDrawing, an ellipse drawn on an image generated from an empty array. The one stumbling block I encountered was that the index location of width and height in the Image.size function was flipped as compared to what the np.zeros function wanted. After some quick trial and error, this was resolved. Now that I had both images with the proper filtering and my transparency mask, all it took was combining them.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages