Click here to Skip to main content
Click here to Skip to main content

.NET Targa Image Reader

By , 15 Dec 2008
Rate this:
Please Sign up or sign in to vote.

Contents

What is TargaImage?

TargaImage was designed to be a simple way to load a TGA image file into a .NET Bitmap object. TargaImage is written entirely in .NET C# code. It does not use any Win32 or other interoperability code, and it does not use any unsafe code blocks. It does not use any third party .NET or unmanaged libraries. If you have the .NET Framework installed, then you can use TargaImage in your code.

TargaImage supports the following commonly used formats:

  • 8, 16, 24, 32 bit pixel depths.
  • 8 bit grayscale images.
  • Indexed and True Color images.
  • Uncompressed and Run-Length Encoded (RLE) compressed image data.

TargaImage does not save images in the Targa format. It was designed for loading Targa images only.

TargaImage does not allow you to convert other image formats into the Targa format. It does, however, allow you to save the loaded image in other formats. See Converting Images for details.

TargaImage was written based on the Truevision TGA Specification 2.0.

TargaImage was written in C# using Visual Studio 2008, and can be used with .NET 2.0 and up.

How to Use TargaImage

TargaImage is very easy to use. After downloading the code, just copy the TargaImage.dll file into your project and add a reference to it. Then, in your code, just call the LoadTargaImage() method. Here is an example.

Examples

//   C# Sample   
//   Loads a targa image and assigns it to the Image of a picturebox control.
this.PictureBox1.Image = Paloma.TargaImage.LoadTargaImage(@"c:\targaimage.tga");
    
//   Creates an instance of the TargaImage class with the specifed file
//   displays a few targa properties and then assigns the targa image
//   to the Image of a picturebox control
Paloma.TargaImage tgaImage = new Paloma.TargaImage(@"c:\targaimage.tga");
this.Label1.Text = tgaImage.Format.ToString();
this.Label2.Text = tgaImage.Header.ImageType.ToString();
this.Label3.Text = tgaImage.Header.PixelDepth.ToString();
this.PictureBox1.Image = Paloma.TargaImage.Image;

VB.NET:

'   VB.NET Sample 
'   Loads a targa image and assigns it to the Image of a picturebox control.
Me.PictureBox1.Image = Paloma.TargaImage.LoadTargaImage("c:\targaimage.tga")

    
'   Creates an instance of the TargaImage class with the specifed file
'   displays a few targa properties and then assigns the targa image
'   to the Image of a picturebox control
Dim tgaImage As New Paloma.TargaImage("c:\targaimage.tga")
Me.Label1.Text = tgaImage.Format.ToString()
Me.Label2.Text = tgaImage.Header.ImageType.ToString()
Me.Label3.Text = tgaImage.Header.PixelDepth.ToString()
Me.PictureBox1.Image = Paloma.TargaImage.Image

The Birth of TargaImage

I needed a way to load .tga image files and display them in a PictureBox control. A PictureBox control requires a Bitmap for its Image property. By looking at MSDN, I found that the Bitmap class does not support the .tga image format.

I started to look on the Internet for way to load a .tga file in .NET. I found lots of image libraries available, but all of them used unmanaged code written in C or C++. To use these libraries in my project would require that I use Interop code to get them to work in .NET. There was plenty of Interop code out there, but the code could be used only if the unmanaged libraries configured correctly. Another problem was that these libraries loaded a .tga file into a Win32 HBITMAP class. This meant I still had to convert the HBitmap object to the Bitmap object I required. I tried to do just that, but the code was cumbersome and messy, and I just didn’t like it. Also, these libraries have many features included with them which seemed to me to be a lot of overhead just to simply load a .tga file.

There was some .NET managed code I found but most of the code could only loaded very specific types of Targa images, like 32bit true color or 8 bit indexed. This would not work for me because the images I had were of many different types.

