KImageFormats

tga.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
4 SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <castano@ludicon.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9/* this code supports:
10 * reading:
11 * uncompressed and run length encoded indexed, grey and color tga files.
12 * image types 1, 2, 3, 9, 10 and 11.
13 * only RGB color maps with no more than 256 colors.
14 * pixel formats 8, 16, 24 and 32.
15 * writing:
16 * uncompressed true color tga files
17 */
18
19#include "tga_p.h"
20#include "util_p.h"
21
22#include <assert.h>
23
24#include <QDataStream>
25#include <QDebug>
26#include <QImage>
27
28typedef quint32 uint;
29typedef quint16 ushort;
30typedef quint8 uchar;
31
32namespace // Private.
33{
34// Header format of saved files.
35uchar targaMagic[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
36
37enum TGAType {
38 TGA_TYPE_INDEXED = 1,
39 TGA_TYPE_RGB = 2,
40 TGA_TYPE_GREY = 3,
41 TGA_TYPE_RLE_INDEXED = 9,
42 TGA_TYPE_RLE_RGB = 10,
43 TGA_TYPE_RLE_GREY = 11,
44};
45
46#define TGA_INTERLEAVE_MASK 0xc0
47#define TGA_INTERLEAVE_NONE 0x00
48#define TGA_INTERLEAVE_2WAY 0x40
49#define TGA_INTERLEAVE_4WAY 0x80
50
51#define TGA_ORIGIN_MASK 0x30
52#define TGA_ORIGIN_LEFT 0x00
53#define TGA_ORIGIN_RIGHT 0x10
54#define TGA_ORIGIN_LOWER 0x00
55#define TGA_ORIGIN_UPPER 0x20
56
57/** Tga Header. */
58struct TgaHeader {
59 uchar id_length = 0;
60 uchar colormap_type = 0;
61 uchar image_type = 0;
62 ushort colormap_index = 0;
63 ushort colormap_length = 0;
64 uchar colormap_size = 0;
65 ushort x_origin = 0;
66 ushort y_origin = 0;
67 ushort width = 0;
68 ushort height = 0;
69 uchar pixel_size = 0;
70 uchar flags = 0;
71
72 enum {
73 SIZE = 18,
74 }; // const static int SIZE = 18;
75};
76
77static 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 return s;
92}
93
94static bool IsSupported(const TgaHeader &head)
95{
96 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
97 && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) {
98 return false;
99 }
100 if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
101 if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
102 return false;
103 }
104 }
105 if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) {
106 if (head.colormap_type != 0) {
107 return false;
108 }
109 }
110 if (head.width == 0 || head.height == 0) {
111 return false;
112 }
113 if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
114 return false;
115 }
116 // If the colormap_type field is set to zero, indicating that no color map exists, then colormap_size, colormap_index and colormap_length should be set to zero.
117 if (head.colormap_type == 0 && (head.colormap_size != 0 || head.colormap_index != 0 || head.colormap_length != 0)) {
118 return false;
119 }
120 return true;
121}
122
123struct Color555 {
124 ushort b : 5;
125 ushort g : 5;
126 ushort r : 5;
127};
128
129struct TgaHeaderInfo {
130 bool rle;
131 bool pal;
132 bool rgb;
133 bool grey;
134
135 TgaHeaderInfo(const TgaHeader &tga)
136 : rle(false)
137 , pal(false)
138 , rgb(false)
139 , grey(false)
140 {
141 switch (tga.image_type) {
142 case TGA_TYPE_RLE_INDEXED:
143 rle = true;
144 Q_FALLTHROUGH();
145 // no break is intended!
146 case TGA_TYPE_INDEXED:
147 pal = true;
148 break;
149
150 case TGA_TYPE_RLE_RGB:
151 rle = true;
152 Q_FALLTHROUGH();
153 // no break is intended!
154 case TGA_TYPE_RGB:
155 rgb = true;
156 break;
157
158 case TGA_TYPE_RLE_GREY:
159 rle = true;
160 Q_FALLTHROUGH();
161 // no break is intended!
162 case TGA_TYPE_GREY:
163 grey = true;
164 break;
165
166 default:
167 // Error, unknown image type.
168 break;
169 }
170 }
171};
172
173static QImage::Format imageFormat(const TgaHeader &head)
174{
175 auto format = QImage::Format_Invalid;
176 if (IsSupported(head)) {
177 // Bits 0-3 are the numbers of alpha bits (can be zero!)
178 const int numAlphaBits = head.flags & 0xf;
179 // However alpha exists only in the 32 bit format.
180 if ((head.pixel_size == 32) && (head.flags & 0xf)) {
181 if (numAlphaBits <= 8) {
182 format = QImage::Format_ARGB32;
183 }
184 }
185 else {
186 format = QImage::Format_RGB32;
187 }
188 }
189 return format;
190}
191
192/*!
193 * \brief peekHeader
194 * Reads the header but does not change the position in the device.
195 */
196static bool peekHeader(QIODevice *device, TgaHeader &header)
197{
198 auto head = device->peek(TgaHeader::SIZE);
199 if (head.size() < TgaHeader::SIZE) {
200 return false;
201 }
202 QDataStream stream(head);
203 stream.setByteOrder(QDataStream::LittleEndian);
204 stream >> header;
205 return true;
206}
207
208static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
209{
210 img = imageAlloc(tga.width, tga.height, imageFormat(tga));
211 if (img.isNull()) {
212 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
213 return false;
214 }
215
216 TgaHeaderInfo info(tga);
217
218 const int numAlphaBits = tga.flags & 0xf;
219 uint pixel_size = (tga.pixel_size / 8);
220 qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
221
222 if (size < 1) {
223 // qDebug() << "This TGA file is broken with size " << size;
224 return false;
225 }
226
227 // Read palette.
228 static const int max_palette_size = 768;
229 char palette[max_palette_size];
230 if (info.pal) {
231 // @todo Support palettes in other formats!
232 const int palette_size = 3 * tga.colormap_length;
233 if (palette_size > max_palette_size) {
234 return false;
235 }
236 const int dataRead = s.readRawData(palette, palette_size);
237 if (dataRead < 0) {
238 return false;
239 }
240 if (dataRead < max_palette_size) {
241 memset(&palette[dataRead], 0, max_palette_size - dataRead);
242 }
243 }
244
245 // Allocate image.
246 uchar *const image = reinterpret_cast<uchar *>(malloc(size));
247 if (!image) {
248 return false;
249 }
250
251 bool valid = true;
252
253 if (info.rle) {
254 // Decode image.
255 char *dst = (char *)image;
256 char *imgEnd = dst + size;
257 qint64 num = size;
258
259 while (num > 0 && valid) {
260 if (s.atEnd()) {
261 valid = false;
262 break;
263 }
264
265 // Get packet header.
266 uchar c;
267 s >> c;
268
269 uint count = (c & 0x7f) + 1;
270 num -= count * pixel_size;
271 if (num < 0) {
272 valid = false;
273 break;
274 }
275
276 if (c & 0x80) {
277 // RLE pixels.
278 assert(pixel_size <= 8);
279 char pixel[8];
280 const int dataRead = s.readRawData(pixel, pixel_size);
281 if (dataRead < (int)pixel_size) {
282 memset(&pixel[dataRead], 0, pixel_size - dataRead);
283 }
284 do {
285 if (dst + pixel_size > imgEnd) {
286 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size));
287 valid = false;
288 break;
289 }
290
291 memcpy(dst, pixel, pixel_size);
292 dst += pixel_size;
293 } while (--count);
294 } else {
295 // Raw pixels.
296 count *= pixel_size;
297 const int dataRead = s.readRawData(dst, count);
298 if (dataRead < 0) {
299 free(image);
300 return false;
301 }
302
303 if ((uint)dataRead < count) {
304 const size_t toCopy = count - dataRead;
305 if (&dst[dataRead] + toCopy > imgEnd) {
306 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);
307 ;
308 valid = false;
309 break;
310 }
311
312 memset(&dst[dataRead], 0, toCopy);
313 }
314 dst += count;
315 }
316 }
317 } else {
318 // Read raw image.
319 const int dataRead = s.readRawData((char *)image, size);
320 if (dataRead < 0) {
321 free(image);
322 return false;
323 }
324 if (dataRead < size) {
325 memset(&image[dataRead], 0, size - dataRead);
326 }
327 }
328
329 if (!valid) {
330 free(image);
331 return false;
332 }
333
334 // Convert image to internal format.
335 int y_start;
336 int y_step;
337 int y_end;
338 if (tga.flags & TGA_ORIGIN_UPPER) {
339 y_start = 0;
340 y_step = 1;
341 y_end = tga.height;
342 } else {
343 y_start = tga.height - 1;
344 y_step = -1;
345 y_end = -1;
346 }
347
348 uchar *src = image;
349
350 for (int y = y_start; y != y_end; y += y_step) {
351 QRgb *scanline = (QRgb *)img.scanLine(y);
352
353 if (info.pal) {
354 // Paletted.
355 for (int x = 0; x < tga.width; x++) {
356 uchar idx = *src++;
357 scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);
358 }
359 } else if (info.grey) {
360 // Greyscale.
361 for (int x = 0; x < tga.width; x++) {
362 scanline[x] = qRgb(*src, *src, *src);
363 src++;
364 }
365 } else {
366 // True Color.
367 if (tga.pixel_size == 16) {
368 for (int x = 0; x < tga.width; x++) {
369 Color555 c = *reinterpret_cast<Color555 *>(src);
370 scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));
371 src += 2;
372 }
373 } else if (tga.pixel_size == 24) {
374 for (int x = 0; x < tga.width; x++) {
375 scanline[x] = qRgb(src[2], src[1], src[0]);
376 src += 3;
377 }
378 } else if (tga.pixel_size == 32) {
379 for (int x = 0; x < tga.width; x++) {
380 // ### TODO: verify with images having really some alpha data
381 const uchar alpha = (src[3] << (8 - numAlphaBits));
382 scanline[x] = qRgba(src[2], src[1], src[0], alpha);
383 src += 4;
384 }
385 }
386 }
387 }
388
389 // Free image.
390 free(image);
391
392 return true;
393}
394
395} // namespace
396
397class TGAHandlerPrivate
398{
399public:
400 TGAHandlerPrivate() {}
401 ~TGAHandlerPrivate() {}
402
403 TgaHeader m_header;
404};
405
406TGAHandler::TGAHandler()
408 , d(new TGAHandlerPrivate)
409{
410}
411
412bool TGAHandler::canRead() const
413{
414 if (canRead(device())) {
415 setFormat("tga");
416 return true;
417 }
418 return false;
419}
420
421bool TGAHandler::read(QImage *outImage)
422{
423 // qDebug() << "Loading TGA file!";
424
425 auto dev = device();
426 auto&& tga = d->m_header;
427 if (!peekHeader(dev, tga) || !IsSupported(tga)) {
428 // qDebug() << "This TGA file is not valid.";
429 return false;
430 }
431
432 if (dev->isSequential()) {
433 dev->read(TgaHeader::SIZE + tga.id_length);
434 } else {
435 dev->seek(TgaHeader::SIZE + tga.id_length);
436 }
437
438 QDataStream s(dev);
440
441 // Check image file format.
442 if (s.atEnd()) {
443 // qDebug() << "This TGA file is not valid.";
444 return false;
445 }
446
447 QImage img;
448 bool result = LoadTGA(s, tga, img);
449
450 if (result == false) {
451 // qDebug() << "Error loading TGA file.";
452 return false;
453 }
454
455 *outImage = img;
456 return true;
457}
458
459bool TGAHandler::write(const QImage &image)
460{
461 QDataStream s(device());
463
464 QImage img(image);
465 const bool hasAlpha = img.hasAlphaChannel();
466 if (hasAlpha && img.format() != QImage::Format_ARGB32) {
468 } else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
470 }
471 if (img.isNull()) {
472 qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
473 return false;
474 }
475 static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
476 static constexpr quint8 alphaChannel8Bits = 0x08;
477
478 for (int i = 0; i < 12; i++) {
479 s << targaMagic[i];
480 }
481
482 // write header
483 s << quint16(img.width()); // width
484 s << quint16(img.height()); // height
485 s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
486 s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
487
488 for (int y = 0; y < img.height(); y++) {
489 auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
490 for (int x = 0; x < img.width(); x++) {
491 auto color = *(ptr + x);
492 s << quint8(qBlue(color));
493 s << quint8(qGreen(color));
494 s << quint8(qRed(color));
495 if (hasAlpha) {
496 s << quint8(qAlpha(color));
497 }
498 }
499 }
500
501 return true;
502}
503
504bool TGAHandler::supportsOption(ImageOption option) const
505{
506 if (option == QImageIOHandler::Size) {
507 return true;
508 }
509 if (option == QImageIOHandler::ImageFormat) {
510 return true;
511 }
512 return false;
513}
514
515QVariant TGAHandler::option(ImageOption option) const
516{
517 QVariant v;
518
519 if (option == QImageIOHandler::Size) {
520 auto&& header = d->m_header;
521 if (IsSupported(header)) {
522 v = QVariant::fromValue(QSize(header.width, header.height));
523 } else if (auto dev = device()) {
524 if (peekHeader(dev, header) && IsSupported(header)) {
525 v = QVariant::fromValue(QSize(header.width, header.height));
526 }
527 }
528 }
529
530 if (option == QImageIOHandler::ImageFormat) {
531 auto&& header = d->m_header;
532 if (IsSupported(header)) {
533 v = QVariant::fromValue(imageFormat(header));
534 } else if (auto dev = device()) {
535 if (peekHeader(dev, header) && IsSupported(header)) {
536 v = QVariant::fromValue(imageFormat(header));
537 }
538 }
539 }
540
541 return v;
542}
543
544bool TGAHandler::canRead(QIODevice *device)
545{
546 if (!device) {
547 qWarning("TGAHandler::canRead() called with no device");
548 return false;
549 }
550
551 TgaHeader tga;
552 if (!peekHeader(device, tga)) {
553 qWarning("TGAHandler::canRead() error while reading the header");
554 return false;
555 }
556
557 return IsSupported(tga);
558}
559
560QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
561{
562 if (format == "tga") {
563 return Capabilities(CanRead | CanWrite);
564 }
565 if (!format.isEmpty()) {
566 return {};
567 }
568 if (!device->isOpen()) {
569 return {};
570 }
571
572 Capabilities cap;
573 if (device->isReadable() && TGAHandler::canRead(device)) {
574 cap |= CanRead;
575 }
576 if (device->isWritable()) {
577 cap |= CanWrite;
578 }
579 return cap;
580}
581
582QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
583{
584 QImageIOHandler *handler = new TGAHandler;
585 handler->setDevice(device);
586 handler->setFormat(format);
587 return handler;
588}
589
590#include "moc_tga_p.cpp"
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QFlags< Capability > Capabilities
bool isEmpty() const const
bool atEnd() const const
int readRawData(char *s, int len)
void setByteOrder(ByteOrder bo)
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
Format format() const const
bool hasAlphaChannel() const const
int height() const const
bool isNull() const const
uchar * scanLine(int i)
int width() const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
bool isOpen() const const
bool isReadable() const const
bool isWritable() const const
QByteArray peek(qint64 maxSize)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:15:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.