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