I kept looking around for code that could load .tga files, but I just couldn’t find anything out there. I started to think that maybe .NET couldn’t handle loading a .tga file and that was why no one had written code to do it. But that’s just not possible, there has to be a way to read and load .tga files in .NET. So I decided that I would write my own .tga image loader using .NET code only. I will not use any unmanged libraries or interop code. Not only would this solve my problem but maybe it would fill in a small void out there in the .NET community.

The Truevision Targa Specification 2.0

I decided to begin by learning exactly what the Targa image format is. I found the Truevision TGA Specification 2.0 document to be very helpful. This document goes into great detail on how information is saved in a Targa file. If there's anything you wanted to know about the Targa format, it's in this document.

Here is the structure of a .tga image file. The picture is from the specification document.

Targa image file structure

Based on the description of the Targa image file structure in this document, I developed TargaImage with the following classes:

  • TargaImage
  • This is the main class and it holds the TargaHeader, TargaExtensionArea, and TargaFooter. The Image Data section is loaded into the Image property of this class.

  • TargaHeader
  • This class holds all of the header properties of a Targa image. This includes the TGA File Header, Image ID, and Color Map section.

  • TargaExtensionArea
  • This class holds all of the Extension Area properties of the Targa image, if any exist in the file.

  • TargaFooter
  • This class holds all of the TGA File Footer properties of the Targa image, if any exist in the file.

Note: The Developer Area section is ignored by TargaImage because this section was designed for custom use by developers, and will only have significance if you know what to do with the data stored in this area.

How it all Works

Loading the File

Because of how the data is structured in the file, we will require the ability to move around to different areas of the file. To do this, we will first load the bytes of the file into memory and then create a BinaryReader based off the bytes. The BinaryReader makes it easy to read a few bytes at a time, while at the same time allowing us to move to different areas of the file.

Following the Targa specification, the first step is to check for and load the Footer section. If a Footer exists, we will need to load it first, because it holds the offset values to other sections within the file, mainly the Extension Area section. After loading the Footer, we can load the Header section and then the Extension Area section. Once all of the sections are loaded, we have all the information we need to properly load the Image Data.

Note: The error checking code was left out for brevity.

// load the file as an array of bytes
filebytes = System.IO.File.ReadAllBytes(this.strFileName);
// create a seekable memory stream of the file bytes
using (filestream = new MemoryStream(filebytes))
{
   // create a BinaryReader used to read the Targa file
   using (binReader = new BinaryReader(filestream))
   {
      // Load each section and the image data itself
      this.LoadTGAFooterInfo(binReader);
      this.LoadTGAHeaderInfo(binReader);
      this.LoadTGAExtensionArea(binReader);
      this.LoadTGAImage(binReader);
   }
}

Loading Image Data into a Bitmap

Before loading the image data, we have to compute the values needed by the Bitmap class so that it can display the image data properly. According to the MSDN documentation, there are five values we need to have to be able to create a Bitmap object using our image data.

  1. The width of the image in pixels.
  2. The height of the image in pixels.
  3. The stride of the image in bytes.
  4. The pixel format of the image. E.g., 32 bit ARGB, 8 bit indexed, etc.
  5. A pointer to an array of bytes that contains the pixel data.

For the width and height, we will use the Width and the Height property of the TargaHeader class.

Now, stride is a bit trickier. In a Bitmap object, stride refers to the length in bytes of each row, or scan line, in an image, and must be aligned on a 32 bit boundary or 4 bytes. What this means is that we need to make sure that the stride value is the closet multiple of 4 that is greater than the width in bytes.

If our image pixel width is 23 pixels and our pixel depth is 16 bits (2 bytes), then the width of our image in bytes is 23 * 2 = 46. Our closest multiple of 4 greater than 46 is 48, so our stride for this image would be 48.

Here is the code that calculates the stride of an image. This code uses bitwise logic and bit shifting to accomplish the calculation.

