CalmBrain
@CalmBrain

First of all shoutout to @nex3 for their syntax highlighter. It's very good and makes writing this a lot easier.

This is a continuation of the post Getting Started with Python if you don't know anything through the medium of dithering and is intended to be a self-guided educational resource. If you just want to use the script to make some art then follow the first post up until you can run the script for the first time, then come back here and follow this until I finish going over the user options.

Dither Shitter is now on Github. Github is a great tool for programmers for collaborating with other programmers and for keeping your code in good order. You can go to this GitHub page at any time and download the latest version of the code, but in this instance we're not going to want the latest, that code may have changed! Instead download the zip below and extract it into the same folder as the original scripts, overwriting the original dither.py file.

The scripts needed for this tutorial

Updating your environment and running the script

Open up VS Code and open your folder with the scripts in again and select dither.py. Right at the top of the file you might have a squiggle underlining one of the packages, and it won't be the right colour.

showing an issue with imageio

Mousing over that error will tell us what's up, our environment can't find whatever imageio is.

The error dialog

Stuff has changed since the first post and we're going to need to install one more package, imageio for saving gif files out. Install this like we installed open-cv in the first post. Once you have this installed your package list should look like this:

Showing new packages installed

Now we can select dither.py in the file explorer, the error should be gone and we're ready to run, do that now and we'll get something new pop up

A file explorer

No longer confined to the folder the script is in, we can select any file, anywhere! When the processing is done it'll spit the file out into the same directory as the original. If you selected a gif you'll also notice some new output, about what frame is being processed. At the end of the script we now also print the elapsed time.

Terminal output

Lastly there are some additional options you can change to change, have a play with them

Options

If all you want to do is use the tool then great, you can stop here. Now we're getting into...

The Pipeline

Here's an overview on how our code executes, each large coloured rectangle is a single function in our program and each different colour represents a different function. Functions always return control to whatever called them. If an arrow is blue it is calling the function. If an arrow is red it is returning control to where it was called from.
Program flow diagram

How things changed

The physical code

The first thing you'll notice is that there's now 3 .py files instead of the 1 .py file. This is done purely for convenience. At the top of dither.py there's a new import:

import imageProcessing as ip # our imageProcessing.py file

This allows us to access that files contents in the way we've been accessing other packages contents.
imageProcessing.py holds our image editing pipeline. If the function takes in an image and spits out an image then it belongs in this file, this is how we call the function pipeline residing in imageProcessing.py:

output = ip.pipeline(original, COLOURISESTILL, MASK, CHUNKSIZE)

masks.py is used for manipulating masks, which will make sense when we get to how the new dithering algo has changed.

Dithering

Crack open imageProcessing.py and look at the function dither, you'll notice this has changed quite a lot from the first post. This is for 2 reasons:

  1. For speed increases
  2. To support chunky dither

The long and short of 1 is that python, while being very accessible, is a slow programming language primarily because it's interpreted instead of pre-compiled. This means that our computer has to convert our written language into machine code on the fly as it runs the program.

Because of this things like running a for loop over every pixel in an image is quite slow. This means that every time we use something like a for loop we should consider if we can achieve the same goal without it. Let's take a look at how the original function worked, and how the new one works with this image, and this mask:

image and mask

In both these examples we'll be looking at the second pixel, that means after processing our first pixel our output looks like this:
current output

The original method

the original method

The new method

The new method employs a technique called slicing and striding, basically we can pull out a sub-matrix from a larger matrix then add it to another matrix of the same size element wise. This starts with first making a new error distribution mask out of our dithering algorithm and is what the function makeMaskMatrix does for us.

the new method

While this seems more complex it's faster because we skip 3 runs through a for loop for every pixel and replace it with adding 2 matrices.

Slicing and Striding

The syntax for slicing and striding can seem confusing at first, but for a 2D matrix we can think of it the column we want to start at, the number of columns we'd like, the row we'd like too start at and the number of rows we'd like.

image[row : row + number of rows, column : column + number of columns]

If we wanted to isolate the highlighted section of image we'd use the code underneath the output on the right.

slicing and striding

Boy, that's a long post

But it should be enough information to dig around in the code and understand a bit more. Next post will cover how the error distribution matrix is made, and how it's applied when the matrices won't fit over each other, like at the end of a row.

Something to try yourself

Dither Shitter can work with videos, but it's currently not configured to. Start looking where the file name is read in dither.py (it's under the Entry section) and get it to accept an mp4. You'll also need to do something about the file type on saving too so look at function processVideo and investigate string replacement.


You must log in to comment.