Transparent Images with C# Winforms

Transparent images with C# WinForms

I was in a similar situation a couple of days ago. You can create a transparent control to host your image.

using System;
using System.Windows.Forms;
using System.Drawing;

public class TransparentControl : Control
{
private readonly Timer refresher;
private Image _image;

public TransparentControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
refresher = new Timer();
refresher.Tick += TimerOnTick;
refresher.Interval = 50;
refresher.Enabled = true;
refresher.Start();
}

protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}

protected override void OnMove(EventArgs e)
{
RecreateHandle();
}

protected override void OnPaint(PaintEventArgs e)
{
if (_image != null)
{
e.Graphics.DrawImage(_image, (Width / 2) - (_image.Width / 2), (Height / 2) - (_image.Height / 2));
}
}

protected override void OnPaintBackground(PaintEventArgs e)
{
//Do not paint background
}

//Hack
public void Redraw()
{
RecreateHandle();
}

private void TimerOnTick(object source, EventArgs e)
{
RecreateHandle();
refresher.Stop();
}

public Image Image
{
get
{
return _image;
}
set
{
_image = value;
RecreateHandle();
}
}
}

Transparent image over a control

(sorry, for missuing the answer function but the answer is to long for a comment)

@TaW
seems like you didnt quite understand the approach, so I will try to explain it in more detail

OP asked if he can make a transparant Image over another control (a progressbar)

I assumed this transparent Image is inside a PictureBox, you seem to assume some other control

to position the control, if my assumption is correct the picturebox, infront of the progress bar all he has to do is right click and click "Bring to Front" on the PictureBox

and there you have it a "transparent" PictureBox infront of a progressbar - but as you mentioned in your answer we cannot stop there since the "transparent" isnt what I expected, but obviously you knew - its this "parent background color picking" that WinForms does and we end up with a not fully transparent image infront of the ProgressBar but instead one with a gray Background

Now the posted url comes in place:
http://www.richardhyland.com/diary/2009/05/26/how-to-truely-make-a-picturebox-background-transparent/

This is the code provided, and explained in that url:

public static System.Drawing.Drawing2D.GraphicsPath Transparent(Image im)
{
int x;
int y;
Bitmap bmp = new Bitmap(im);
System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
Color mask = bmp.GetPixel(0, 0);

for (x = 0; x <= bmp.Width - 1; x++)
{
for (y = 0; y <= bmp.Height - 1; y++)
{
if (!bmp.GetPixel(x, y).Equals(mask))
{
gp.AddRectangle(new Rectangle(x, y, 1, 1));
}
}
}
bmp.Dispose();
return gp;
}

With this we can achieve a fully transparent Picture box infront of a Progress bar.

So without this Code, we have this:

Sample Image

But with that Code:

Sample Image

Notice, this approach has some downsides:

  1. doesn't work perfectly - as you can see gray pixels around the edges of the image
  2. performs poorly on big Images - since getting each pixel with GetPixel is "challange"

(Please, ignore the fact that the image shows "JPG" and I am talking about transparent Images - this was just the first image Google search presented me and yes, the file is a transparent png)

How to make picturebox transparent?

One way to do this is by changing the parent of the overlapping picture box to the PictureBox over which it is lapping. Since the Visual Studio designer doesn't allow you to add a PictureBox to a PictureBox, this will have to be done in your code (Form1.cs) and within the Intializing function:

public Form1()
{
InitializeComponent();
pictureBox7.Controls.Add(pictureBox8);
pictureBox8.Location = new Point(0, 0);
pictureBox8.BackColor = Color.Transparent;
}

Just change the picture box names to what ever you need. This should return:

Sample Image

WinForms picturebox not transparent

It is quite easy all you have to do is make the canvas PictureBox of the same size and set its location also the same as your first picture box. Then set canvas PictureBox back colour to transparent. Now set your first PictureBox as the parent of canvas PictureBox.

You can write the below code on the form load event.

pictureBoxCanvas.Size = pictureBox1.Size;
pictureBoxCanvas.Location = pictureBox1.Location;
pictureBoxCanvas.BackColor = Color.Transparent;
pictureBoxCanvas.BringToFront();
pictureBoxCanvas.Parent = this.pictureBox1;

