Allow loading 8-bit TGA images when wxUSE_PALETTE is set to 0.
[wxWidgets.git] / src / common / imagtga.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: imagtga.cpp
3 // Purpose: wxImage TGA handler
4 // Author: Seth Jackson
5 // CVS-ID: $Id$
6 // Copyright: (c) 2005 Seth Jackson
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #if wxUSE_IMAGE && wxUSE_TGA
26
27 #ifndef WX_PRECOMP
28 #include "wx/palette.h"
29 #endif
30
31 #include "wx/imagtga.h"
32 #include "wx/log.h"
33 #include "wx/scopeguard.h"
34
35 // ----------------------------------------------------------------------------
36 // constants
37 // ----------------------------------------------------------------------------
38
39 // TGA error codes.
40 enum
41 {
42 wxTGA_OK,
43 wxTGA_INVFORMAT,
44 wxTGA_MEMERR,
45 wxTGA_IOERR
46 };
47
48 // TGA header bytes.
49 enum
50 {
51 HDR_OFFSET = 0,
52 HDR_COLORTYPE = 1,
53 HDR_IMAGETYPE = 2,
54 HDR_PALETTESTART = 3,
55 HDR_PALETTELENGTH = 5,
56 HDR_PALETTEBITS = 7,
57 HDR_XORIGIN = 8,
58 HDR_YORIGIN = 10,
59 HDR_WIDTH = 12,
60 HDR_HEIGHT = 14,
61 HDR_BPP = 16,
62 HDR_ORIENTATION = 17,
63 HDR_SIZE
64 };
65
66 // TGA color types.
67 enum
68 {
69 wxTGA_UNMAPPED = 0,
70 wxTGA_MAPPED = 1
71 };
72
73 // ============================================================================
74 // implementation
75 // ============================================================================
76
77 IMPLEMENT_DYNAMIC_CLASS(wxTGAHandler, wxImageHandler)
78
79 #if wxUSE_STREAMS
80
81 // ----------------------------------------------------------------------------
82 // worker functions
83 // ----------------------------------------------------------------------------
84
85 static
86 void FlipTGA(unsigned char* imageData, int width, int height, short pixelSize)
87 {
88 int lineLength = width * pixelSize;
89 unsigned char *line1 = imageData;
90 unsigned char *line2 = &imageData[lineLength * (height - 1)];
91
92 unsigned char temp;
93 for ( ; line1 < line2; line2 -= (lineLength * 2))
94 {
95 for (int index = 0; index < lineLength; line1++, line2++, index++)
96 {
97 temp = *line1;
98 *line1 = *line2;
99 *line2 = temp;
100 }
101 }
102 }
103
104 // return wxTGA_OK or wxTGA_IOERR
105 static
106 int DecodeRLE(unsigned char* imageData, unsigned long imageSize,
107 short pixelSize, wxInputStream& stream)
108 {
109 unsigned long index = 0;
110 unsigned char current;
111 unsigned int length;
112 unsigned char buf[4];
113
114 while (index < imageSize)
115 {
116 int ch = stream.GetC();
117 if ( ch == wxEOF )
118 return wxTGA_IOERR;
119
120 current = ch;
121
122 // RLE packet.
123 if ( current & 0x80 )
124 {
125 // Get the run length of the packet.
126 current &= 0x7f;
127
128 current++;
129
130 length = current;
131
132 index += current * pixelSize;
133
134 // Repeat the pixel length times.
135 if ( !stream.Read(buf, pixelSize) )
136 return wxTGA_IOERR;
137
138 for (unsigned int i = 0; i < length; i++)
139 {
140 memcpy(imageData, buf, pixelSize);
141
142 imageData += pixelSize;
143 }
144 }
145 else // Raw packet.
146 {
147 // Get the run length of the packet.
148 current++;
149
150 length = current * pixelSize;
151
152 index += length;
153
154 // Write the next length pixels directly to the image data.
155 if ( !stream.Read(imageData, length) )
156 return wxTGA_IOERR;
157
158 imageData += length;
159 }
160 }
161
162 return wxTGA_OK;
163 }
164
165 /*
166 Mimic the behaviour of wxPalette.GetRGB and the way the TGA image handler
167 used it. That is: don't check the return value of GetRGB and continue decoding
168 using previous RGB values.
169
170 It might be better to check for palette index bounds and stop decoding if
171 it's out of range (and add something like wxTGA_DATAERR to indicate unexpected
172 pixel data).
173 */
174 static
175 void Palette_GetRGB(const unsigned char *palette, unsigned int paletteCount,
176 unsigned int index,
177 unsigned char *red, unsigned char *green, unsigned char *blue)
178 {
179 if (index >= paletteCount)
180 {
181 return;
182 }
183
184 *red = palette[index];
185 *green = palette[(paletteCount * 1) + index];
186 *blue = palette[(paletteCount * 2) + index];
187 }
188
189 static
190 void Palette_SetRGB(unsigned char *palette, unsigned int paletteCount,
191 unsigned int index,
192 unsigned char red, unsigned char green, unsigned char blue)
193 {
194 palette[index] = red;
195 palette[(paletteCount * 1) + index] = green;
196 palette[(paletteCount * 2) + index] = blue;
197 }
198
199 static
200 int ReadTGA(wxImage* image, wxInputStream& stream)
201 {
202 // Read in the TGA header
203 unsigned char hdr[HDR_SIZE];
204 stream.Read(hdr, HDR_SIZE);
205
206 short offset = hdr[HDR_OFFSET] + HDR_SIZE;
207 short colorType = hdr[HDR_COLORTYPE];
208 short imageType = hdr[HDR_IMAGETYPE];
209 unsigned int paletteLength = hdr[HDR_PALETTELENGTH]
210 + 256 * hdr[HDR_PALETTELENGTH + 1];
211 int width = (hdr[HDR_WIDTH] + 256 * hdr[HDR_WIDTH + 1]) -
212 (hdr[HDR_XORIGIN] + 256 * hdr[HDR_XORIGIN + 1]);
213 int height = (hdr[HDR_HEIGHT] + 256 * hdr[HDR_HEIGHT + 1]) -
214 (hdr[HDR_YORIGIN] + 256 * hdr[HDR_YORIGIN + 1]);
215 short bpp = hdr[HDR_BPP];
216 short orientation = hdr[HDR_ORIENTATION] & 0x20;
217
218 image->Create(width, height);
219
220 if (!image->Ok())
221 {
222 return wxTGA_MEMERR;
223 }
224
225 const short pixelSize = bpp / 8;
226
227 const unsigned long imageSize = width * height * pixelSize;
228
229 unsigned char *imageData = (unsigned char* )malloc(imageSize);
230
231 if (!imageData)
232 {
233 return wxTGA_MEMERR;
234 }
235
236 wxON_BLOCK_EXIT1(free, imageData);
237
238 unsigned char *dst = image->GetData();
239
240 unsigned char* alpha = NULL;
241 if (bpp == 16 || bpp == 32)
242 {
243 image->SetAlpha();
244
245 alpha = image->GetAlpha();
246 }
247
248 // Seek from the offset we got from the TGA header.
249 if (stream.SeekI(offset, wxFromStart) == wxInvalidOffset)
250 return wxTGA_INVFORMAT;
251
252 unsigned char *palette = NULL;
253 // Load a palette if we have one.
254 if (colorType == wxTGA_MAPPED)
255 {
256 unsigned char buf[3];
257
258 palette = (unsigned char *) malloc(paletteLength * 3);
259
260 for (unsigned int i = 0; i < paletteLength; i++)
261 {
262 stream.Read(buf, 3);
263
264 Palette_SetRGB(palette, paletteLength, i, buf[2], buf[1], buf[0]);
265 }
266
267 #if wxUSE_PALETTE
268 // Set the palette of the image.
269 image->SetPalette(wxPalette((int) paletteLength, &palette[0],
270 &palette[paletteLength * 1], &palette[paletteLength * 2]));
271 #endif // wxUSE_PALETTE
272
273 }
274
275 wxON_BLOCK_EXIT1(free, palette);
276
277 // Handle the various TGA formats we support.
278
279 switch (imageType)
280 {
281 // Raw indexed.
282
283 case 1:
284 {
285 unsigned char r;
286 unsigned char g;
287 unsigned char b;
288
289 // No compression read the data directly to imageData.
290
291 stream.Read(imageData, imageSize);
292
293 // If orientation == 0, then the image is stored upside down.
294 // We need to store it right side up.
295
296 if (orientation == 0)
297 {
298 FlipTGA(imageData, width, height, pixelSize);
299 }
300
301 // Handle the different pixel depths.
302
303 switch (bpp)
304 {
305 // 8 bpp.
306
307 case 8:
308 {
309 for (unsigned long index = 0; index < imageSize; index += pixelSize)
310 {
311 Palette_GetRGB(palette, paletteLength,
312 imageData[index], &r, &g, &b);
313
314 *(dst++) = r;
315 *(dst++) = g;
316 *(dst++) = b;
317 }
318 }
319 break;
320
321 // 16 bpp.
322
323 case 16:
324 {
325 for (unsigned long index = 0; index < imageSize; index += pixelSize)
326 {
327 Palette_GetRGB(palette, paletteLength,
328 imageData[index], &r, &g, &b);
329
330 *(dst++) = r;
331 *(dst++) = g;
332 *(dst++) = b;
333 *(alpha++) = (imageData[index + 1] & 0x80) ? 0 : 255;
334 }
335 }
336 break;
337
338 default:
339 return wxTGA_INVFORMAT;
340 }
341 }
342 break;
343
344 // Raw RGB.
345
346 case 2:
347 {
348 // No compression read the data directly to imageData.
349
350 stream.Read(imageData, imageSize);
351
352 // If orientation == 0, then the image is stored upside down.
353 // We need to store it right side up.
354
355 if (orientation == 0)
356 {
357 FlipTGA(imageData, width, height, pixelSize);
358 }
359
360 // Handle the different pixel depths.
361
362 switch (bpp)
363 {
364 //16 bpp.
365
366 case 16:
367 {
368 unsigned char temp;
369
370 for (unsigned long index = 0; index < imageSize; index += pixelSize)
371 {
372 temp = (imageData[index + 1] & 0x7c) << 1;
373 temp |= temp >> 5;
374 *(dst++) = temp;
375
376 temp = ((imageData[index + 1] & 0x03) << 6) | ((imageData[index] & 0xe0) >> 2);
377 temp |= temp >> 5;
378 *(dst++) = temp;
379
380 temp = (imageData[index] & 0x1f) << 3;
381 temp |= temp >> 5;
382 *(dst++) = temp;
383
384 *(alpha++) = (imageData[index + 1] & 0x80) ? 0 : 255;
385 }
386 }
387 break;
388
389 // 24 bpp.
390
391 case 24:
392 {
393 for (unsigned long index = 0; index < imageSize; index += pixelSize)
394 {
395 *(dst++) = imageData[index + 2];
396 *(dst++) = imageData[index + 1];
397 *(dst++) = imageData[index];
398 }
399 }
400 break;
401
402 // 32 bpp.
403
404 case 32:
405 {
406 for (unsigned long index = 0; index < imageSize; index += pixelSize)
407 {
408 *(dst++) = imageData[index + 2];
409 *(dst++) = imageData[index + 1];
410 *(dst++) = imageData[index];
411 *(alpha++) = imageData[index + 3];
412 }
413 }
414 break;
415
416 default:
417 return wxTGA_INVFORMAT;
418 }
419 }
420 break;
421
422 // Raw grayscale.
423
424 case 3:
425 {
426 // No compression read the data directly to imageData.
427
428 stream.Read(imageData, imageSize);
429
430 // If orientation == 0, then the image is stored upside down.
431 // We need to store it right side up.
432
433 if (orientation == 0)
434 {
435 FlipTGA(imageData, width, height, pixelSize);
436 }
437
438 // Handle the different pixel depths.
439
440 switch (bpp)
441 {
442 // 8 bpp.
443
444 case 8:
445 {
446 for (unsigned long index = 0; index < imageSize; index += pixelSize)
447 {
448 *(dst++) = imageData[index];
449 *(dst++) = imageData[index];
450 *(dst++) = imageData[index];
451 }
452 }
453 break;
454
455 // 16 bpp.
456
457 case 16:
458 {
459 for (unsigned long index = 0; index < imageSize; index += pixelSize)
460 {
461 *(dst++) = imageData[index];
462 *(dst++) = imageData[index];
463 *(dst++) = imageData[index];
464 *(alpha++) = imageData[index + 1];
465 }
466 }
467 break;
468
469 default:
470 return wxTGA_INVFORMAT;
471 }
472 }
473 break;
474
475 // RLE indexed.
476
477 case 9:
478 {
479 unsigned char r;
480 unsigned char g;
481 unsigned char b;
482
483 // Decode the RLE data.
484
485 int rc = DecodeRLE(imageData, imageSize, pixelSize, stream);
486 if ( rc != wxTGA_OK )
487 return rc;
488
489 // If orientation == 0, then the image is stored upside down.
490 // We need to store it right side up.
491
492 if (orientation == 0)
493 {
494 FlipTGA(imageData, width, height, pixelSize);
495 }
496
497 // Handle the different pixel depths.
498
499 switch (bpp)
500 {
501 // 8 bpp.
502
503 case 8:
504 {
505 for (unsigned long index = 0; index < imageSize; index += pixelSize)
506 {
507 Palette_GetRGB(palette, paletteLength,
508 imageData[index], &r, &g, &b);
509
510 *(dst++) = r;
511 *(dst++) = g;
512 *(dst++) = b;
513 }
514 }
515 break;
516
517 // 16 bpp.
518
519 case 16:
520 {
521 for (unsigned long index = 0; index < imageSize; index += pixelSize)
522 {
523 Palette_GetRGB(palette, paletteLength,
524 imageData[index], &r, &g, &b);
525
526 *(dst++) = r;
527 *(dst++) = g;
528 *(dst++) = b;
529 *(alpha++) = (imageData[index + 1] & 0x80) ? 0 : 255;
530 }
531 }
532 break;
533
534 default:
535 return wxTGA_INVFORMAT;
536 }
537 }
538 break;
539
540 // RLE RGB.
541
542 case 10:
543 {
544 // Decode the RLE data.
545
546 int rc = DecodeRLE(imageData, imageSize, pixelSize, stream);
547 if ( rc != wxTGA_OK )
548 return rc;
549
550 // If orientation == 0, then the image is stored upside down.
551 // We need to store it right side up.
552
553 if (orientation == 0)
554 {
555 FlipTGA(imageData, width, height, pixelSize);
556 }
557
558 // Handle the different pixel depths.
559
560 switch (bpp)
561 {
562 //16 bpp.
563
564 case 16:
565 {
566 unsigned char temp;
567
568 for (unsigned long index = 0; index < imageSize; index += pixelSize)
569 {
570 temp = (imageData[index + 1] & 0x7c) << 1;
571 temp |= temp >> 5;
572 *(dst++) = temp;
573
574 temp = ((imageData[index + 1] & 0x03) << 6) | ((imageData[index] & 0xe0) >> 2);
575 temp |= temp >> 5;
576 *(dst++) = temp;
577
578 temp = (imageData[index] & 0x1f) << 3;
579 temp |= temp >> 5;
580 *(dst++) = temp;
581
582 *(alpha++) = (imageData[index + 1] & 0x80) ? 0 : 255;
583 }
584 }
585 break;
586
587 // 24 bpp.
588
589 case 24:
590 {
591 for (unsigned long index = 0; index < imageSize; index += pixelSize)
592 {
593 *(dst++) = imageData[index + 2];
594 *(dst++) = imageData[index + 1];
595 *(dst++) = imageData[index];
596 }
597 }
598 break;
599
600 // 32 bpp.
601
602 case 32:
603 {
604 for (unsigned long index = 0; index < imageSize; index += pixelSize)
605 {
606 *(dst++) = imageData[index + 2];
607 *(dst++) = imageData[index + 1];
608 *(dst++) = imageData[index];
609 *(alpha++) = imageData[index + 3];
610 }
611 }
612 break;
613
614 default:
615 return wxTGA_INVFORMAT;
616 }
617 }
618 break;
619
620 // RLE grayscale.
621
622 case 11:
623 {
624 // Decode the RLE data.
625
626 int rc = DecodeRLE(imageData, imageSize, pixelSize, stream);
627 if ( rc != wxTGA_OK )
628 return rc;
629
630 // If orientation == 0, then the image is stored upside down.
631 // We need to store it right side up.
632
633 if (orientation == 0)
634 {
635 FlipTGA(imageData, width, height, pixelSize);
636 }
637
638 // Handle the different pixel depths.
639
640 switch (bpp)
641 {
642 // 8 bpp.
643
644 case 8:
645 {
646 for (unsigned long index = 0; index < imageSize; index += pixelSize)
647 {
648 *(dst++) = imageData[index];
649 *(dst++) = imageData[index];
650 *(dst++) = imageData[index];
651 }
652 }
653 break;
654
655 // 16 bpp.
656
657 case 16:
658 {
659 for (unsigned long index = 0; index < imageSize; index += pixelSize)
660 {
661 *(dst++) = imageData[index];
662 *(dst++) = imageData[index];
663 *(dst++) = imageData[index];
664 *(alpha++) = imageData[index + 1];
665 }
666 }
667 break;
668
669 default:
670 return wxTGA_INVFORMAT;
671 }
672 }
673 break;
674
675 default:
676 return wxTGA_INVFORMAT;
677 }
678
679 return wxTGA_OK;
680 }
681
682 static
683 int SaveTGA(const wxImage& image, wxOutputStream *stream)
684 {
685 bool hasAlpha = image.HasAlpha();
686 unsigned bytesPerPixel = 3 + (hasAlpha ? 1 : 0);
687 wxSize size = image.GetSize();
688 size_t scanlineSize = size.x * bytesPerPixel;
689 unsigned char *scanlineData = (unsigned char *) malloc(scanlineSize);
690 if (!scanlineData)
691 {
692 return wxTGA_MEMERR;
693 }
694
695 wxON_BLOCK_EXIT1(free, scanlineData);
696
697 // Compose and write the TGA header
698 unsigned char hdr[HDR_SIZE];
699 (void) memset(&hdr, 0, HDR_SIZE);
700
701 hdr[HDR_COLORTYPE] = wxTGA_UNMAPPED;
702 hdr[HDR_IMAGETYPE] = 2 /* Uncompressed truecolour */;
703
704 hdr[HDR_WIDTH] = size.x & 0xFF;
705 hdr[HDR_WIDTH + 1] = (size.x >> 8) & 0xFF;
706
707 hdr[HDR_HEIGHT] = size.y & 0xFF;
708 hdr[HDR_HEIGHT + 1] = (size.y >> 8) & 0xFF;
709
710 hdr[HDR_BPP] = hasAlpha ? 32 : 24;
711 hdr[HDR_ORIENTATION] = 1 << 5; // set bit to indicate top-down order
712 if (hasAlpha)
713 {
714 hdr[HDR_ORIENTATION] |= 8; // number of alpha bits
715 }
716
717 if ( !stream->Write(hdr, HDR_SIZE) )
718 {
719 return wxTGA_IOERR;
720 }
721
722
723 // Write image data, converting RGB to BGR and adding alpha if applicable
724
725 unsigned char *src = image.GetData();
726 unsigned char *alpha = image.GetAlpha();
727 for (int y = 0; y < size.y; ++y)
728 {
729 unsigned char *dst = scanlineData;
730 for (int x = 0; x < size.x; ++x)
731 {
732 dst[0] = src[2];
733 dst[1] = src[1];
734 dst[2] = src[0];
735 if (alpha)
736 {
737 dst[3] = *(alpha++);
738 }
739 src += 3;
740 dst += bytesPerPixel;
741 }
742 if ( !stream->Write(scanlineData, scanlineSize) )
743 {
744 return wxTGA_IOERR;
745 }
746 }
747
748 return wxTGA_OK;
749 }
750
751 // ----------------------------------------------------------------------------
752 // wxTGAHandler
753 // ----------------------------------------------------------------------------
754
755 bool wxTGAHandler::LoadFile(wxImage* image,
756 wxInputStream& stream,
757 bool verbose,
758 int WXUNUSED(index))
759 {
760 if ( !CanRead(stream) )
761 {
762 if ( verbose )
763 {
764 wxLogError(wxT("TGA: this is not a TGA file."));
765 }
766
767 return false;
768 }
769
770 image->Destroy();
771
772 int error = ReadTGA(image, stream);
773 if ( error != wxTGA_OK )
774 {
775 if ( verbose )
776 {
777 switch ( error )
778 {
779 case wxTGA_INVFORMAT:
780 wxLogError(wxT("TGA: image format unsupported."));
781 break;
782
783 case wxTGA_MEMERR:
784 wxLogError(wxT("TGA: couldn't allocate memory."));
785 break;
786
787 case wxTGA_IOERR:
788 wxLogError(wxT("TGA: couldn't read image data."));
789 break;
790
791 default:
792 wxLogError(wxT("TGA: unknown error!"));
793 }
794 }
795
796 image->Destroy();
797
798 return false;
799 }
800
801 return true;
802 }
803
804 bool wxTGAHandler::SaveFile(wxImage* image, wxOutputStream& stream, bool verbose)
805 {
806 int error = SaveTGA(*image, &stream);
807
808 if ( error != wxTGA_OK )
809 {
810 if ( verbose )
811 {
812 switch ( error )
813 {
814 case wxTGA_MEMERR:
815 wxLogError(wxT("TGA: couldn't allocate memory."));
816 break;
817
818 case wxTGA_IOERR:
819 wxLogError(wxT("TGA: couldn't write image data."));
820 break;
821
822 default:
823 wxLogError(wxT("TGA: unknown error!"));
824 }
825 }
826
827 return false;
828 }
829
830 return true;
831 }
832
833 bool wxTGAHandler::DoCanRead(wxInputStream& stream)
834 {
835 // read the fixed-size TGA headers
836 unsigned char hdr[HDR_SIZE];
837 stream.Read(hdr, HDR_SIZE); // it's ok to modify the stream position here
838
839 // Check wether we can read the file or not.
840
841 short colorType = hdr[HDR_COLORTYPE];
842 if ( colorType != wxTGA_UNMAPPED && colorType != wxTGA_MAPPED )
843 {
844 return false;
845 }
846
847 short imageType = hdr[HDR_IMAGETYPE];
848 if ( imageType == 0 || imageType == 32 || imageType == 33 )
849 {
850 return false;
851 }
852
853 short bpp = hdr[HDR_BPP];
854 if ( bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32 )
855 {
856 return false;
857 }
858
859 return true;
860 }
861
862 #endif // wxUSE_STREAMS
863
864 #endif // wxUSE_IMAGE && wxUSE_TGA