Sketch is a Common Lisp framework for the creation of electronic art, computer graphics, visual design, game making and more. It is inspired by Processing and OpenFrameworks.
Sketch is a Common Lisp environment for the creation of electronic art, visual design, game prototyping, game making, computer graphics, exploration of human-computer interaction and more. It is inspired by Processing Language and shares some of the API.
Since April 2016, Sketch is available in Quicklisp, Common Lisp's de facto package manager. This makes getting and running Sketch as easy as
To make Sketch run correctly, though, a few requirements must be met.
18.104.22.168Common Lisp ImplementationSketch should be compatible with all major Common Lisp implementations and all major operating systems - more specifically, all CL implementations and operating systems that cl-sdl2 runs on. Incompatibility with any of those is considered a bug.
Sketch is known to work with:
- CCL 1.11 on Mac OS X El Capitan
- CCL SVN 1.12.dev.r16617 on Arch Linux
- CCL 1.11 on Windows 10 64bit
- SBCL on Debian Unstable
- SBCL 1.2.16 on Arch Linux
- SBCL 1.3.1 on Linux Mint 17
- SBCL 1.3.6 on Windows 10 64bit
Sketch is known to not work with:
- SBCL 1.2.15 on Mac OS X
If you test Sketch on other systems, please send a pull request to include your results.
22.214.171.124.1SDL2SDL2 is currently Sketch's only backend. It is a C library which you will need to download manually from libsdl webpage. Select the release compatible with your operating system, or compile from the source code.
126.96.36.199.2SDL2 Image & SDL2 TTFFor loading image and font files, Sketch relies on SDL2 Image and SDL2 TTF, respectively, both part of the SDL project.
188.8.131.52.3libffiSome users have reported that libffi needed to be installed to make Sketch work.
184.108.40.206.4OpenGLSketch requires graphics hardware and drivers with support for GL version 3.3.
220.127.116.11Installing and running Sketch on WindowsSketch works on both CCL and SBCL, but installing all prerequisites might not be as straightforward as it is on the other platforms.
18.104.22.168.1LibrariesDownload SDL2, SDL2_IMAGE and SDL2_TTF dlls from libsdl webpage and copy them somewhere Windows can find them -
\Windows\System32will work. When copying SDL2_TTF, make sure to copy all of the dlls provided in the archive, and not just the TTF one.
Now you will need to get a libffi dll. One of the ways of doing this is compiling from the source, but for a quick and easy solution, you can just find a trusted source and use their version. For example, if you are using Emacs on Windows, you can find
emacs\bin. Copy it to the same directory you copied sdl2 dlls to earlier.
22.214.171.124.2GCCTo bootstrap cffi-libffi, you are going to need a C compiler, more specifically the one from the GNU Compiler Collection. Also, libffi headers and pkg-config are needed. Luckily, you can get all these things (and more) with MSYS2. Go to https://msys2.github.io and follow the instructions for installing the 64bit version.
From its console, install gcc, libffi headers and pkg-config by running
pacman -S gcc libffi libffi-devel pkg-config.
126.96.36.199.3Environment variablesFrom the Control Panel, open System properties, go to the Advanced tab, and click "Environment Variables..." - or click the Start button, start typing "environment" and select "Edit the system environment variables".
Double click "Path" from the list of System variables and make sure that both your lisp implementation's path (something like
C:\Program Files\Steel Bank Common Lisp\1.3.6\) and MSYS path (probably
C:\msys64\usr\bin) are listed. If not, click "New" and add them now.
If you needed to change anything here, restart the computer now.
188.8.131.52.4SLIMEIf you are using SLIME, you won't be able to load or run Sketch if you start SWANK from emacs (by running
M-x slime). Instead, you should open the Command Prompt (the regular one, not MSYS), start your lisp and eval
(ql:quickload :swank)=(swank:create-server)=. From Emacs, type
M-x slime-connect, and finally, press enter twice (for localhost and port 4005).
If you did everything correctly, you should be able to
(ql:quickload :sketch) and move on to the tutorial.
184.108.40.206If you are obtaining Sketch from this repository, instead of using Quicklisp releasesPlease make sure to also get the following libraries to your
local-projectsdirectory. This is not necessary otherwise.
1.1.2Running provided examplesTo get a feel for what Sketch can do, and also to make sure that everything has been installed correctly, you can look at the examples. The code below will run all four currently provided examples at once. Note that on older machines running four sketches at once might result in a small degradation in performance, so you might want to run sketches separately.
CL-USER> (ql:quickload :sketch-examples) CL-USER> (make-instance 'sketch-examples:hello-world) CL-USER> (make-instance 'sketch-examples:sinewave) CL-USER> (make-instance 'sketch-examples:brownian) CL-USER> (make-instance 'sketch-examples:life) ; Click to toggle cells, ; any key to toggle iteration
1.1.3Running example code from this pageIn all the following examples, we're going to assume that Sketch is loaded with
(ql:quickload :sketch), and that we're in package
:TUTORIAL, which is set to use
CL-USER> (ql:quickload :sketch) CL-USER> (defpackage :tutorial (:use :cl :sketch)) CL-USER> (in-package :tutorial) TUTORIAL> ;; ready
1.2TutorialNOTE: This tutorial is using the revised =DEFSKETCH= macro, introduced in May 2016. Until this release hits Quicklisp, you'll have to install Sketch manually to your =local-projects= /directory, along with https://github.com/lispgames/cl-sdl2 andhttps://github.com/lispgames/sdl2kit. More about this here./
Defining sketches is done with the
DEFSKETCH macro, that wraps
DEFCLASS is still possible, but
DEFSKETCH makes everything so much easier, and in these examples, we're going to pretend that's the only way.
(defsketch tutorial ()) (make-instance 'tutorial)
If all goes well, this should give you an unremarkable gray window.
1.2.1ShapesLet's draw something!
(rect x y w h) draws a rectangle where
y specify the top-left corner of the rectangle, and
h are the width and height. By default, the origin (0, 0) is at the top-left corner of the drawing area, and the positive y direction is facing down.
(defsketch tutorial () (rect 100 100 200 200))
(defsketch tutorial () (dotimes (i 10) (rect (* i 40) (* i 40) 40 40)))
(defsketch tutorial () (dotimes (i 10) (rect 0 (* i 40) (* (+ i 1) 40) 40)))
(defsketch tutorial () (dotimes (i 10) (rect 0 (* i 40) (* (+ i 1) 40) 40)) (circle 300 100 50))
(defsketch tutorial () (line 0 0 400 400) (line 400 0 0 400))
(defsketch tutorial () (polyline 100 100 200 150 300 100 200 200 100 100))
(defsketch tutorial () (polygon 100 100 200 150 300 100 200 200))
(defsketch tutorial () (dotimes (i 4) (ngon (+ i 3) (+ 50 (* i 100)) 200 20 20 (* i 20))))
(defsketch tutorial () (bezier 0 400 100 100 300 100 400 400))
1.2.2ColorsGrayscale imagery is nice, but let's add color and make our sketch more vibrant. Assuming that you're using Emacs + SLIME, or a similarly capable environment, you can just re-evaluate with the following code:
(defsketch tutorial () (background +yellow+))
The window becomes yellow. There are a couple of things to note. Drawing code doesn't need to go into a special function or method, or be binded to a sketch explicitly.
DEFSKETCH is defined as
(defsketch sketch-name bindings &body body): that body is your drawing code. We will get to
BINDINGS later. The other thing is that Sketch comes with its own color library.
220.127.116.11Predefined colorsThere are constants for commonly used colors:
18.104.22.168RGB, HSB, GRAYIf you want to be more specific about the colors you want, you are welcome to use
(rgb red green blue &optional (alpha 1.0)),
(hsb hue saturation brightness &optional (alpha 1.0))or
(gray amount &optional (alpha 1.0)). The arguments to these functions are values from 0 to 1. You can use these functions in the same way you just used
+YELLOW+. Hopefully the function names and their arguments are self-explanatory, but if not, you can learn about the RGB color model here and about HSB (also called HSV) here.
(gray amount &optional (alpha 1.0))is really just a convenient alias for
(rgb amount amount amount &optional (alpha 1.0)), and can be used for brevity when a shade of gray needs to be defined.
This might be a good place to note that function names in Sketch use the American English spellings, like "gray" and "color". It's just a choice that needed to be made, in pursue of uniformity and good style.
(defsketch tutorial () (background (rgb 1 1 0.5)))
This will give you a lighter yellow.
All functions have an additional,
ALPHA parameter. It determines the amount of transparency that the color should have.
22.214.171.124RGB-255, HSB-360, GRAY-255Sometimes it's easier to think about color values in non-normalized ranges. That's why Sketch offers
This is how these functions map to their normalized variants.
|(rgb-255 r g b a)||(rgb (/ r 255) (/ g 255) (/ b 255) (/ a 255))|
|(hsb-360 h s b a)||(hsb (/ h 360) (/ s 100) (/ b 100) (/ a 255))|
|(gray-255 g a)||(gray (/ g 255) (/ a 255))|
HSB-360 is using different ranges, because hue is represented in degrees (0-360), and saturation and brightness are represented as percentages (0-100).
126.96.36.199HEX-TO-COLORIf you are used to working with colors in hex, like in CSS, you can use
(hex-to-color string), where
STRINGis the color in one of the following formats: "4bc", "#4bc", "4bcdef", and "#4bcdef".
188.8.131.52Generating colorsIf you don't care about fiddling with the exact values, but still need different colors, you can use one of the following functions.
Lerping is a fancy way of saying linear interpolation. This function takes the starting color and the ending color, and returns the color between them, which is an
(lerp-color (start-color end-color amount &key (mode :hsb)))
AMOUNTaway from the starting color. When
AMOUNTequals zero, the returned color equals the starting color, and when
AMOUNTequals one, the ending color is returned. Amounts between zero and one give colors that are "in-between". These colors are calculated according to the specified
MODE, which is
:HSBby default, meaning that the resulting color's hue is between the starting and ending hue, as is the case with its saturation and brightness.
(defsketch lerp-test ((title "lerp-color") (width 400) (height 100)) (dotimes (i 4) (with-pen (make-pen :fill (lerp-color +red+ +yellow+ (/ i 4))) (rect (* i 100) 0 100 100))))
Returns a random color. You probably don't want to use this, because much of the returned colors are either too dark, or too light. You do get to choose the
(random-color (&optional (alpha 1.0)))
(defparameter *colors* (loop for i below 16 collect (random-color))) (defsketch random-color-test ((title "random-color") (width 400) (height 100)) (dotimes (x 8) (dotimes (y 2) (with-pen (make-pen :fill (elt *colors* (+ x (* y 8)))) (rect (* x 50) (* y 50) 50 50)))))
This is probably the function you're looking for, if you just want to create a non-repeating set of colors quickly. It maps all numbers to "interesting" (not too dark, not too light) colors. You can use this for coloring procedurally generated objects, when prototyping and just trying to make things look different quickly, when making palettes, looking for "the right" color, and many other things.
(hash-color (n &optional (alpha 1.0)))
(defsketch hash-color-test ((title "hash-color") (width 400) (height 100)) (dotimes (i 128) (with-pen (make-pen :fill (hash-color i)) (rect (* i (/ 400 128)) 0 (/ 400 128) 100))))
184.108.40.206Color filtersSometimes you have a color, and would like to transform it in some way. That's what color filters are for.
220.127.116.11.1GrayscaleTo convert colors to grayscale, you can use
color-filter-grayscale. Two modes of grayscale conversion are implemented:
:luminosity, the default, which is luminance-preserving
:average, which sets all color channels to their average
(defsketch grayscale-test ((title "grayscale") (width 400) (height 300)) (dotimes (i 10) (let ((color (hash-color i))) (with-pen (make-pen :fill (color-filter-grayscale color)) (rect (* i 40) 0 40 100)) (with-pen (make-pen :fill color) (rect (* i 40) 100 40 100)) (with-pen (make-pen :fill (color-filter-grayscale color :average)) (rect (* i 40) 200 40 100)))))
18.104.22.168.2InvertTo invert a color, use
(defsketch invert-test ((title "invert") (width 300) (height 300) (i 0)) (background +white+) (incf i 0.01) (let ((color (rgb (abs (sin i)) (abs (cos i)) 0))) (with-pen (make-pen :fill color) (circle 100 150 50)) (with-pen (make-pen :fill (color-filter-invert color)) (circle 200 150 50))))
22.214.171.124.3RotateRotating a color in Sketch using
color-filter-rotatesets the value of its red channel to theprevious value of the green channel; green to blue, and blue tored. The operation is intended to be used in palette generation,because the rotated colors usually work pretty well together.
(defsketch rotate-test ((title "rotate") (width 300) (height 300) (i 0) (color (rgb 0.2 0.8 1.0))) (background +white+) (incf i 1) (when (zerop (mod i 60)) (setf color (color-filter-rotate color))) (with-pen (make-pen :fill color) (rect 100 100 100 100)))
126.96.36.199.4HSBHSB stands for Hue/Saturation/Brightness. You can use
color-filter-hsbto adjust hue, saturation and brightness of an existing color.
(defsketch hsb-test ((title "hsb") (width 400) (height 300) (color (rgb 0.2 0.5 0.6))) (dotimes (i 4) (with-pen (make-pen :fill (color-filter-hsb color :hue (* 0.1 (+ i 1)))) (rect (* i 100) 0 100 100)) (with-pen (make-pen :fill (color-filter-hsb color :saturation (* 0.1 (+ i 1)))) (rect (* i 100) 100 100 100)) (with-pen (make-pen :fill (color-filter-hsb color :brightness (* 0.1 (+ i 1)))) (rect (* i 100) 200 100 100))))
1.2.3PensPens are used to draw shapes. If no pen is specified, the default pen sets
:stroketo black, and
188.8.131.52.1Creating and Using PensSay you want to draw a red square and a blue circle. You would need to use two different pens.
(defsketch pen-test ((title "pens")) (with-pen (make-pen :fill +red+) (rect 100 100 100 100)) ; this rect will be red (with-pen (make-pen :fill +blue+) (circle 315 315 50))) ; this rect will be blue
184.108.40.206.2Fill/StrokeThe squares in the previous example were filled because we specified the
make-pen.If we wanted to just draw the outline of the square, we would use
(defsketch outline-square ((title "Outline Square")) (with-pen (make-pen :stroke +red+) (rect 100 100 100 100)))
(defsketch fill-stroke ((title "Fill and Stroke")) (background +white+) (with-pen (make-pen :stroke (rgb .5 0 .6) :fill (rgb 0 .8 .8)) (rect 50 50 100 75) (circle 300 220 100)))
220.127.116.11.3WeightWe can also change the thickness of the lines and shapes that we draw by changing the pen
(defsketch weight-test ((title "Weight Test")) (dotimes (i 10) (with-pen (make-pen :stroke +white+ :weight (+ i 1)) ; pen weight can't be zero (line 50 (* i 20) 350 (* i 20)))))
:curve-stepsis used to change the smoothness (resolution) of curves like
(defsketch curve-test ((title "Curve-steps")) (dotimes (i 99) (with-pen (make-pen :stroke +red+ :curve-steps (+ i 1)) ; as curve-step increases, curve becomes "smoother" (bezier 0 400 100 100 300 100 400 400))))
(translate dx dy),
(rotate angle &optional (cx 0) (cy 0))and
(scale sx &optional sy (cx 0) (cy 0))are available to change the view matrix that is applied to coordinates.
(with-translate (dx dy) &body body),
(with-rotate (angle &optional (cx 0) (cy 0)) &body body) and
(with-scale (sx &optional sy (cx 0) (cy 0)) &body body) can be used to restore the view matrix after executing the body.
The current view can also be saved on a stack and restored with
(pop-matrix), which are analogous to
pop() in p5.js. The macro
(with-identity-matrix &body body) pushes the current view matrix onto the stack, sets the view matrix to the identity matrix, executes
body, and then pops the view matrix.
(with-current-matrix &body body) is the same, except it doesn't change the view matrix after pushing it.
In this example, translation and rotation are used to draw a triangle in the centre of the screen, without explicitly defining the coordinates of the vertices.
(defsketch transform-test ((title "Transform test") (width 500) (height 500) (side 100) (y-offset (/ side (* 2 (tan (radians 60)))))) (with-translate (250 250) (loop repeat 3 do (line (- (* 1/2 side)) y-offset (* 1/2 side) y-offset) do (rotate 120))))
This example draws a sequence of increasingly shrinking squares using scaling.
(defsketch transform-test ((width 400) (height 400) (title "Scale test")) (translate 100 100) (dotimes (x 5) (rect 0 0 100 100) (translate 150 0) (scale 1/2)))
(text text-string x y &optional width height)to draw text, where
yspecify the top-left corner of the rectangle containing the text.
heightcontrol the shape of the text box. There is support for changing the font.
(defsketch text-test ((title "Hello, world!")) (text title 0 0 100))
(load-resource filename ...)to load the image from a given file, then
(image image-resource x y &optional width height)to draw the image with its top-left corner at
(x, y)and with the given
(defsketch image-test ((title "Hello, image!") (rsc (load-resource "/path/to/img.png"))) (image rsc 10 10 200 200))
Images can be cropped using
(crop (image-resource image) x y w h), where
y indicate the top-left corner of the cropping rectangle and
h indicate the width & height. Image flipping can be accomplished by using negative
1.2.7InputInput is handled by overriding methods provided by sdl2kit; the sketch window is a subclass of the
WINDOWclass provided by
sdl2kit, which provides these generic functions for handling input. Drawing functions cannot be called from these methods, only from the body of
In this example, we draw a new rectangle every time there is a click. The important parameters of the event method:
state is a keyword symbol indicating the state of the mouse,
ts is a timestamp,
y are the coordinates of the mouse click.
(defsketch input-test ((title "Hello, input") (rectangles nil)) (loop for (x y) in rectangles do (rect x y 50 50))) (defmethod kit.sdl2:mousebutton-event ((window input-test) state ts b x y) (with-slots (rectangles) window (when (eq state :mousebuttondown) (push (list x y) rectangles))))
In this example, all keyboard text input is echoed to the screen.
text is a string / character array containing a single character.
(defsketch input-test ((title "Hello, input") (text-to-write nil)) (loop for s in text-to-write do (text s 0 0 20 20) do (translate 20 0))) (defmethod kit.sdl2:textinput-event ((window input-test) ts text) (with-slots (text-to-write) window (setf text-to-write (nconc text-to-write (list text)))))
Finally, here is an example where a pair of eyes follow the mouse (the pupils are restricted to a rectangle, it would look better if they were restricted to a circle).
(defsketch input-test ((looking-at (list 0 0)) (cx (/ width 2)) (cy (/ height 2))) (let ((cx-1 (- cx 50)) (cx-2 (+ cx 50)) (mx (car looking-at)) (my (cadr looking-at))) (with-pen (make-pen :fill +white+) (ellipse cx-1 cy 40 80) (ellipse cx-2 cy 40 80)) (with-pen (make-pen :fill +black+) (flet ((move-towards (x1 x2) (let ((diff (- x2 x1))) (+ x1 (if (< (abs diff) 10) diff (* (signum diff) 10)))))) (circle (move-towards cx-1 mx) (move-towards cy my) 10) (circle (move-towards cx-2 mx) (move-towards cy my) 10))))) (defmethod kit.sdl2:mousemotion-event ((window input-test) ts bm x y xrel yrel) (with-slots (looking-at) window (setf (car looking-at) x (cadr looking-at) y)))
1.2.8SetupThe generic function
(setup instance &key &allow-other-keys)is a hook that gets called before the window is initialised. Since the window has not been initialised, it can't be used to draw anything, but the background colour can be set. Here is an example from brownian.lisp.
(defmethod setup ((instance brownian) &key &allow-other-keys) (background (gray 1)))
1.2.9Saving a picture
(save-png pathname)can be called within the body of
defsketchto save a PNG of the currently running sketch. A keyboard shortcut could be set up to take screenshots, as follows.
(defsketch save-test ((should-save nil) (copy-pixels t)) (rect (random width) (random height) 10 10) (when should-save (setf should-save nil) (save-png "/tmp/my-sketch.png"))) (defmethod kit.sdl2:textinput-event ((window save-test) ts text) (when (string= text "s") (setf (slot-value window 'should-save) t)))
1.2.10Drawing with a canvas
(make-canvas width height)can be used to create a rectangular grid of pixels. The shape of the grid is defined by
(canvas-paint canvas color x y) sets the color of a pixel within the grid.
(canvas-lock canvas) freezes the appearance of the canvas. Any calls to
(canvas-image canvas) will show an image of the canvas when
canvas-lock was last called.
(canvas-unlock canvas) allows the image of the canvas to be modified again.
To draw the canvas:
(with-pen (make-pen :fill (canvas-image canvas)) (rect 0 0 (canvas-width canvas) (canvas-height canvas)))
1.3Made with Sketch
1.4.1I'm trying to compile my defsketch definition, but it keeps telling me that :TITLE (or :WIDTH, :HEIGHT, etc.) is not of the expected type LIST. Why is this happening?You're probably trying to use the old way of defining sketches -
(defsketch name window-parameters slot-bindings &body body).
DEFSKETCHhas been changed to
(defsketch name bindings &body body). It's still possible to define the title and other window parameters, though.
(defsketch foo (:title "Foo" :width 400) ((a 3)) (rect 100 100 200 200)) ;;; Becomes (defsketch foo ((title "Foo") (width 400) (a 3)) (rect 100 100 200 200))
For more, read about "Bindings" in the tutorial above.
1.5OutroFor everything else, read the code or ask vydd at #lispgames.
Go make something pretty!
Copyright (c) 2015, 2016, 2017 Danilo Vidovic (vydd)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.