
Creating Transparent GIF Images
This process is easy enough when you know how but is nevertheless a little more complex than it should be. To save a GIF with a transparency key you need to modify the colour palette of the image. There are a few problems associated with this. If you're creating an image, you'll be using a true colour format such as 24 or 32 bit per pixel non indexed. This is because you cannot obtain a Graphics object using Graphics.FromImage for any images with an indexed pixel format. Saving such an image as a GIF file will create a standard spread palette for you with the range of colours seen in figure 1.
Figure 1: The standard spread palette provided by GDI+ Unfortunately, at no point during the save process are you given the opportunity to choose a transparent colour so you need to take the saved image and re-save it with a modified palette. This in itself presents a problem because once a GIF image has been created, even though you can get hold of and manipulate the palette using the Bitmap.Palette property, GDI+ refuses to save the image with anything other than its original palette. To work around these limitations it's necessary to create a new, blank 8 bit per pixel, indexed palette image, modify it's bitmap to be the same as the original images, copy all the pixel data from the original to the new and then save the new image. As a demonstration of this process, and to provide a useful tool, the code in listing 1 is an application that enables you to load a GIF image, choose a transparent colour and save the GIF. panel1_Click is the method with the actual GIF manipulation. Listing 1: TransparentGifCreator.cs using
System; using
System.IO; using
System.Drawing; using
System.Drawing.Imaging; using
System.Collections; using
System.ComponentModel; using
System.Windows.Forms; using
System.Data; namespace
TransparentGifCreator { ///
<summary> ///
Summary description for Form1. ///
</summary> public class
Form1 : System.Windows.Forms.Form { private
System.Windows.Forms.PictureBox pictureBox1; private
System.Windows.Forms.Panel panel1; private
System.Windows.Forms.Button button1; private
System.Windows.Forms.Button button2; private
System.Windows.Forms.Button button3; private
System.ComponentModel.IContainer components; Image _gifImage; private
System.Windows.Forms.Timer timer1; ColorPalette
cp; int
CurrentEntry; public
Form1() { // //
Required for Windows Form Designer support // InitializeComponent(); // //
TODO: Add any constructor code after InitializeComponent call // } ///
<summary> ///
Clean up any resources being used. ///
</summary> protected
override void
Dispose( bool disposing ) { if(
disposing ) { if
(components != null) { components.Dispose(); } } base.Dispose(
disposing ); }
#region
Windows Form Designer generated code ///
<summary> ///
Required method for Designer support - do not modify ///
the contents of this method with the code editor. ///
</summary> private
void InitializeComponent() { this.components
= new System.ComponentModel.Container(); this.pictureBox1
= new System.Windows.Forms.PictureBox(); this.panel1
= new System.Windows.Forms.Panel(); this.button1
= new System.Windows.Forms.Button(); this.button2
= new System.Windows.Forms.Button(); this.button3
= new System.Windows.Forms.Button(); this.timer1
= new System.Windows.Forms.Timer(this.components); this.SuspendLayout(); //
//
pictureBox1 //
this.pictureBox1.Location
= new System.Drawing.Point(96, 8); this.pictureBox1.Name
= "pictureBox1"; this.pictureBox1.Size
= new System.Drawing.Size(144, 144); this.pictureBox1.SizeMode
= System.Windows.Forms.PictureBoxSizeMode.StretchImage; this.pictureBox1.TabIndex
= 0; this.pictureBox1.TabStop
= false; //
//
panel1 //
this.panel1.Location
= new System.Drawing.Point(8, 168); this.panel1.Name
= "panel1"; this.panel1.Size
= new System.Drawing.Size(144, 144); this.panel1.TabIndex
= 1; this.panel1.Click
+= new System.EventHandler(this.panel1_Click); this.panel1.Paint
+= new
System.Windows.Forms.PaintEventHandler(this.panel1_Paint); this.panel1.MouseMove
+= new
System.Windows.Forms.MouseEventHandler(this.panel1_MouseMove); //
//
button1 //
this.button1.Location
= new System.Drawing.Point(200, 176); this.button1.Name
= "button1"; this.button1.Size
= new System.Drawing.Size(88, 24); this.button1.TabIndex
= 2; this.button1.Text
= "Open"; this.button1.Click
+= new System.EventHandler(this.button1_Click); //
//
button2 //
this.button2.Location
= new System.Drawing.Point(200, 216); this.button2.Name
= "button2"; this.button2.Size
= new System.Drawing.Size(88, 24); this.button2.TabIndex
= 2; this.button2.Text
= "Save"; this.button2.Click
+= new System.EventHandler(this.button2_Click); //
//
button3 //
this.button3.Location
= new System.Drawing.Point(200, 256); this.button3.Name
= "button3"; this.button3.Size
= new System.Drawing.Size(88, 24); this.button3.TabIndex
= 2; this.button3.Text
= "Exit"; this.button3.Click
+= new System.EventHandler(this.button3_Click); //
//
timer1 //
this.timer1.Enabled
= true; this.timer1.Interval
= 250; this.timer1.Tick
+= new System.EventHandler(this.timer1_Tick); //
//
Form1 //
this.AutoScaleBaseSize
= new System.Drawing.Size(5, 13); this.ClientSize
= new System.Drawing.Size(328, 325); this.Controls.Add(this.button1); this.Controls.Add(this.panel1); this.Controls.Add(this.pictureBox1); this.Controls.Add(this.button2); this.Controls.Add(this.button3); this.Name
= "Form1"; this.Text
= "Form1"; this.ResumeLayout(false); }
#endregion ///
<summary> ///
The main entry point for the application. ///
</summary> [STAThread] static
void Main() { Application.Run(new
Form1()); } private
void panel1_Paint(object
sender, System.Windows.Forms.PaintEventArgs e) { if(cp==null) return; for(float
y=0;y<16;y++) for(float
x=0;x<16;x++) { Color
c=Color.Black; if(
((16*y) + x)<cp.Entries.Length) c=cp.Entries[(int)((16*y)+x)]; SolidBrush
sb=new SolidBrush(Color.FromArgb(255,c)); float
w=((float)this.panel1.Width)/16; float
h=((float)this.panel1.Height)/16; e.Graphics.FillRectangle(sb,w*x,h*y,w,h); if(c.A!=255) { if(showTrans) e.Graphics.DrawRectangle(Pens.Black,w*x,h*y,w-1,h-1); else e.Graphics.DrawRectangle(Pens.White,w*x,h*y,w-1,h-1); } sb.Dispose(); } } private
void panel1_MouseMove(object
sender, System.Windows.Forms.MouseEventArgs e) { int
y=(int)(((float)e.Y)/(((float)this.panel1.Width)/16f)); int
x=(int)(((float)e.X)/(((float)this.panel1.Height)/16f)); CurrentEntry=(int)((16*y)+x); if(cp!=null) { if(CurrentEntry>=cp.Entries.Length) CurrentEntry=cp.Entries.Length-1; //Little
bit of diagnostic for the palette chooser below //System.Diagnostics.Trace.WriteLine(string.Format("{0},{1},
adjusted={4},{5} entry={2} Colour={3}",e.X,e.Y,CurrentEntry,cp.Entries[CurrentEntry].ToString(),x,y)); } } private
void panel1_Click(object
sender, System.EventArgs e) { //Creates
a new GIF image with a modified colour palette if(cp!=null) { //Create
a new 8 bit per pixel image Bitmap
bm=new
Bitmap(_gifImage.Width,_gifImage.Height,PixelFormat.Format8bppIndexed); //get
it's palette ColorPalette
ncp=bm.Palette; //copy
all the entries from the old palette removing any transparency int
n=0; foreach(Color
c in cp.Entries) ncp.Entries[n++]=Color.FromArgb(255,c); //Set
the newly selected transparency ncp.Entries[CurrentEntry]=Color.FromArgb(0,cp.Entries[CurrentEntry]); //re-insert
the palette bm.Palette=ncp; //now
to copy the actual bitmap data //lock
the source and destination bits BitmapData
src=((Bitmap)_gifImage).LockBits(new
Rectangle(0,0,_gifImage.Width,_gifImage.Height),ImageLockMode.ReadOnly,_gifImage.PixelFormat); BitmapData
dst=bm.LockBits(new
Rectangle(0,0,bm.Width,bm.Height),ImageLockMode.WriteOnly,bm.PixelFormat); //uses
pointers so we need unsafe code. //the
project is also compiled with /unsafe unsafe { //steps
through each pixel for(int
y=0;y<_gifImage.Height;y++) for(int
x=0;x<_gifImage.Width;x++) { //transferring
the bytes ((byte
*)dst.Scan0.ToPointer())[(dst.Stride*y)+x]=((byte
*)src.Scan0.ToPointer())[(src.Stride*y)+x]; } } //all
done, unlock the bitmaps ((Bitmap)_gifImage).UnlockBits(src); bm.UnlockBits(dst); //clear
out the picturebox this.pictureBox1.Image=null; _gifImage.Dispose(); //set
the new image in place _gifImage=bm; cp=_gifImage.Palette; this.pictureBox1.Image=_gifImage; } } private
void button1_Click(object
sender, System.EventArgs e) { OpenFileDialog
dlg=new OpenFileDialog(); dlg.Filter="GIF
files|*.GIF"; if(dlg.ShowDialog()==DialogResult.OK) { _gifImage=Image.FromFile(dlg.FileName); this.pictureBox1.Image=_gifImage; cp=_gifImage.Palette; this.panel1.Invalidate(); } } private
void button2_Click(object
sender, System.EventArgs e) { SaveFileDialog
dlg=new SaveFileDialog(); dlg.Filter="GIF
files|*.gif"; dlg.DefaultExt=".gif"; dlg.AddExtension=true; if(dlg.ShowDialog()==DialogResult.OK) { _gifImage.Save(dlg.FileName,ImageFormat.Gif); } } private
void button3_Click(object
sender, System.EventArgs e) { Application.Exit(); } bool
showTrans; private
void timer1_Tick(object
sender, System.EventArgs e) { showTrans^=true; Graphics
g=this.panel1.CreateGraphics(); //I
do this rather than invalidate the panel because //the
panel draws its background ans so flickers horribly. PaintEventArgs
pe=new PaintEventArgs(g,new
Rectangle(0,0,this.panel1.Width,this.panel1.Height)); this.panel1_Paint(this,pe); g.Dispose(); } } } To perform the same task in VB the Marshal class can be used to access the image byte array as shown in the following listing.
Imports System Imports System.IO Imports System.Drawing Imports System.Drawing.Imaging Imports System.Collections Imports System.ComponentModel Imports System.Windows.Forms Imports System.Data Imports System.Runtime.InteropServices
Listing 2. TransparentGifCreator.VB
Namespace TransparentGifCreator '/ <summary> '/ Summary description for Form1. '/ </summary>
Public Class Form1 Inherits System.Windows.Forms.Form Private pictureBox1 As System.Windows.Forms.PictureBox Private WithEvents panel1 As System.Windows.Forms.Panel Private WithEvents button1 As System.Windows.Forms.Button Private WithEvents button2 As System.Windows.Forms.Button Private WithEvents button3 As System.Windows.Forms.Button Private components As System.ComponentModel.IContainer
Private _gifImage As Image Private WithEvents timer1 As System.Windows.Forms.Timer Private cp As ColorPalette Private CurrentEntry As Integer
Public Sub New() ' ' Required for Windows Form Designer support ' InitializeComponent() End Sub 'New
' ' TODO: Add any constructor code after InitializeComponent call '
'/ <summary> '/ Clean up any resources being used. '/ </summary> Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub 'Dispose
#Region "Windows Form Designer generated code"
'/ <summary> '/ Required method for Designer support - do not modify '/ the contents of this method with the code editor. '/ </summary> Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container Me.pictureBox1 = New System.Windows.Forms.PictureBox Me.panel1 = New System.Windows.Forms.Panel Me.button1 = New System.Windows.Forms.Button Me.button2 = New System.Windows.Forms.Button Me.button3 = New System.Windows.Forms.Button Me.timer1 = New System.Windows.Forms.Timer(Me.components) Me.SuspendLayout() ' ' pictureBox1 ' Me.pictureBox1.Location = New System.Drawing.Point(96, 8) Me.pictureBox1.Name = "pictureBox1" Me.pictureBox1.Size = New System.Drawing.Size(144, 144) Me.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage Me.pictureBox1.TabIndex = 0 Me.pictureBox1.TabStop = False ' ' panel1 ' Me.panel1.Location = New System.Drawing.Point(8, 168) Me.panel1.Name = "panel1" Me.panel1.Size = New System.Drawing.Size(144, 144) Me.panel1.TabIndex = 1 ' ' button1 ' Me.button1.Location = New System.Drawing.Point(200, 176) Me.button1.Name = "button1" Me.button1.Size = New System.Drawing.Size(88, 24) Me.button1.TabIndex = 2 Me.button1.Text = "Open" ' ' button2 '
|