Language agnostic graphics (Project in CL + LTK)
Posted: Sun Oct 13, 2013 9:43 pm
So some time ago I was wondering about writing a language agnostic graphics terminal.
My plans fell a bit short and I was only able to product a display device.

This is not complete yet but supports drawing.
Right now it just reads a file with commands and produces the shapes.
Eventually I will enable the program to receive "piped" commands so you'll not need to produce a file.
Q: Why did I make this?
A: Well some languages (especially very old or archaic ones) were designed before graphics terminals were popular. This gives people a way to draw static graphics (think science data) without having to resort to building libraries at the system level.
Q: Why are you not blitting shapes to a canvas?
A: I'm using LTK, it's canvas object demands shapes be stored in memory.
(might move to displaying bitmaps later)
Q: But we can already draw shapes in CL right? This isn't really needed.
A: Yes but this goes beyond that, any language can now draw shapes without libraries.
Even horrible ones.
Q: Ok you're just loading shapes from a file. Not a spectacular feat.
A: For now yes, however eventually I will enable command piping for Linux/Unix OS's.
This means you can just pipe the commands into the display rather than have to export them to file.
Here are the commands for drawing, (each one must be on it's own line in the source file)
Also, please only use whole numbers in drawing commands.
I've not yet accounted for floats and fractions. Use ROUND or FLOOR if need be before feeding commands in.
Enough already here is my current source code:
My plans fell a bit short and I was only able to product a display device.

This is not complete yet but supports drawing.
Right now it just reads a file with commands and produces the shapes.
Eventually I will enable the program to receive "piped" commands so you'll not need to produce a file.
Q: Why did I make this?
A: Well some languages (especially very old or archaic ones) were designed before graphics terminals were popular. This gives people a way to draw static graphics (think science data) without having to resort to building libraries at the system level.
Q: Why are you not blitting shapes to a canvas?
A: I'm using LTK, it's canvas object demands shapes be stored in memory.
(might move to displaying bitmaps later)
Q: But we can already draw shapes in CL right? This isn't really needed.
A: Yes but this goes beyond that, any language can now draw shapes without libraries.
Even horrible ones.
Q: Ok you're just loading shapes from a file. Not a spectacular feat.
A: For now yes, however eventually I will enable command piping for Linux/Unix OS's.
This means you can just pipe the commands into the display rather than have to export them to file.
Here are the commands for drawing, (each one must be on it's own line in the source file)
Also, please only use whole numbers in drawing commands.
I've not yet accounted for floats and fractions. Use ROUND or FLOOR if need be before feeding commands in.
Code: Select all
All commands occupy a single line!
All color triplets range from 0 to 255
State setting commands are uppercase, drawing are lowercase
example
l 12 12 33 34
Draws a line from (12, 12) to (33, 34)
Set pen color (red green blue)
P
Set brush color (red green blue)
B
Use filled brush (0 for false 1 for true)
U
Set pen width (number)
W
Set canvas color (red green blue)
C
Draw line (x1 y1 x2 y2)
l
Draw rectangle (x1 y1 x2 y2)
r
Draw triangle (x1 y1 x2 y2 x3 y3)
t
Draw polygon (x1 y1 x2 y2 x3 y3 ...)
p - create-polygon
Draw oval (x1 y1 x2 y2)
o
Code: Select all
;;; Ryan Burnside 2013 graphics terminal.
(load "ltk.fasl")
(in-package :ltk)
;;; Some global parameters
(defparameter *pen-color* "#ffffff")
(defparameter *brush-color* "#aaaaaa")
(defparameter *canvas-color* "#000000")
(defparameter *pen-width* 1)
;; This function toggles using the brush to fill shapes (boolean)
(defparameter *use-brush* 1)
;; Make a lookup table binding LTK shape symbols to regex symbols
(defparameter *shape-lookup* '((#\P . set-pen-color)
(#\B . set-brush-color)
(#\U . set-use-brush)
(#\W . set-pen-width)
(#\C . set-canvas-color)
(#\l . create-line)
(#\r . create-rectangle-cords)
(#\t . create-polygon)
(#\p . create-polygon)
(#\o . create-oval-cords)))
(defun set-pen-color (col)
(setf *pen-color* col))
(defun set-brush-color (col)
(setf *brush-color* col))
(defun set-canvas-color (col canvas)
(configure canvas :background col))
(defun set-pen-width (width)
(setf *pen-width* width))
(defun set-use-brush (zero-or-one)
(if (= zero-or-one 0)
(setf *use-brush* nil)
(setf *use-brush* t)))
(defun create-oval-cords (canvas cords)
"Substitute function, takes a cords list"
(make-oval canvas (nth 0 cords)
(nth 1 cords)
(nth 2 cords)
(nth 3 cords)))
(defun create-rectangle-cords (canvas cords)
"Substitute function, takes a cords list"
(make-rectangle canvas (nth 0 cords)
(nth 1 cords)
(nth 2 cords)
(nth 3 cords)))
(defun make-color (r g b)
"Returns a hex color string given 3 parameters 0-255 per channel"
(format nil "#~2,'0X~2,'0X~2,'0X" r g b))
(defun make-color2 (RGB-list)
"Returns a hex color string given 3 parameters 0-255 per channel in a list"
(format nil "#~2,'0X~2,'0X~2,'0X" (nth 0 RGB-list)
(nth 1 RGB-list)
(nth 2 RGB-list)))
(defun tokenize-string (string)
"Returns a list of items delimited by #\Space"
(loop for start = 0 then (1+ finish)
for finish = (position #\Space string :start start)
collecting (subseq string start finish)
until (null finish)))
(defun parse-line (line canvas-object)
"Parses the line, maps the first letter to a drawing funciton,
turns the args into a list, passes as list to drawing function"
(let* ((str (string-trim " " line))
(begin (aref str 0))
(args (mapcar #'parse-integer (subseq (tokenize-string str) 1))))
;; If this is just a state change command exit now that it ran
(when (equal begin #\C)
(funcall (cdr (assoc begin *shape-lookup*))
(make-color2 args) canvas-object)
(return-from parse-line t))
;; Canvas color and Use brush variables
(when (or (equal begin #\W) (equal begin #\U))
(funcall (cdr (assoc begin *shape-lookup*)) (car args))
(return-from parse-line t))
;; Pen and Brush color setting
(when (or (equal begin #\P) (equal begin #\B))
(funcall (cdr (assoc begin *shape-lookup*)) (make-color2 args))
(return-from parse-line t))
(let ((l (funcall (cdr (assoc begin *shape-lookup*)) canvas-object args)))
(itemconfigure canvas-object l :fill (if *use-brush* *brush-color* ""))
(if (not (equal begin #\l)) ; Lines don't get outline attribute
(itemconfigure canvas-object l :outline *pen-color*))
(itemconfigure canvas-object l :width *pen-width*))))
(defun load-file-shapes (canvas)
"Open a file and parse out the shapes, adding them to the global canvas"
(with-open-file (stream (get-open-file))
(do ((line (read-line stream nil)
(read-line stream nil)))
((null line))
(parse-line line canvas))))
(defun main-function()
(with-ltk ()
(let* ((frame (make-instance 'frame))
(sc (make-instance 'scrolled-canvas))
(canvas (canvas sc))
(m (make-menubar))
(mfile (make-menu m "File")))
(make-menubutton mfile "Load File" (lambda () (load-file-shapes canvas)))
(make-menubutton mfile "Export Canvas" (lambda () (get-save-file)))
(make-menubutton mfile "Clear Canvas" (lambda () (clear canvas)))
(make-menubutton mfile "Quit" (lambda () (setf *exit-mainloop* t)))
(set-geometry *tk* 800 600 0 0)
(wm-title *tk* "Ryan Burnside's Canvas")
(pack frame :side :bottom)
(pack sc :expand 1 :fill :both)
(scrollregion canvas 0 0 800 600)
(configure frame :relief :sunken))))
;Start program
(main-function)