Kestis
Kestis

Reputation: 163

Partially transparent splash screen in C#

I'm trying to make a splash screen for my application. I have an .PNG image which has some partially transparent parts.

I've made the form borderless and disabled the controlbox. However, background color is causing problems.

First I placed the image in a picturebox. Then I made the form background transparent this way:

this.BackColor = Color.Magenta;
this.TransparencyKey = Color.Magenta;

It sort of worked. The fully transparent parts of the screen are transparent, but magenta can be seen in the partially transparent parts.

Then I tried putting the image as a background image of the form. I tried using this code to make the background of the form transparent:

private void Form1_Load(object sender, EventArgs e)
{
   this.SetStyle(System.Windows.Forms.ControlStyles.SupportsTransparentBackColor, true);
   this.BackColor = System.Drawing.Color.Transparent;
}

It didn't work either. The background still isn't transparent. What can I do?

Upvotes: 2

Views: 2813

Answers (4)

Amro Omran
Amro Omran

Reputation: 1

Hi all here is the updated code. I added the missing parts of the code to convert it from Splash Screen into Form 1-timer to update the form image 2-image resizing to match form size 3-custom paint when there is no back image on the form 4-draw the controls on the updated image to show all the controls with updated status 5-WndProc for dragging and resizing the form

but this code needs some memory management to decrease memory usage if anyone has an idea how to do it please update this code

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Amro_Omran
{
    public partial class Form2 : Form
    {
        IntPtr memDc, hBmp, hOldBmp;
        Bitmap backgroundBmp;
        Timer refreshTimer;

        public Form2()
        {
            InitializeComponent();
            this.FormBorderStyle = FormBorderStyle.None; // Remove the form border
            InitializeTimer();
            this.Load += Form2_Load;
        }

        const Int32 HTCAPTION = 0x02; // Identifier for the window title bar
        const Int32 WM_NCHITTEST = 0x84;

        protected override void WndProc(ref Message message)
        {
            if (message.Msg == WM_NCHITTEST)
            {
                // Inform Windows that the user is on the title bar (to allow dragging)
                message.Result = (IntPtr)HTCAPTION;
            }
            else
            {
                base.WndProc(ref message);
            }
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams createParam = base.CreateParams;
                createParam.ExStyle |= APIHelp.WS_EX_LAYERED; // Set the window as a layered window for transparency
                return createParam;
            }
        }

        private void Form2_Load(object sender, EventArgs e)
        {
            SetupBackgroundImage();
            DrawControlsOnBackground();
            ApplyTransparency();
        }

        private void InitializeTimer()
        {
            refreshTimer = new Timer();
            refreshTimer.Interval = 500; // Refresh every 0.5 seconds
            refreshTimer.Tick += (s, e) => { UpdateBackground(); };
            refreshTimer.Start();
        }

        private void SetupBackgroundImage()
        {
            // Dispose of any existing background image to avoid memory leaks
            if (backgroundBmp != null)
            {
                backgroundBmp.Dispose();
            }

            if (this.BackgroundImage != null)
            {
                // Use the form's current background, resizing it to fit the form
                backgroundBmp = ResizeImage(new Bitmap(this.BackgroundImage), this.Width, this.Height);
            }
            else
            {
                // Create a transparent background with a specified color if no image is present
                backgroundBmp = new Bitmap(this.Width, this.Height);
                using (Graphics g = Graphics.FromImage(backgroundBmp))
                {
                    g.Clear(Color.FromArgb(128, 0, 0, 0)); // Transparent background
                    using (Pen borderPen = new Pen(Color.Black, 2))
                    {
                        g.DrawRectangle(borderPen, 0, 0, this.Width - 1, this.Height - 1); // Draw a border
                    }
                }
            }
        }

        private void DrawControlsOnBackground()
        {
            // Draw all form controls onto the background image
            using (Graphics g = Graphics.FromImage(backgroundBmp))
            {
                foreach (Control ctrl in Controls)
                {
                    Bitmap controlBmp = new Bitmap(ctrl.Width, ctrl.Height);
                    ctrl.DrawToBitmap(controlBmp, new Rectangle(0, 0, ctrl.Width, ctrl.Height));
                    g.DrawImage(controlBmp, ctrl.Location);
                    controlBmp.Dispose();
                }
            }
        }

        private void ApplyTransparency()
        {
            // Set the transparency blend function parameters
            APIHelp.BLENDFUNCTION blend = new APIHelp.BLENDFUNCTION
            {
                BlendOp = APIHelp.AC_SRC_OVER,
                BlendFlags = 0,
                SourceConstantAlpha = 255,
                AlphaFormat = APIHelp.AC_SRC_ALPHA
            };

            // Apply the transparent layer to the form window
            IntPtr screenDc = APIHelp.GetDC(IntPtr.Zero);
            memDc = APIHelp.CreateCompatibleDC(screenDc);
            hBmp = backgroundBmp.GetHbitmap(Color.FromArgb(0));
            hOldBmp = APIHelp.SelectObject(memDc, hBmp);
            APIHelp.DeleteDC(screenDc);

            APIHelp.Size newSize = new APIHelp.Size { cx = this.Width, cy = this.Height };
            APIHelp.Point newLocation = new APIHelp.Point { x = this.Location.X, y = this.Location.Y };
            APIHelp.Point sourceLocation = new APIHelp.Point { x = 0, y = 0 };

            APIHelp.UpdateLayeredWindow(this.Handle, IntPtr.Zero, ref newLocation, ref newSize, memDc, ref sourceLocation, 0, ref blend, APIHelp.ULW_ALPHA);
        }

        private void UpdateBackground()
        {
            SetupBackgroundImage();
            DrawControlsOnBackground();
            ApplyTransparency();
        }

        protected override void OnFormClosed(FormClosedEventArgs e)
        {
            // Clean up resources when the form is closed
            APIHelp.SelectObject(memDc, hOldBmp);
            APIHelp.DeleteObject(hBmp);
            APIHelp.DeleteDC(memDc);
            backgroundBmp.Dispose();
            refreshTimer.Dispose();
            base.OnFormClosed(e);
        }

        // Method to resize an image to the specified width and height
        private Bitmap ResizeImage(Image img, int width, int height)
        {
            Bitmap newImage = new Bitmap(width, height);
            using (Graphics g = Graphics.FromImage(newImage))
            {
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.DrawImage(img, 0, 0, width, height);
            }
            return newImage;
        }

        // APIHelper class for layered window and GDI operations
        public class APIHelp
        {
            public const int WS_EX_LAYERED = 0x80000; // Extended window style for layered windows
            public const int ULW_ALPHA = 0x02; // Flag for per-pixel alpha blending
            public const byte AC_SRC_OVER = 0x00; // Blend operation flag
            public const byte AC_SRC_ALPHA = 0x01; // Alpha format flag

            [StructLayout(LayoutKind.Sequential)]
            public struct Point
            {
                public int x;
                public int y;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct Size
            {
                public int cx;
                public int cy;
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct BLENDFUNCTION
            {
                public byte BlendOp;
                public byte BlendFlags;
                public byte SourceConstantAlpha;
                public byte AlphaFormat;
            }

            [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 pptSrc, uint crKey, [In] ref BLENDFUNCTION pblend, uint dwFlags);

            [DllImport("gdi32.dll", SetLastError = true)]
            public static extern IntPtr CreateCompatibleDC(IntPtr hdc);

            [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
            public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

            [DllImport("user32.dll")]
            public static extern IntPtr GetDC(IntPtr hWnd);

            [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
            public static extern bool DeleteDC(IntPtr hdc);

            [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
            public static extern bool DeleteObject(IntPtr hObject);
        }
    }
}


Result

enter image description here

Upvotes: 0

You can accomplish it with layered windows and UpdateLayeredWindow() Api:

protected override CreateParams CreateParams
{
    get
    {
        // Add the layered extended style (WS_EX_LAYERED) to this window
        CreateParams createParam = base.CreateParams;
        createParam.ExStyle = (createParam.ExStyle | APIHelp.WS_EX_LAYERED);
        return createParam;
    }
}

And in your form load event:

IntPtr memDc, hBmp, hOldBmp;

private void Form1_Load(object sender, EventArgs e)
{
    APIHelp.BLENDFUNCTION blend;

    //Only works with a 32bpp bitmap
    blend.BlendOp = APIHelp.AC_SRC_OVER;
    //Always 0
    blend.BlendFlags = 0;
    //Set to 255 for per-pixel alpha values
    blend.SourceConstantAlpha = 255;
    //Only works when the bitmap contains an alpha channel
    blend.AlphaFormat = APIHelp.AC_SRC_ALPHA;

    IntPtr screenDc;

    screenDc = APIHelp.GetDC(IntPtr.Zero);

    Bitmap bmp;

    using (bmp = (Bitmap)Bitmap.FromFile(@"C:\.......png")) //the image must be the same size as your form
    {
        memDc = APIHelp.CreateCompatibleDC(screenDc);
        hBmp = bmp.GetHbitmap(Color.FromArgb(0));
        hOldBmp = APIHelp.SelectObject(memDc, hBmp); //memDc is a device context that contains our image
    }

    APIHelp.DeleteDC(screenDc);

    APIHelp.Size newSize;
    APIHelp.Point newLocation;
    APIHelp.Point sourceLocation;

    newLocation.x = this.Location.X;
    newLocation.y = this.Location.Y;

    sourceLocation.x = 0;
    sourceLocation.y = 0;

    newSize.cx = this.Width;
    newSize.cy = this.Height;

    APIHelp.UpdateLayeredWindow(Handle, IntPtr.Zero, ref newLocation, ref newSize, memDc, ref sourceLocation, 
           0, ref blend, APIHelp.ULW_ALPHA);
}

Class APIHelp:

class APIHelp
{
    public const Int32 WS_EX_LAYERED = 524288;
    public const Int32 ULW_ALPHA = 2;
    public const byte AC_SRC_OVER = 0;
    public const byte AC_SRC_ALPHA = 1;

    [StructLayout(LayoutKind.Sequential)] 
    public struct Point
    {
        public Int32 x;
        public Int32 y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct Size
    {
        public Int32 cx;
        public Int32 cy;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ARGB
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Alpha;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct BLENDFUNCTION
    {
        public Byte BlendOp;
        public Byte BlendFlags;
        public Byte SourceConstantAlpha;
        public Byte AlphaFormat;
    }

    [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 pptSrc, uint crKey,
       [In] ref BLENDFUNCTION pblend, uint dwFlags);


    [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC", SetLastError = true)]
    public static extern IntPtr CreateCompatibleDC([In] IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
    public static extern IntPtr SelectObject([In] IntPtr hdc, [In] IntPtr hgdiobj);

    [DllImport("user32.dll")]
    public static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
    public static extern bool DeleteDC([In] IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool DeleteObject([In] IntPtr hObject);
}

When the form closes release the resources:

APIHelp.SelectObject(memDc, hOldBmp);
APIHelp.DeleteObject(hBmp);
APIHelp.DeleteDC(memDc);

Upvotes: 2

Peter Wishart
Peter Wishart

Reputation: 12270

TransparencyKey will set the form to be fully opaque, except pixels of the exact color specified. AFAIK it does this by actually defining the WindowRegion i.e. window shape based on pixels matching the colour - those pixels are no longer part of your window and can be clicked through.

Sounds like your PNG has an alpha layer i.e. transparency levels 0..255, which are blended over your background colour (magenta) and after that only the pure magenta pixels become transparent.

You could try:

  • Pick a colour e.g. Magenta not used in your splash and put that in your splash PNG in place of the fully transparent areas as opaque pixels
  • Capture a copy of the desktop bitmap under your window before it shows, and put this in a PictureBox under your splash.

The partially transparent areas of the splash would then blend with the background image, the fully transparent areas will match TransparencyKey and be transparent.

But you will get visual artefacts if the rest of the desktop changes while the splash is showing, if your splash screen moves etc.

edit: Seems there's an easier solution here without using TransparencyKey

Upvotes: 3

Baddack
Baddack

Reputation: 2053

Why magenta? Try using White. I set the back color White and transparency key White. In the image below, the random box with a picture is my splash.

Results

Upvotes: 3

Related Questions