Advanced C# splash screen

I once posted about how to make a splash screen with C#; yet it’s still a rectangle. While it’s true that you can replace my picture with something that have curvy edges, you’ll have aliased edges i.e. crooked and crude pixels around the edges.

Example of an aliased picture, the edges are not smooth and you can't see through the semitransparent parts

There’s a solution: alpha-blend the picture and you can see through the form; pixels with alpha value of neither 0 or 255 will be mixed appropriately with the background.

Alpha-blended picture. The edges are smoother and you can see through the form

To achieve this, you have to write your custom painting function with windows API, specifically this one

[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);

Which can be a daunting task. Luckily we have someone from Visual C# kicks did the hard work and released the source for public use! But that’s not the end of my story just yet. I wanted the splash screen to fades in and out. The timing part was easy, just a timer and several more lines of code and it’s done.

        enum Phases {
            FadeIn,
            Hold,
            FadeOut
        };
        Phases CurrentPhase = Phases.FadeIn;
        int FadeInStep = 0;
        const int FadeInLast = 20;
        int HoldStep = 0;
        const int HoldLast = 80;
        int FadeOutStep = 20;
        const int FadeOutLast = 0;
        const int OpacityMultiplier = 5;

        private void timerFade_Tick(object sender, EventArgs e)
        {
            if (CurrentPhase == Phases.FadeIn)
            {
                if (FadeInStep < FadeInLast)
                {
                    FadeInStep++;
                    this.Opacity = (OpacityMultiplier * FadeInStep) / 100.0;
                }
                else
                    CurrentPhase = Phases.Hold;
            }
            else if (CurrentPhase == Phases.Hold)
            {
                if (HoldStep < HoldLast)
                {
                    HoldStep++;
                    this.UpdateFormDisplay(this.BackgroundImage);
                }
                else
                    CurrentPhase = Phases.FadeOut;
            }
            else if (CurrentPhase == Phases.FadeOut)
            {
                {
                    FadeOutStep--;
                    this.Opacity = (OpacityMultiplier * FadeOutStep) / 100.0;
                }
                else
                    this.Close();
            }

I have even altered the custom paint code to utilize the form’s transparency factor

                //Set up blending options
                API.BLENDFUNCTION blend = new API.BLENDFUNCTION();
                blend.BlendOp = API.AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = (byte)(255 * this.Opacity);
                blend.AlphaFormat = API.AC_SRC_ALPHA;

                API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, this.BackColor.ToArgb(), ref blend, API.ULW_ALPHA);

But that didn’t work to my expectation! The form is fading in and out gradually but the alpha blending effect is lost.

Fugly!

After fiddling around, I figured out that the custom paint code somehow didn’t override all the necessary painting functions and the setter of this.Opacity call those functions every time it’s changed. Searching around the list of functions can be overridden I found nothing interesting

That function doesn't help...

So I created a new variable and leave the form’s default Opacity alone. After all, if I have adjusted transparency myself in the custom paint function, who needs .NET’s alpha-blend-incapable render? The code above becomes:

        double MyOpacity = 0;

            if (CurrentPhase == Phases.FadeIn)
            {
                if (FadeInStep < FadeInLast)
                {
                    FadeInStep++;
                    MyOpacity = (OpacityMultiplier * FadeInStep) / 100.0;
                    this.UpdateFormDisplay(this.BackgroundImage);
                }
                else
                    CurrentPhase = Phases.Hold;
            }
            else if (CurrentPhase == Phases.Hold)
            {
                if (HoldStep < HoldLast)                 {                     HoldStep++;                     this.UpdateFormDisplay(this.BackgroundImage);                 }                 else                     CurrentPhase = Phases.FadeOut;             }             else if (CurrentPhase == Phases.FadeOut)             {                 if (FadeOutStep > FadeOutLast)
                {
                    FadeOutStep--;
                    MyOpacity = (OpacityMultiplier * FadeOutStep) / 100.0;
                    this.UpdateFormDisplay(this.BackgroundImage);
                }
                else
                    this.Close();
            }
        }
                //Set up blending options
                API.BLENDFUNCTION blend = new API.BLENDFUNCTION();
                blend.BlendOp = API.AC_SRC_OVER;
                blend.BlendFlags = 0;
                blend.SourceConstantAlpha = (byte)(255 * MyOpacity);
                blend.AlphaFormat = API.AC_SRC_ALPHA;

                API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, this.BackColor.ToArgb(), ref blend, API.ULW_ALPHA);

And Voilà!

Alpha blended splash against my blog in the background, you can see through the semi-transparent form

You can get the project, source code and complied binary here.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.