.
GDI+ FAQ
Skip Navigation LinksWelcome : GDI+ FAQ : Transparent GIF images

Creating Transparent GIF Images

Now includes a VB version of the listing.

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

      '

      Me.button2.Location = New System.Drawing.Point(200, 216)

      Me.button2.Name = "button2"

      Me.button2.Size = New System.Drawing.Size(88, 24)

      Me.button2.TabIndex = 2

      Me.button2.Text = "Save"

      '

      ' button3

      '

      Me.button3.Location = New System.Drawing.Point(200, 256)

      Me.button3.Name = "button3"

      Me.button3.Size = New System.Drawing.Size(88, 24)

      Me.button3.TabIndex = 2

      Me.button3.Text = "Exit"

      '

      ' timer1

      '

      Me.timer1.Enabled = True

      Me.timer1.Interval = 250

      '

      ' Form1

      '

      Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)

      Me.ClientSize = New System.Drawing.Size(328, 325)

      Me.Controls.Add(button1)

      Me.Controls.Add(panel1)

      Me.Controls.Add(pictureBox1)

      Me.Controls.Add(button2)

      Me.Controls.Add(button3)

      Me.Name = "Form1"

      Me.Text = "Form1"

      Me.ResumeLayout(False)

    End Sub 'InitializeComponent

#End Region

 

 

    '/ <summary>

    '/ The main entry point for the application.

    '/ </summary>

    <STAThread()> _

    Shared Sub Main()

      Application.Run(New Form1)

    End Sub 'Main

 

 

    Private Sub panel1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles panel1.Paint

      If cp Is Nothing Then

        Return

      End If

      Dim y As Single

      For y = 0 To 15

        Dim x As Single

        For x = 0 To 15

          Dim c As Color = Color.Black

          If 16 * y + x < cp.Entries.Length Then

            c = cp.Entries(CInt(16 * y + x))

          End If

          Dim sb As New SolidBrush(Color.FromArgb(255, c))

          Dim w As Single = CSng(Me.panel1.Width) / 16

          Dim h As Single = CSng(Me.panel1.Height) / 16

          e.Graphics.FillRectangle(sb, w * x, h * y, w, h)

          If c.A <> 255 Then

            If showTrans Then

              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)

            End If

          End If

          sb.Dispose()

        Next x

      Next y

    End Sub 'panel1_Paint

 

 

    Private Sub panel1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles panel1.MouseMove

      Dim y As Integer = CInt(CSng(e.Y) / (CSng(Me.panel1.Width) / 16.0F))

      Dim x As Integer = CInt(CSng(e.X) / (CSng(Me.panel1.Height) / 16.0F))

      CurrentEntry = CInt(16 * y + x)

      If Not (cp Is Nothing) Then

        If CurrentEntry >= cp.Entries.Length Then

          CurrentEntry = cp.Entries.Length - 1

        End If 'Little bit of diagnostic for the palette chooser below

      End If '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));

    End Sub 'panel1_MouseMove

 

 

    Private Sub panel1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles panel1.Click

      'Creates a new GIF image with a modified colour palette

      If Not (cp Is Nothing) Then

        'Create a new 8 bit per pixel image

        Dim bm As New Bitmap(_gifImage.Width, _gifImage.Height, PixelFormat.Format8bppIndexed)

        'get it's palette

        Dim ncp As ColorPalette = bm.Palette

 

        'copy all the entries from the old palette removing any transparency

        Dim n As Integer = 0

        Dim c As Color

        For Each c In cp.Entries

          ncp.Entries(n) = Color.FromArgb(255, c)

          n += 1

        Next 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

        Dim src As BitmapData = CType(_gifImage, Bitmap).LockBits(New Rectangle(0, 0, _gifImage.Width, _gifImage.Height), ImageLockMode.ReadOnly, _gifImage.PixelFormat)

        Dim dst As BitmapData = bm.LockBits(New Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.WriteOnly, bm.PixelFormat)

 

        If (True) Then

          'steps through each pixel

          Dim y As Integer

          For y = 0 To _gifImage.Height - 1

            Dim x As Integer

            For x = 0 To _gifImage.Width - 1

              'transferring the bytes

              Marshal.WriteByte(dst.Scan0, dst.Stride * y + x, Marshal.ReadByte(src.Scan0, src.Stride * y + x))

            Next x

          Next y

        End If

        'all done, unlock the bitmaps

        CType(_gifImage, Bitmap).UnlockBits(src)

        bm.UnlockBits(dst)

 

        'clear out the picturebox

        Me.pictureBox1.Image = Nothing

        _gifImage.Dispose()

        'set the new image in place

        _gifImage = bm

        cp = _gifImage.Palette

        Me.pictureBox1.Image = _gifImage

      End If

    End Sub 'panel1_Click

 

 

    Private Sub button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button1.Click

      Dim dlg As New OpenFileDialog

      dlg.Filter = "GIF files|*.GIF"

      If dlg.ShowDialog() = DialogResult.OK Then

        _gifImage = Image.FromFile(dlg.FileName)

        Me.pictureBox1.Image = _gifImage

        cp = _gifImage.Palette

        Me.panel1.Invalidate()

      End If

    End Sub 'button1_Click

 

 

    Private Sub button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button2.Click

      Dim dlg As New SaveFileDialog

      dlg.Filter = "GIF files|*.gif"

      dlg.DefaultExt = ".gif"

      dlg.AddExtension = True

      If dlg.ShowDialog() = DialogResult.OK Then

        _gifImage.Save(dlg.FileName, ImageFormat.Gif)

      End If

    End Sub 'button2_Click

 

 

    Private Sub button3_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button3.Click

      Application.Exit()

    End Sub 'button3_Click

 

    Private showTrans As Boolean

 

 

    Private Sub timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles timer1.Tick

      showTrans ^= True

      Dim g As Graphics = Me.panel1.CreateGraphics()

      'I do this rather than invalidate the panel because

      'the panel draws its background ans so flickers horribly.

      Dim pe As New PaintEventArgs(g, New Rectangle(0, 0, Me.panel1.Width, Me.panel1.Height))

      Me.panel1_Paint(Me, pe)

      g.Dispose()

    End Sub 'timer1_Tick

  End Class 'Form1

End Namespace 'TransparentGifCreator

 

Figure 2 shows the application in action.

Figure 2: Before and after changing the transparent colour.

Back to the GDI+ FAQ

Copyright Robert W Powell 2003. All rights reserved.

 

Sponsored By
DaraizeTechnologies.com
Bob Powell

Create your badge

Copyright © Bob Powell 2000-2012.  All rights reserved.