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 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" 
19using namespace tSystem
20namespace tImage 
21
22 
23 
24bool tImageTGA::Load(const tString& tgaFile
25
26 Clear(); 
27 
28 if (tSystem::tGetFileType(tgaFile) != tSystem::tFileType::TGA
29 return false
30 
31 if (!tFileExists(tgaFile)) 
32 return false
33 
34 int numBytes = 0
35 uint8* tgaFileInMemory = tLoadFile(tgaFile, nullptr, &numBytes); 
36 bool success = Set(tgaFileInMemory, numBytes); 
37 delete[] tgaFileInMemory
38 
39 return success
40
41 
42 
43bool tImageTGA::Set(const uint8* tgaFileInMemory, int numBytes
44
45 Clear(); 
46 if ((numBytes <= 0) || !tgaFileInMemory
47 return false
48 
49 #pragma pack(push, r1, 1) 
50 struct TGAHeader 
51
52 int8 IDLength
53 int8 ColourMapType
54 int8 DataTypeCode
55 int16 ColourMapOrigin
56 int16 ColourMapLength
57 int8 ColourMapDepth
58 int16 OriginX
59 int16 OriginY
60 
61 int16 Width
62 int16 Height
63 int8 BitDepth
64 
65 // If Bit 5 of orientation is set the image will be upside down (like BMP). 
66 int8 Orientation
67 }; 
68 #pragma pack(pop, r1) 
69 tStaticAssert(sizeof(TGAHeader) == 18); 
70 
71 TGAHeader* header = (TGAHeader*)tgaFileInMemory
72 Width = header->Width
73 Height = header->Height
74 int bitDepth = header->BitDepth
75 int dataType = header->DataTypeCode
76 
77 // We support 16, 24, and 32 bit depths. We support data type mode 2 (uncompressed RGB) and mode 10 (Run-length 
78 // encoded RLE RGB). We allow a colour map to be present, but don't use it. 
79 if 
80
81 ((bitDepth != 16) && (bitDepth != 24) && (bitDepth != 32)) || 
82 ((dataType != 2) && (dataType != 10)) || 
83 ((header->ColourMapType != 0) && (header->ColourMapType != 1)) 
84
85
86 Clear(); 
87 return false
88
89 SrcPixelFormat = tPixelFormat::R8G8B8A8
90 if (bitDepth == 16
91 SrcPixelFormat = tPixelFormat::G3B5A1R5G2
92 else if (bitDepth == 24
93 SrcPixelFormat = tPixelFormat::R8G8B8
94 uint8* srcData = (uint8*)(header + 1); 
95 
96 // These usually are zero. In most cases the pixel data will follow directly after the header. iColourMapType is a 
97 // boolean 0 or 1. 
98 srcData += header->IDLength
99 srcData += header->ColourMapType * header->ColourMapLength
100 
101 int numPixels = Width * Height
102 Pixels = new tPixel[numPixels]; 
103 
104 // Read the image data. 
105 int bytesPerPixel = bitDepth >> 3
106 int pixel = 0
107 
108 while (pixel < numPixels
109
110 switch (dataType
111
112 case 10
113
114 // Image data is compressed. 
115 int j = srcData[0] & 0x7f
116 uint8 rleChunk = srcData[0] & 0x80
117 
118 tColouri firstColour
119 ReadColourBytes(firstColour, srcData+1, bytesPerPixel); 
120 ReadColourBytes(Pixels[pixel], srcData+1, bytesPerPixel); 
121 pixel++; 
122 srcData += bytesPerPixel+1
123 
124 if (rleChunk
125
126 // Chunk is run length encoded. 
127 for (int i = 0; i < j; i++) 
128
129 Pixels[pixel] = firstColour
130 pixel++; 
131
132
133 else 
134
135 // Chunk is normal. 
136 for (int i = 0; i < j; i++) 
137
138 ReadColourBytes(Pixels[pixel], srcData, bytesPerPixel); 
139 pixel++; 
140 srcData += bytesPerPixel
141
142
143 break
144
145 
146 case 2
147 default
148
149 // Not compressed. 
150 ReadColourBytes(Pixels[pixel], srcData, bytesPerPixel); 
151 pixel++; 
152 srcData += bytesPerPixel
153 break
154
155
156
157 
158 return true
159
160 
161 
162void tImageTGA::ReadColourBytes(tColouri& dest, const uint8* src, int bytesPerPixel
163
164 switch (bytesPerPixel
165
166 case 4
167 dest.R = src[2]; 
168 dest.G = src[1]; 
169 dest.B = src[0]; 
170 dest.A = src[3]; 
171 break
172 
173 case 3
174 dest.R = src[2]; 
175 dest.G = src[1]; 
176 dest.B = src[0]; 
177 dest.A = 0xFF
178 break
179 
180 case 2
181 dest.R = (src[1] & 0x7c) << 1
182 dest.G = ((src[1] & 0x03) << 6) | ((src[0] & 0xe0) >> 2); 
183 dest.B = (src[0] & 0x1f) << 3
184 dest.A = (src[1] & 0x80); 
185 break
186 
187 default
188 dest.MakeBlack(); 
189 break
190
191
192 
193 
194bool tImageTGA::Set(tPixel* pixels, int width, int height, bool steal
195
196 Clear(); 
197 if (!pixels || (width <= 0) || (height <= 0)) 
198 return false
199 
200 Width = width
201 Height = height
202 
203 if (steal
204
205 Pixels = pixels
206
207 else 
208
209 Pixels = new tPixel[Width*Height]; 
210 tStd::tMemcpy(Pixels, pixels, Width*Height*sizeof(tPixel)); 
211
212 
213 SrcPixelFormat = tPixelFormat::R8G8B8A8
214 return true
215
216 
217 
218tImageTGA::tFormat tImageTGA::Save(const tString& tgaFile, tFormat format, tCompression compression) const 
219
220 if (!IsValid() || (format == tFormat::Invalid)) 
221 return tFormat::Invalid
222 
223 if (tSystem::tGetFileType(tgaFile) != tSystem::tFileType::TGA
224 return tFormat::Invalid
225 
226 if (format == tFormat::Auto
227
228 if (IsOpaque()) 
229 format = tFormat::Bit24
230 else 
231 format = tFormat::Bit32
232
233 
234 bool success = false
235 switch (compression
236
237 case tCompression::None
238 success = SaveUncompressed(tgaFile, format); 
239 break
240 
241 case tCompression::RLE
242 success = SaveCompressed(tgaFile, format); 
243 break
244
245 
246 if (!success
247 return tFormat::Invalid
248 
249 return format
250
251 
252 
253bool tImageTGA::SaveUncompressed(const tString& tgaFile, tFormat format) const 
254
255 if ((format != tFormat::Bit24) && (format != tFormat::Bit32)) 
256 return false
257  
258 tFileHandle file = tOpenFile(tgaFile.ConstText(), "wb"); 
259 if (!file
260 return false
261 
262 uint8 bitDepth = (format == tFormat::Bit24) ? 24 : 32
263 
264 // imageDesc has the following important fields: 
265 // Bits 0-3: Number of attribute bits associated with each pixel. For a 16bit image, this would be 0 or 1. For a 
266 // 24-bit image, it should be 0. For a 32-bit image, it should be 8. 
267 // Bit 5: Orientation. If set, the image is upside down. 
268 uint8 imageDesc = 0x00
269 imageDesc |= (bitDepth == 24) ? 0 : 8
270 
271 // We'll be writing a 24 or 32bit uncompressed tga. 
272 tPutc(0, file); // ID string length. 
273 tPutc(0, file); // Colour map type. 
274 tPutc(2, file); // 2 = Uncompressed True Colour (2=true colour + no compression bit). Not palletized. 
275 tPutc(0, file); tPutc(0, file); 
276 tPutc(0, file); tPutc(0, file); 
277 tPutc(0, file); 
278 tPutc(0, file); tPutc(0, file); // X origin. 
279 tPutc(0, file); tPutc(0, file); // Y origin. 
280 uint16 w = Width
281 uint16 h = Height
282 tPutc((w & 0x00FF), file); // Width. 
283 tPutc((w & 0xFF00) >> 8, file); 
284 tPutc((h & 0x00FF), file); // Height. 
285 tPutc((h & 0xFF00) >> 8, file); 
286 tPutc(bitDepth, file); // 24 or 32 bit depth. RGB or RGBA. 
287 tPutc(imageDesc, file); // Image desc. See above. 
288 
289 // If we had a non-zero ID string length, we'd write length characters here. 
290 int numPixels = Width*Height
291 for (int p = 0; p < numPixels; p++) 
292
293 tPixel& pixel = Pixels[p]; 
294 tPutc(pixel.B, file); 
295 tPutc(pixel.G, file); 
296 tPutc(pixel.R, file); 
297 
298 if (format == tFormat::Bit32
299 tPutc(pixel.A, file); 
300
301 
302 tCloseFile(file); 
303 return true
304
305 
306 
307bool tImageTGA::SaveCompressed(const tString& tgaFile, tFormat format) const 
308
309 if ((format != tFormat::Bit24) && (format != tFormat::Bit32)) 
310 return false
311 
312 // Open the file. 
313 tFileHandle file = tOpenFile(tgaFile.ConstText(), "wb"); 
314 if (!file
315 return false
316 
317 uint8 bitDepth = (format == tFormat::Bit24) ? 24 : 32
318 int bytesPerPixel = bitDepth / 8
319 
320 // imageDesc has the following important fields: 
321 // Bits 0-3: Number of attribute bits associated with each pixel. For a 16bit image, this would be 0 or 1. For a 
322 // 24-bit image, it should be 0. For a 32-bit image, it should be 8. 
323 // Bit 5: Orientation. If set, the image is upside down. 
324 uint8 imageDesc = 0
325 imageDesc |= (bitDepth == 24) ? 0 : 8
326 
327 // We'll be writing a 24 or 32bit compressed tga. 
328 tPutc(0, file); // ID string length. 
329 tPutc(0, file); // Colour map type. 
330 tPutc(10, file); // 10 = RLE Compressed True Colour (2=true colour + 8=RLE). Not palletized. 
331 tPutc(0, file); tPutc(0, file); 
332 tPutc(0, file); tPutc(0, file); 
333 tPutc(0, file); 
334 
335 tPutc(0, file); tPutc(0, file); // X origin. 
336 tPutc(0, file); tPutc(0, file); // Y origin. 
337 uint16 w = Width
338 uint16 h = Height
339 tPutc((w & 0x00FF), file); // Width. 
340 tPutc((w & 0xFF00) >> 8, file); 
341 tPutc((h & 0x00FF), file); // Height. 
342 tPutc((h & 0xFF00) >> 8, file); 
343 
344 tPutc(bitDepth, file); // 24 or 32 bit depth. RGB or RGBA. 
345 tPutc(imageDesc, file); // Image desc. See above. 
346 
347 int numPixels = Height * Width
348 int index = 0
349 uint32 colour = 0
350 uint32* chunkBuffer = new uint32[128]; 
351 
352 // Now we write the pixel packets. Each packet is either raw or rle. 
353 while (index < numPixels
354
355 bool rlePacket = false
356 tPixel& pixelColour = Pixels[index]; 
357 
358 // Note that we process alphas as zeros if we are writing 24bits only. This ensures the colour comparisons work 
359 // properly -- we ignore alpha. Zero is used because the uint32 colour values are initialized to all 0s. 
360 uint8 alpha = (bytesPerPixel == 4) ? pixelColour.A : 0
361 colour = pixelColour.B + (pixelColour.G << 8) + (pixelColour.R << 16) + (alpha << 24); 
362 
363 chunkBuffer[0] = colour
364 int rleCount = 1
365 
366 // We try to find repeating bytes with a minimum length of 2 pixels. Maximum repeating chunk size is 128 pixels 
367 // as the first bit of the count is used for the packet type. 
368 while (index + rleCount < numPixels
369
370 tPixel& nextPixelColour = Pixels[index+rleCount]; 
371 uint8 alp = (bytesPerPixel == 4) ? nextPixelColour.A : 0
372 uint32 nextCol = nextPixelColour.B + (nextPixelColour.G << 8) + (nextPixelColour.R << 16) + (alp << 24); 
373 
374 if (colour != nextCol || rleCount == 128
375
376 rlePacket = (rleCount > 1) ? true : false
377 break
378
379 rleCount++; 
380
381 
382 if (rlePacket
383
384 tPutc(128 | (rleCount - 1), file); 
385 tWriteFile(file, &colour, bytesPerPixel); 
386
387 else 
388
389 rleCount = 1
390 while (index + rleCount < numPixels
391
392 tPixel& nextPixelColour = Pixels[index+rleCount]; 
393 uint8 alp = (bytesPerPixel == 4) ? nextPixelColour.A : 0
394 uint32 nextCol = nextPixelColour.B + (nextPixelColour.G << 8) + (nextPixelColour.R << 16) + (alp << 24); 
395 
396 if ((colour != nextCol && rleCount < 128) || rleCount < 3
397
398 chunkBuffer[rleCount] = colour = nextCol
399
400 else 
401
402 // Check if the exit condition was the start of a repeating colour. 
403 if (colour == nextCol
404 rleCount -= 2
405 break
406
407 rleCount++; 
408
409 
410 // Write the raw packet data. 
411 tPutc(rleCount - 1, file); 
412 for (int i = 0; i < rleCount; i++) 
413
414 colour = chunkBuffer[i]; 
415 tWriteFile(file, &colour, bytesPerPixel); 
416
417
418 index += rleCount
419
420 
421 delete[] chunkBuffer
422 tCloseFile(file); 
423 return true
424
425 
426 
427bool tImageTGA::IsOpaque() const 
428
429 for (int p = 0; p < (Width*Height); p++) 
430
431 if (Pixels[p].A < 255
432 return false
433
434 
435 return true
436
437 
438 
439tPixel* tImageTGA::StealPixels() 
440
441 tPixel* pixels = Pixels
442 Pixels = nullptr
443 Width = 0
444 Height = 0
445 return pixels
446
447 
448 
449
450