| 1 | // tImageTGA.cpp  |
| 2 | //  |
| 3 | // This class knows how to load and save targa (.tga) files into tPixel arrays. These tPixels may be 'stolen' by the  |
| 4 | // tPicture's constructor if a targa file is specified. After the array is stolen the tImageTGA is invalid. This is  |
| 5 | // purely for performance.  |
| 6 | //  |
| 7 | // Copyright (c) 2006, 2017, 2019, 2020, 2023, 2024 Tristan Grimmer.  |
| 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 9 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 10 | //  |
| 11 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 12 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 13 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 14 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 15 | // PERFORMANCE OF THIS SOFTWARE.  |
| 16 |   |
| 17 | #include <System/tFile.h>  |
| 18 | #include "Image/tImageTGA.h"  |
| 19 | #include "Image/tPicture.h"  |
| 20 | using namespace tSystem;  |
| 21 | namespace tImage  |
| 22 | {  |
| 23 |   |
| 24 |   |
| 25 | // Helper functions, enums, and types for parsing TGA files.  |
| 26 | namespace tTGA  |
| 27 | {  |
| 28 | #pragma pack(push, r1, 1)  |
| 29 | struct   |
| 30 | {  |
| 31 | int8 ;  |
| 32 | int8 ;  |
| 33 |   |
| 34 | // 0 - No image data included.  |
| 35 | // 1 - Uncompressed, color-mapped images.  |
| 36 | // 2 - [Supported] Uncompressed, RGB images.  |
| 37 | // 3 - Uncompressed, black and white images.  |
| 38 | // 9 - Runlength encoded color-mapped images.  |
| 39 | // 10 - [Supported] Runlength encoded RGB images.  |
| 40 | // 11 - Compressed, black and white images.  |
| 41 | // 32 - Compressed color-mapped data, using Huffman, Delta, and runlength encoding.  |
| 42 | // 33 - Compressed color-mapped data, using Huffman, Delta, and runlength encoding. 4-pass quadtree-type process.  |
| 43 | int8 ;  |
| 44 | int16 ;  |
| 45 | int16 ;  |
| 46 | int8 ;  |
| 47 | int16 ;  |
| 48 | int16 ;  |
| 49 |   |
| 50 | int16 ;  |
| 51 | int16 ;  |
| 52 | int8 ;  |
| 53 |   |
| 54 | // LS bits 0 to 3 give the alpha channel depth.  |
| 55 | // Bit 4 (0-based) is left/right ordering.  |
| 56 | // Bit 5 (0-based) is up/down ordering. If Bit 5 is set the image will be upside down (like BMP).  |
| 57 | int8 ;  |
| 58 | bool () const { return (ImageDesc & 0x10) ? true : false; }  |
| 59 | bool () const { return (ImageDesc & 0x20) ? true : false; }  |
| 60 | };  |
| 61 | #pragma pack(pop, r1)  |
| 62 | tStaticAssert(sizeof(Header) == 18);  |
| 63 | }  |
| 64 |   |
| 65 |   |
| 66 | bool tImageTGA::Load(const tString& tgaFile, const LoadParams& params)  |
| 67 | {  |
| 68 | Clear();  |
| 69 |   |
| 70 | if (tSystem::tGetFileType(file: tgaFile) != tSystem::tFileType::TGA)  |
| 71 | return false;  |
| 72 |   |
| 73 | if (!tFileExists(file: tgaFile))  |
| 74 | return false;  |
| 75 |   |
| 76 | int numBytes = 0;  |
| 77 | uint8* tgaFileInMemory = tLoadFile(file: tgaFile, buffer: nullptr, fileSize: &numBytes);  |
| 78 | bool success = Load(tgaFileInMemory, numBytes, params);  |
| 79 | delete[] tgaFileInMemory;  |
| 80 |   |
| 81 | return success;  |
| 82 | }  |
| 83 |   |
| 84 |   |
| 85 | bool tImageTGA::Load(const uint8* tgaFileInMemory, int numBytes, const LoadParams& params)  |
| 86 | {  |
| 87 | Clear();  |
| 88 | if ((numBytes <= 0) || !tgaFileInMemory)  |
| 89 | return false;  |
| 90 |   |
| 91 | // Safety for corrupt files that aren't even as big as the tga header.  |
| 92 | // Checks later on will ensure data size is sufficient.  |
| 93 | if (numBytes < sizeof(tTGA::Header))  |
| 94 | return false;  |
| 95 |   |
| 96 | tTGA::Header* = (tTGA::Header*)tgaFileInMemory;  |
| 97 | Width = header->Width;  |
| 98 | Height = header->Height;  |
| 99 | int bitDepth = header->BitDepth;  |
| 100 | int dataType = header->DataTypeCode;  |
| 101 |   |
| 102 | // We support 16, 24, and 32 bit depths. We support data type mode 2 (uncompressed RGB) and mode 10 (Run-length  |
| 103 | // encoded RLE RGB). We allow a colour map to be present, but don't use it.  |
| 104 | if  |
| 105 | (  |
| 106 | ((bitDepth != 16) && (bitDepth != 24) && (bitDepth != 32)) ||  |
| 107 | ((dataType != 2) && (dataType != 10)) ||  |
| 108 | ((header->ColourMapType != 0) && (header->ColourMapType != 1))  |
| 109 | )  |
| 110 | {  |
| 111 | Clear();  |
| 112 | return false;  |
| 113 | }  |
| 114 | PixelFormatSrc = tPixelFormat::R8G8B8A8;  |
| 115 | if (bitDepth == 16)  |
| 116 | PixelFormatSrc = tPixelFormat::G3B5A1R5G2;  |
| 117 | else if (bitDepth == 24)  |
| 118 | PixelFormatSrc = tPixelFormat::R8G8B8;  |
| 119 |   |
| 120 | // The +1 adds sizeof header bytes.  |
| 121 | uint8* srcData = (uint8*)(header + 1);  |
| 122 |   |
| 123 | // These usually are zero. In most cases the pixel data will follow directly after the header. iColourMapType is a  |
| 124 | // boolean 0 or 1.  |
| 125 | srcData += header->IDLength;  |
| 126 | srcData += header->ColourMapType * header->ColourMapLength;  |
| 127 | const uint8* endData = tgaFileInMemory + numBytes;  |
| 128 |   |
| 129 | int numPixels = Width * Height;  |
| 130 | Pixels = new tPixel4b[numPixels];  |
| 131 |   |
| 132 | // Read the image data.  |
| 133 | int bytesPerPixel = bitDepth >> 3;  |
| 134 | int pixel = 0;  |
| 135 |   |
| 136 | while (pixel < numPixels)  |
| 137 | {  |
| 138 | switch (dataType)  |
| 139 | {  |
| 140 | case 10:  |
| 141 | {  |
| 142 | // Safety for corrupt tga files that don't have enough data.  |
| 143 | int available = endData - srcData;  |
| 144 | if (available < bytesPerPixel+1)  |
| 145 | {  |
| 146 | Clear();  |
| 147 | return false;  |
| 148 | }  |
| 149 |   |
| 150 | // Image data is compressed.  |
| 151 | int j = srcData[0] & 0x7f;  |
| 152 | uint8 rleChunk = srcData[0] & 0x80;  |
| 153 | srcData += 1;  |
| 154 |   |
| 155 | tColour4b firstColour;   |
| 156 | ReadColourBytes(dest&: firstColour, src: srcData, bitDepth: bytesPerPixel, alphaOpacity: (params.Flags & LoadFlag_AlphaOpacity));  |
| 157 | Pixels[pixel] = firstColour;  |
| 158 | pixel++;  |
| 159 | srcData += bytesPerPixel;  |
| 160 |   |
| 161 | if (rleChunk)  |
| 162 | {  |
| 163 | // Chunk is run length encoded.  |
| 164 | for (int i = 0; i < j; i++)  |
| 165 | {  |
| 166 | Pixels[pixel] = firstColour;  |
| 167 | pixel++;  |
| 168 | }  |
| 169 | }  |
| 170 | else  |
| 171 | {  |
| 172 | // Safety for corrupt tga files that don't have enough data.  |
| 173 | available = endData - srcData;  |
| 174 | if (available < bytesPerPixel*j)  |
| 175 | {  |
| 176 | Clear();  |
| 177 | return false;  |
| 178 | }  |
| 179 |   |
| 180 | // Chunk is normal.  |
| 181 | for (int i = 0; i < j; i++)  |
| 182 | {  |
| 183 | ReadColourBytes(dest&: Pixels[pixel], src: srcData, bitDepth: bytesPerPixel, alphaOpacity: (params.Flags & LoadFlag_AlphaOpacity));  |
| 184 | pixel++;  |
| 185 | srcData += bytesPerPixel;  |
| 186 | }  |
| 187 | }  |
| 188 | break;  |
| 189 | }  |
| 190 |   |
| 191 | case 2:  |
| 192 | default:  |
| 193 | {  |
| 194 | // Safety for corrupt tga files that don't have enough data.  |
| 195 | int available = endData - srcData;  |
| 196 | if (available < bytesPerPixel)  |
| 197 | {  |
| 198 | Clear();  |
| 199 | return false;  |
| 200 | }  |
| 201 |   |
| 202 | // Not compressed.  |
| 203 | ReadColourBytes(dest&: Pixels[pixel], src: srcData, bitDepth: bytesPerPixel, alphaOpacity: (params.Flags & LoadFlag_AlphaOpacity));  |
| 204 | pixel++;  |
| 205 | srcData += bytesPerPixel;  |
| 206 | break;  |
| 207 | }  |
| 208 | }  |
| 209 | }  |
| 210 |   |
| 211 | bool flipV = header->IsFlippedV();  |
| 212 | bool flipH = header->IsFlippedH();  |
| 213 | if (flipV || flipH)  |
| 214 | {  |
| 215 | tPixel4b* flipBuf = new tPixel4b[numPixels];  |
| 216 | for (int y = 0; y < Height; y++)  |
| 217 | for (int x = 0; x < Width; x++)  |
| 218 | {  |
| 219 | int row = flipV ? Height-y-1 : y;  |
| 220 | int col = flipH ? Width-x-1 : x;  |
| 221 | flipBuf[row*Width + col] = Pixels[y*Width + x];  |
| 222 | }  |
| 223 |   |
| 224 | delete[] Pixels;  |
| 225 | Pixels = flipBuf;  |
| 226 | }  |
| 227 |   |
| 228 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 229 |   |
| 230 | // TGA files are assumed to be in sRGB.  |
| 231 | ColourProfileSrc = tColourProfile::sRGB;  |
| 232 | ColourProfile = tColourProfile::sRGB;  |
| 233 |   |
| 234 | return true;  |
| 235 | }  |
| 236 |   |
| 237 |   |
| 238 | void tImageTGA::ReadColourBytes(tColour4b& dest, const uint8* src, int bytesPerPixel, bool alphaOpacity)  |
| 239 | {  |
| 240 | switch (bytesPerPixel)  |
| 241 | {  |
| 242 | case 4:  |
| 243 | dest.R = src[2];  |
| 244 | dest.G = src[1];  |
| 245 | dest.B = src[0];  |
| 246 | dest.A = alphaOpacity ? src[3] : (0xFF - src[3]);  |
| 247 | break;  |
| 248 |   |
| 249 | case 3:  |
| 250 | dest.R = src[2];  |
| 251 | dest.G = src[1];  |
| 252 | dest.B = src[0];  |
| 253 | dest.A = 0xFF;  |
| 254 | break;  |
| 255 |   |
| 256 | case 2:  |
| 257 | dest.R = (src[1] & 0x7c) << 1;  |
| 258 | dest.G = ((src[1] & 0x03) << 6) | ((src[0] & 0xe0) >> 2);  |
| 259 | dest.B = (src[0] & 0x1f) << 3;  |
| 260 | if (alphaOpacity)  |
| 261 | dest.A = (src[1] & 0x80) ? 0xFF : 0;  |
| 262 | else  |
| 263 | dest.A = (src[1] & 0x80) ? 0 : 0xFF;  |
| 264 | break;  |
| 265 |   |
| 266 | default:  |
| 267 | dest.MakeBlack();  |
| 268 | break;  |
| 269 | }  |
| 270 | }  |
| 271 |   |
| 272 |   |
| 273 | bool tImageTGA::Set(tPixel4b* pixels, int width, int height, bool steal)  |
| 274 | {  |
| 275 | Clear();  |
| 276 | if (!pixels || (width <= 0) || (height <= 0))  |
| 277 | return false;  |
| 278 |   |
| 279 | Width = width;  |
| 280 | Height = height;  |
| 281 |   |
| 282 | if (steal)  |
| 283 | {  |
| 284 | Pixels = pixels;  |
| 285 | }  |
| 286 | else  |
| 287 | {  |
| 288 | Pixels = new tPixel4b[Width*Height];  |
| 289 | tStd::tMemcpy(dest: Pixels, src: pixels, numBytes: Width*Height*sizeof(tPixel4b));  |
| 290 | }  |
| 291 |   |
| 292 | PixelFormatSrc = tPixelFormat::R8G8B8A8;  |
| 293 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 294 | ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB.  |
| 295 | ColourProfile = tColourProfile::sRGB;  |
| 296 |   |
| 297 | return true;  |
| 298 | }  |
| 299 |   |
| 300 |   |
| 301 | bool tImageTGA::Set(tFrame* frame, bool steal)  |
| 302 | {  |
| 303 | Clear();  |
| 304 | if (!frame || !frame->IsValid())  |
| 305 | return false;  |
| 306 |   |
| 307 | PixelFormatSrc = frame->PixelFormatSrc;  |
| 308 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 309 | ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB.  |
| 310 | ColourProfile = tColourProfile::sRGB;  |
| 311 |   |
| 312 | Set(pixels: frame->GetPixels(steal), width: frame->Width, height: frame->Height, steal);  |
| 313 | if (steal)  |
| 314 | delete frame;  |
| 315 |   |
| 316 | return true;  |
| 317 | }  |
| 318 |   |
| 319 |   |
| 320 | bool tImageTGA::Set(tPicture& picture, bool steal)  |
| 321 | {  |
| 322 | Clear();  |
| 323 | if (!picture.IsValid())  |
| 324 | return false;  |
| 325 |   |
| 326 | PixelFormatSrc = picture.PixelFormatSrc;  |
| 327 | PixelFormat = tPixelFormat::R8G8B8A8;  |
| 328 | // We don't know colour profile of tPicture.  |
| 329 |   |
| 330 | // This is worth some explanation. If steal is true the picture becomes invalid and the  |
| 331 | // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the  |
| 332 | // 'set' call will memcpy them out... which makes sure the picture is still valid after and  |
| 333 | // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must  |
| 334 | // succeed if picture was valid.  |
| 335 | tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels();  |
| 336 | bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal);  |
| 337 | tAssert(success);  |
| 338 | return true;  |
| 339 | }  |
| 340 |   |
| 341 |   |
| 342 | tFrame* tImageTGA::GetFrame(bool steal)  |
| 343 | {  |
| 344 | if (!IsValid())  |
| 345 | return nullptr;  |
| 346 |   |
| 347 | tFrame* frame = new tFrame();  |
| 348 | frame->PixelFormatSrc = PixelFormatSrc;  |
| 349 |   |
| 350 | if (steal)  |
| 351 | {  |
| 352 | frame->StealFrom(src: Pixels, width: Width, height: Height);  |
| 353 | Pixels = nullptr;  |
| 354 | }  |
| 355 | else  |
| 356 | {  |
| 357 | frame->Set(srcPixels: Pixels, width: Width, height: Height);  |
| 358 | }  |
| 359 |   |
| 360 | return frame;  |
| 361 | }  |
| 362 |   |
| 363 |   |
| 364 | tImageTGA::tFormat tImageTGA::Save(const tString& tgaFile, tFormat format, tCompression compression) const  |
| 365 | {  |
| 366 | SaveParams params;  |
| 367 | params.Format = format;  |
| 368 | params.Compression = compression;  |
| 369 | return Save(tgaFile, params);  |
| 370 | }  |
| 371 |   |
| 372 |   |
| 373 | tImageTGA::tFormat tImageTGA::Save(const tString& tgaFile, const SaveParams& params) const  |
| 374 | {  |
| 375 | tFormat format = params.Format;  |
| 376 | if (!IsValid() || (format == tFormat::Invalid))  |
| 377 | return tFormat::Invalid;  |
| 378 |   |
| 379 | if (tSystem::tGetFileType(file: tgaFile) != tSystem::tFileType::TGA)  |
| 380 | return tFormat::Invalid;  |
| 381 |   |
| 382 | if (format == tFormat::Auto)  |
| 383 | {  |
| 384 | if (IsOpaque())  |
| 385 | format = tFormat::BPP24;  |
| 386 | else  |
| 387 | format = tFormat::BPP32;  |
| 388 | }  |
| 389 |   |
| 390 | bool success = false;  |
| 391 | switch (params.Compression)  |
| 392 | {  |
| 393 | case tCompression::None:  |
| 394 | success = SaveUncompressed(tgaFile, format);  |
| 395 | break;  |
| 396 |   |
| 397 | case tCompression::RLE:  |
| 398 | success = SaveCompressed(tgaFile, format);  |
| 399 | break;  |
| 400 | }  |
| 401 |   |
| 402 | if (!success)  |
| 403 | return tFormat::Invalid;  |
| 404 |   |
| 405 | return format;  |
| 406 | }  |
| 407 |   |
| 408 |   |
| 409 | bool tImageTGA::SaveUncompressed(const tString& tgaFile, tFormat format) const  |
| 410 | {  |
| 411 | if ((format != tFormat::BPP24) && (format != tFormat::BPP32))  |
| 412 | return false;  |
| 413 |   |
| 414 | tFileHandle file = tOpenFile(file: tgaFile.Chr(), mode: "wb" );  |
| 415 | if (!file)  |
| 416 | return false;  |
| 417 |   |
| 418 | uint8 bitDepth = (format == tFormat::BPP24) ? 24 : 32;  |
| 419 |   |
| 420 | // imageDesc has the following important fields:  |
| 421 | // Bits 0-3: Number of attribute bits associated with each pixel. For a 16bit image, this would be 0 or 1. For a  |
| 422 | // 24-bit image, it should be 0. For a 32-bit image, it should be 8.  |
| 423 | // Bit 4: Horizontal col flip. If set, the image is left-to-right.  |
| 424 | // Bit 5: Vertical row flip. If set, the image is upside down.  |
| 425 | uint8 imageDesc = 0x00;  |
| 426 | imageDesc |= (bitDepth == 24) ? 0 : 8;  |
| 427 |   |
| 428 | // We'll be writing a 24 or 32bit uncompressed tga.  |
| 429 | tPutc(ch: 0, file); // ID string length.  |
| 430 | tPutc(ch: 0, file); // Colour map type.  |
| 431 | tPutc(ch: 2, file); // 2 = Uncompressed True Colour (2=true colour + no compression bit). Not palletized.  |
| 432 | tPutc(ch: 0, file); tPutc(ch: 0, file);  |
| 433 | tPutc(ch: 0, file); tPutc(ch: 0, file);  |
| 434 | tPutc(ch: 0, file);  |
| 435 | tPutc(ch: 0, file); tPutc(ch: 0, file); // X origin.  |
| 436 | tPutc(ch: 0, file); tPutc(ch: 0, file); // Y origin.  |
| 437 | uint16 w = Width;  |
| 438 | uint16 h = Height;  |
| 439 | tPutc(ch: (w & 0x00FF), file); // Width.  |
| 440 | tPutc(ch: (w & 0xFF00) >> 8, file);  |
| 441 | tPutc(ch: (h & 0x00FF), file); // Height.  |
| 442 | tPutc(ch: (h & 0xFF00) >> 8, file);  |
| 443 | tPutc(ch: bitDepth, file); // 24 or 32 bit depth. RGB or RGBA.  |
| 444 | tPutc(ch: imageDesc, file); // Image desc. See above.  |
| 445 |   |
| 446 | // If we had a non-zero ID string length, we'd write length characters here.  |
| 447 | int numPixels = Width*Height;  |
| 448 | for (int p = 0; p < numPixels; p++)  |
| 449 | {  |
| 450 | tPixel4b& pixel = Pixels[p];  |
| 451 | tPutc(ch: pixel.B, file);  |
| 452 | tPutc(ch: pixel.G, file);  |
| 453 | tPutc(ch: pixel.R, file);  |
| 454 |   |
| 455 | if (format == tFormat::BPP32)  |
| 456 | tPutc(ch: pixel.A, file);  |
| 457 | }  |
| 458 |   |
| 459 | tCloseFile(f: file);  |
| 460 | return true;  |
| 461 | }  |
| 462 |   |
| 463 |   |
| 464 | bool tImageTGA::SaveCompressed(const tString& tgaFile, tFormat format) const  |
| 465 | {  |
| 466 | if ((format != tFormat::BPP24) && (format != tFormat::BPP32))  |
| 467 | return false;  |
| 468 |   |
| 469 | // Open the file.  |
| 470 | tFileHandle file = tOpenFile(file: tgaFile.Chr(), mode: "wb" );  |
| 471 | if (!file)  |
| 472 | return false;  |
| 473 |   |
| 474 | uint8 bitDepth = (format == tFormat::BPP24) ? 24 : 32;  |
| 475 | int bytesPerPixel = bitDepth / 8;  |
| 476 |   |
| 477 | // imageDesc has the following important fields:  |
| 478 | // Bits 0-3: Number of attribute bits associated with each pixel. For a 16bit image, this would be 0 or 1. For a  |
| 479 | // 24-bit image, it should be 0. For a 32-bit image, it should be 8.  |
| 480 | // Bit 5: Orientation. If set, the image is upside down.  |
| 481 | uint8 imageDesc = 0;  |
| 482 | imageDesc |= (bitDepth == 24) ? 0 : 8;  |
| 483 |   |
| 484 | // We'll be writing a 24 or 32bit compressed tga.  |
| 485 | tPutc(ch: 0, file); // ID string length.  |
| 486 | tPutc(ch: 0, file); // Colour map type.  |
| 487 | tPutc(ch: 10, file); // 10 = RLE Compressed True Colour (2=true colour + 8=RLE). Not palletized.  |
| 488 | tPutc(ch: 0, file); tPutc(ch: 0, file);  |
| 489 | tPutc(ch: 0, file); tPutc(ch: 0, file);  |
| 490 | tPutc(ch: 0, file);  |
| 491 |   |
| 492 | tPutc(ch: 0, file); tPutc(ch: 0, file); // X origin.  |
| 493 | tPutc(ch: 0, file); tPutc(ch: 0, file); // Y origin.  |
| 494 | uint16 w = Width;  |
| 495 | uint16 h = Height;  |
| 496 | tPutc(ch: (w & 0x00FF), file); // Width.  |
| 497 | tPutc(ch: (w & 0xFF00) >> 8, file);  |
| 498 | tPutc(ch: (h & 0x00FF), file); // Height.  |
| 499 | tPutc(ch: (h & 0xFF00) >> 8, file);  |
| 500 |   |
| 501 | tPutc(ch: bitDepth, file); // 24 or 32 bit depth. RGB or RGBA.  |
| 502 | tPutc(ch: imageDesc, file); // Image desc. See above.  |
| 503 |   |
| 504 | int numPixels = Height * Width;  |
| 505 | int index = 0;  |
| 506 | uint32 colour = 0;  |
| 507 | uint32* chunkBuffer = new uint32[128];  |
| 508 |   |
| 509 | // Now we write the pixel packets. Each packet is either raw or rle.  |
| 510 | while (index < numPixels)  |
| 511 | {  |
| 512 | bool rlePacket = false;  |
| 513 | tPixel4b& pixelColour = Pixels[index];  |
| 514 |   |
| 515 | // Note that we process alphas as zeros if we are writing 24bits only. This ensures the colour comparisons work  |
| 516 | // properly -- we ignore alpha. Zero is used because the uint32 colour values are initialized to all 0s.  |
| 517 | uint8 alpha = (bytesPerPixel == 4) ? pixelColour.A : 0;  |
| 518 | colour = pixelColour.B + (pixelColour.G << 8) + (pixelColour.R << 16) + (alpha << 24);  |
| 519 |   |
| 520 | chunkBuffer[0] = colour;  |
| 521 | int rleCount = 1;  |
| 522 |   |
| 523 | // We try to find repeating bytes with a minimum length of 2 pixels. Maximum repeating chunk size is 128 pixels  |
| 524 | // as the first bit of the count is used for the packet type.  |
| 525 | while (index + rleCount < numPixels)  |
| 526 | {  |
| 527 | tPixel4b& nextPixelColour = Pixels[index+rleCount];  |
| 528 | uint8 alp = (bytesPerPixel == 4) ? nextPixelColour.A : 0;  |
| 529 | uint32 nextCol = nextPixelColour.B + (nextPixelColour.G << 8) + (nextPixelColour.R << 16) + (alp << 24);  |
| 530 |   |
| 531 | if (colour != nextCol || rleCount == 128)  |
| 532 | {  |
| 533 | rlePacket = (rleCount > 1) ? true : false;  |
| 534 | break;  |
| 535 | }  |
| 536 | rleCount++;  |
| 537 | }  |
| 538 |   |
| 539 | if (rlePacket)  |
| 540 | {  |
| 541 | tPutc(ch: 128 | (rleCount - 1), file);  |
| 542 | tWriteFile(handle: file, buffer: &colour, sizeBytes: bytesPerPixel);  |
| 543 | }  |
| 544 | else  |
| 545 | {  |
| 546 | rleCount = 1;  |
| 547 | while (index + rleCount < numPixels)  |
| 548 | {  |
| 549 | tPixel4b& nextPixelColour = Pixels[index+rleCount];  |
| 550 | uint8 alp = (bytesPerPixel == 4) ? nextPixelColour.A : 0;  |
| 551 | uint32 nextCol = nextPixelColour.B + (nextPixelColour.G << 8) + (nextPixelColour.R << 16) + (alp << 24);  |
| 552 |   |
| 553 | if ((colour != nextCol && rleCount < 128) || rleCount < 3)  |
| 554 | {  |
| 555 | chunkBuffer[rleCount] = colour = nextCol;  |
| 556 | }  |
| 557 | else  |
| 558 | {  |
| 559 | // Check if the exit condition was the start of a repeating colour.  |
| 560 | if (colour == nextCol)  |
| 561 | rleCount -= 2;  |
| 562 | break;  |
| 563 | }  |
| 564 | rleCount++;  |
| 565 | }  |
| 566 |   |
| 567 | // Write the raw packet data.  |
| 568 | tPutc(ch: rleCount - 1, file);  |
| 569 | for (int i = 0; i < rleCount; i++)  |
| 570 | {  |
| 571 | colour = chunkBuffer[i];  |
| 572 | tWriteFile(handle: file, buffer: &colour, sizeBytes: bytesPerPixel);  |
| 573 | }  |
| 574 | }  |
| 575 | index += rleCount;  |
| 576 | }  |
| 577 |   |
| 578 | delete[] chunkBuffer;  |
| 579 | tCloseFile(f: file);  |
| 580 | return true;  |
| 581 | }  |
| 582 |   |
| 583 |   |
| 584 | bool tImageTGA::IsOpaque() const  |
| 585 | {  |
| 586 | for (int p = 0; p < (Width*Height); p++)  |
| 587 | {  |
| 588 | if (Pixels[p].A < 255)  |
| 589 | return false;  |
| 590 | }  |
| 591 |   |
| 592 | return true;  |
| 593 | }  |
| 594 |   |
| 595 |   |
| 596 | tPixel4b* tImageTGA::StealPixels()  |
| 597 | {  |
| 598 | tPixel4b* pixels = Pixels;  |
| 599 | Pixels = nullptr;  |
| 600 | Width = 0;  |
| 601 | Height = 0;  |
| 602 | return pixels;  |
| 603 | }  |
| 604 |   |
| 605 |   |
| 606 | }  |
| 607 | |