Transparent background image for Form - Smooth edge shape for the Form

You can use Layered Windows:

Using a layered window can significantly improve performance and
visual effects for a window that has a complex shape, animates its
shape, or wishes to use alpha blending effects. The system
automatically composes and repaints layered windows and the windows of
underlying applications. As a result, layered windows are rendered
smoothly, without the flickering typical of complex window regions. In
addition, layered windows can be partially translucent, that is,
alpha-blended.

Create layered window in Windows Forms

Here is some code from msdn code gallery which demonstrates creating Layered Windows in Windows Forms. It allows you to create a shaped splash screen and let you to move it by mouse.

Add PerPixelAlphaForm to the project and then it's enough to inherit from this form and call its SelectBitmap and pass your png to the method to create a layered window.

Sample Image

PerPixelAlphaForm.cs

#region Using directives
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
#endregion
namespace CSWinFormLayeredWindow
{
public partial class PerPixelAlphaForm : Form
{
public PerPixelAlphaForm()
{
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.Load += PerPixelAlphaForm_Load;
}

void PerPixelAlphaForm_Load(object sender, EventArgs e)
{
this.TopMost = true;
}
protected override CreateParams CreateParams
{
get
{
// Add the layered extended style (WS_EX_LAYERED) to this window.
CreateParams createParams = base.CreateParams;
if(!DesignMode)
createParams.ExStyle |= WS_EX_LAYERED;
return createParams;
}
}
/// <summary>
/// Let Windows drag this window for us (thinks its hitting the title
/// bar of the window)
/// </summary>
/// <param name="message"></param>
protected override void WndProc(ref Message message)
{
if (message.Msg == WM_NCHITTEST)
{
// Tell Windows that the user is on the title bar (caption)
message.Result = (IntPtr)HTCAPTION;
}
else
{
base.WndProc(ref message);
}
}
/// <summary>
///
/// </summary>
/// <param name="bitmap"></param>
public void SelectBitmap(Bitmap bitmap)
{
SelectBitmap(bitmap, 255);
}
/// <summary>
///
/// </summary>
/// <param name="bitmap">
///
/// </param>
/// <param name="opacity">
/// Specifies an alpha transparency value to be used on the entire source
/// bitmap. The SourceConstantAlpha value is combined with any per-pixel
/// alpha values in the source bitmap. The value ranges from 0 to 255. If
/// you set SourceConstantAlpha to 0, it is assumed that your image is
/// transparent. When you only want to use per-pixel alpha values, set
/// the SourceConstantAlpha value to 255 (opaque).
/// </param>
public void SelectBitmap(Bitmap bitmap, int opacity)
{
// Does this bitmap contain an alpha channel?
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
{
throw new ApplicationException("The bitmap must be 32bpp with alpha-channel.");
}

// Get device contexts
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;

try
{
// Get handle to the new bitmap and select it into the current
// device context.
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);

// Set parameters for layered window update.
Size newSize = new Size(bitmap.Width, bitmap.Height);
Point sourceLocation = new Point(0, 0);
Point newLocation = new Point(this.Left, this.Top);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = (byte)opacity;
blend.AlphaFormat = AC_SRC_ALPHA;

// Update the window.
UpdateLayeredWindow(
this.Handle, // Handle to the layered window
screenDc, // Handle to the screen DC
ref newLocation, // New screen position of the layered window
ref newSize, // New size of the layered window
memDc, // Handle to the layered window surface DC
ref sourceLocation, // Location of the layer in the DC
0, // Color key of the layered window
ref blend, // Transparency of the layered window
ULW_ALPHA // Use blend as the blend function
);
}
finally
{
// Release device context.
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
#region Native Methods and Structures

const Int32 WS_EX_LAYERED = 0x80000;
const Int32 HTCAPTION = 0x02;
const Int32 WM_NCHITTEST = 0x84;
const Int32 ULW_ALPHA = 0x02;
const byte AC_SRC_OVER = 0x00;
const byte AC_SRC_ALPHA = 0x01;

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

public Point(Int32 x, Int32 y)
{ this.x = x; this.y = y; }
}

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

public Size(Int32 cx, Int32 cy)
{ this.cx = cx; this.cy = cy; }
}

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

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

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
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);

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr CreateCompatibleDC(IntPtr hDC);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteDC(IntPtr hdc);

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject(IntPtr hObject);

