Zooming and panning

Many Processing sketches would benefit from being able zoom in and focus on specific areas in detail. Thanks to the transformation methods scale() and translate(), this is relatively easy to achieve. What is slightly more difficult is to implement a robust zooming and panning facility that can be 'plugged in' to any existing sketch. The ZoomPan class in giCentre Utilities makes adding zooming and panning in any sketch easy.

zoomPan1.jpg
 

Using the ZoomPan class in Processing 

To add zooming and panning to your sketch you need to import the move package into your sketch with the line

 
import org.gicentre.utils.move.*;
 

For full details of the methods available see the ZoomPan API reference. See also, the ZoomExample sketch supplied in the examples folder of the giCentre utilities library.

The class ZoomPan provides mouse-controlled zooming and panning functionality with minimal changes required when adding it to a Processing sketch. To use, simply declare a ZoomPan object outside of any methods, then initialise it inside setup(). To perform the transformations necessary for zooming and panning, call the object's transform() method at the start of your draw() method. A simple example is shown below:

Simple zooming and panning of a circle

import org.gicentre.utils.move.*;

// Simple sketch to demonstrate the ZoomPan class for interactively
// zooming and panning a sketch's display.
// Version 1.3, 5th November, 2013

ZoomPan zoomer;    // This should be declared outside any methods.

void setup()
{
  size(400, 400);
  zoomer = new ZoomPan(this);  // Initialise the zoomer.
}

void draw()
{
  // This enables zooming/panning and should be in the draw method.
  zoomer.transform();

  // Any normal drawing commands below.
  background(218, 205, 192); 
  ellipse(width/2, height/2, 50, 50);
}
 

When run, zooming is controlled with by drag gin a mouse with the left mouse button or equivalent interaction on a trackpad or tablet. Panning involves dragging with the right mouse button (or two fingered drag on a track pad).

 

Resolving mouse conflicts 

ZoomPan uses the left and right mouse drags to perform zooming and panning. But what if your sketch already uses mouse actions to perform some other task? You can add keyboard modifiers to ZoomPan so that the mouse is only activated when an extra key is held down. For example, if you call the method setMouseMask(SHIFT), zooming and panning happens only when mouse dragging occurs while the shift key is held down:

 
zoomer = new ZoomPan(this);  // Initialise the zoomer.
zoomer.setMouseMask(SHIFT);  // Only active while shift key held down.
 

Valid values for setMouseMask() are SHIFTALTCONTROL and 0 (no keyboard mask).

 

Where to transform

If you wish to allow your entire sketch to be zoomed and panned, you can call the transform() method in the first line of a sketch's draw() method. This results in all subsequent drawing being zoomed in response to mouse actions. Occasionally though there may be a need to have some display activity that is independent of zooming. Examples include image captioning or 'heads-up' displays that sit on top of the zoomed image. Also, if drawing does not involve using the background method to clear the screen on each redraw (e.g. instead drawing a transparent rectangle in the display area to achieve a blur effect), it may be necessary to perform this before zooming.

Two approaches can be taken for zoom-indenpendent drawing. The first approach is to place the zoom-independent instructions before calling the transform() method. The zoom-dependent drawing should then be placed after calling transform() For example, the following code draws a transparent rectangle over the display space before zooming. This creates a series of 'trails' as the image is zoomed and panned.

Panning with untransformed transparent rectangle to produce trail.

void draw()
{
  // Transparent background to give trails effect
  noStroke();
  fill(218, 205, 192, 31);
  rect(0, 0, width, height);

  // Enable zooming and panning.
  zoomer.transform();

  // Any normal drawing commands below.
  stroke(0);
  fill(170, 128, 128);
  ellipse(width/2, height/2, 50, 50);
}
 

Anything before the transform() method remains in the same place on the screen at all times, while any code after it will be zoomed and panned in response to mouse actions. The only disadvantage with this approach is that the zoomed drawing will appear on top of the unzoomed drawing if they overlap.

The second approach is useful for legends, annotations and 'heads-up displays' where some graphics need to be overlaid on top of the zoomed graphics. This can be achieved by using pushMatrix() to store a copy of the unzoomed screen transformations, perform the zooming transformation and drawing, then restore the original transformation with popMatrix() before drawing the unzoomed legend/annotation.

Zoomed circle and untransformed text using pushMatrix() and popMatrix()

void draw()
{
  background(218, 205, 192, 31);
  
  pushMatrix();    // Store the unzoomed screen transformation.
  
  // Enable the zooming/panning.
  zoomer.transform();
  
  // Do some drawing that can be zoomed and panned.  
  fill(170,128,128);
  ellipse(width/2,height/2,50,50);
  
  // Do some drawing that will not be zoomed or panned.
  popMatrix();    // Restore the unzoomed screen transformation.
  fill(255,255,255,200);
  rect(0,height-21,width-1,20);
  fill(80);
  text("Some text here that does not move",10,height-4);
}
 

Extracting mouse position

Sketches that enable zooming and panning, but still need to retrieve the mouse position will require slight modification. After zooming or panning, the Processing variables mouseX and mouseY will no longer correspond to locations in the original untransformed coordinate space. Instead, use the method getMouseCoord() which retrieves the mouse location as if the sketch had not been zoomed or panned. This method returns a PVector object that stores both the x and y locations of the mouse. For example:

 
void draw()
{
  zoomer.transform();
  
  PVector mousePosition = zoomer.getMouseCoord();
  int mx =int(mousePosition.x);    // Equivalent to mouseX
  int my =int(mousePosition.y);    // Equivalent to mouseY
  println("Mouse at "+mx+","+my);
 
  // Any normal drawing commands below.
  
}
 

Listening for the end of zooming and panning events

Sometimes you may wish to perform some specific action after a user has completed some zooming or panning. A common example might be to update the state of a sketch after zooming. To listen out for when zooming is complete, you need to add a ZoomPanListener to the zoomer. This can be done simply by creating a new nested class that implements the ZoomPanListener interface and add two methods to it - panEnded() and zoomEnded() - which will perform the actions when the user stops a zoom or pan action:

 
void setup()
{
  // Normal sketch setup here...
  
  zoomer = new ZoomPan(this);
  zoomer.addZoomPanListener(new MyListener());
}

// Class for responding to the end of a zoom or pan action.
class MyListener implements ZoomPanListener
{
  void panEnded()
  {
    println("Panning stopped");
  }
  
  void zoomEnded()
  {
    println("Zooming stopped");
  }
}
 

Limitations

Zooming and panning in 3D is a more complex issue and using ZoomPan in a 3D sketch is likely to produce confusing results at best. Note also that the camera() method will override the zooming and panning transformations, so should not be used together.