COOLNAMEHERE

Drawing Celtic Knotwork

1 October 2004

Thanks to everybody for the positive feedback on the first MIRE. I still have your attention, so I'll move on to my next exercise. This one is a little more involved than the first, but bear with me - the results should be worth it.

The Problem

I have been a big fan of Celtic knotwork for many years, and have been drawing it myself as an occasional distraction for the last eight years or so. I don't think I'm very good at it, but I've occasionally made myself proud. Heck, I've even designed a couple tattoos for folks, making a few bucks in the process. Any time you can profit from your hobbies is good. Any time somebody likes your work so much that they want it embedded in their skin with a sharp needle is also good.

My preferred technique has always been manual and therefore labor-intensive. You lay out a grid, block out your major areas, draw your knotwork paths, and then embellish a little for personality. It's simple and repetitive, and I like it that way. There's a meditative element to such things which can't be ignored. Still, I'm here to write a programming exercise, not to wax eloquently about the virtues of being hunched over a sheet of paper for hours at a time.

Any time a geek hears the phrase "simple and repetitive," the geek's mind turns to ways of automating the task being described. Well, *this* geek's mind does. I wonder how hard it would be to write a Ruby program which could, given some dimensions by the user, create a simple knotwork panel of the desired size and then write that panel to an image file?

Finding a Solution

As it turns out, somebody else has already solved a fair chunk of the problem. Andy Sloss's book "How to Draw Celtic Knotwork: A Practical Guide" provides a detailed overview of his approach, which essentially boils down to arranging a set of image tiles on a grid so that the end result looks like an attractive knotwork image. He's even gone so far as to provide each of the tiles that can be used in his system.

The Plan

This project is a little more complicated than the last MIRE, so I want to step through it a little more carefully. Actually, it's not more complicated. It's just that less of the work has already been done for me. Instead of jumping right into creating bitmap images on the fly, I'm going to use ASCII art to create my knotwork at first.

Let's step through what we'll be doing later in code. Here is a collection of image tiles arranged in a grid.

  . . . . x x x x x  x x . . . . . x x  x x x x x . . . .
  . . . . x . . . .  . . x x . x x . .  . . . . x . . . .
  . . . . x . . . x  . . . . x . . . .  x . . . x . . . .
  . . . . x . . x .  x x . . . x . x x  . x . . x . . . .
  . . . . x . . x .  . . x . . . x . .  . x . . x . . . .
  . . . . . x . . x  x . . . x . . . x  x . . x . . . . .
  . . . . . x . . x  . . . x . x . . x  . . . x . . . . .
  . . . . . . x x .  . . x . . . x x .  . . x . . . . . .
  
  . . . . . . x . .  . . x . . . x . .  . x x . . . . . .
  . . . . . x . . .  . x . x . x . . .  x . . x . . . . .
  . . . . . x . . .  x . . . x . . . x  x . . x . . . . .
  . . . . x . . . x  . x . . . x . x .  . x . . x . . . .
  . . . . x . . x .  . . x . . . x . .  . x . . x . . . .
  . . . . x . . x .  . x . x . . . x .  . x . . x . . . .
  . . . . . x . . x  x . . . x . . . x  x . . x . . . . .
  . . . . . x . . x  . . . x . x . x .  . . . x . . . . .
  . . . . . . x x .  . . x . . . x . .  . . x . . . . . .
  
  . . . . . . x . .  . x x . . . x . .  . x x . . . . . .
  . . . . . x . . .  x . . x . x . . .  x . . x . . . . .
  . . . . . x . . x  x . . . x . . . x  x . . x . . . . .
  . . . . x . . x .  . x . . . x . x .  . x . . x . . . .
  . . . . x . . x .  . . x . . . x . .  . x . . x . . . .
  . . . . x . . . x  x x . x . . . x x  x . . . x . . . .
  . . . . x . . . .  . . . . x . . . .  . . . . x . . . .
  . . . . x . . . .  . . x x . x x . .  . . . . x . . . .
  . . . . x x x x x  x x . . . . . x x  x x x x x . . . .

