See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Loading BMP/JPG/TGA In One Function
  Submitted by



Recently I sought to write a single picture-loading function that could do the following:

  • 1) Work with any type of input, be it a file, resource, pointer to an open file, or raw data array
  • 2) Identify the image type from the image data itself
  • 3) Work even when dealing with file archives (many files compiled into one)
  • 4) Support all color formats for bitmaps, jpegs, and targa images
  • While researching code and documents on loading the different file formats, I was a bit peeved that no one really wrote their code in a manner good for learning from. Always they'd use different objects and functions so the code jumps all over the place, and sometimes even different files. So I wrote this, which is much easier to learn from. However, although I could have given it a data array as input, I decided on using a "Stream" class which you'd have to replace with your own file wrapper. The reason is this is much clearer and neater looking, so it's easy to replace the Stream functions with your own.

    Also, the JPEG code requires the official JPEG library (just a set of .c and .h files, no runtimes or anything) which you can download here: http://members.aol.com/Wannatolk/jpeg.zip.

    Download Associated File: LoadPicture.txt (39,759 bytes)

    /*

    LoadPicture.txt by Edward "CGameProgrammer" Resnick (cgp@gdnmail.net) This file was finished at 7:11 am eastern time, Febuary 4th, 2002.

    You may use it however you wish and modify it as you wish, though credit would be appreciated... or even better, e-mailing me if you use it. But it is just the code for loading file formats other people developed, nothing special really, so no big deal.

    The JPEG-loading code is based on the CxImageJPG implementation. Credits pasted from ximajpg.h:

    PASTE: CxImageJPG (c) 07/Aug/2001 <ing.davide.pizzolato@libero.it> PASTE: Special thanks to Troels Knakkergaard for new features, enhancements and bugfixes

    PASTE: original CImageJPG and CImageIterator implementation are: PASTE: Copyright: (c) 1995, Alejandro Aguilar Sierra <asierra@servidor.unam.mx>

    PASTE: This software is based in part on the work of the Independent JPEG Group. PASTE: Copyright (C) 1991-1998, Thomas G. Lane.

    ---------------- BEGIN SOURCE CODE -------------------

    */


    /////////////////////////////////////// // HEADERS YOU WOULDN'T NORMALLY USE // /////////////////////////////////////// #include <jpeglib.h> // The JPEG library #include <setjmp.h> // We need this to use the JPEG library. /////////// // FLAGS // /////////// #define PICTURE_NONE 0x0000 // No picture loaded // File Format Flags #define PICTURE_BITMAP 0x1000 // Bitmap (*.bmp) #define PICTURE_JPEG 0x2000 // JPEG (*.jpg) #define PICTURE_TARGA 0x4000 // Targa (*.tga) // Compression Flags #define PICTURE_UNCOMPRESSED 0x0100 // Always true with bitmaps, valid for targas #define PICTURE_COMPRESSED 0x0200 // Always true with jpegs, valid with targas // Bits-Per-Pixel Flags #define PICTURE_1BIT 0x0001 // Valid with bitmaps #define PICTURE_MONOCHROME 0x0001 #define PICTURE_4BIT 0x0002 // Valid with bitmaps #define PICTURE_16COLORS 0x0002 #define PICTURE_8BIT 0x0004 // Valid with bitmaps, jpegs, and targas #define PICTURE_256COLORS 0x0004 #define PICTURE_16BIT 0x0008 // Valid with bitmaps #define PICTURE_HIGHCOLOR 0x0008 #define PICTURE_24BIT 0x0010 // Most common bit depth, valid with bitmaps, jpegs, and targas #define PICTURE_TRUECOLOR 0x0010 #define PICTURE_32BIT 0x0020 // Valid with bitmaps // Gray-Scale Flag #define PICTURE_GRAYSCALE 0x0040 // Valid with jpegs and targas ///////////////////////// // CLASSES AND STRUCTS // ///////////////////////// // This is our picture class struct Picture { int Flags; // Example: PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_24BIT int Width; int Height; BYTE* RGB; // The RGB data array. Size is 3*Width*Height. Order is Red-Green-Blue };

    // We need these structs to use the JPEG library. struct JPGError { jpeg_error_mgr Public; jmp_buf SetJmp; };

    static void JPEG_OnError ( j_common_ptr JPGInfo ) { JPGError* Error; char Buffer [JMSG_LENGTH_MAX];

    Error = (JPGError*)JPGInfo->err; Error->Public.format_message( JPGInfo, Buffer );

    longjmp( Error->SetJmp, 1 ); }

    ///////////////// // THE BIG ONE // ///////////////// /* Note that this function requires as its only parameters a pointer to a Stream and, optionally, the size of the picture's part of that stream. Stream is a class I wrote, and you can modify the function to use either your file class, or a FILE*, or a filename, or whatever.

    Picture::Load() uses only four of its functions:

    bool Stream::Input( LPVOID Pointer, int Size ) This reads Size bytes and puts it in Pointer.

    bool Stream::Seek( int Position, int Flag ) ::Seek(0,STREAM_BEGINNING) is like fseek( File, 0, SEEK_SET ) ::Seek(0,STREAM_END) is like fseek( File, 0, SEEK_END )

    int Stream::Position() This works like ftell. It returns the caret position.

    int Stream::Size() This returns the file size, and is only called if you don't specify the file size to Picture::Load().

    So it's pretty easy to integrate this code into your own project, I hope.

    You would use this function like this:

    File.Open( STREAM_FILE, STREAM_READ, "Test.jpg" ); Pic.Load( &File, 0 ); File.Close( ); */


    bool Picture :: Load ( Stream* File, int PictureSize ) { // General-purpose variables BYTE TryToGetType [2]; BYTE Palette [768]; int NumColors; int CurrPixel; int Dest; int Src; int Front; int HStart; int HEnd; int HDir;

    // Bitmap variables BYTE BMRed; BYTE BMGreen; BYTE BMBlue; BYTE BMBlank; BITMAPFILEHEADER BMFile; BITMAPINFOHEADER BMInfo; int BMPadding; int BMPad; BYTE BMGroup; BYTE BMPixel8; WORD BMPixel16; int BMScanWidth;

    // JPEG variables jpeg_decompress_struct JPGInfo; JPGError JPGErr; JSAMPARRAY JPGBuffer; int JPGStride; BYTE* JPGRed; BYTE* JPGGreen; BYTE* JPGBlue; BYTE* JPGSource; FILE* JPGTempFile; int JPGImageSize; BYTE* JPGImage; BYTE* JPGPixel; BYTE* JPGContents;

    // Targa variables BYTE TGAFile [26]; BYTE TGAHeader [18]; BYTE TGAID [256]; int TGANumColors; BYTE TGAPacket; int TGANumPixels; BYTE TGAScanline [384]; BYTE* TGAImage;

    Flags = PICTURE_NONE; Width = 0; Height = 0; RGB = NULL;

    if( !FileSize ) FileSize = File->Size( );

    // Find out where in the file we are. Do this in case it's an archive // and the picture might start in the middle somewhere: Front = File->Position( );

    // Read the first two bytes to figure out what image this is: if( !File->Input( TryToGetType, 2 ) ) { // You will see this alot. When an error occurs, I // seek to what was supposed to be the end of the image data, // in case the stream is an archive. So the next item in // the archive can be loaded. File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    // Go back to where we were if( !File->Seek( Front, STREAM_BEGINNING ) ) return false;

    if( TryToGetType[0] == 'B' && TryToGetType[1] == 'M' ) { // The file is a bitmap if( !File->Input( &BMFile, sizeof BMFile ) || !File->Input( &BMInfo, sizeof BMInfo ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    // Only uncompressed bitmaps are supported by this code if( BMInfo.biCompression != BI_RGB ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    // Create the palette now, if we must if( BMInfo.biBitCount <= 8 ) { NumColors = 1 << BMInfo.biBitCount;

    for( int C = 0; C < NumColors * 3; C += 3 ) { /* Palette information in bitmaps is stored in BGR_ format. That means it's blue-green-red-blank, for each entry. */ if( !File->Input( &BMBlue, 1 ) || !File->Input( &BMGreen, 1 ) || !File->Input( &BMRed, 1 ) || !File->Input( &BMBlank, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    Palette[C] = BMRed; Palette[C+1] = BMGreen; Palette[C+2] = BMBlue; } }

    Width = BMInfo.biWidth;

    if( BMInfo.biHeight < 0 ) { Height = -BMInfo.biHeight; HStart = 0; HEnd = Height; HDir = 1; } else { Height = BMInfo.biHeight; HStart = Height - 1; HEnd = -1; HDir = -1; }

    RGB = new BYTE [Width * Height * 3];

    if( BMInfo.biBitCount == 1 ) { // Note that this file is not necessarily grayscale, since it's possible // the palette is blue-and-white, or whatever. But of course most image // programs only write 1-bit images if they're black-and-white. Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_MONOCHROME;

    // For bitmaps, each scanline is dword-aligned. BMScanWidth = (Width+7) >> 3; if( BMScanWidth & 3 ) BMScanWidth += 4 - (BMScanWidth & 3);

    for( int H = HStart; H != HEnd; H += HDir ) { CurrPixel = 0;

    for( int W = 0; W < BMScanWidth; W ++ ) { if( !File->Input( &BMGroup, 1 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    /* Now we read the pixels. Usually there are eight pixels per byte, since each pixel is represented by one bit, but if the width is not a multiple of eight, the last byte will have some bits set, with the others just being extra. Plus there's the dword-alignment padding. So we keep checking to see if we've already read "Width" number of pixels. */ if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x80) >> 7); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x40) >> 6); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x20) >> 5); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x10) >> 4); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x08) >> 3); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x04) >> 2); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0x02) >> 1); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * (BMGroup & 0x01); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; } } } } else if( BMInfo.biBitCount == 4 ) { Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_4BIT;

    // For bitmaps, each scanline is dword-aligned. BMScanWidth = (Width+1) >> 1; if( BMScanWidth & 3 ) BMScanWidth += 4 - (BMScanWidth & 3);

    for( int H = HStart; H != HEnd; H += HDir ) { CurrPixel = 0;

    for( int W = 0; W < BMScanWidth; W ++ ) { if( !File->Input( &BMGroup, 1 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    /* Now we read the pixels. Usually there are two pixels per byte, since each pixel is represented by four bits, but if the width is not a multiple of two, the last byte will have only four bits set, with the others just being extra. Plus there's the dword-alignment padding. So we keep checking to see if we've already read "Width" number of pixels. */ if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * ((BMGroup & 0xF0) >> 4); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * (BMGroup & 0x0F); RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; } } } } else if( BMInfo.biBitCount == 8 ) { Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_8BIT;

    // For bitmaps, each scanline is dword-aligned. BMScanWidth = Width; if( BMScanWidth & 3 ) BMScanWidth += 4 - (BMScanWidth & 3);

    for( int H = HStart; H != HEnd; H += HDir ) { CurrPixel = 0;

    for( int W = 0; W < BMScanWidth; W ++ ) { if( !File->Input( &BMPixel8, 1 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( CurrPixel < Width ) { Dest = 3 * ((H * Width) + CurrPixel); Src = 3 * BMPixel8; RGB[Dest] = Palette[Src]; RGB[Dest+1] = Palette[Src+1]; RGB[Dest+2] = Palette[Src+2]; CurrPixel ++; } } } } else if( BMInfo.biBitCount == 16 ) { Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_16BIT;

    // For bitmaps, each scanline is dword-aligned. BMScanWidth = Width << 1;

    if( BMScanWidth & 3 ) BMPadding = 4 - (BMScanWidth & 3); else BMPadding = 0;

    for( int H = HStart; H != HEnd; H += HDir ) { for( int W = 0; W < Width; W ++ ) { if( !File->Input( &BMPixel16, 2 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    /* Now we seperate each WORD into the correct RGB values. The highest bit is zero, the next 5 are red, the next 5 are green, and the lowest 5 are blue. */ BMRed = ((BMPixel16 >> 10) & 0x1F) << 3; BMGreen = ((BMPixel16 >> 5) & 0x1F) << 3; BMBlue = (BMPixel16 & 0x1F) << 3;

    Dest = ((H * Width) + W) << 1; RGB[Dest] = BMRed; RGB[Dest+1] = BMGreen; RGB[Dest+2] = BMBlue; }

    if( BMPadding ) File->Input( &BMPad, BMPadding ); } } else if( BMInfo.biBitCount == 24 ) { Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_24BIT;

    // For bitmaps, each scanline is dword-aligned. BMScanWidth = Width * 3;

    if( BMScanWidth & 3 ) BMPadding = 4 - (BMScanWidth & 3); else BMPadding = 0;

    for( int H = HStart; H != HEnd; H += HDir ) { for( int W = 0; W < Width; W ++ ) { if( !File->Input( &BMBlue, 1 ) || !File->Input( &BMGreen, 1 ) || !File->Input( &BMRed, 1 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    Dest = 3 * ((H * Width) + W); RGB[Dest] = BMRed; RGB[Dest+1] = BMGreen; RGB[Dest+2] = BMBlue; }

    if( BMPadding ) File->Input( &BMPad, BMPadding ); } } else if( BMInfo.biBitCount == 32 ) { Flags = PICTURE_BITMAP | PICTURE_UNCOMPRESSED | PICTURE_32BIT;

    // Since each pixel is a dword, it is obviously dword-aligned for( int H = HStart; H != HEnd; H += HDir ) { for( int W = 0; W < Width; W ++ ) { if( !File->Input( &BMBlank, 1 ) || !File->Input( &BMBlue, 1 ) || !File->Input( &BMGreen, 1 ) || !File->Input( &BMRed, 1 ) ) { delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    Dest = 3 * ((H * Width) + W); RGB[Dest] = BMRed; RGB[Dest+1] = BMGreen; RGB[Dest+2] = BMBlue; } } } else // We support all possible bit depths, so if the {// code gets here, it's not even a real bitmap. delete [] RGB; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; } } else if( TryToGetType[0] == 0xFF && // These unlikely-looking characters are TryToGetType[1] == 0xD8 ) // used to identify JPEGs { /* Unfortunately, the JPEG library seems to require a FILE* passed as a parameter, as far as I can tell. I may be wrong, but for now I save the JPEG to a temporary file and load from that. This is necessary if it's loaded from a resource, for example. */ JPGContents = new BYTE [FileSize];

    if( !File->Input( JPGContents, FileSize ) ) { delete [] JPGContents; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    File->Seek( Front + FileSize, STREAM_BEGINNING );

    // JPGContents now holds the contents of the JPEG. JPGInfo.err = jpeg_std_error( &JPGErr.Public ); JPGErr.Public.error_exit = JPEG_OnError;

    if( setjmp( JPGErr.SetJmp ) ) { // This part of the code is only called when a JPEG // library function fails. When it does, it will jump // to this point. jpeg_destroy_decompress( &JPGInfo ); File->Seek( Front + FileSize, STREAM_BEGINNING ); fclose( JPGTempFile ); return false; }

    JPGTempFile = fopen( "__PicLoad.jpg", "wb" ); // Create the temporary File. fwrite( JPGContents, 1, FileSize, JPGTempFile ); fclose( JPGTempFile );

    delete [] JPGContents; // No longer needed JPGTempFile = fopen( "__PicLoad.jpg", "rb" );

    jpeg_create_decompress( &JPGInfo ); jpeg_stdio_src( &JPGInfo, JPGTempFile ); jpeg_read_header( &JPGInfo, TRUE ); jpeg_start_decompress( &JPGInfo );

    Width = JPGInfo.image_width; Height = JPGInfo.image_height;

    if( JPGInfo.jpeg_color_space == JCS_GRAYSCALE ) { Flags = PICTURE_JPEG | PICTURE_COMPRESSED | PICTURE_8BIT | PICTURE_GRAYSCALE;

    for( int N = 0; N < 256; N ++ ) { Palette[N*3] = N; Palette[(N*3)+1] = N; Palette[(N*3)+2] = N; } } else { if( JPGInfo.num_components == 1 ) Flags = PICTURE_JPEG | PICTURE_COMPRESSED | PICTURE_8BIT; else Flags = PICTURE_JPEG | PICTURE_COMPRESSED | PICTURE_24BIT;

    if( JPGInfo.quantize_colors && JPGInfo.num_components == 1 ) { JPGRed = JPGInfo.colormap[0]; JPGGreen = JPGInfo.colormap[1]; JPGBlue = JPGInfo.colormap[2];

    if( !JPGRed ) { fclose( JPGTempFile ); jpeg_destroy_decompress( &JPGInfo ); return false; }

    if( !JPGGreen ) JPGGreen = JPGRed; if( !JPGBlue ) JPGBlue = JPGGreen;

    for( int N = 0; N < 256; N ++ ) { Palette[N*3] = JPGRed[N]; Palette[(N*3)+1] = JPGGreen[N]; Palette[(N*3)+2] = JPGBlue[N]; } } }

    JPGStride = JPGInfo.output_width * JPGInfo.num_components; JPGBuffer = (*JPGInfo.mem->alloc_sarray)( (j_common_ptr)&JPGInfo, JPOOL_IMAGE, JPGStride, 1 );

    JPGImageSize = JPGInfo.num_components * Width * Height; JPGImage = new BYTE [JPGImageSize]; JPGPixel = JPGImage;

    while( JPGInfo.output_scanline < JPGInfo.output_height ) { jpeg_read_scanlines( &JPGInfo, JPGBuffer, 1 );

    if( JPGInfo.num_components == 4 && !JPGInfo.quantize_colors ) { JPGSource = JPGBuffer[0];

    for( int X1=0, X2=0; X1 < JPGInfo.num_components * Width && X2 < JPGStride; X1 += 3, X2 += 4 ) { JPGPixel[X1] = (JPGSource[X2+3] * JPGSource[X2+2]) / 255; JPGPixel[X1+1] = (JPGSource[X2+3] * JPGSource[X2+1]) / 255; JPGPixel[X1+2] = (JPGSource[X2+3] * JPGSource[X2]) / 255; } } else { if( JPGStride < JPGInfo.num_components * Width ) memcpy( JPGPixel, JPGBuffer[0], JPGStride ); else memcpy( JPGPixel, JPGBuffer[0], JPGInfo.num_components * Width ); }

    JPGPixel += JPGInfo.num_components * Width; }

    jpeg_finish_decompress( &JPGInfo ); jpeg_destroy_decompress( &JPGInfo );

    if( JPGInfo.num_components == 1 ) { RGB = new BYTE [Width * Height * 3];

    for( int N = 0; N < JPGImageSize; N ++ ) { RGB[N*3] = Palette[JPGImage[N]*3]; RGB[(N*3)+1] = Palette[(JPGImage[N]*3)+1]; RGB[(N*3)+2] = Palette[(JPGImage[N]*3)+2]; }

    delete [] JPGImage; } else RGB = JPGImage;

    fclose( JPGTempFile ); remove( "__PicLoad.jpg" ); } else { // The image is either a Targa or unknown /* We want to read the last 26 bytes of the File-> We could seek to the end, but in the case of archives this is not desirable. So we do it this way. */ if( !File->Seek( Front + FileSize - 26, STREAM_BEGINNING ) ) return false;

    if( !File->Input( TGAFile, 26 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( !File->Seek( Front, STREAM_BEGINNING ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( memcmp( &TGAFile[8], "TRUEVISION-XFILE", 16 ) ) { // The image is not a Targa File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    // The image is a Targa if( !File->Input( TGAHeader, 18 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    Width = ((WORD*)TGAHeader)[6]; Height = ((WORD*)TGAHeader)[7];

    /* The ID field is an optional string that you can use to give the image a name or description or whatever. */ if( TGAHeader[0] && !File->Input( TGAID, TGAHeader[0] ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( TGAHeader[2] == 1 ) { Flags = PICTURE_TARGA | PICTURE_UNCOMPRESSED | PICTURE_8BIT;

    TGANumColors = ((WORD*)(TGAHeader+1))[2];

    /* Unlike bitmaps, Targas support palettes that are in other bit depths besides 24-bit. But I haven't seen any and I am lazy so this code ignores them. */ if( TGAHeader[7] != 24 ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( !File->Input( Palette, 3 * TGANumColors ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    TGAImage = new BYTE [Width * Height];

    if( !File->Input( TGAImage, Width * Height ) ) { delete [] TGAImage; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    RGB = new BYTE [Width * Height * 3];

    for( int N = 0; N < Width * Height; N ++ ) { Src = 3 * TGAImage[N];

    // The palette is in blue-green-red order, so we read it backwards RGB[N*3] = Palette[Src+2]; RGB[(N*3)+1] = Palette[Src+1]; RGB[(N*3)+2] = Palette[Src]; }

    delete [] TGAImage; } else if( TGAHeader[2] == 2 ) { Flags = PICTURE_TARGA | PICTURE_UNCOMPRESSED | PICTURE_24BIT;

    TGAImage = new BYTE [Width * Height * 3]; if( !File->Input( TGAImage, Width * Height * 3 ) ) { delete [] TGAImage; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    RGB = new BYTE [Width * Height * 3];

    for( long N = 0; N < Width * Height * 3; N += 3 ) { // The image data is in blue-green-red format, so reverse it RGB[N] = TGAImage[N+2]; RGB[N+1] = TGAImage[N+1]; RGB[N+2] = TGAImage[N]; }

    delete [] TGAImage; } else if( TGAHeader[2] == 3 ) { Flags = PICTURE_TARGA | PICTURE_UNCOMPRESSED | PICTURE_8BIT | PICTURE_GRAYSCALE;

    TGAImage = new BYTE [Width * Height]; if( !File->Input( TGAImage, Width * Height ) ) { delete [] TGAImage; File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    RGB = new BYTE [Width * Height * 3];

    for( long N = 0; N < Width * Height; N ++ ) { RGB[N*3] = TGAImage[N]; RGB[(N*3)+1] = TGAImage[N]; RGB[(N*3)+2] = TGAImage[N]; }

    delete [] TGAImage; }

    /* The following TGA formats are RLE encoded. This means the image data comes in two types of packets: RLE packets and raw packets.

    RLE Packets: If there is a row of 12 red pixels in a 24-bit image, the RLE packet consists of one byte whose value is 12, and three more bytes whose values are 255,0,0. Red=255, Green=0, Blue=0.

    Raw Packets: For parts of the image that don't have nice long rows of the same color, rather than use RLE packets for each individual pixel, it's fastest to do it with raw packets. If, after the last packet read, there is a red pixel, a green pixel, and a yellow pixel, and then a row of blue pixels, the first byte of the raw packet will be 3, and it will be followed by the pixel data for the red, green, and yellow pixels. Then a RLE packet will be written for the blue pixels.

    That's basically the theory behind RLE encoding. The way TGAs use it, the highest bit of the first byte of the packet is used to tell whether it's a RLE or raw packet. Then the other 7 bits are used for the length of the packet pixels. Also packets cannot span several scanlines. So if you have a 8x8 image that's pure white, then theoretically you'd be able to use one RLE packet for the entire thing. But TGAs don't allow that. You'd need 8 RLE packets, one for each scanline. */
    else if( TGAHeader[2] == 9 ) { Flags = PICTURE_TARGA | PICTURE_COMPRESSED | PICTURE_8BIT;

    TGANumColors = ((WORD*)(TGAHeader+1))[2];

    /* Unlike bitmaps, Targas support palettes that are in other bit depths besides 24-bit. But I haven't seen any and I am lazy so this code ignores them. */ if( TGAHeader[7] != 24 ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    if( !File->Input( Palette, 3 * TGANumColors ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; }

    TGAImage = new BYTE [Width * Height];

    for( int H = 0; H < Height; H ++ ) { CurrPixel = 0;

    while( CurrPixel < Width ) { if( !File->Input( &TGAPacket, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    TGANumPixels = 1 + (TGAPacket & 0x7F);

    if( TGAPacket & 0x80 ) // Run-length packet { if( !File->Input( TGAScanline, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels; N ++ ) { TGAImage[(H*Width) + CurrPixel] = TGAScanline[0]; CurrPixel ++; } } else // Raw packet { if( !File->Input( TGAScanline, TGANumPixels ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels; N ++ ) { TGAImage[(H*Width) + CurrPixel] = TGAScanline[N]; CurrPixel ++; } } } }

    RGB = new BYTE [Width * Height * 3]; for( int N = 0; N < Width * Height; N ++ ) { Src = 3 * TGAImage[N];

    RGB[N*3] = Palette[Src+2]; RGB[(N*3)+1] = Palette[Src+1]; RGB[(N*3)+2] = Palette[Src]; }

    delete [] TGAImage; } else if( TGAHeader[2] == 10 ) { Flags = PICTURE_TARGA | PICTURE_COMPRESSED | PICTURE_24BIT;

    TGAImage = new BYTE [Width * Height * 3];

    for( int H = 0; H < Height; H ++ ) { CurrPixel = 0;

    while( CurrPixel < Width * 3 ) { if( !File->Input( &TGAPacket, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    TGANumPixels = 1 + (TGAPacket & 0x7F);

    if( TGAPacket & 0x80 ) // Run-length packet { if( !File->Input( TGAScanline, 3 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels; N ++ ) { TGAImage[(H*Width*3) + CurrPixel] = TGAScanline[0]; TGAImage[(H*Width*3) + CurrPixel+1] = TGAScanline[1]; TGAImage[(H*Width*3) + CurrPixel+2] = TGAScanline[2];

    CurrPixel += 3; } } else // Raw packet { if( !File->Input( TGAScanline, TGANumPixels * 3 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels * 3; N += 3 ) { TGAImage[(H*Width*3) + CurrPixel] = TGAScanline[N]; TGAImage[(H*Width*3) + CurrPixel+1] = TGAScanline[N+1]; TGAImage[(H*Width*3) + CurrPixel+2] = TGAScanline[N+2];

    CurrPixel += 3; } } } }

    RGB = new BYTE [Width * Height * 3]; for( int N = 0; N < Width * Height * 3; N += 3 ) { RGB[N] = TGAImage[N+2]; RGB[N+1] = TGAImage[N+1]; RGB[N+2] = TGAImage[N]; }

    delete [] TGAImage; } else if( TGAHeader[2] == 11 ) { Flags = PICTURE_TARGA | PICTURE_COMPRESSED | PICTURE_8BIT | PICTURE_GRAYSCALE;

    TGAImage = new BYTE [Width * Height];

    for( int H = 0; H < Height; H ++ ) { CurrPixel = 0;

    while( CurrPixel < Width ) { if( !File->Input( &TGAPacket, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    TGANumPixels = 1 + (TGAPacket & 0x7F);

    if( TGAPacket & 0x80 ) // Run-length packet { if( !File->Input( TGAScanline, 1 ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels; N ++ ) { TGAImage[(H*Width) + CurrPixel] = TGAScanline[0]; CurrPixel ++; } } else // Raw packet { if( !File->Input( TGAScanline, TGANumPixels ) ) { File->Seek( Front + FileSize, STREAM_BEGINNING ); delete [] TGAImage; return false; }

    for( int N = 0; N < TGANumPixels; N ++ ) { TGAImage[(H*Width) + CurrPixel] = TGAScanline[N]; CurrPixel ++; } } } }

    RGB = new BYTE [Width * Height * 3]; for( int N = 0; N < Width * Height; N ++ ) { RGB[N*3] = TGAImage[N]; RGB[(N*3)+1] = TGAImage[N]; RGB[(N*3)+2] = TGAImage[N]; }

    delete [] TGAImage; } else { // Unsupported image type, but we supported all six, // so this should never happen. File->Seek( Front + FileSize, STREAM_BEGINNING ); return false; } }

    return true; // Success! Yay! }

    The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

     

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.