The Sunday Blog: Modern GUI design

Fade in, fade out and more

In the past, we have already seen some of the astonishing graphic capabilities of the Nextion Intelligent Series HMI displays, for example on May, 10th, 2021. What we did not yet talk about in detail, was transparency and (with the help of a few lines of code) fading effects. So, let’s have a closer look onto this! There is (as always) an example project and the code explained, so that you might then use these techniques in your own developments.

What is transparency in this context?

Transparency is – in short words – the opposite of opaqueness. Something, for example a screen component, is called 100% opaque if it fully hides everything behind it, for example a screen background color or picture. It’s called 100% transparent if it’s not visible at all (but it is there and might even react on touch events) and the background content shines at 100% through. In-between those two extreme cases, all degrees of partial transparency are possible, a transparency of 60% for example will let 40% of the background shine through.

To quantify the degree of transparency, one might instead of using percentages also use a simple integer value to simplify calculations. On many graphic systems, a so-called Alpha value is used. If alpha (the .aph attribute) is 0, the component is fully transparent. At the other end, alpha=127 gives a fully opaque rendering of it. While these both extremes are still easy to handle for an embedded processor, intermediate values require some calculations. That’s why we find the .aph attribute only on the Intelligent or P series HMIs.

To draw a pixel with partial transparency, the processor needs not only to know its R,G, and B values, but also the R, G, and B values of the corresponding background pixel. Then, it’s a series of mathematical operations, calculating a so called weighted sum of the separated foreground and background color channels for each pixel:

R_drawn = (alpha * R_foreground + (127 – alpha) * R_background)/127
G_drawn = (alpha * G_foreground + (127 – alpha) * G_background)/127
B_drawn = (alpha * B_foreground + (127 – alpha) * B_background)/127

You see, it’s much more complex than simply writing the same pixel’s RGB values directly to the LCD screen buffer!

And fading ?

When the transparency (or the alpha value) is not constant but changes, it’s called fading. There is no function for that in the Nextion firmware. So, we’ll have to write a few lines of code to enable a smooth transition between alpha=0 and alpha=127 (fade in) or vice versa (fade out).

The example project

In our example project, we have as you can see on the picture above, 3 buttons: “Fade in”, “Fade out”, and “Continuous”. When you start the project, the huge red text field saying “Fading…” is not visible, its alpha value is 0 by default. Clicking on “Fade in” will make it slowly appear. The speed of appearance can be set with the slider below the text component. The function of “Fade out” is similar, but the text field will more or less slowly disappear. “Continuous” will, as the name says, continuously fade the text in and out until you press one of the other buttons.

How does it work

In the program.s file, we define a few variables first, to control our animation: “con” is for the continuous effect, it will be set to 1 by the “Continuous” button and to 0 by both others, so that the action of the latter remains one-shot, which means that the animation will stop when full opaqueness or full transparency is reached. “dir” is to control if we increment (1) the alpha value or if we decrement (-1) it. “cid” is the object id of the component to be animated. Using indirect addressing (b[cid].aph) in the animation code allows to easily swap the text field against any other component without having to modify all occurrences of t0.aph. Finally, we declare sys0 as internal buffer variable for our animation routine.

int con=0 //0=once or 1=continuous
int dir=1 //1=fade in or -1=fade out
int cid=1 //component id to fade
int sys0  //temporary variable for internal purpose
page 0

All three buttons have similar functionality, that’s why I decided to pack their touch event code into a TouchCap component. “Fade in” sets dir=1 (fade in), con=0 (one shot), and enables the animation timer. “Fade out” sets dir=-1 (fade out), con=0 (one shot), and enables the animation timer. “Continuous” does not affect the dir value, it continues in the previous direction and will, thanks to con=1 this time, revert automatically the direction when full transparency or opaqueness is reached.

if(tc0.val==3)
{
  con=0 //once
  dir=1 //fade in
  tm0.en=1 //start timer
}else if(tc0.val==4)
{
  con=0 //once
  dir=-1 //fade out
  tm0.en=1 //start timer
}else if(tc0.val==5)
{
  con=1 //continuous
  //dir remains unchanged
  tm0.en=1 //start timer
}

The core animation, controlled by these variables and by the animation speed slider happens inside a 50ms timer. First, the next alpha step is calculated by multiplying dir with h0.val put into sys0 with the current alpha value b[cid].aph added. Then, different cases have to be distinguished. If sys0 which holds the theoretical next alpha value is between 0 and 127, all the if() clauses aren’t executed and the code jumps directly to the last line where b[cid].aph gets its new value, sys0.

But there are cases where the calculated new alpha value in sys0 would exceed 127 (imagine it was 125 and the slider is set to 5, so that the next step would be 130) and an error condition would happen. To prevent this, we check if sys0 is > 127 and limit or saturate it to exactly 127. Then, since we are at the maximum, we check if continuous mode is enabled and revert the animation direction, or if not, we simply disable the timer because the animation goal is reached.

A similar code block is executed for preventing alpha to go below 0. The animation continuity or stop check is then the same.

sys0=h0.val*dir+b[cid].aph
if(sys0>127)
{
  sys0=127
  if(con>0)
  {
    dir*=-1 //reverse
  }else
  {
    tm0.en=0 //or stop
  }
}else if(sys0<0) 
{ 
  sys0=0 
  if(con>0)
  {
    dir*=-1 //reverse
  }else
  {
    tm0.en=0 //or stop
  }
}
b[cid].aph=sys0

And that’s it. No black magic, no rocket science, just a few lines of code…

The HMI file for this example project is here: drawing_with_transparency.HMI

Even if you don’t have an 4.3″ Intelligent Series HMI display at hands, you may run everything in the Debugger/Simulator and find your inspiration there.

Questions, comments, suggestions, bugs? Open a discussion in our forums!

Happy Nextioning!