// calculate the stride, in bytes, of the image (32bit aligned width of each image row)
this.intStride = (((int)this.objTargaHeader.Width * 
                 (int)this.objTargaHeader.PixelDepth + 31) & ~31) >> 3;

The pixel format has to be one of the PixelFormat enumeration values. To determine the PixelFormat, we use the TargaImage.Format, TargaHeader.PixelDepth, and TargaExtensionArea.AttributesType properties.

/// <summary>
/// Gets the PixelFormat to be used by the Image
/// based on the Targa file's attributes
/// </summary>

private PixelFormat GetPixelFormat(){ 
PixelFormat pfTargaPixelFormat = PixelFormat.Undefined;

switch (this.objTargaHeader.PixelDepth)
{
   case 8:
      pfTargaPixelFormat = PixelFormat.Format8bppIndexed;
      break;

   case 16:
      if (this.Format == TGAFormat.NEW_TGA)
      {
         switch (this.objTargaExtensionArea.AttributesType){
            case 0:
            case 1:
            case 2: // no alpha data
               pfTargaPixelFormat = PixelFormat.Format16bppRgb555;
               break;

            case 3: // useful alpha data
               pfTargaPixelFormat = PixelFormat.Format16bppArgb1555;
               break;
            }
      }
      else 
         pfTargaPixelFormat = PixelFormat.Format16bppRgb555;
      
      break;

      case 24:
         pfTargaPixelFormat = PixelFormat.Format24bppRgb;
         break;
 
      case 32:
         if (this.Format == TGAFormat.NEW_TGA)
         {
            switch (this.objTargaExtensionArea.AttributesType){
               case 1:
               case 2: // no alpha data
                  pfTargaPixelFormat = PixelFormat.Format32bppRgb;
                  break;

               case 0:
               case 3: // useful alpha data
                  pfTargaPixelFormat = PixelFormat.Format32bppArgb;
                  break;

               case 4: // premultiplied alpha data
                  pfTargaPixelFormat = PixelFormat.Format32bppPArgb;
                  break;
            }
         }
         else
            pfTargaPixelFormat = PixelFormat.Format32bppRgb;
         
        break;
   }
   return pfTargaPixelFormat;
}

Now, we have to get a pointer to the array of bytes that holds the pixel data. To do this, we will load the pixel data into a byte array, and then using a GCHandle class, we can "pin" the memory address of the array so that the garbage collector will not move or delete the memory. We can also use the GCHandle class to get a pointer to the "pinned" memory by calling the AddrOfPinnedObject() method. This will return a memory pointer as an IntPtr value. IntPtrs are the way .NET represents a pointer to a memory address.

See Loading the Image Data to see how the image data is loaded into the byte array:

// get the image data bytes
byte[] bimagedata = this.LoadImageBytes(binReader);

// since the Bitmap constructor requires a pointer to an array of image bytes
// we have to pin down the memory used by the byte array and use the pointer 
// of this pinned memory to create the Bitmap.
// This tells the Garbage Collector to leave the memory alone and DO NOT touch it.
this.ImageByteHandle = GCHandle.Alloc(bimagedata, GCHandleType.Pinned);

Now that we have the width, height, stride, pixel format, and image data byte array ready to go, we can create a Bitmap object.

// create a Bitmap object using the image Width, Height,
// Stride, PixelFormat and the pointer to the pinned byte array.
this.bmpTargaImage = new Bitmap((int)this.objTargaHeader.Width,
                                (int)this.objTargaHeader.Height,
                                     this.intStride,
                                     pfPixelFormat,
                                     this.ImageByteHandle.AddrOfPinnedObject());

Loading the Image Data

The goal of loading the image data is to get the bytes of the data into a byte array that we can then use to create the Bitmap object. TargaImage supports both Run-Length Encoding (RLE) compressed and uncompressed image data. To determine if an image is RLE compressed or uncompressed, you can check the TargaHeader.ImageType property.

If the image data is stored as uncompressed bytes, we can load them in the order they are stored in the file.

