203: A Simple Picture Language in the Style of SICP

by Vladimir Nikishkin (lockywolf gmail.com)

Status

This SRFI is currently in final status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-203@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

This SRFI proposes a simple library for programmatic drawing of pictures compatible with Section 2.2.4 of Structure and Interpretation of Computer Programs.

It aims to close the gap between the Scheme suggested for study in the book and portable Scheme.

Rationale

Structure and Interpretation of Computer Programs (SICP), by Harold Abelson and Gerald Jay Sussman with Julie Sussman, is one of the world's most famous programming textbooks. The code examples in the book are given in Scheme, and the exercises are mostly expected to be done in Scheme. The examples and exercises are best executed with a Scheme system that implements a reasonable subset of the Scheme language. Furthermore, the textbook assumes the existence of several primitives not included in any of the Scheme reports. Most of these primitives are already either covered by other relevant SRFIs, or can be implemented on top of those. The picture language of Section 2.2.4 cannot, however, be portably implemented using any of the portable Scheme libraries. This SRFI aims to fill this gap.

Potentially, this SRFI may serve as one of the building blocks on which another SRFI, dedicated to providing in full all the language facilities assumed by SICP, may be implemented.

This SRFI is not a complete implementation of the picture language as it is presented in Section 2.2.4. This is intentional. The scope of this SRFI is not to implement the whole drawing library presented, but only to implement a minimal subset on top of which the code from the book can be run. This leads to procedures accepting as arguments certain data structures for which constructors are not provided within this SRFI. Those should be sufficiently easy to implement using the code from the book. Nevertheless, the code from the sample implementation test file can be reused.

This SRFI can be compared to SRFI 96, which is to SLIB as this SRFI is to the SICP drawing language. SRFI 96 makes no attempt to document SLIB (which has over 200 modules), much less to extend it. Instead, it documents the constants, variables, procedures, and syntax forms that a Scheme must provide in order to fully host SLIB, which amount to less than forty.

Specification

A "canvas" is anything that the drawing procedures draw on. It may be a window, or a file, or some other drawing device. Due to the great variety of available Scheme implementations and hardware on which they run, it is hard to specify what is a canvas precisely, but it should be a certain location where the result of the drawing procedures is stored. It may be a file, a buffer, a window, or a certain Scheme object dedicated to keep a list of instructions to be flushed to a real graphics system later.

As far as the code of SICP is concerned, it is legitimate to call any drawing routine at any time, without prior initialization of a canvas. So, each of the drawing routines has to create a canvas if it does not exist. They should, however, draw on an existing one if it does exist.

Many systems are likely to use a so-called "double-buffering" or "delayed drawing" technique, in which drawing and displaying what has been drawn are two different operations. On the other hand, some systems are expected to try and implement this SRFI in a "value-based" way, that is to change the state of the display by using some display operation on a Scheme value. (This is the approach used by the Kawa sample implementation, which displays the "picture value" returned by canvas-refresh in a graphical REPL.) Other systems may use other routines not specified by this SRFI to display the "picture value", if pictures are represented as values.

Procedure: (canvas-reset)
Ensures that the canvas on which subsequent calls to drawing procedures will draw is blank. The return value is unspecified.

Explanation: Graphics systems differ, and giving a precise definition to (canvas-reset) is hard. If the graphics system only supports a single graphical display (as the DOS systems used to), this procedure is expected to make this display empty. If the system is window-based, it will probably create a new window. If the only drawing is done at the moment that the "picture value" is output into the REPL, this procedure is expected to do some off-screen operations that will ensure that subsequent calls to the drawing procedures will be drawing on an empty canvas.

Procedure: (canvas-refresh)
Updates the display in an implementation-defined manner to reflect the state of the canvas. Returns a system-dependent identifier (not to be confused with a Scheme identifier) of the canvas. The canvas may not exist unless it has been drawn on.

Explanation: (canvas-refresh) is an attempt to reconcile as many drawing approaches as possible.

After a call to (canvas-refresh) the state of the display (whatever it may be) is expected to be a faithful representation of the image drawn by a sequence of calls to the drawing routines, regardless of the underlying approach that a system is using.

If the system "displays" the return value in some sort of a graphical REPL (like the Kawa sample implementation does), (canvas-refresh) returns a "picture value", that is expected to be displayed by the REPL. If the system has a separate windowing subsystem, not connected to the REPL in any meaningful way, the call to (canvas-refresh) should guarantee that the window on which the canvas is displayed is presenting the final result of the drawing, and displaying it to the user.

