summaryrefslogtreecommitdiffstats
path: root/kimgio/tga.cpp
blob: 3a9d885158d0411fe637209a4a0941849870cfb8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/* This file is part of the KDE project
   Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
   Copyright (C) 2004 Ignacio Castaņo <castano@ludicon.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

/* this code supports:
 * reading:
 *     uncompressed and run length encoded indexed, grey and color tga files.
 *     image types 1, 2, 3, 9, 10 and 11.
 *     only RGB color maps with no more than 256 colors.
 *     pixel formats 8, 15, 24 and 32.
 * writing:
 *     uncompressed true color tga files
 */

#include "tga.h"

#include <assert.h>

#include <tqimage.h>
#include <tqdatastream.h>

#include <kdebug.h>

typedef TQ_UINT32 uint;
typedef TQ_UINT16 ushort;
typedef TQ_UINT8 uchar;

namespace {	// Private.

	// Header format of saved files.
	uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

	enum TGAType {
		TGA_TYPE_INDEXED		= 1,
		TGA_TYPE_RGB			= 2,
		TGA_TYPE_GREY			= 3,
		TGA_TYPE_RLE_INDEXED	= 9,
		TGA_TYPE_RLE_RGB		= 10,
		TGA_TYPE_RLE_GREY		= 11
	};

#define TGA_INTERLEAVE_MASK	0xc0
#define TGA_INTERLEAVE_NONE	0x00
#define TGA_INTERLEAVE_2WAY	0x40
#define TGA_INTERLEAVE_4WAY	0x80

#define TGA_ORIGIN_MASK		0x30
#define TGA_ORIGIN_LEFT		0x00
#define TGA_ORIGIN_RIGHT	0x10
#define TGA_ORIGIN_LOWER	0x00
#define TGA_ORIGIN_UPPER	0x20

	/** Tga Header. */
	struct TgaHeader {
		uchar id_length;
		uchar colormap_type;
		uchar image_type;
		ushort colormap_index;
		ushort colormap_length;
		uchar colormap_size;
		ushort x_origin;
		ushort y_origin;
		ushort width;
		ushort height;
		uchar pixel_size;
		uchar flags;
	
		enum { SIZE = 18 }; // const static int SIZE = 18;
	};

	static TQDataStream & operator>> ( TQDataStream & s, TgaHeader & head )
	{
		s >> head.id_length;
		s >> head.colormap_type;
		s >> head.image_type;
		s >> head.colormap_index;
		s >> head.colormap_length;
		s >> head.colormap_size;
		s >> head.x_origin;
		s >> head.y_origin;
		s >> head.width;
		s >> head.height;
		s >> head.pixel_size;
		s >> head.flags;
		return s;
	}

	static bool IsSupported( const TgaHeader & head )
	{
		if( head.image_type != TGA_TYPE_INDEXED &&
			head.image_type != TGA_TYPE_RGB &&
			head.image_type != TGA_TYPE_GREY &&
			head.image_type != TGA_TYPE_RLE_INDEXED &&
			head.image_type != TGA_TYPE_RLE_RGB &&
			head.image_type != TGA_TYPE_RLE_GREY )
		{
			return false;
		}
		if( head.image_type == TGA_TYPE_INDEXED ||
			head.image_type == TGA_TYPE_RLE_INDEXED )
		{
			if( head.colormap_length > 256 || head.colormap_size != 24 )
			{
				return false;
			}
		}
		if( head.width == 0 || head.height == 0 )
		{
			return false;
		}
		if( head.pixel_size != 8 && head.pixel_size != 16 &&
			head.pixel_size != 24 && head.pixel_size != 32 )
		{
			return false;
		}
		return true;
	}

	struct Color555 {
		ushort b : 5;
		ushort g : 5;
		ushort r : 5;
	};
	
	struct TgaHeaderInfo {
		bool rle;
		bool pal;
		bool rgb;
		bool grey;
		bool supported;
	
		TgaHeaderInfo( const TgaHeader & tga ) : rle(false), pal(false), rgb(false), grey(false), supported(true)
		{
			switch( tga.image_type ) {
				case TGA_TYPE_RLE_INDEXED:
					rle = true;
					// no break is intended!
				case TGA_TYPE_INDEXED:
					if( tga.colormap_type!=1 || tga.colormap_size!=24 || tga.colormap_length>256 ) {
						supported = false;
					}
					pal = true;
					break;
		
				case TGA_TYPE_RLE_RGB:
					rle = true;
					// no break is intended!
				case TGA_TYPE_RGB:
					rgb = true;
					break;
		
				case TGA_TYPE_RLE_GREY:
					rle = true;
					// no break is intended!
				case TGA_TYPE_GREY:
					grey = true;
					break;
		
				default:
					// Error, unknown image type.
					supported = false;
			}
		}
	};

