Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Visualizing time-dependent data with distortion portals

Create useful visualization of data by linking their positions in time

Nathan Harrington, Programmer, IBM 
Nathan Harrington
Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies.

Summary:  Create an SDL-enabled application that allows you to create distortion portals in sequential image frames to explore the relationship of data sets through time.

Date:  10 Jun 2008
Level:  Introductory
Also available in:   Japanese

Activity:  5101 views
Comments:  

There are many ways to visualize data as snapshots, or sequential images showing trending and time progressions. Few options exist for exploring the relationships between data sets through time with an interactive interface. This article demonstrates code and techniques to create what I call "animated distortion portals" in the data to provide time-dependent visualizations of various parts of the image. Additionally, certain aspects of the code are presented that allow for effective visualization on slower-computing platforms without sacrificing usefulness. The code presented here will allow new insights into application-flow models and usage patterns by exploring various data sets and how they move through time.

What is SDL? What is blitting?

In this article, Simple DirectMedia Layer (SDL) provides easy cross-platform access to video modes and input devices. A certain level of familiarity is expected with SDL in general, as well as double-buffering, blitting and other 2-D image-processing techniques.

Wikipedia defines bit blit (bitblt, blitting, etc.) as a computer graphics operation in which several bitmap patterns are combined into one using a "raster operator." The name derives from the BitBLT machine instruction for the Xerox Alto computer, standing for "Bit Block Transfer."

Requirements

Hardware

Modern fast hardware is required for effective utilization of the visualization algorithms in this article. Although developed on an IBM® ThinkPad running at 1.8 GHz, a much-faster processor and associated hardware data channels are recommended for per-pixel correct visualizations. To retain usefulness, the algorithms presented allow for a "chunk-drawing" effect, which retains much of the usefulness of the temporal distortions while still being able to run quickly on slower hardware. Fast hardware is still required, and a high-powered video card is recommended.

Software

The code presented here is capable of running on a variety of operating systems. We used Linux®. To follow along, you need a modern Linux distribution that includes a development environment capable of compiling C programs. You need the SDL and SDL_image libraries. And you need mplayer to extract video frames into a format usable by the application we develop here (see Resources).


Developing a data set

Consult the demonstration video (see Resources) for some ideas on data sets for with temporal visualizations. Keep in mind that the choice to have a static or moving background image can create many issues with the clarity of the visualization. Static backgrounds are recommended for those starting with these types of visualizations, as their image progressions are easier to integrate with traditional frameworks.

Possible sources of data include video of natural phenomena, simulations, or user-built sequences of images. Consider the files in the included temporal.images/ directory, which consist of radar-like precipitation-reflectivity images created during a rainstorm.


Basic program structure

All the functionality of the demonstration program is contained in one file called temporalVisualizer.c. Follow along with this article to build your own copy or download the complete source code. Listing 1 shows the beginning of the program.


Listing 1. temporalVisualizer.c includes, defines
                
//temporalVisualizer.c - display temporal distortion portals in video
#include <stdio.h>
#include <math.h>
#include "SDL.h"
#include "SDL_image.h"

#define WIDTH  1024     // screen dimensions
#define HEIGHT 768
#define MAX_IMAGES 110  // number of frames to read from disk
#define PORTAL_DIA 50   // center distortion portal size

// use 1 for per pixel correctness, multiples of ten for faster 'chunking'
int chunkSize=1;

int pixels[WIDTH][HEIGHT];      // frame number at each pixel coordinate
int animateGrid[WIDTH][HEIGHT]; // record animation position at each pixel

SDL_Surface *screen;            // surface to display to user
SDL_Event event;                // keyboard, mouse event handling
SDL_Surface* immutableImage;    // base image to overwrite each frame
SDL_Surface* frame[MAX_IMAGES]; // array of frames to animate
SDL_Rect baseRect;              // immutable image clipping rect
SDL_Rect src;                   // current frame clipping rect

