Parola A to Z – Text Animation

azparola_animationThe key function of the Parola library is to display text using different animations. These animations are built around a core supporting framework and largely follow the same patterns. This article explores how Parola animations code is constructed so that advanced users of the library have enough information to be able to write (and contribute!) their own new animations.

What is Animation?

Animation is a series of still images (frames) that follow each other which, through a persistence of vision effect, we ‘see’ as moving in a continuous and smooth manner. The frame rate (expressed in frames per second or FPS) is the frequency at which consecutive frames are shown in an animated display.

For the Parola library, an animation is broken up into 3 parts

  1. A text effect while entering the display.
  2. A pause to allow reading the message.
  3. A text effect when leaving the display.

The frame rate is governed by the speed setting, which determines how often the animation code is invoked, and entry and exit effects can be different.

A crucial part of the animation is the pause in the middle. The text position at this point is identical for all animations, based on a combination of the text displayed and the justification (left, center or right) selected. This rule allows the selection of different entry and exit effects, as the starting point for the exit sequence and the ending point for the entry sequence are the same in all text effects. Specifically, the common location is where the commonPrint() method displays the text.

Creating a Text Effect

An effect can be created in one of 2 ways. Which is used depends on the coder and the complexity of the effect:

  • Rewind/Play: Using this method the text is initially placed in the display buffer using the commonPrint() method. For the entry effect the text is then ‘rewound’ and, conversely, for an exit effect it is ‘played’, to where it would be in the current frame. Most animations are coded using this straightforward algorithm.
  • Frame-by-Frame: Using this method the frames are incrementally built up column by column, using the font management functions getFirstChar() and getNextChar() described in this previous blog. Only a few text effects use this method as the code can be much more complex. SLICE and SCROLLING are examples of frame-by-frame animation.

Each text effect is implemented as a standalone private method containing a finite state machine (FSM) in 2 parts. One part implements the text move into the display (parameter bIn is true) and the other the text move out of the display (bIn is false). Because the entry and exit effects can be different for a display cycle, the code cannot assume that either separate part was called.

The FSM can use a number of defined states:

  • INITIALISE: A one time initialization of for text entry effect. The exit effect can be initialized when the state is PAUSE and bIn is false.
  • GET_FIRST_CHAR: Get and display the first character. Any character related initialization (eg, reset displayed column count) should be done here as well.
  • GET_NEXT_CHAR: Any character other than the first. In many animations only one of GET_FIRST_CHAR and GET_NEXT_CHAR is needed.
  • PUT_CHAR: In the middle of placing a character on the display. Typically this is displaying columns of the character on the display in frame-by-frame animations.
  • PUT_FILLER: The method should be placing filler (blank) columns into the display. This is often not implemented by the text effect.
  • PAUSE: Set by the text effect when the text has reached the ‘pause’ location and the first phase of the animation is completed. The library will then delay the exit effect by the specified pause time.
  • END: set by the FSM when the exit effect has completed and the display cycle has completed.

The general flow within the FSM  is the entry phase starting with _fsmState set to INITIALISE and ending when the state is set to PAUSE within the effect method. The exit phase starts from the PAUSE state and ends when the state is set to END by the method. Aside from the INITIALISE state, which is set by displayReset(), all other states are controlled by the method itself. Additionally, all states except PAUSE and END only have local meaning.

Time between frames and the pause between entry and exit are managed by the main library code.

Global variables within the class are used to track the status for the current text effect to avoid static declarations (and wasted memory) in the text effect.

  • _fsmState holds the state set during the last invocation.
  • _limitLeft and _limitRight are the text column limits for the ‘pause’ position of the text.
  • _nextPos, _posOffset, _startPos, and _endPos are available for the text effect to maintain counters and status between calls.

Integrating an Effect into Library

Integrating a new text effect is relatively straightforward:

  1. Choose a name for the effect. The convention is “effect[Name]” for the method located in a source file called “MD_Parola_[Name]”.
  2. Add the identifier for the text effect to the textEffect_t enumerated type.
  3. Add the function prototype for the new effect to the MD_PZone class definition in the MD_Parola.h file.
  4. Modify the zoneAnimate() method in MD_PZone.cpp to invoke the new method from the scheduler.
  5. Clone an existing method and modify it according to what the effect needs to do. This is the hardest part!

The Simplest Example – the PRINT effect

The simplest text effect is PRINT, shown below. The entry is the text being printed and the exit is the display cleared.

This example shows the basic layout of the in and out sections, which is common to every other text effect. Note _fsmState is also set to PAUSE and END to signal the end of each animation phase.

void MD_PZone::effectPrint(bool bIn)
// Just print the message in the justification selected
{
  if (bIn) // incoming
  {
    commonPrint();
    _fsmState = PAUSE;
  }
  else //exiting
  {
    zoneClear();
    _fsmState = END;
  }
}