#endregion
}
}

SplashScreen.cs

public partial class Form4 : CSWinFormLayeredWindow.PerPixelAlphaForm
{
public Form4()
{
InitializeComponent();
this.SelectBitmap(Properties.Resources.splash);
}
}

Note

The original answer was based on turning double buffer off and overriding OnPaintBackground and drawing the image without calling base method. The answer had a known issue; while the form was moveless it was working well but if form was moving or the window behind the form was changed the window was not updating. You can see previous code in revisions. The current edit which is completely based on an MSDN code doesn't have any known issue.

Override a transparent picturebox in C# windows application form?

Transparency in Windows Forms isn't implemented as one would expect. Having a transparent background actually means that a control uses the background of its parent. This means that you need to make your overlay control a child of the original picture box:

PictureBox overlay = new PictureBox();

overlay.Dock = DockStyle.Fill;
overlay.BackColor = Color.FromArgb(128, Color.Blue);

pictureBox2.Controls.Add(overlay);

If you want the overlay picture box to contain a transparent image you need to actually change the image:

PictureBox overlay = new PictureBox();
overlay.Dock = DockStyle.Fill;
overlay.BackColor = Color.Transparent;

Bitmap transparentImage = new Bitmap(overlayImage.Width, overlayImage.Height);
using (Graphics graphics = Graphics.FromImage(transparentImage))
{
ColorMatrix matrix = new ColorMatrix();
matrix.Matrix33 = 0.5f;

ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

graphics.DrawImage(overlayImage, new Rectangle(0, 0, transparentImage.Width, transparentImage.Height), 0, 0, overlayImage.Width, overlayImage.Height, GraphicsUnit.Pixel, attributes);
}

overlay.Image = transparentImage;

pictureBox2.Controls.Add(overlay);

Transparent background on winforms?

The manner I have used before is to use a wild color (a color no one in their right mind would use) for the BackColor and then set the transparency key to that.

this.BackColor = Color.LimeGreen;
this.TransparencyKey = Color.LimeGreen;

WinForms create transparent clearable pictureBox overlays

Thanks to @Reza Aghaei who guided me in chat to solution.

For me acceptable solution was in building multilayer image and assigning it to pictureBox.Image attribute.

I built image by loading image from file:

Image im = new Bitmap(path); // loads image from file

Then create graphics from this image:

var g = Graphics.FromImage(im); // creates graphics from loaded image

Draw all needed rectangles to this image and backup this image to some global Image instance:

var pen = new Pen(Color.Black, 1);
// draws all rectangles on the image
annotationAOIs.ForEach(a =>
{
g.DrawRectangle(pen, a.Start.X, a.Start.Y, (a.End.X - a.Start.X), (a.End.Y - a.Start.Y));
});
g.Dispose(); // disposes used graphics
ImageBackup = new Bitmap(im); // backup image with rectangles

In above part I created a static part of an image, which will not change and I backed it up so next time I will just create new Image instance from backup without any rectangle drawing.

Then when I want to show up new circle over this image I just:

var image = new Bitmap(ImageBackup); // creates new instance of image with rectangles from backup
var g = Graphics.FromImage(image); // creates graphics from image

// in this part draw circle at specific point
var f = Fixations[fixationIndex];
sizeOfFixation = (int)f.Length / FIX_SIZE_COEFICIENT;
g.FillEllipse(dotBrush, f.PosX - 1, f.PosY - 1, 3, 3);
g.FillEllipse(brush, (f.PosX - sizeOfFixation), (f.PosY - sizeOfFixation), sizeOfFixation * 2, sizeOfFixation * 2);

pictureBox.Image.Dispose(); // dispose old pictureBox image
pictureBox.Image = image; // set new image

imageOverlay = pictureBox.CreateGraphics(); // get transparent graphics overlay for pictureBox so we can draw anything else over picture (in my case highlighting rectangles over which I hover a mouse)
g.Dispose(); // dispose used graphics


Related Topics



Leave a reply



Submit