emWin FadeImage

From SEGGER Wiki
Jump to navigation Jump to search

The following tutorial will demonstrate how to fade in and out an image by using the Window Manager as well as the animation and memory device modules of emWin.

What it does

On creation of a window an image gets faded in and out again. This can be useful if the user intends to show some sort of an intro. With help of a memory device an image gets drawn on the screen with an alpha value set. The alpha value will be calculated by an animation.

The final example can be downloaded here.

Tutorial

Getting started

In MainTask(), the entry point of most emWin applications, we initialize emWin, set a callback function for the desktop window (WM_HBKWIN) and create a window. This window will take care of the animation, as well as set up and draw the memory device.

The callback function of WM_HBKWIN will do nothing else than clearing the desktop with a color. Otherwise emWin wouldn't "know" how to clear the desktop, therefore artefacts from the foreground window would appear.

/*********************************************************************
*
*       _cbBk
*/
static void _cbBk(WM_MESSAGE * pMsg) {
  switch (pMsg->MsgId) {
  case WM_PAINT:
    GUI_SetBkColor(GUI_LIGHTGRAY);
    GUI_Clear();
    break;
  default:
    WM_DefaultProc(pMsg);
    break;
  }
}

/*********************************************************************
*
*       Public code
*
**********************************************************************
*/
/*********************************************************************
*
*       MainTask
*/
void MainTask(void) {
  int xPos;
  int yPos;

  GUI_Init();
  //
  // Enable multi buffering to avoid flickering effects when fading in and out.
  //
  WM_MULTIBUF_Enable(1);
  //
  // Set a callback function for the desktop window.
  //
  WM_SetCallback(WM_HBKWIN, _cbBk);
  //
  // Create a window centered on the display with the size of the logo we want to animate.
  //
  xPos = (LCD_GetXSize() - bmLogo_110x55.XSize) / 2;
  yPos = (LCD_GetYSize() - bmLogo_110x55.YSize) / 2;
  WM_CreateWindowAsChild(xPos, yPos, bmLogo_110x55.XSize, bmLogo_110x55.YSize, WM_HBKWIN, WM_CF_SHOW | WM_CF_HASTRANS, _cbWinFadeImage, 0);
  while (1) {
    GUI_Delay(100);
  }
}

Custom animation data

To make use of the animation module we recommend to set up a custom animation data structure. This structure holds information about the current animation process and will make it easier to access data from within the animation and the window.

This structure gets defined by the user and should contain anything required for the animation. For further information on animations you might also want to refer to Creating animations.

In this case the structure looks like below:

typedef struct {
  WM_HWIN hWin;
  int     Alpha;
  int     MaxAlpha;
  int     Dir;
} ANIM_DATA;
  • hWin: This handle is used to invalidate the window which should draw the image.
  • Alpha: The alpha value calculated by the animation and used for blending the image.
  • MaxAlpha: The maximum value 'Alpha' can have.
  • Dir: The direction of the animation (in or out).

Window callback function

Next step is to create a callback function for the window. This callback function manages the memory device, starts the animation and displays the memory device/ image.

The callback function reacts on three messages, any other message gets handled by the default callback function WM_DefaultProc().

The messages this callback function reacts on are the following:

  • WM_CREATE
  • WM_PAINT
  • WM_DELETE

WM_CREATE

This message gets send to the window callback when the window is created. Here, a memory device is set up and an animation is started.

The memory device needs to have a color depths of 32bpp and the same size as the window. Once created, the device gets cleared with transparency and the image is drawn into the device. Important is that the memory device handle is static, so it stays valid every time we land in this callback function.

    xPos  = 0;
    yPos  = 0;
    xSize = WM_GetWindowSizeX(pMsg->hWin);
    ySize = WM_GetWindowSizeY(pMsg->hWin);
    //
    // Create and fill the memory device.
    //
    hMemLogo = GUI_MEMDEV_CreateFixed32(xPos, yPos, xSize, ySize);
    GUI_MEMDEV_Select(hMemLogo);
    GUI_SetBkColor(GUI_TRANSPARENT);
    GUI_Clear();
    GUI_DrawBitmap(&bmLogo_110x55, xPos, yPos);
    GUI_MEMDEV_Select(0);

