1/* This file is part of the KDE project
2 Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
3 Copyright (C) 2004 Ignacio CastaƱo <castano@ludicon.com>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the Lesser GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9*/
10
11/* this code supports:
12 * reading:
13 * uncompressed and run length encoded indexed, grey and color tga files.
14 * image types 1, 2, 3, 9, 10 and 11.
15 * only RGB color maps with no more than 256 colors.
16 * pixel formats 8, 15, 24 and 32.
17 * writing:
18 * uncompressed true color tga files
19 */
20
21#include "tga.h"
22
23#include <assert.h>
24
25#include <QtGui/QImage>
26#include <QtCore/QDataStream>
27
28#include <kdebug.h>
29
30typedef quint32 uint;
31typedef quint16 ushort;
32typedef quint8 uchar;
33
34namespace { // Private.
35
36 // Header format of saved files.
37 uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
38
39 enum TGAType {
40 TGA_TYPE_INDEXED = 1,
41 TGA_TYPE_RGB = 2,
42 TGA_TYPE_GREY = 3,
43 TGA_TYPE_RLE_INDEXED = 9,
44 TGA_TYPE_RLE_RGB = 10,
45 TGA_TYPE_RLE_GREY = 11
46 };
47
48#define TGA_INTERLEAVE_MASK 0xc0
49#define TGA_INTERLEAVE_NONE 0x00
50#define TGA_INTERLEAVE_2WAY 0x40
51#define TGA_INTERLEAVE_4WAY 0x80
52
53#define TGA_ORIGIN_MASK 0x30
54#define TGA_ORIGIN_LEFT 0x00
55#define TGA_ORIGIN_RIGHT 0x10
56#define TGA_ORIGIN_LOWER 0x00
57#define TGA_ORIGIN_UPPER 0x20
58
59 /** Tga Header. */
60 struct TgaHeader {
61 uchar id_length;
62 uchar colormap_type;
63 uchar image_type;
64 ushort colormap_index;
65 ushort colormap_length;
66 uchar colormap_size;
67 ushort x_origin;
68 ushort y_origin;
69 ushort width;
70 ushort height;
71 uchar pixel_size;
72 uchar flags;
73
74 enum { SIZE = 18 }; // const static int SIZE = 18;
75 };
76
77 static QDataStream & operator>> ( QDataStream & s, TgaHeader & head )
78 {
79 s >> head.id_length;
80 s >> head.colormap_type;
81 s >> head.image_type;
82 s >> head.colormap_index;
83 s >> head.colormap_length;
84 s >> head.colormap_size;
85 s >> head.x_origin;
86 s >> head.y_origin;
87 s >> head.width;
88 s >> head.height;
89 s >> head.pixel_size;
90 s >> head.flags;
91 /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
92 qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
93 qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/
94 return s;
95 }
96
97 static bool IsSupported( const TgaHeader & head )
98 {
99 if( head.image_type != TGA_TYPE_INDEXED &&
100 head.image_type != TGA_TYPE_RGB &&
101 head.image_type != TGA_TYPE_GREY &&
102 head.image_type != TGA_TYPE_RLE_INDEXED &&
103 head.image_type != TGA_TYPE_RLE_RGB &&
104 head.image_type != TGA_TYPE_RLE_GREY )
105 {
106 return false;
107 }
108 if( head.image_type == TGA_TYPE_INDEXED ||
109 head.image_type == TGA_TYPE_RLE_INDEXED )
110 {
111 if( head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1 )
112 {
113 return false;
114 }
115 }
116 if( head.image_type == TGA_TYPE_RGB ||
117 head.image_type == TGA_TYPE_GREY ||
118 head.image_type == TGA_TYPE_RLE_RGB ||
119 head.image_type == TGA_TYPE_RLE_GREY )
120 {
121 if( head.colormap_type != 0 )
122 {
123 return false;
124 }
125 }
126 if( head.width == 0 || head.height == 0 )
127 {
128 return false;
129 }
130 if( head.pixel_size != 8 && head.pixel_size != 16 &&
131 head.pixel_size != 24 && head.pixel_size != 32 )
132 {
133 return false;
134 }
135 return true;
136 }
137
138 struct Color555 {
139 ushort b : 5;
140 ushort g : 5;
141 ushort r : 5;
142 };
143
144 struct TgaHeaderInfo {
145 bool rle;
146 bool pal;
147 bool rgb;
148 bool grey;
149
150 TgaHeaderInfo( const TgaHeader & tga ) : rle(false), pal(false), rgb(false), grey(false)
151 {
152 switch( tga.image_type ) {
153 case TGA_TYPE_RLE_INDEXED:
154 rle = true;
155 // no break is intended!
156 case TGA_TYPE_INDEXED:
157 pal = true;
158 break;
159
160 case TGA_TYPE_RLE_RGB:
161 rle = true;
162 // no break is intended!
163 case TGA_TYPE_RGB:
164 rgb = true;
165 break;
166
167 case TGA_TYPE_RLE_GREY:
168 rle = true;
169 // no break is intended!
170 case TGA_TYPE_GREY:
171 grey = true;
172 break;
173
174 default:
175 // Error, unknown image type.
176 break;
177 }
178 }
179 };
180
181
182
183 static bool LoadTGA( QDataStream & s, const TgaHeader & tga, QImage &img )
184 {
185 // Create image.
186 img = QImage( tga.width, tga.height, QImage::Format_RGB32 );
187
188 TgaHeaderInfo info(tga);
189
190 // Bits 0-3 are the numbers of alpha bits (can be zero!)
191 const int numAlphaBits = tga.flags & 0xf;
192 // However alpha exists only in the 32 bit format.
193 if( ( tga.pixel_size == 32 ) && ( tga.flags & 0xf ) ) {
194 img = QImage( tga.width, tga.height, QImage::Format_ARGB32 );
195 }
196
197 uint pixel_size = (tga.pixel_size/8);
198 uint size = tga.width * tga.height * pixel_size;
199
200 if (size < 1)
201 {
202 kDebug(399) << "This TGA file is broken with size " << size;
203 return false;
204 }
205
206 // Read palette.
207 char palette[768];
208 if( info.pal ) {
209 // @todo Support palettes in other formats!
210 s.readRawData( palette, 3 * tga.colormap_length );
211 }
212
213 // Allocate image.
214 uchar * const image = new uchar[size];
215
216 if( info.rle ) {
217 // Decode image.
218 char * dst = (char *)image;
219 int num = size;
220
221 while (num > 0) {
222 // Get packet header.
223 uchar c;
224 s >> c;
225
226 uint count = (c & 0x7f) + 1;
227 num -= count * pixel_size;
228
229 if (c & 0x80) {
230 // RLE pixels.
231 assert(pixel_size <= 8);
232 char pixel[8];
233 s.readRawData( pixel, pixel_size );
234 do {
235 memcpy(dst, pixel, pixel_size);
236 dst += pixel_size;
237 } while (--count);
238 }
239 else {
240 // Raw pixels.
241 count *= pixel_size;
242 s.readRawData( dst, count );
243 dst += count;
244 }
245 }
246 }
247 else {
248 // Read raw image.
249 s.readRawData( (char *)image, size );
250 }
251
252 // Convert image to internal format.
253 int y_start, y_step, y_end;
254 if( tga.flags & TGA_ORIGIN_UPPER ) {
255 y_start = 0;
256 y_step = 1;
257 y_end = tga.height;
258 }
259 else {
260 y_start = tga.height - 1;
261 y_step = -1;
262 y_end = -1;
263 }
264
265 uchar * src = image;
266
267 for( int y = y_start; y != y_end; y += y_step ) {
268 QRgb * scanline = (QRgb *) img.scanLine( y );
269
270 if( info.pal ) {
271 // Paletted.
272 for( int x = 0; x < tga.width; x++ ) {
273 uchar idx = *src++;
274 scanline[x] = qRgb( palette[3*idx+2], palette[3*idx+1], palette[3*idx+0] );
275 }
276 }
277 else if( info.grey ) {
278 // Greyscale.
279 for( int x = 0; x < tga.width; x++ ) {
280 scanline[x] = qRgb( *src, *src, *src );
281 src++;
282 }
283 }
284 else {
285 // True Color.
286 if( tga.pixel_size == 16 ) {
287 for( int x = 0; x < tga.width; x++ ) {
288 Color555 c = *reinterpret_cast<Color555 *>(src);
289 scanline[x] = qRgb( (c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2) );
290 src += 2;
291 }
292 }
293 else if( tga.pixel_size == 24 ) {
294 for( int x = 0; x < tga.width; x++ ) {
295 scanline[x] = qRgb( src[2], src[1], src[0] );
296 src += 3;
297 }
298 }
299 else if( tga.pixel_size == 32 ) {
300 for( int x = 0; x < tga.width; x++ ) {
301 // ### TODO: verify with images having really some alpha data
302 const uchar alpha = ( src[3] << ( 8 - numAlphaBits ) );
303 scanline[x] = qRgba( src[2], src[1], src[0], alpha );
304 src += 4;
305 }
306 }
307 }
308 }
309
310 // Free image.
311 delete [] image;
312
313 return true;
314 }
315
316} // namespace
317
318
319TGAHandler::TGAHandler()
320{
321}
322
323bool TGAHandler::canRead() const
324{
325 if (canRead(device())) {
326 setFormat("tga");
327 return true;
328 }
329 return false;
330}
331
332bool TGAHandler::read(QImage *outImage)
333{
334 //kDebug(399) << "Loading TGA file!";
335
336 QDataStream s( device() );
337 s.setByteOrder( QDataStream::LittleEndian );
338
339
340 // Read image header.
341 TgaHeader tga;
342 s >> tga;
343 s.device()->seek( TgaHeader::SIZE + tga.id_length );
344
345 // Check image file format.
346 if( s.atEnd() ) {
347 kDebug(399) << "This TGA file is not valid.";
348 return false;
349 }
350
351 // Check supported file types.
352 if( !IsSupported(tga) ) {
353 kDebug(399) << "This TGA file is not supported.";
354 return false;
355 }
356
357
358 QImage img;
359 bool result = LoadTGA(s, tga, img);
360
361 if( result == false ) {
362 kDebug(399) << "Error loading TGA file.";
363 return false;
364 }
365
366
367 *outImage = img;
368 return true;
369}
370
371bool TGAHandler::write(const QImage &image)
372{
373 QDataStream s( device() );
374 s.setByteOrder( QDataStream::LittleEndian );
375
376 const QImage& img = image;
377 const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
378 for( int i = 0; i < 12; i++ )
379 s << targaMagic[i];
380
381 // write header
382 s << quint16( img.width() ); // width
383 s << quint16( img.height() ); // height
384 s << quint8( hasAlpha ? 32 : 24 ); // depth (24 bit RGB + 8 bit alpha)
385 s << quint8( hasAlpha ? 0x24 : 0x20 ); // top left image (0x20) + 8 bit alpha (0x4)
386
387 for( int y = 0; y < img.height(); y++ )
388 for( int x = 0; x < img.width(); x++ ) {
389 const QRgb color = img.pixel( x, y );
390 s << quint8( qBlue( color ) );
391 s << quint8( qGreen( color ) );
392 s << quint8( qRed( color ) );
393 if( hasAlpha )
394 s << quint8( qAlpha( color ) );
395 }
396
397 return true;
398}
399
400QByteArray TGAHandler::name() const
401{
402 return "tga";
403}
404
405bool TGAHandler::canRead(QIODevice *device)
406{
407 if (!device) {
408 qWarning("TGAHandler::canRead() called with no device");
409 return false;
410 }
411
412 qint64 oldPos = device->pos();
413 QByteArray head = device->read(TgaHeader::SIZE);
414 int readBytes = head.size();
415
416 if (device->isSequential()) {
417 for (int pos = readBytes - 1; pos >= 0; --pos) {
418 device->ungetChar(head[pos]);
419 }
420 } else {
421 device->seek(oldPos);
422 }
423
424 if (readBytes < TgaHeader::SIZE) {
425 return false;
426 }
427
428 QDataStream stream(head);
429 stream.setByteOrder(QDataStream::LittleEndian);
430 TgaHeader tga;
431 stream >> tga;
432 return IsSupported(tga);
433}
434
435
436class TGAPlugin : public QImageIOPlugin
437{
438public:
439 QStringList keys() const;
440 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
441 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
442};
443
444QStringList TGAPlugin::keys() const
445{
446 return QStringList() << "tga" << "TGA";
447}
448
449QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
450{
451 if (format == "tga" || format == "TGA")
452 return Capabilities(CanRead | CanWrite);
453 if (!format.isEmpty())
454 return 0;
455 if (!device->isOpen())
456 return 0;
457
458 Capabilities cap;
459 if (device->isReadable() && TGAHandler::canRead(device))
460 cap |= CanRead;
461 if (device->isWritable())
462 cap |= CanWrite;
463 return cap;
464}
465
466QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
467{
468 QImageIOHandler *handler = new TGAHandler;
469 handler->setDevice(device);
470 handler->setFormat(format);
471 return handler;
472}
473
474Q_EXPORT_STATIC_PLUGIN(TGAPlugin)
475Q_EXPORT_PLUGIN2(tga, TGAPlugin)
476