Squish them together into one big image, and guess what? We have a knot!

  . . . . x x x x x x x . . . . . x x x x x x x . . . .
  . . . . x . . . . . . x x . x x . . . . . . x . . . .
  . . . . x . . . x . . . . x . . . . x . . . x . . . .
  . . . . x . . x . x x . . . x . x x . x . . x . . . .
  . . . . x . . x . . . x . . . x . . . x . . x . . . .
  . . . . . x . . x x . . . x . . . x x . . x . . . . .
  . . . . . x . . x . . . x . x . . x . . . x . . . . .
  . . . . . . x x . . . x . . . x x . . . x . . . . . .
  . . . . . . x . . . . x . . . x . . . x x . . . . . .
  . . . . . x . . . . x . x . x . . . x . . x . . . . .
  . . . . . x . . . x . . . x . . . x x . . x . . . . .
  . . . . x . . . x . x . . . x . x . . x . . x . . . .
  . . . . x . . x . . . x . . . x . . . x . . x . . . .
  . . . . x . . x . . x . x . . . x . . x . . x . . . .
  . . . . . x . . x x . . . x . . . x x . . x . . . . .
  . . . . . x . . x . . . x . x . x . . . . x . . . . .
  . . . . . . x x . . . x . . . x . . . . x . . . . . .
  . . . . . . x . . . x x . . . x . . . x x . . . . . .
  . . . . . x . . . x . . x . x . . . x . . x . . . . .
  . . . . . x . . x x . . . x . . . x x . . x . . . . .
  . . . . x . . x . . x . . . x . x . . x . . x . . . .
  . . . . x . . x . . . x . . . x . . . x . . x . . . .
  . . . . x . . . x x x . x . . . x x x . . . x . . . .
  . . . . x . . . . . . . . x . . . . . . . . x . . . .
  . . . . x . . . . . . x x . x x . . . . . . x . . . .
  . . . . x x x x x x x . . . . . x x x x x x x . . . .

You don't quite see it? Well, squint a little and tilt your head a bit. Still nothing? Just bear with me. We will get to actual pictures soon, I promise.

Okay, let's find a way to automate this process.

Turning the Plan Into Code

This works, doesn't it?

 knot =<<HERE
   . . . . x x x x x x x . . . . . x x x x x x x . . . .
   . . . . x . . . . . . x x . x x . . . . . . x . . . .
   . . . . x . . . x . . . . x . . . . x . . . x . . . .
   . . . . x . . x . x x . . . x . x x . x . . x . . . .
   . . . . x . . x . . . x . . . x . . . x . . x . . . .
   . . . . . x . . x x . . . x . . . x x . . x . . . . .
   . . . . . x . . x . . . x . x . . x . . . x . . . . .
   . . . . . . x x . . . x . . . x x . . . x . . . . . .
   . . . . . . x . . . . x . . . x . . . x x . . . . . .
   . . . . . x . . . . x . x . x . . . x . . x . . . . .
   . . . . . x . . . x . . . x . . . x x . . x . . . . .
   . . . . x . . . x . x . . . x . x . . x . . x . . . .
   . . . . x . . x . . . x . . . x . . . x . . x . . . .
   . . . . x . . x . . x . x . . . x . . x . . x . . . .
   . . . . . x . . x x . . . x . . . x x . . x . . . . .
   . . . . . x . . x . . . x . x . x . . . . x . . . . .
   . . . . . . x x . . . x . . . x . . . . x . . . . . .
   . . . . . . x . . . x x . . . x . . . x x . . . . . .
   . . . . . x . . . x . . x . x . . . x . . x . . . . .
   . . . . . x . . x x . . . x . . . x x . . x . . . . .
   . . . . x . . x . . x . . . x . x . . x . . x . . . .
   . . . . x . . x . . . x . . . x . . . x . . x . . . .
   . . . . x . . . x x x . x . . . x x x . . . x . . . .
   . . . . x . . . . . . . . x . . . . . . . . x . . . .
   . . . . x . . . . . . x x . x x . . . . . . x . . . .
   . . . . x x x x x x x . . . . . x x x x x x x . . . .
 END
 puts knot

I know, "Ha ha, very funny. You are so clever and witty. We are being sarcastic, if you didn't guess, Mister Writer." I know that this is cheating, but it works doesn't it? This would be good enough if all you wanted was some sort of ASCII art knot.

Then again, Laziness taken too far does become plain old laziness. This isn't good enough for any of us. The idea is to be able to draw a knotwork panel of any size that we want.

Let's look at the nouns we've used when describing the problem:

We want to create a knotwork panel by arranging tiles on a grid, then merging them into a single image.
  • knotwork panel - I threw the adjective in for a little descriptiveness
  • grid
  • tile
  • image

I think these nouns make a good start for the class names in our program.

   class KnotworkPanel
   end
     
   class Grid
   end
   
   class Tile
   end
   
   class Image
   end

Zenspider once mentioned a style of commenting classes that was like the classes were describing themselves in the first person. I don't know why, but I really liked that concept. I've stuck with it in a lot of my own code ever since.

  # I am a single small section of a knotwork image. I know about my 
  # dimensions, and can describe myself on a pixel-by-pixel basis.
  class Tile
  end
  
  # I am a 2-dimensional collection of tiles. I know where each of my Tiles are located,
  # and can describe them as if they were a single large entity.
  class Grid
  end
  
  # I am a lovely Celtic knotwork panel. I know my dimensions, and can output myself
  # as ASCII art.
  class KnotworkPanel
  end

Now I know each of the major objects in this program, and the duties that they must fill. It's time to blaze through the highlights of writing the code. For convenience, we can put the application code and the testing code in the same file for now.

Copyright 1999 - 2008 Brian Wisti

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 United States License.