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 | |
30 | typedef quint32 uint; |
31 | typedef quint16 ushort; |
32 | typedef quint8 uchar; |
33 | |
34 | namespace { // 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 { |
61 | uchar ; |
62 | uchar ; |
63 | uchar ; |
64 | ushort ; |
65 | ushort ; |
66 | uchar ; |
67 | ushort ; |
68 | ushort ; |
69 | ushort ; |
70 | ushort ; |
71 | uchar ; |
72 | uchar ; |
73 | |
74 | enum { = 18 }; // const static int SIZE = 18; |
75 | }; |
76 | |
77 | static QDataStream & ( 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 ( 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 { |
145 | bool ; |
146 | bool ; |
147 | bool ; |
148 | bool ; |
149 | |
150 | ( 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 ( 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 | |
319 | TGAHandler::TGAHandler() |
320 | { |
321 | } |
322 | |
323 | bool TGAHandler::canRead() const |
324 | { |
325 | if (canRead(device())) { |
326 | setFormat("tga" ); |
327 | return true; |
328 | } |
329 | return false; |
330 | } |
331 | |
332 | bool 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 | |
371 | bool 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 | |
400 | QByteArray TGAHandler::name() const |
401 | { |
402 | return "tga" ; |
403 | } |
404 | |
405 | bool 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 | |
436 | class TGAPlugin : public QImageIOPlugin |
437 | { |
438 | public: |
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 | |
444 | QStringList TGAPlugin::keys() const |
445 | { |
446 | return QStringList() << "tga" << "TGA" ; |
447 | } |
448 | |
449 | QImageIOPlugin::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 | |
466 | QImageIOHandler *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 | |
474 | Q_EXPORT_STATIC_PLUGIN(TGAPlugin) |
475 | Q_EXPORT_PLUGIN2(tga, TGAPlugin) |
476 | |