for (int i = 0; i < (int)this.objTargaHeader.Height; i++)
{
    for (int j = 0; j < intImageRowByteSize; j++)
    {
       row.Add(binReader.ReadByte());
    }
    rows.Add(row);
    row = new System.Collections.Generic.List<byte>();
}

When an image has been compressed using RLE, you will have to decode the RLE compression. RLE is a simple compression that encodes runs of identical pixels into a single packet.

There are two types of RLE packets, Run-Length and Raw packets. A Run-Length packet has a pixel value and the number of times this pixel value has to be repeated. A Raw packet has a pixel count and then the pixel data itself.

To load the pixel data, you first have to determine which type of RLE packet you have, then for a Run-Length packet, repeat the pixel value the specified number of times, or with a Raw packet, read the number of pixels specified. See the Targa Specification 2.0 document for more details on how RLE works.

Here is the code that decodes each type of RLE packet:

// check the RLE packet type
if ((RLEPacketType)intRLEPacketType == RLEPacketType.RUN_LENGTH)
{
   // get the pixel color data
   bRunLengthPixel = binReader.ReadBytes((int)this.objTargaHeader.BytesPerPixel);
   // add the number of pixels specified using the read pixel color
   for (int i = 0; i < intRLEPixelCount; i++)
   {
      foreach (byte b in bRunLengthPixel)
         row.Add(b);
    
      // increment the byte counts
      intImageRowBytesRead += bRunLengthPixel.Length;
      intImageBytesRead += bRunLengthPixel.Length;

      // if we have read a full image row
      // add the row to the row list and clear it
      // restart row byte count
      if (intImageRowBytesRead == intImageRowByteSize)
      {
         rows.Add(row);
         row = new System.Collections.Generic.List<byte>();
         intImageRowBytesRead = 0;
      }
   }
}
else if ((RLEPacketType)intRLEPacketType == RLEPacketType.RAW)
{
   // get the number of bytes to read based on the read pixel count
   int intBytesToRead = intRLEPixelCount * (int)this.objTargaHeader.BytesPerPixel;

   // read each byte
   for (int i = 0;i < intBytesToRead;i++)
   {
      row.Add(binReader.ReadByte());

      // increment the byte counts
      intImageBytesRead++;
      intImageRowBytesRead++;

      // if we have read a full image row
      // add the row to the row list and clear it
      // restart row byte count
      if (intImageRowBytesRead == intImageRowByteSize)
      {
         rows.Add(row);
         row = new System.Collections.Generic.List<byte>();
                                            intImageRowBytesRead = 0;
      }
   }
}

We must also take into account the ordering in which the pixels were saved. We need to check the objTargaHeader.FirstPixelDestination property, and based on its value, return the bytes in the proper order.

// use FirstPixelDestination to determine the alignment of the 
// image data byte
switch (this.objTargaHeader.FirstPixelDestination)
{
   case FirstPixelDestination.TOP_LEFT:
      blnRowsReverse = false;
      blnEachRowReverse = true;
      break;

   case FirstPixelDestination.TOP_RIGHT:
      blnRowsReverse = false;
      blnEachRowReverse = false;
      break;

   case FirstPixelDestination.BOTTOM_LEFT:
      blnRowsReverse = true;
      blnEachRowReverse = true;
      break;

   case FirstPixelDestination.BOTTOM_RIGHT:
   case FirstPixelDestination.UNKNOWN:
      blnRowsReverse = true;
      blnEachRowReverse = false;
      break;
}

// write the bytes from each row into a memory stream and get the 
// resulting byte array
using (msData = new MemoryStream())
{
   // do we reverse the rows in the row list.
   if (blnRowsReverse == true)
      rows.Reverse();

   // go through each row
   for (int i = 0; i < rows.Count; i++)
   {
      // do we reverse the bytes in the row
      if (blnEachRowReverse == true)
         rows[i].Reverse();

      // get the byte array for the row
      byte[] brow = rows[i].ToArray();

      // write the row bytes and padding bytes to the memory streem
      msData.Write(brow, 0, brow.Length);
      msData.Write(padding, 0, padding.Length);
   }
      
   // get the image byte array
   data = msData.ToArray(); 
                        
}