After the memory device is finished, the animation gets set up and started.

At first the custom animation structure (see above) gets initialized.

    AnimData.hWin     = pMsg->hWin;            // Set hWin to "this" window since we want to invalidate it.
    AnimData.Alpha    = GUI_MAKE_TRANS(0xFF);  // Conversion macro inverts alpha depending on GUI_USE_ARGB, expects alpha value 0x00 opaque and 0xFF transparent.
    AnimData.MaxAlpha = GUI_MAKE_TRANS(0x00);  // Maximum should be fully opaque. This can be any other value, too. Again this macro expects the alpha value as mentioned above.
    AnimData.Dir      = 0;                     // Dir indicates fade in (0) or out (1).

Now we can use this structure to create, configure and start the animation. The functions _AnimFade() and _OnDelete() will be explained later on in this tutorial.

    hAnim = GUI_ANIM_Create(FADING_PERIOD, 25, (void *)&AnimData, NULL);
    //
    // Add two animation items. The first one starts at 0 and ends at FADING_PERIOD / 2 - 500.
    // The second one starts a second after the first one and ends after 6 seconds.
    //
    GUI_ANIM_AddItem(hAnim, 0,                       FADING_PERIOD / 2 - 500, ANIM_LINEAR, (void *)&AnimData, _AnimFade);
    GUI_ANIM_AddItem(hAnim, FADING_PERIOD / 2 + 500, FADING_PERIOD,           ANIM_LINEAR, (void *)&AnimData, _AnimFade);
    //
    // Start animation, gets deleted after 1 cycle and calls _OnDelete().
    //
    GUI_ANIM_StartEx(hAnim, 1, _OnDeleteFade);

WM_PAINT

On this message the memory device is drawn into the window with the alpha value calculated by the animation. To draw a memory device with alpha blending the function GUI_MEMDEV_WriteAlphaAt() can be used.

Since memory devices are not drawn relative to windows, we have to use the desktop coordinates of the window and draw the memory device at these coordinates.

    xPos  = WM_GetWindowOrgX(pMsg->hWin);
    yPos  = WM_GetWindowOrgY(pMsg->hWin);
    GUI_MEMDEV_WriteAlphaAt(hMemLogo, AnimData.Alpha, xPos, yPos);

WM_DELETE

When the window is deleted, it is important to delete the memory device as well. Otherwise the application might run out of memory quite quickly depending on how often a window with this callback gets created.

GUI_MEMDEV_Delete(hMemLogo);

Complete callback function

/*********************************************************************
*
*       _cbWinFadeImage
*/
static void _cbWinFadeImage(WM_MESSAGE * pMsg) {
  static GUI_MEMDEV_Handle hMemLogo;
  int                      xPos;
  int                      yPos;
  int                      xSize;
  int                      ySize;
  static ANIM_DATA         AnimData;
  static GUI_ANIM_HANDLE   hAnim;

  switch (pMsg->MsgId) {
  case WM_CREATE:
    xPos  = 0;
    yPos  = 0;
    xSize = WM_GetWindowSizeX(pMsg->hWin);
    ySize = WM_GetWindowSizeY(pMsg->hWin);
    //
    // Create and fill the memory device.
    //
    hMemLogo = GUI_MEMDEV_CreateFixed32(xPos, yPos, xSize, ySize);
    GUI_MEMDEV_Select(hMemLogo);
    GUI_SetBkColor(GUI_TRANSPARENT);
    GUI_Clear();
    GUI_DrawBitmap(&bmLogo_110x55, xPos, yPos);
    GUI_MEMDEV_Select(0);
    //
    // Set up animation data
    //
    AnimData.hWin     = pMsg->hWin;            // Set hWin to "this" window since we want to invalidate it.
    AnimData.Alpha    = GUI_MAKE_TRANS(0xFF);  // Conversion macro inverts alpha depending on GUI_USE_ARGB, expects alpha value 0x00 opaque and 0xFF transparent.
    AnimData.MaxAlpha = GUI_MAKE_TRANS(0x00);  // Maximum should be fully opaque. This can be any other value, too. Again this macro expects the alpha value as mentioned above.
    AnimData.Dir      = 0;                     // Dir indicates fade in (0) or out (1).
    //
    // Create Animation
    //
    hAnim = GUI_ANIM_Create(FADING_PERIOD, 25, (void *)&AnimData, NULL);
    //
    // Add two animation items. The first one starts at 0 and ends at FADING_PERIOD / 2 - 500.
    // The second one start a second after the first one and ends after 6 seconds.
    //
    GUI_ANIM_AddItem(hAnim, 0,                       FADING_PERIOD / 2 - 500, ANIM_LINEAR, (void *)&AnimData, _AnimFade);
    GUI_ANIM_AddItem(hAnim, FADING_PERIOD / 2 + 500, FADING_PERIOD,           ANIM_LINEAR, (void *)&AnimData, _AnimFade);
    //
    // Start animation, gets deleted after 1 cycle and calls _OnDelete().
    //
    GUI_ANIM_StartEx(hAnim, 1, _OnDeleteFade);
    break;
  case WM_PAINT:
    //
    // Write the memory device with the calculated alpha value at the window position.
    //
    xPos  = WM_GetWindowOrgX(pMsg->hWin);
    yPos  = WM_GetWindowOrgY(pMsg->hWin);
    GUI_MEMDEV_WriteAlphaAt(hMemLogo, AnimData.Alpha, xPos, yPos);
    break;
  case WM_DELETE:
    //
    // Don't forget to delete the memory device.
    //
    GUI_MEMDEV_Delete(hMemLogo);
    break;
  default:
    WM_DefaultProc(pMsg);
    break;
  }
}