If (canvas-refresh) is called and no canvas exists, the implementations are expected to do something reasonable. In the case of the sample file-based implementation, (canvas-refresh) returns a file name that could have been used for drawing, which may or may not exist. In case of a window-based implementation, it is reasonable to return a value that could serve as a valid window identifier. If an implementation displays images in the REPL, (canvas-refresh) might return an empty picture value.

(canvas-refresh) thus has both "pure" and side-effecting semantics: it may change the display, or it may simply return a value which can be displayed, or it may do nothing.

Procedure: (canvas-cleanup)
Deletes the current canvas and cleans the space it occupies. The return value is unspecified.
Procedure: (draw-line start end)
Draws a line on the canvas starting at (car start) , (car (cdr start)) and ending at (car end) , (car (cdr end)). It is an error if start is not a list of at least two numbers. It is an error if end is not a list of at least two numbers. If the canvas does not exist, it is created.
Procedure: (rogers v-list)

Draws an image of MIT's founder, William Barton Rogers, as shown in Figure 2.11 of SICP, on the canvas so that it fits the frame defined by the vectors (car v-list), (car (cdr v-list)) and (car (cdr (cdr v-list))) in a natural linear algebraic way.

It is an error if v-list is not a list of at least three elements, each of which is a list of at least two numbers.

If the canvas does not exist, it is created.

v-list may be one of the possible implementations of a frame, in the SICP terminology. However, in order to avoid nudging the potential users of the library into a particular implementation of frames, we opted to specify one of the possible underlying data structures directly. Converting between this representation, as well as any other representation, should be a relatively simple exercise.

Additional procedures

These procedures, although they are not strictly needed for SICP, may make programming with the picture language less mundane.

Procedure: (jpeg-file->painter file-name)

If file-name exists and is a JPEG file, jpeg-file->painter returns a painter similar to rogers, that is a procedure that accepts a frame and draws the image located at file-name shifted, rotated, and skewed to fit into the frame. Otherwise, the result is unspecified.

Procedure: (image-file->painter file-name)

If image-name exists, and is a file that can be interpreted as containing a representation of an image, and the implementation supports loading such a kind of files, image-file->painter returns a painter similar to rogers, that is a procedure that accepts a frame and draws the image located at file-name shifted, rotated, and skewed to fit into the frame. Otherwise, the result is unspecified.

In the simplest case this procedure can be a synonym to jpeg-file->painter.

Examples

The exemplar use of this SRFI can be found in the attached srfi-sicp-pictures-test.org file, which exploits the org-display-inline-images feature of Emacs' org-mode.

The code below is more or less a copy of the code from the test. When in doubt, consider referring to the org file.
(import (scheme red))
(import (srfi 203))

(define (make-vect xcor ycor . o)
  (append (list xcor) (list ycor) o))

(define (make-frame origin edge1 edge2)
  (list origin edge1 edge2))

(canvas-reset)
(landau (make-frame (make-vect 0 0) (make-vect 0.5 0.0) (make-vect 0.0 0.5)))
(rogers (make-frame (make-vect 0.5 0.5) (make-vect 0.5 0.0) (make-vect 0.0 0.5)))
(draw-line (make-vect 0.5 0.5) (make-vect 0 1.0))

(define testpainter (jpeg-file->painter "lambda.jpg"))
(testpainter (make-frame (make-vect 0.5 0.0) (make-vect 0.5 0.1) (make-vect 0.1 0.5)))
(display (canvas-refresh))

Implementation

The 203.sld and 203.scm files constitute a simple file-based implementation, in which the "system-dependent identifier" corresponds to a file name.

canvas-name and canvas-size should be parameterised by the same parameterize form.

The sample implementation litters the file system with bogus files. This is a consequence of the generality. Scheme system specific implementations are encouraged to provide a method of garbage collection.

The extra directory provides an additional implementation for Kawa scheme, as well as a test file kawatest.scm and additional resources that may be found helpful when implementing this SRFI.

References

  1. Racket (complete) implementation of the picture language. Link
  2. Racket SICP Library, Picture Language. Link Source
  3. Structure and Interpretation of Computer Programs, companion web site. Link
  4. Structure and Interpretation of Computer Programs, companion web site. Problem Set 4 (Picture Language). Link
  5. The Picture Language implementation in script-fu by mngu2382. Link
  6. The Picture Language implementation in Racket by skanev. Link
  7. Functional Geometry by Peter Henderson. Link
  8. Unofficial PDF of SICP, second edition. PDF , Source
  9. Kawa Picture Language. It is not compatible with SICP, but can be used as a substrate for an implementation if using a DomTerm-based REPL. Reference , Tutorial

Acknowledgements

Copyright

© 2020 Vladimir Nikishkin.

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 (including the next paragraph) 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.


Editor: Arthur A. Gleckler