More Complexity – the FADE Effect

For the FADE effect, entry is a display intensity increase from 0 to the current intensity setting. The exit text effect is the same thing in reverse.

This example implements a strategy similar to Rewind/Play without the added complexity of moving or masking the text bitmap whilst still using more FSM cases.

INITIALISE is used to set the class variable to the start and end intensity of the fade sequence before clearing the display, as we start from no test being displayed. The next call (with _fsmState == GET_FIRST_CHAR), the intensity is increased and the text printed using commonPrint(). Subsequent calls (_fsmState == PUT_CHAR) only change the intensity as the text is already displayed. The FSM sets the PAUSE state when it has reached the last intensity setting.

void MD_PZone::effectFade(bool bIn)
// Fade the display in and out.
// If the overall intensity is changed while the animation is running, the
// intensity at the start of the animation will be restored at the end, overriding
// any user code changes.
{
  if (bIn) // incoming
  {
    switch (_fsmState)
    {
    case INITIALISE:
      _nextPos = 0;
      _endPos = getIntensity();
      zoneClear();
      _fsmState = GET_FIRST_CHAR;
      break;

    case GET_FIRST_CHAR:
      setIntensity(_nextPos++);
      commonPrint();
      _fsmState = PUT_CHAR;
      break;

    case PUT_CHAR:
      // check if we have finished
      if (_nextPos > _endPos)
        _fsmState = PAUSE;
      else
        setIntensity(_nextPos++);
      break;

    default:
      _fsmState = PAUSE;
    }
  }
  else // exiting
  {
    switch (_fsmState)
    {
    case PAUSE:
      _nextPos = _endPos = getIntensity();
      setIntensity(_nextPos);
      commonPrint();
      _fsmState = PUT_CHAR;
      break;

    case PUT_CHAR:
      // check if we have finished
      if (_nextPos < 0)
      {
        setIntensity(_endPos); // set to original conditions
        zoneClear(); // display nothing - we are currently at 0
        _fsmState = END;
      }
      else
        setIntensity(_nextPos--);
      break;

    default:
      _fsmState = END;
    }
  }
}

Full Complexity – SCAN_HORIZ Effect

The SCAN_HORIZ text entry and exit effects display the text as a single column scanning end to end. At fast frame rates, persistence of vision creates the impression of a vertical letterbox slit moving over the text.

This effect is of the Rewind/Play type. INITIALISE sets up the global variable through a helper function that initializes:

 _startPos = _nextPos = (_textAlignment == PA_RIGHT ? _limitRight : _limitLeft);
 _endPos = (_textAlignment == PA_RIGHT ? _limitLeft : _limitRight);
 _posOffset = (_textAlignment == PA_RIGHT ? 1 : -1);

Each subsequent call to the FSM  loads entire text into the display buffer using commonPrint() and all the columns except the currently scanned column are then removed PAUSE is reached when the last text column is scanned.

The exit effect is identical except the display is left with no text at the end.

void MD_PZone::effectHScan(bool bIn)
// Scan the message over with a new one
// Print up the whole message and then remove the parts we
// don't need in order to do the animation.
{
  if (bIn) // incoming
  {
    switch (_fsmState)
    {
    case INITIALISE:
      setInitialEffectConditions();
      _fsmState = PUT_CHAR;
      // fall through to next state

    case PUT_CHAR:
      commonPrint();
      // check if we have finished
      if (_nextPos == _endPos)
      {
        _fsmState = PAUSE;
        break;
      }

      // blank out the part of the display we don't need
      for (uint8_t i=_startPos; i != _endPos+_posOffset; i += _posOffset)
      {
        if (i != _nextPos)
        _MX->setColumn(i, EMPTY_BAR);
      }
      _nextPos += _posOffset; // for the next time around
      break;

    default:
      _fsmState = PAUSE;
    }
  }
  else // exiting
  {
    switch (_fsmState)
    {
    case PAUSE:
      setInitialEffectConditions();
      _fsmState = PUT_CHAR;
      // fall through to next state

    case PUT_CHAR:
      commonPrint();

      // blank out the part of the display we don't need
      for (uint8_t i=_startPos; i != _endPos+_posOffset; i += _posOffset)
      {
        if (i != _nextPos)
          _MX->setColumn(i, EMPTY_BAR);
      }

      // check if we have finished
      if (_nextPos == _endPos) 
        _fsmState = END;
      _nextPos += _posOffset; // for the next time around
      break;

    default:
      _fsmState = END;
      break;
    }
  }
}

Frame-by-frame animations are considerably more complex that the Rewind/Play type, because many more things need to be tracked during the animation, but in principle they work the same way.

Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s