Pan Tutorial
Home Gallery Papers Creating Effects Download

The essential ideas of the Pan library/language are described in Functional Images, illustrated with numerous examples.  Be aware that the paper uses a somewhat simplified notation, as described below.   Here I'll develop a few simple complete examples, including UI (user interface) construction, which is not described in the paper.  Pan is not really an independent language, but is embedded in the language Haskell. To really understand and use what you read here, you'll need to get a working knowledge of Haskell.  Here's a primer with references.

Images

In Pan, images are functions from continuous, infinite 2D space to colors with partial opacity.  It's useful to generalize to images over arbitrary "pixel" type, so we make Image be a type constructor:
type Image c = PointE -> c

type PointE = (FloatE, FloatE)

We will explain below why we use an "E" suffix in type names.  For now, you can think of FloatE as synonymous with Float, etc.

Two useful special cases are color-valued images and spatial regions:
type ImageC = Image ColorE

type Region = Image BoolE

Note that any function over 2D space is an image, no matter how you define it.   You can define one entirely from scratch if you want.  For instance here is a Sierpinski-like region.
gasket :: Region
gasket (x,y) = i .|. j <=* i 
  where i = f2i x; j = f2i y
The f2i function truncates floats into ints, and the ".|." operator is bitwise or.

You don't have to create images from scratch, because Pan provides many useful image-producing functions.  For instance, bitmap importation, stretching, turning, moving, swirling, and tiling.  See Functional Images for some of what can be done, although that paper takes some liberties with notation.  See also the implementation's Image module and the examples in DemoSrc.

Transforms

Spatial transforms are simply mappings from space to itself:
type TransformE = PointE -> PointE

Again, anything of this type is a transform, no matter how it's defined.  Again, you can make your own from scratch, or use the building blocks that Pan provides.  As an example, here are the definitions of translation (shifting) and scaling:
translateP, scaleP :: (FloatE,FloatE) -> TransformE
translateP (dx,dy) (x,y) = (x + dx, y + dy)
scaleP     (sx,sy) (x,y) = (sx * x, sy * y)

Rotation has the same form, but a more complicated calculation, using trig:
rotateP :: FloatE -> TransformE
rotateP ang (x,y) = (x * c - y * s, y * c + x * s)
 where c = cos ang
       s = sin ang

Note the selective use of Currying here.  The translate, scale, and rotate functions are typically applied to a single argument, yielding transform, which is another function.

Since transforms are just space-to-space functions, you can compose them with general function composition (".").

What about transforming images?  The trick is to compose the image (itself a function) with the transform's inverse.  Since it's not always possible to invert functions, Pan provides some convenient definitions that form inverse functions and perform the composition.  For instance,
translate (dx,dy) im = im . translateP (-dx, -dy)
scale     (sx,sy) im = im . scaleP     (1/sx, 1/sy)
rotate     ang    im = im . rotateP    (-ang)

There are many more transform building blocks.  See the paper, examples, and the Transform module.

UIs

Our first examples are drawn from the module "Simple" (in the file Simple.hs) in the DemoSrc directory.  This module mainly contains definitions of "UIs", which encapsulate both an image effect and the user interface to that effect.  These encapsulated effects and their user interfaces can be combined into more complex and specialized ones.  (That is, they are compositional, "first class values".)  Let's look at a first example:
rotateUI :: UI (ImageXf a)
rotateUI = do angle <- angleUI "rotate" (-180,180) 45
              return $ rotate angle

The "do notation" we use comes from Haskell's more general support for "monads".  Monads are type constructors that serve to contain or generate values.  (You do not need to understand monads in general to use Pan.)  Here, UI is the monad -- it not only presents controls to the user, but also generates a value.  In this case, the value is an image-transforming function, for which Pan provides a convenient name:
type Filter c = Image c -> Image c

The polymorphism here means that rotateUI can be applied to any image with any type of "pixels".  The most common two are ColorE and BoolE, with the latter representing arbitrary regions of 2D space.

Pan's rotate function is being partially applied to an angle.  As always, application of a two-argument (Curried) function to one argument produces another function that accepts the second argument -- in this case the image to be rotated.

The angleUI function used here is defined in terms of the slider primitive.  It interprets the slider value as degrees, with positive to the right (clockwise) and negative to the left (counter-clockwise). This convention matches the slider, kinesthetically, and is mapped to radians with positive counter-clockwise and negative clockwise.
angleUI :: Label -> (Float, Float) -> Float -> UI FloatE
angleUI lab range def = do angDeg <- slider lab range def
                           return $ angDeg * (-pi/180)

Note the common pattern in these two definitions: a value is extracted from a UI and used to construct a new value, which is then returned.  Haskell is very good at being able to abstract patterns, and in fact already has a suitable monadic combinator: liftM.  We can use liftM because it is defined to work on all type constructors declared to be monads.
liftM :: (Monad m) => (a -> b) -> m a -> m b
liftM f a = do a' <- a
               return $ f a'

Using liftM allows us to rewrite turnUI and angleUI more succinctly:
turnUI = liftM turn $ angleUI "turn" (-180,180) 45

angleUI lab range def = liftM (* (-pi/180)) $ slider lab range def
For angleUI, we've used Haskell's "section" notation for partially applied infix operators (here "*").

As another example, here's the definition of the ripple UI, which is based on the rippleRadius image-producing function.  Since rippleRadius takes two arguments instead of one, there are two sliders in our composite UI:
rippleUI = do n <- islider "# ripples" (1,15) 3
              d <- slider  "depth" (-0.5,1.5) 0.3
              return $ rippleRadius n d

Again, there is a concise monadic equivalent:
rippleUI = liftM2 rippleRadius
             (islider "# ripples" (1,15) 3)
             (slider  "depth" (-0.5,1.5) 0.3)

For more examples, see Simple.hs, and the other modules in the DemoSrc directory.

Type and function names

The examples above use the type name "FloatE" for floating-point values.  Why not use Haskell's "Float" type instead, as in the Functional Images paper?  The reason is due to the implementation style, which is described in Compiling Embedded Languages.  Types like FloatE are syntactic, containing expressions (syntax trees) rather than simple values.  Pan supplies overloadings wherever possible so that you can write and read familiar-looking notation and mostly forget that you're manipulating syntax.  Compiler functions like compileFilter generate code for effects, based on their optimized, composed expressions, for the accompanying UIs.

Sometimes overloading is not possible, because the original type declarations were not general enough.  In such cases, Pan provides a function whose name ends in "E" or "*", depending on whether the original is alphanumeric or not.
"if-then-else" is not overloadable, so Pan uses "ifE".
Equality and inequality relations, and boolean connectives, e.g., "x >* 1", "notE" and "a ||* b".
Some number functions, evenE, oddE, truncateE, roundE, ceilingE, floorE.

If you forget to append a "*" or "E" when needed, you should get a helpful error message.

 

Conal Elliott
Copyright © 1999,2000 Microsoft Corp. All rights reserved.
Revised: January 02, 2001.