top of page
Search

Fractals with Lisp

Updated: Nov 22, 2021


If you can find a graphics library for a Common Lisp implementation (or cobble together your own solution), then it is quite easy to generate simple 2D fractals with Lisp, mostly because Common Lisp handles floating point operations competently and comes with a built-in complex number type.

[1]> (setq c (complex 3.0 2.5))
#C(3.0 2.5)

Without going into the messy details, I've put together a simple graphics package for CLISP named pixel that includes the following operations:

(pixel:init)
(pixel:draw-pixel x y (vector r g b))
(pixel:draw-line x1 y1 x2 y2 (vector r g b))
(pixel:draw-circle x y radius thickness (vector r g b))
(pixel:shutdown)

The function (pixel:init) opens a canvas of an x by y size. Currently, the "world" has dimensions of -1000 to 1000 in both the x and y directions. The functions (pixel:draw-pixel), (pixel:draw-line), and (pixel:draw-circle), operate in this world coordinate system and the graphics system internally scales to the correct canvas coordinates. There are also hard-coded color constants e.g. pixel:*red*, pixel:*blue* for convenience, to replace (vector r g b), where r, g, b are integers from 0 to 255.


The pixel graphics library

Since I am not too proud of this graphics solution for CLISP on Linux, I won't go into details here except to say that it was born out of the desire to get some pixels up on the screen as fast as possible and involves a Python/Pygame server for the actual graphics, a crude textual protocol for issuing graphics commands to the Pygame server over a socket and then a layer in a Common Lisp package named "pixel" for the actual CL graphics functions. It is quite convoluted, and not worth sharing at this time, but it does illustrate once again how you can MacGyver things together on a Linux/Unix system pretty easily. From the CLISP side of things, two necessary extensions which enabled this type of hackery involve (ext:run-program) and (ext:socket-connect).

(defun launch-server ()
  (progn
    (format t "~% [*] Launching Python/Pygame graphics server...")
    (ext:run-program "../libs/launch-pixel-server.sh")
    (sleep 2)))
(defun init ()
  (progn
    (launch-server)
    (format t "~% [*] Connecting Lisp to Pixel Server with ip: ~a 
               socket: ~a" *ip* *socket*)
    (defvar *pixel-stream* (ext:socket-connect *socket* *ip*))))
(defun shutdown ()
  (close *pixel-stream*))

An actual graphics primitive sends a message over the stream:


(defun draw-pixel (x y color)
  (setf mesg (format nil "{ PIXEL ~a ~a ~a ~a ~a }"
		     x y
		     (svref color 0)
		     (svref color 1)
		     (svref color 2)
		     ))
  (print mesg *pixel-stream*))

(defun draw-line (x1 y1 x2 y2 color)
  (setf mesg (format nil "{ LINE ~a ~a ~a ~a ~a ~a ~a }"
		     x1 y1 x2 y2
		     (svref color 0)
		     (svref color 1)
		     (svref color 2)))	
  (print mesg *pixel-stream*))

The Python coding of the server (the Pygame main loop and message parsing) are beyond the scope of this blog, but suffice it to say, it's a round about way of adding graphics to Lisp and I'm sure there are better alternatives out there. I just happened to have been coding with Pygame recently and have done something similar in the past with a Java server and Python clients so it was a solution that seemed doable in a reasonable amount of time.

 

At any rate, starting with these simple graphics primitives (open a canvas, draw pixel), a crude monochrome rendering of a Julia set can be done quite easily with Common Lisp:

(defconstant julia (complex -1 0.0))
(defconstant rabbit (complex -0.1 0.8))
(defconstant lion (complex 0.360284 0.100376))

(defparameter *c* julia)
(defparameter *scale_x* 300)
(defparameter *scale_y* 300)
(defparameter *num_iter* 30)
(defparameter *delay* 0.005)

(pixel:init)

(let ((x -2.0) (y 0.0)
      (n 0) (z nil))  
  (loop while (< x 2.0) do
       (setf y 0.0)
       (loop while (< y 2.0) do
            (setf n 1)
            (setf z (complex x y))
            (loop while (and (< (abs z) 2.0)
                             (< n *num_iter*)) do
                 (setf z (* z z))
                 (setf z (+ z *c*))
                 (incf n))
            (when (< (abs z) 2.0)
              (progn
                (setf plot_x (round (* x *scale_x*)))
                (setf plot_y (round (* y *scale_y*)))
                (pixel:draw-pixel plot_x plot_y pixel:*green*)
                (pixel:draw-pixel (- plot_x) (- plot_y) pixel:*green*)
                (sleep *delay*)))
            (incf y .01))
       (incf x 0.015)))

(pixel:shutdown))

Other shapes can be generated by changing the parameter *c*. Such as:


The Lion

and The Rabbit


The algorithm for these simple fractals was ported from the TI-89 Basic version from the book Advanced Algebra with the TI-89 by Brendan Kelly. Future work will involve more sophisticated fractals with proper scaling and coloring.


Stay tuned!


References

Kelly, B. Advanced Algebra with the TI-89. Brendan Kelly Pub. Inc, 2007.

102 views0 comments

Recent Posts

See All

Getting Started

So you've heard of Lisp but how do you do Lisp? For being such a classic language that has been around for more than 60 years, sometimes it feels like there's a barrier to entry to get a full Lisp sys

Post: Blog2_Post
bottom of page