emWin FadeImage

From SEGGER Wiki
Revision as of 14:12, 9 July 2020 by Sven (talk | contribs) (Created page with "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 do...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, 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 will initialize emWin, set a callback function for the desktop window (WM_HKWIN) and create a window which will take care of the animation as well as setting up and drawing the memroy 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 and we would experience artefacts from the foreground window.

/*********************************************************************
*
*       _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 of course displays the memory device/image.

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

The messages the this callback function reacts on are the following:

  • WM_CREATE
  • WM_PAINT
  • WM_DELETE

WM_CREATE

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

The memory device needs to have a color depths of 32bpp and the same size as the window. Once created the device gets clear with transparency and the image gets drawn into it. Important is that the memory device handle is static, so it stays valid every time we get into 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 and create, configure and start the animation. The functions _AnimFade() and _OnDelete() are getting explained further down 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 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);

WM_PAINT

On this message the memory device gets 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 getting drawn relative to a window we have to get the desktop coordinates of the window and the memory device to this coordinates.

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

WM_DELETE

When the window gets deleted it is important to delete the memory device as well. Otherwise the application might run out of memory quite quick 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 gets calculated. Once the first item ends (pInfo->State == GUI_ANIM_END) the direction gets changed and the next item will reduce the calculated alpha value.

After the 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 complete 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);
}

Note

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 not 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 gets 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.

Downloads

The source code of this example can be downloaded here.