Defining the animation behavior

In the callback above an animation item with a function pointer to _AnimFade() as parameter gets added to the animation. Also, a pointer to _OnDelete() gets used when starting the animation.

_AnimFade() defines how the alpha value is calculated. Once the first item ends (pInfo->State == GUI_ANIM_END), the direction will be changed and the next item will reduce the calculated alpha value.

After the alpha value has been calculated, the window (pData->hWin) gets invalidated to trigger a redraw. Within this drawing operation the new alpha value will be used.

/*********************************************************************
*
*       _AnimFade
*/
static void _AnimFade(GUI_ANIM_INFO * pInfo, void * p) {
  ANIM_DATA * pData;

  pData = (ANIM_DATA *)p;
  //
  // Depending on "Dir" we calculate the alpha value differently.
  //
  if (pData->Dir == 0) {
    pData->Alpha = (pData->MaxAlpha * pInfo->Pos) / GUI_ANIM_RANGE;
  } else {
    pData->Alpha = pData->MaxAlpha - (pData->MaxAlpha * pInfo->Pos) / GUI_ANIM_RANGE;
  }
  if (pInfo->State == GUI_ANIM_END) {
    pData->Dir ^= 1;  // Next animation will be in the other direction (fade out).
  }
  WM_InvalidateWindow(pData->hWin);
}

The function _OnDelete() gets called after the full animation has ended. Within this function we use the handle in the animation structure to delete the window.

/*********************************************************************
*
*       _OnDeleteFade
*/
static void _OnDeleteFade(void * p) {
  ANIM_DATA * pData;
  //
  // "p" points to the third parameter of GUI_ANIM_Create().
  //
  pData = (ANIM_DATA *)p;
  WM_DeleteWindow(pData->hWin);
}

Trivia

With the Window Manager almost anything can be done within the callback functions of windows. As you can see in this tutorial, it is only necessary to call WM_CreateWindowAsChild() to create an animated image. Anything required to fade in and out the image is done within the callback function. This makes it easier to structure your GUI application having one window for a specific task and it is sufficient to create it.

Animations are quite powerful. They can be used for anything animated, they're not specific to only alpha values. It is also possible to add items which will be executed simultaneously or overlapping each other.

Since memory devices can be drawn just as any other bitmap (also with hardware acceleration), they can be used as a buffer to pre-render and/or modify image data. For example, a JPEG drawn into a memory device once and instead of drawing (and decoding) the JPEG again and again, only the memory device gets displayed. This should be way faster than drawing the JPEG. A sample on this can be found here.

Downloads

The source code of this example can be downloaded here.