adapt patch from Mitz Pettel <mitz@webkit.org>
[kdelibs.git] / kimgio / tga.cpp
blob5153c8243eb99be24101667bb6c5d16c33f16900
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>
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 */
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
21 #include "tga.h"
23 #include <assert.h>
25 #include <qimage.h>
26 #include <qdatastream.h>
28 #include <kdebug.h>
30 typedef quint32 uint;
31 typedef quint16 ushort;
32 typedef quint8 uchar;
34 namespace { // Private.
36 // Header format of saved files.
37 uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
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
48 #define TGA_INTERLEAVE_MASK 0xc0
49 #define TGA_INTERLEAVE_NONE 0x00
50 #define TGA_INTERLEAVE_2WAY 0x40
51 #define TGA_INTERLEAVE_4WAY 0x80
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
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;
74 enum { SIZE = 18 }; // const static int SIZE = 18;
77 static QDataStream & operator>> ( QDataStream & s, TgaHeader & head )
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 return s;
94 static bool IsSupported( const TgaHeader & head )
96 if( head.image_type != TGA_TYPE_INDEXED &&
97 head.image_type != TGA_TYPE_RGB &&
98 head.image_type != TGA_TYPE_GREY &&
99 head.image_type != TGA_TYPE_RLE_INDEXED &&
100 head.image_type != TGA_TYPE_RLE_RGB &&
101 head.image_type != TGA_TYPE_RLE_GREY )
103 return false;
105 if( head.image_type == TGA_TYPE_INDEXED ||
106 head.image_type == TGA_TYPE_RLE_INDEXED )
108 if( head.colormap_length > 256 || head.colormap_size != 24 )
110 return false;
113 if( head.width == 0 || head.height == 0 )
115 return false;
117 if( head.pixel_size != 8 && head.pixel_size != 16 &&
118 head.pixel_size != 24 && head.pixel_size != 32 )
120 return false;
122 return true;
125 struct Color555 {
126 ushort b : 5;
127 ushort g : 5;
128 ushort r : 5;
131 struct TgaHeaderInfo {
132 bool rle;
133 bool pal;
134 bool rgb;
135 bool grey;
136 bool supported;
138 TgaHeaderInfo( const TgaHeader & tga ) : rle(false), pal(false), rgb(false), grey(false), supported(true)
140 switch( tga.image_type ) {
141 case TGA_TYPE_RLE_INDEXED:
142 rle = true;
143 // no break is intended!
144 case TGA_TYPE_INDEXED:
145 if( tga.colormap_type!=1 || tga.colormap_size!=24 || tga.colormap_length>256 ) {
146 supported = false;
148 pal = true;
149 break;
151 case TGA_TYPE_RLE_RGB:
152 rle = true;
153 // no break is intended!
154 case TGA_TYPE_RGB:
155 rgb = true;
156 break;
158 case TGA_TYPE_RLE_GREY:
159 rle = true;
160 // no break is intended!
161 case TGA_TYPE_GREY:
162 grey = true;
163 break;
165 default:
166 // Error, unknown image type.
167 supported = false;
174 static bool LoadTGA( QDataStream & s, const TgaHeader & tga, QImage &img )
176 // Create image.
177 img = QImage( tga.width, tga.height, QImage::Format_RGB32 );
179 TgaHeaderInfo info(tga);
180 if( !info.supported ) {
181 // File not supported.
182 kDebug(399) << "This TGA file is not supported." << endl;
183 return false;
186 // Bits 0-3 are the numbers of alpha bits (can be zero!)
187 const int numAlphaBits = tga.flags & 0xf;
188 // However alpha exists only in the 32 bit format.
189 if( ( tga.pixel_size == 32 ) && ( tga.flags & 0xf ) ) {
190 img.convertToFormat( QImage::Format_ARGB32 );
193 uint pixel_size = (tga.pixel_size/8);
194 uint size = tga.width * tga.height * pixel_size;
196 if (size < 1)
198 kDebug(399) << "This TGA file is broken with size " << size << endl;
199 return false;
202 // Read palette.
203 char palette[768];
204 if( info.pal ) {
205 // @todo Support palettes in other formats!
206 s.readRawData( palette, 3 * tga.colormap_length );
209 // Allocate image.
210 uchar * const image = new uchar[size];
212 if( info.rle ) {
213 // Decode image.
214 char * dst = (char *)image;
215 int num = size;
217 while (num > 0) {
218 // Get packet header.
219 uchar c;
220 s >> c;
222 uint count = (c & 0x7f) + 1;
223 num -= count * pixel_size;
225 if (c & 0x80) {
226 // RLE pixels.
227 assert(pixel_size <= 8);
228 char pixel[8];
229 s.readRawData( pixel, pixel_size );
230 do {
231 memcpy(dst, pixel, pixel_size);
232 dst += pixel_size;
233 } while (--count);
235 else {
236 // Raw pixels.
237 count *= pixel_size;
238 s.readRawData( dst, count );
239 dst += count;
243 else {
244 // Read raw image.
245 s.readRawData( (char *)image, size );
248 // Convert image to internal format.
249 int y_start, y_step, y_end;
250 if( tga.flags & TGA_ORIGIN_UPPER ) {
251 y_start = 0;
252 y_step = 1;
253 y_end = tga.height;
255 else {
256 y_start = tga.height - 1;
257 y_step = -1;
258 y_end = -1;
261 uchar * src = image;
263 for( int y = y_start; y != y_end; y += y_step ) {
264 QRgb * scanline = (QRgb *) img.scanLine( y );
266 if( info.pal ) {
267 // Paletted.
268 for( int x = 0; x < tga.width; x++ ) {
269 uchar idx = *src++;
270 scanline[x] = qRgb( palette[3*idx+2], palette[3*idx+1], palette[3*idx+0] );
273 else if( info.grey ) {
274 // Greyscale.
275 for( int x = 0; x < tga.width; x++ ) {
276 scanline[x] = qRgb( *src, *src, *src );
277 src++;
280 else {
281 // True Color.
282 if( tga.pixel_size == 16 ) {
283 for( int x = 0; x < tga.width; x++ ) {
284 Color555 c = *reinterpret_cast<Color555 *>(src);
285 scanline[x] = qRgb( (c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2) );
286 src += 2;
289 else if( tga.pixel_size == 24 ) {
290 for( int x = 0; x < tga.width; x++ ) {
291 scanline[x] = qRgb( src[2], src[1], src[0] );
292 src += 3;
295 else if( tga.pixel_size == 32 ) {
296 for( int x = 0; x < tga.width; x++ ) {
297 // ### TODO: verify with images having really some alpha data
298 const uchar alpha = ( src[3] << ( 8 - numAlphaBits ) );
299 scanline[x] = qRgba( src[2], src[1], src[0], alpha );
300 src += 4;
306 // Free image.
307 delete [] image;
309 return true;
312 } // namespace
315 TGAHandler::TGAHandler()
319 bool TGAHandler::canRead() const
321 return canRead(device());
324 bool TGAHandler::read(QImage *outImage)
326 //kDebug(399) << "Loading TGA file!" << endl;
328 QDataStream s( device() );
329 s.setByteOrder( QDataStream::LittleEndian );
332 // Read image header.
333 TgaHeader tga;
334 s >> tga;
335 s.device()->seek( TgaHeader::SIZE + tga.id_length );
337 // Check image file format.
338 if( s.atEnd() ) {
339 kDebug(399) << "This TGA file is not valid." << endl;
340 return false;
343 // Check supported file types.
344 if( !IsSupported(tga) ) {
345 kDebug(399) << "This TGA file is not supported." << endl;
346 return false;
350 QImage img;
351 bool result = LoadTGA(s, tga, img);
353 if( result == false ) {
354 kDebug(399) << "Error loading TGA file." << endl;
355 return false;
359 *outImage = img;
360 return true;
363 bool TGAHandler::write(const QImage &image)
365 QDataStream s( device() );
366 s.setByteOrder( QDataStream::LittleEndian );
368 const QImage& img = image;
369 const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
370 for( int i = 0; i < 12; i++ )
371 s << targaMagic[i];
373 // write header
374 s << quint16( img.width() ); // width
375 s << quint16( img.height() ); // height
376 s << quint8( hasAlpha ? 32 : 24 ); // depth (24 bit RGB + 8 bit alpha)
377 s << quint8( hasAlpha ? 0x24 : 0x20 ); // top left image (0x20) + 8 bit alpha (0x4)
379 for( int y = 0; y < img.height(); y++ )
380 for( int x = 0; x < img.width(); x++ ) {
381 const QRgb color = img.pixel( x, y );
382 s << quint8( qBlue( color ) );
383 s << quint8( qGreen( color ) );
384 s << quint8( qRed( color ) );
385 if( hasAlpha )
386 s << quint8( qAlpha( color ) );
389 return true;
392 QByteArray TGAHandler::name() const
394 return "tga";
397 bool TGAHandler::canRead(QIODevice *device)
399 if (!device) {
400 qWarning("TGAHandler::canRead() called with no device");
401 return false;
404 qint64 oldPos = device->pos();
405 QByteArray head = device->readLine(64);
406 int readBytes = head.size();
408 if (device->isSequential()) {
409 while (readBytes > 0)
410 device->ungetChar(head[readBytes-- - 1]);
411 } else {
412 device->seek(oldPos);
415 const QRegExp regexp("^.\x01[\x01-\x03\x09-\x0b]\x01{3}.[\x01\x18]");
416 QString data(head);
418 return data.contains(regexp);
422 class TGAPlugin : public QImageIOPlugin
424 public:
425 QStringList keys() const;
426 Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
427 QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const;
430 QStringList TGAPlugin::keys() const
432 return QStringList() << "tga" << "TGA";
435 QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
437 if (format == "tga" || format == "TGA")
438 return Capabilities(CanRead | CanWrite);
439 if (!format.isEmpty())
440 return 0;
441 if (!device->isOpen())
442 return 0;
444 Capabilities cap;
445 if (device->isReadable() && TGAHandler::canRead(device))
446 cap |= CanRead;
447 if (device->isWritable())
448 cap |= CanWrite;
449 return cap;
452 QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
454 QImageIOHandler *handler = new TGAHandler;
455 handler->setDevice(device);
456 handler->setFormat(format);
457 return handler;
460 Q_EXPORT_STATIC_PLUGIN(TGAPlugin)
461 Q_EXPORT_PLUGIN2(tga, TGAPlugin)