int mouseX = 0;
int mouseY = 0;
int mouseIsDown = 0;
int stopMainLoop = 0;

The variables defined will be used mostly in the various functions within the program. Consider the following function declarations.


Listing 2. Function definitions
                
void initScreen();
void loadImages();
void checkEvents();
void resetPixels();
void circleSetPixels(float, float, float);   
void lineSet(int, int, int, int, int);
void topCircle(float, float, int, float);
void bottomCircle(float, float, int, float);
void drawPixels();
void animateFrames();

These definitions illustrate the overall logic flow of the program. First, the screen is set up and images are loaded. Then, each pass of the main loop checks for events, resets the draw state, draws the circular "portal" windows, then animates their distortion back to the current time frame.


SDL configuration and loading images from disk

SDL provides a wide array of configuration options for configuring video modes and display surfaces. Listing 3 shows the display options set by the initScreen function.


Listing 3. initScreen function
                
void initScreen()
{
  const SDL_VideoInfo *info;
  Uint8  video_bpp;
  Uint32 videoflags;

  if ( SDL_Init(SDL_INIT_VIDEO) < 0 )
  {
    fprintf(stderr, "Problem initializing SDL: %s\n",SDL_GetError());
    exit(1);
  }
  atexit(SDL_Quit);

  info = SDL_GetVideoInfo();
  video_bpp = info->vfmt->BitsPerPixel;

  // store surfaces in hardware video memory where possible, and enable 
  // double buffering on the displayable surface
  videoflags = SDL_HWSURFACE | SDL_RESIZABLE | SDL_DOUBLEBUF;

  if ( (screen=SDL_SetVideoMode(WIDTH,HEIGHT,video_bpp,videoflags)) == NULL )
  {
    fprintf(stderr, "Video mode error: %s\n",SDL_GetError());
    exit(2);
  }
  SDL_WM_SetCaption("temporalVisualization","temporalVisualization");

}//initScreen

After defining variables and checking to see if the video interface is available, a display surface is initialized that uses hardware video memory. This will help reduce some of the speed issues associated with blitting multiple surfaces in a traditional 2-D context. Note that the SDL library has many options for video interfaces that are not covered here. If you have difficulty loading this basic configuration, consult the SDL documentation or one of the many available tutorials for assistance (see Resources).

With the display surface set up, the next step is to load the images from disk. Listing 4 shows the loadImages function.


Listing 4. loadImages function
                
void loadImages()
{
  int i=0;
  char filename[100];

  for( i=1; i<=MAX_IMAGES; i++ )
  {
    sprintf(filename,"temporal.images/%d.jpg",i);

    SDL_Surface *tempSurface;
    if( (tempSurface=IMG_Load(filename))==NULL )
    {
      fprintf(stderr, "Image load error for file %s\n",filename);
      exit(3);
    }

    frame[i] = SDL_ConvertSurface(tempSurface, screen->format, SDL_HWSURFACE);

    fprintf(stdout,"loaded image %d\n",i);
  }//for i

  sprintf(filename,"temporal.images/%d.jpg",1);
  immutableImage = SDL_ConvertSurface( IMG_Load(filename),
                    screen->format, SDL_HWSURFACE);

  baseRect.x = 1;
  baseRect.y = 1;
  baseRect.w = immutableImage->w;
  baseRect.h = immutableImage->h;

}//loadImages

Various image formats will use different color spaces from the display surface. After using the IMG_Load function to load each video frame into a temporary surface, the SDL_ConvertSurface function is used to make sure all the surfaces used will have the same color format. This pre-conversion is critical for maintaining color state through transitions of different frames as their portions are blitted to the buffer and, eventually, the screen.

Next up, after image loading and screen configuration, is tracking the application events.


Handling application events

The interface strategy is to have a "distortion portal" opened at the current mouse coordinates when the mouse button is down. In addition to video-interface libraries, SDL provides a framework to do much of the event-handling work, as shown below.


