Drawing The Koch Snowflake Fractal With GIMP

I once found myself discussing in some forums about conversion of many pictures from one format to another. I knew that many image processors can work in batch mode. The first image processor I thaught of was GIMP. As expected, you can get help on this tool when run from the command-line with the option ‘–help’. In linux you can use ‘man gimp” to get more details about the tool. I found that ‘-b’ is the option to use to run GIMP in batch mode, and that the default scripting language is Script-Fu. Now, what is Script-Fu?  From the menu bar: “Help -> GIMP-Online -> User Manual Web Site” leads to the following page:


Now, choose your language and version, and go to the chapter about scripting. Choose section ‘Using Script-Fu Scripts’. This language is based on Scheme, a language very similar to list. To work with Script Fu, choose ‘from the menu bar “Filters->Script-Fu”.

To load an image, use the function ‘gimp-file-load’.

Ti save a file, use the function ‘gimp-file-save’

.Well, this is not the tool I would use to convert image files.

I decided to challenge myself in drawing the Koch-Snowflake  in Script-Fu. The following picture is what the fractal looks like:


Now, how to draw this fractal? Simply draw an equilateral triangle, and follow the following steps until you no longer have a polygon:

  1.  Split each side of the polygon into three sections equal in length.
  2. Replace the middle section by two sides of an equilateral triangle, pointing outwards.

Now, how to do it with Script-Fu?

Copy the draw-line function from here.

define a function to draw the fractal, with an inner function to draw a side (origianlly, a side of a triangle).

This is the code:

(define (sqr x) (* x x))

(define (draw-line layer x-from y-from x-to y-to) 
  (let* (
          (points (cons-array 4 'double))

        (aset points 0 x-from)
        (aset points 1 y-from)
        (aset points 2 x-to  )
        (aset points 3 y-to  )

        (gimp-pencil layer 4 points)

(define (draw-koch-snowflake layer height width)
  (let* (
      ; sin60, cos60 will be used for rotating lines 60 degrees
      (sin60 (/ (sqrt 3) 2)) ; sqrt(3)/2 - No operator precedence in LISP/Scheme
      (cos60 0.5)

      (min-size (min height width))
      (side-length (* sin60 min-size))

    (define (draw-side layer from-x from-y to-x to-y)
      (let* (
           (diff-x (- to-x from-x))
           (diff-y (- to-y from-y))
           (third-diff-x (/ diff-x 3))
           (third-diff-y (/ diff-y 3))
           (squared-length (+ (sqr diff-x) (sqr diff-y)))
       (if (<= squared-length 1)                    ;; The (squared) distance is less than or equal to 1 pixel?
          (draw-line layer from-x from-y to-x to-y) ;; Yes, just draw the line
          (begin                                    ;; No, recursively split the side and draw it.
            ;; recursively draw the 1st section
             (define end-section-1-x (+ from-x third-diff-x))
             (define end-section-1-y (+ from-y third-diff-y))
             (draw-side layer from-x from-y end-section-1-x end-section-1-y)

             ;; create a new vertex, the 2nd section is replaced by 2 sides
             ;; of an equilateral triangle
             ;; This is how you rotate a vector 60 degrees counterclockwise. 
             (define new-vertex-x
               (+ end-section-1-x (* cos60 third-diff-x) (* sin60 third-diff-y))
             ); define new-vertex-y
             (define new-vertex-y
               (+ end-section-1-y (* (- sin60) third-diff-x) (* cos60 third-diff-y))
             ); define new-vertex-y
             (draw-side layer end-section-1-x end-section-1-y new-vertex-x new-vertex-y)

             ;; recursively draw the last section
             ;; and the section from the new vertex to the beginning of
             ;; the last section.
             (define start-section-3x (- to-x third-diff-x))
             (define start-section-3y (- to-y third-diff-y))
             (draw-side layer new-vertex-x new-vertex-y start-section-3x start-section-3y)
             (draw-side layer start-section-3x start-section-3y to-x to-y)
          ) ; End of command block
      );let* in draw-side
    ); define draw-side
    (define first-vertex-x (/ (- width side-length) 2))
    (define first-vertex-y (- (- height (* side-length sin60)) 
                              (/ (- height min-size) 2)))
    (define start-x first-vertex-x)
    (define start-y first-vertex-y)
    (define end-x (+ start-x side-length))
    (define end-y start-y)
    (draw-side layer start-x start-y end-x end-y)

    (set! start-x end-x)
    (set! start-y end-y)
    (set! end-x (- end-x (* cos60 side-length)))
    (set! end-y (+ end-y (* sin60 side-length)))
    (draw-side layer start-x start-y end-x end-y)

    (set! start-x end-x)
    (set! start-y end-y)
    (set! end-x first-vertex-x)
    (set! end-y first-vertex-y)
    (draw-side layer start-x start-y end-x end-y)

  );  let* in draw-koch-snowflake

; The caller function:

(define (koch-caller)
     (my-image (car (gimp-image-new 600 600 RGB)))
     (my-layer (car (gimp-layer-new my-image 600 600 RGB-IMAGE "my layer" 100 NORMAL))) 
     ;; You add a layer because you cannot draw directly on the image object.
   (gimp-image-add-layer my-image my-layer -1)    ; Add the layer to the image
   (gimp-context-set-background '(255 255 255))   ; White background
   (gimp-context-set-foreground '(000 000 000))   ; Black foreground
   (gimp-context-set-opacity 100)                 ; So you can see the picture
   (gimp-drawable-fill my-layer BACKGROUND-FILL)  ; First, fill the layer with background color.
   (gimp-brushes-set-brush "Circle (01)")         ; The thinnest line.
   (draw-koch-snowflake my-layer 600 600)         ; Draw a 600 X 600 snowflake
   (gimp-display-new my-image)                    ; Physically display the result

Now, to better understand how to rotate a vector, refer to this Wikipedia entry.