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

      '