Listing 5. checkEvents function
                
void checkEvents()
{
  while ( SDL_PollEvent(&event) )
  {
    if( event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
    {
      stopMainLoop = 1;
    }//if escape pressed

    if( event.type == SDL_MOUSEBUTTONUP   ){ mouseIsDown = 0; }
    if( event.type == SDL_MOUSEBUTTONDOWN ){ mouseIsDown = 1; }

    if( event.type == SDL_MOUSEMOTION )
    {
      mouseX = event.motion.x;
      mouseY = event.motion.y;

      //snap mouse to grid if not per-pixel mode
      if( chunkSize > 1 )
      {
        mouseX = (int) mouseX/chunkSize;
        mouseX *= chunkSize;
        mouseY = (int) mouseY/chunkSize;
        mouseY *= chunkSize;
      }//if not trying high precision
    }//mouse motion

  }//while pollevent
}//checkEvents

Consult the "Speed considerations" section for the ideas behind transforming mouse coordinates into a grid. Before the image components are copied to the drawable surface, their locations are recorded to ensure that they are drawn in the correct order. Listing 6 shows the resetPixels function, which re-initializes the pixels' 2-D array before each drawing pass.


Listing 6. resetPixels function
                
void resetPixels()
{
  int x,y =0;
  for( x=0; x<=WIDTH; x+=chunkSize )
  {
    for( y=0; y<=HEIGHT; y+=chunkSize )
    {
      pixels[x][y]= 1;
    }//y
  }//x
}//resetPixels


Setting the pixels blitting frame

Drawing frames of video at various positions in time that are potentially overlapping requires special consideration in the pre-rendering steps. Additionally, distorting the frames correctly as they move toward the edge of the distortion portal is implemented as a multipass set of overlapping circles. To keep these image components drawn in the correct order, each pixel's — or chunk's (more on that below) — frame is determined prior to the actual blitting steps.

circleSetPixels, topCircle, and bottomCircle functions

For a given set of coordinates and an ending frame, the circleSetPixels function will draw a series of overlapping circles. Each circle will have the appropriate frame number to show a graduated distortion zone between the start and end frames. Listing 7 shows the circleSetPixels function.


Listing 7. circleSetPixels function
                
void circleSetPixels(float startx, float starty, float inFrame)
{
  float frameInc = (inFrame-1) / PORTAL_DIA;
  float i=0;
  float currFrame=1;

  for( i=(PORTAL_DIA*2); i>=PORTAL_DIA; i-- )
  {
    int xshift = (PORTAL_DIA-i) * 2;
    int yshift = PORTAL_DIA-i;

    topCircle(    startx+xshift, starty+yshift, i, currFrame );
    bottomCircle( startx+xshift, starty+yshift, i, currFrame );
    currFrame += frameInc;
  }//for i
}//circleSetPixels

Circles are drawn in top and bottom halves, from largest diameter to smallest. Each half-circle is offset by computed x and y coordinates in order to ensure that they are concentric. As each circle is drawn, the currFrame variable moves to the next frame between the base image (defined in the immutableImage surface) and the distortion-portal end frame. As the distortion-portal end frame moves back through time, the frames in between are spread across the available distortion zone. Thus, the frameInc variable is defined to provide the proper frame increment between 1 and (Portal Frame/2) in the blended distortion zone.

Listing 8 shows the functions topCircle and bottomCircle as called by circleSetPixels.


Listing 8. bottomCircle and topCircle functions
                
void bottomCircle(float startx, float starty, int r, float inFrame)
{
  int i = 1;
  float nexty = starty +(r*2);
  for( i=r; i>=1; i-- )
  {
    int length = (int) (sqrt( cos(0.5f * 3.14159 * (i-r)/r)) * r * 2);
    int ofs = (r*2) - (length/2);
    lineSet(startx+ofs, nexty-i, length, chunkSize, inFrame );
  }//i
}//bottomCircle

void topCircle(float startx, float starty, int r, float inFrame)
{
  int i = 1;
  for( i=1; i<=r; i++ )
  {
    int length = (int) (sqrt( cos(0.5f * 3.14159 * (i-r)/r)) * r * 2);
    int ofs = (r*2) - (length/2);
    lineSet(startx+ofs, starty+i, length, chunkSize, inFrame );
  }//i
}//topCircle

Each top- and bottom-circle half is divided into a collection of horizontal lines. The functions above determine the offset from the circle edge and length of each line. The lineSet function, as shown in Listing 9, traverses the entire line.


Listing 9. lineSet function
                
void lineSet(int inX, int inY, int inWidth, int inHeight, int frameNum )
{
  if( chunkSize > 1 )
  {
    inX = (int) inX/chunkSize;
    inX *= chunkSize;
    inY = (int) inY/chunkSize;
    inY *= chunkSize;
  }//snap to grid if not in per-pixel mode

  int x=0;
  for( x=inX; x<=(inX+inWidth); x+= chunkSize )
  {
    int y=0;
    for( y=inY; y<=(inY+inHeight); y+=chunkSize )
    {
      if( x < WIDTH && y < HEIGHT )
      {
        if( frameNum > pixels[x][y] ){ pixels[x][y] = frameNum; }
      }//if on screen

    }//for y
  }//for x
}//lineSet

After the now-familiar chunking code (see "Speed Considerations"), each pixel on each line is set to the current frame if it is more than the current frame number. This will prevent overlapping distortion windows from showing conflicting portions of video frames. Additionally, this approach to predetermining the frame numbers at each pixel (or chunk) drastically reduces the number of blitting operations required to draw the correct visual.


Rendering the frames at each pixel and animating the frames

Pre-processing the displayable visuals to select the correct pixels from the appropriate images contains much of the complexity of the program. Actually rendering the pixels to the drawable surface is simple once the precise pixel values have been determined. Listing 10 shows the drawPixels function.


Listing 10. drawPixels function
                
void drawPixels()
{
  int x,y = 0;
  for( x=0; x<=WIDTH; x+=chunkSize )
  {
    for( y=0; y<=HEIGHT; y+=chunkSize )
    {
      src.x=x;
      src.y=y;
      src.w = chunkSize;
      src.h = chunkSize;
      SDL_BlitSurface( frame[pixels[x][y]], &src, screen, &src);
    }//y
  }//x
}//drawPixels

Notice how in this function a rectangle of chunkSize by chunkSize pixels is copied from the appropriate frame to the drawable surface. At a chunkSize of 1, this will create a per-pixel correct rendering of the distortion algorithm, while each increase in chunkSize will drastically reduce the number of SDL_BlitSurface calls required. After copying the drawable visuals, the animation of each frame is controlled by the animateFrames function.


Listing 11. animateFrames function
                
void animateFrames()
{
  int x,y =0;
  for( x=0; x<=WIDTH; x+=chunkSize )
  {
    for( y=0; y<=HEIGHT; y+=chunkSize )
    {
      if( animateGrid[x][y] > 1 ){ animateGrid[x][y]--; }
    }//y
  }//x
}//animateFrames

This function moves each frame back in time from the last image in the sequence to the first. Consult the next section for the code block that sets each frame when the mouse is down.


Main logic loop and usage

Completing main()

Recall from Listing 2 the main logic flow of setting up the screen, loading images, processing events, setting pixel states, drawing, and animating the frames. Listing 12 shows the code in main() and its implementation of this order.


Listing 12. main() program code
                
int main(int argc, char *argv[])
{
  initScreen();
  loadImages();

  while ( stopMainLoop == 0 )
  {
    checkEvents();
    if( mouseIsDown ){ animateGrid[mouseX][mouseY] = MAX_IMAGES-1; }
    resetPixels();

    int x,y = 0;
    for( x=0; x<=WIDTH; x+=chunkSize )
    {
      for( y=0; y<=HEIGHT; y+=chunkSize )
      {
        if( animateGrid[x][y] > 1 ){ circleSetPixels(x,y, animateGrid[x][y] ); }
      }//y
    }//x

    SDL_BlitSurface(immutableImage, NULL, screen, &baseRect);
    drawPixels();
    SDL_Flip(screen);

    animateFrames();

  }// while stopMainLoop == 0

  SDL_Quit();
  return(0);
}//main

Once the screen is set up and the images are loaded, each pass of the main loop runs checkEvents and resetPixels. In between, the last frame state is set for the coordinates of the current mouse position if the mouse button is pressed. The next loop section controls setting pixels coordinates only if the frame at that position is greater than the start frame. SDL_BlitSurface clears the screen with the base image (immutableImage), and the drawPixels function call fills in the drawable surface with the appropriate image components. Finally, SDL_Flip transfers the back buffer to the screen buffer, and animateFrames updates the smooth progression of the video frames.

Usage

Included in the article source code distribution is a temporal.images directory with NOAA weather service radar images suitable for experimentation. Populate the directory with images extracted from your video of choice, or simply use the existing images with the command gcc `sdl-config --cflags --libs` -lSDL_image temporalVisualizer.c && ./a.out. Note that you'll need to have the appropriate SDL and SDL_image libraries installed for the compilation command above to work. Try modifying the chunkSize parameter to 10 at line 13 and recompile to drastically increase the speed of the visualizations.


Speed considerations

Adapting the code to support useful visualizations on computing platforms of various speeds is controlled through the use of the chunkSize variable. In the checkEvents function, the stored mouse coordinates are forced into a grid of chunkSize by chunkSize squares. The lines shown below and the similar entries in the lineSet function ensure that the drawable pixels all start and end on the correct grid. Each increase in the chunkSize variable drastically reduces the number of comparisons, as well as surface blits when drawing the portals.


Listing 13. Ensuring that the drawable pixels all start and end on the correct grid
                
        mouseX = (int) mouseX / chunkSize;
        mouseX *= chunkSize;
        mouseY = (int) mouseY / chunkSize;
        mouseY *= chunkSize;


Conclusion and further examples

With the code above and your completed temporalVisualizer.c program, you now have a functional program for visualizing frames from video or other sequential data sets in a new and useful way.

The temporalVisualizer.c program was designed to be a framework for further enhancements. As such, consider making the frames play backward or forward in time depending on which button was clicked. Add the option to mouse over a portal to have it freeze its current position. Send the frames further back in time the longer the mouse button is pressed.

Use the code here and your own ideas to create new visualizations for understanding and exploring your data through time.



Download

DescriptionNameSizeDownload method
Sample codeos-timedep.temporalVisualizer_0.1.zip3224KB HTTP

Information about download methods


Resources

Learn

  • View the author's demonstration video of time-dependent visualization at YouTube.com.

  • Read about the Simple DirectMedia Layer project, a cross-platform multimedia library.

  • Read about SDL_image, an image-loading library used with the SDL library.

  • There are many excellent tutorials on using SDL. Three are available at the SDL Web site, Lazy Foo' Productions, and Jari Komppa's tutorials.

  • MPlayer is a video player that supports the playback of a wide array of video file formats. Download MPlayer for Linux, Windows, and OS X. It's also available package form for some Linux distributions.

  • This article was inspired by the Khronos Projector.

  • To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.

  • Stay current with developerWorks' Technical events and webcasts.

  • Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.

  • Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.

  • Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.

Get products and technologies

  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

  • Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

About the author

Nathan Harrington

Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=312345
ArticleTitle=Visualizing time-dependent data with distortion portals
publish-date=06102008
author1-email=harrington.nathan@gmail.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers