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" 
20using namespace tSystem
21namespace tImage 
22
23 
24 
25// Helper functions, enums, and types for parsing TGA files. 
26namespace tTGA 
27
28 #pragma pack(push, r1, 1) 
29 struct Header 
30
31 int8 IDLength
32 int8 ColourMapType
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 DataTypeCode
44 int16 ColourMapOrigin
45 int16 ColourMapLength
46 int8 ColourMapDepth
47 int16 OriginX
48 int16 OriginY
49 
50 int16 Width
51 int16 Height
52 int8 BitDepth
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 ImageDesc
58 bool IsFlippedH() const { return (ImageDesc & 0x10) ? true : false; } 
59 bool IsFlippedV() const { return (ImageDesc & 0x20) ? true : false; } 
60 }; 
61 #pragma pack(pop, r1) 
62 tStaticAssert(sizeof(Header) == 18); 
63
64 
65 
66bool 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 
85bool 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* 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 
238void 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 
273bool 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 
301bool 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 
320bool 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 
342tFrame* 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 
364tImageTGA::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 
373tImageTGA::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 
409bool 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 
464bool 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 
584bool 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 
596tPixel4b* tImageTGA::StealPixels() 
597
598 tPixel4b* pixels = Pixels
599 Pixels = nullptr
600 Width = 0
601 Height = 0
602 return pixels
603
604 
605 
606
607