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"  |
19 | using namespace tSystem;  |
20 | namespace tImage  |
21 | {  |
22 |   |
23 |   |
24 | bool 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 |   |
43 | bool 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   |
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* = (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 |   |
162 | void 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 |   |
194 | bool 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 |   |
218 | tImageTGA::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 |   |
253 | bool 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 |   |
307 | bool 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 |   |
427 | bool 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 |   |
439 | tPixel* tImageTGA::StealPixels()  |
440 | {  |
441 | tPixel* pixels = Pixels;  |
442 | Pixels = nullptr;  |
443 | Width = 0;  |
444 | Height = 0;  |
445 | return pixels;  |
446 | }  |
447 |   |
448 |   |
449 | }  |
450 | |