	static bool LoadTGA( TQDataStream & s, const TgaHeader & tga, TQImage &img )
	{
		// Create image.
		if( !img.create( tga.width, tga.height, 32 )) {
			return false;
		}

		TgaHeaderInfo info(tga);
		if( !info.supported ) {
			// File not supported.
  			kdDebug(399) << "This TGA file is not supported." << endl;
			return false;
		}
		
                // Bits 0-3 are the numbers of alpha bits (can be zero!)
                const int numAlphaBits = tga.flags & 0xf;
                // However alpha exists only in the 32 bit format.
		if( ( tga.pixel_size == 32 ) && ( tga.flags & 0xf ) ) {
			img.setAlphaBuffer( true );
		}

		uint pixel_size = (tga.pixel_size/8);
		uint size = tga.width * tga.height * pixel_size;

		if (size < 1)
		{
			kdDebug(399) << "This TGA file is broken with size " << size << endl;
			return false;
		}

		
		// Read palette.
		char palette[768];
		if( info.pal ) {
			// @todo Support palettes in other formats!
			s.readRawBytes( palette, 3 * tga.colormap_length );
		}

		// Allocate image.
		uchar * const image = new uchar[size];

		if( info.rle ) {
			// Decode image.
			char * dst = (char *)image;
			int num = size;
	
			while (num > 0) {
				// Get packet header.
				uchar c; 
				s >> c;
	
				uint count = (c & 0x7f) + 1;
				num -= count * pixel_size;
	
				if (c & 0x80) {
					// RLE pixels.
                                        assert(pixel_size <= 8);
					char pixel[8];
					s.readRawBytes( pixel, pixel_size );
					do {
						memcpy(dst, pixel, pixel_size);
						dst += pixel_size;
					} while (--count);
				}
				else {
					// Raw pixels.
					count *= pixel_size;
					s.readRawBytes( dst, count );
					dst += count;
				}
			}
		}
		else {
			// Read raw image.
			s.readRawBytes( (char *)image, size );
		}

		// Convert image to internal format.				
		int y_start, y_step, y_end;
		if( tga.flags & TGA_ORIGIN_UPPER ) {
			y_start = 0;
			y_step = 1;
			y_end = tga.height;
		}
		else {
			y_start = tga.height - 1;
			y_step = -1;
			y_end = -1;
		}

		uchar * src = image;
               
		for( int y = y_start; y != y_end; y += y_step ) {
			QRgb * scanline = (QRgb *) img.scanLine( y );
		
			if( info.pal ) {
				// Paletted.
				for( int x = 0; x < tga.width; x++ ) {
					uchar idx = *src++;
					scanline[x] = tqRgb( palette[3*idx+2], palette[3*idx+1], palette[3*idx+0] );
				}
			}
			else if( info.grey ) {
				// Greyscale.
				for( int x = 0; x < tga.width; x++ ) {
					scanline[x] = tqRgb( *src, *src, *src );
					src++;
				}
			}
			else {
				// True Color.
				if( tga.pixel_size == 16 ) {
					for( int x = 0; x < tga.width; x++ ) {
						Color555 c = *reinterpret_cast<Color555 *>(src);
						scanline[x] = tqRgb( (c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2) );
						src += 2;
					}
				}
				else if( tga.pixel_size == 24 ) {
					for( int x = 0; x < tga.width; x++ ) {
						scanline[x] = tqRgb( src[2], src[1], src[0] );
						src += 3;
					}
				}
				else if( tga.pixel_size == 32 ) {
					for( int x = 0; x < tga.width; x++ ) {
                                                // ### TODO: verify with images having really some alpha data
                                                const uchar alpha = ( src[3] << ( 8 - numAlphaBits ) );
						scanline[x] = tqRgba( src[2], src[1], src[0], alpha );
						src += 4;
					}
				}
			}
		}

		// Free image.
		delete [] image;
		
		return true;
	}
	
} // namespace


KDE_EXPORT void kimgio_tga_read( TQImageIO *io )
{
	//kdDebug(399) << "Loading TGA file!" << endl;
	
	TQDataStream s( io->ioDevice() );
	s.setByteOrder( TQDataStream::LittleEndian );


	// Read image header.
	TgaHeader tga;
	s >> tga;
	s.device()->at( TgaHeader::SIZE + tga.id_length );

	// Check image file format.
	if( s.atEnd() ) {
		kdDebug(399) << "This TGA file is not valid." << endl;
		io->setImage( TQImage() );
		io->setStatus( -1 );
		return;
	}

	// Check supported file types.
	if( !IsSupported(tga) ) {
		kdDebug(399) << "This TGA file is not supported." << endl;
		io->setImage( TQImage() );
		io->setStatus( -1 );
		return;
	}
				

	TQImage img;
	bool result = LoadTGA(s, tga, img);
		
	if( result == false ) {
  		kdDebug(399) << "Error loading TGA file." << endl;
		io->setImage( TQImage() );
		io->setStatus( -1 );
		return;
	}


    io->setImage( img );
    io->setStatus( 0 );
}


KDE_EXPORT void kimgio_tga_write( TQImageIO *io )
{
    TQDataStream s( io->ioDevice() );
    s.setByteOrder( TQDataStream::LittleEndian );

    const TQImage img = io->image();
    const bool hasAlpha = img.hasAlphaBuffer();
    for( int i = 0; i < 12; i++ )
        s << targaMagic[i];

    // write header
    s << TQ_UINT16( img.width() ); // width
    s << TQ_UINT16( img.height() ); // height
    s << TQ_UINT8( hasAlpha ? 32 : 24 ); // depth (24 bit RGB + 8 bit alpha)
    s << TQ_UINT8( hasAlpha ? 0x24 : 0x20 ); // top left image (0x20) + 8 bit alpha (0x4)

    for( int y = 0; y < img.height(); y++ )
        for( int x = 0; x < img.width(); x++ ) {
            const QRgb color = img.pixel( x, y );
            s << TQ_UINT8( tqBlue( color ) );
            s << TQ_UINT8( tqGreen( color ) );
            s << TQ_UINT8( tqRed( color ) );
            if( hasAlpha )
                s << TQ_UINT8( tqAlpha( color ) );
        }

    io->setStatus( 0 );
}