Converting Images

Since TargaImage loads the image into a Bitmap class, you can use TargaImage to convert images from Targa to any format supported by Bitmap.

//   C# Sample
//   Load a targa image and save it in various image formats.
Bitmap tga = Paloma.TargaImage.LoadTargaImage(@"c:\targaimage.tga");

tga.Save(@"c:\targaimage.jpg");
tga.Save(@"c:\targaimage.gif");
tga.Save(@"c:\targaimage.png");
tga.Save(@"c:\targaimage.bmp");

VB.NET:

'   VB.NET Sample 
'   Load a targa image and save it in various image formats.
Dim tga As Bitmap = Paloma.TargaImage.LoadTargaImage("c:\targaimage.tga")
    
tga.Save("c:\targaimage.jpg")
tga.Save("c:\targaimage.gif")
tga.Save("c:\targaimage.png")
tga.Save("c:\targaimage.bmp")

TargaImage Demo Application

TargaImage Demo Application Screenshot

To see TargaImage in action, you can download the demo application. It comes with several sample .tga images that you can load to view and inspect their properties.

If you have a .tga file of your own, you can use the “Browse” button to load it using TargaImage.

If TargaImage cannot load your .tga file, please send me a copy of the image so I can implement any fixes, or if you fix the code yourself, send me the changes you made. Any contributions will be a great help in keeping TargaImage as versatile as possible.

Conclusion

Working on TargaImage has taught me that the .NET framework puts a vast library of tools at the programmer's disposal, and problems can be solved without relying on Win32 or unmanaged code. I also learned that with those tools, it is possible to do low-level byte and bit programming in .NET without resorting to unmanaged code.

I hope you find that TargaImage makes it easy to load and display Targa images in your own .NET projects. I have been using TargaImage in a couple of projects, and it has been performing beautifully.

Helpful References

History

  • 12/15/2008 - Release of TargaImage 1.0.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Polomis
Web Developer ID Solutions
United States United States
No Biography provided

Comments and Discussions

 
QuestionWPF Version PinmemberDanielku1525-May-13 8:07 
BugProblem with alpha channel Pinmemberr2d2rigo17-Jan-13 6:54 
SuggestionVery useful code [small fix] PinmemberAfr027-Dec-12 5:32 
QuestionThank you very much,it works well PinmemberERIKAAYA11-Sep-12 18:54 
QuestionPuzzles about memory use. [modified] PinmemberDaveLannan1-Jun-12 19:44 
GeneralMy vote of 5 Pinmembermanoj kumar choubey13-Feb-12 0:06 
GeneralMy vote of 5 PinmemberTony's Toy18-Dec-11 20:23 
QuestionDDS would be nice too. Found this one for XNA... trying to convert to Image/Bitmap PinmemberHypnotron19-Oct-11 17:44 
QuestionOne small correction [modified] PinmemberSabarinathan Arthanari27-Sep-11 23:30 
GeneralGreat Thanks!! PinmemberTall_Tony15-Mar-11 3:34 
GeneralGreat work (Simple fix needed) PinmemberFadiii25-Aug-10 1:10 
GeneralRe: Great work (Simple fix needed) PinmemberGuavamanX1-Apr-12 12:53 
GeneralPerfect Solution PinmemberDoof36912-Feb-09 11:25 
GeneralGood job! PinmemberShane Story17-Dec-08 2:56 
Generaljust what i was looking for!! PinmemberMember 424077616-Dec-08 9:42 
GeneralGreat post ! Pinmemberdefconhaya16-Dec-08 1:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 16 Dec 2008
Article Copyright 2008 by David Polomis
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid