Abishek Goda
My Experiments Learning Lisp


My Experiments Learning Lisp


Photo by Jan Antonin Kolar on Unsplash


Image format conversion using Common Lisp

Abishek Goda's photo
Abishek Goda
·Jan 3, 2023·

2 min read

I've been tinkering with ROS using ROS2Djs over ROS bridge and their excellent simulation environment. Along the way, I learnt that ROS saves its maps as a PGM file. An excellent idea, if you think about it. It is textual and lends itself well to algorithmic processing. But I am not aware of a way to display a PGM file on the browser like the image that it is.

I was initially experimenting with Python since ROS has excellent support for python. Then I tried to repeat my experiments using Common Lisp. ROS1 has pretty good support for CL, if not at Python levels. I used the excellent PIL API in python to get the conversion going. The code was as simple as this:

# python2. yikes!
buffer = cStringIO.StringIO()
pgm = Image.open(filename)
pgm.save(buffer, format="png")

I could have tried the same thing using Common Lisp libraries for image magick. But then, I wanted to give myself a little challenge. There is an excellent png library for common lisp and another excellent netpbm library for common lisp too. So I decided to write a simple glue code combining these two libraries with doing my bidding. Here is what that glue looks like:

;; damn, there is still no support for common lisp syntax? I should
;; move away I guess

(defun read-pgm (pgm-filename)
  "reads a pgm file and returns the data array"
  (netpbm:read-from-file pgm-filename))

(defun convert-to-1d-array (2d-data)
  "read-pgm returns a 2d simple-array. We need a 1d vector for png generation."
  (let* ((rows (array-dimension 2d-data 0))
         (cols (array-dimension 2d-data 1))
         (1d-data '()))
    (dotimes (r rows)
      (dotimes (c cols)
        (setf 1d-data (append 1d-data (list (aref 2d-data r c))))))
    (make-array (* rows cols) :initial-contents 1d-data :element-type '(unsigned-byte 8))))

(defun generate-png (pgm-filename)
  (let* ((pgm-data (read-pgm pgm-filename))
         (w (array-dimension pgm-data 0))
         (h (array-dimension pgm-data 1))
         (image-data (convert-to-1d-array pgm-data)))
    (type-of image-data)
    (make-instance 'zpng:png
      :color-type :grayscale
      :width w
      :height h
      :image-data image-data)))

(defun write-pngfile (pgm-filename png-filename)
  "reads in a pgm file and writes out a png image"
  (let ((png-data (generate-png pgm-filename)))
    (format t "generating png file.")
    (zpng:write-png png-data png-filename)))

I hope you find that helpful. I am considering making a package with these utilities: pgm -> png, jpg; files and base64 strings and releasing it into the wild.

Share this