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 TgaHeaderInfo info(head);
178
179 // Bits 0-3 are the numbers of alpha bits (can be zero!)
180 const int numAlphaBits = head.flags & 0xf;
181 // However alpha should exists only in the 32 bit format.
182 if ((head.pixel_size == 32) && (numAlphaBits)) {
183 if (numAlphaBits <= 8) {
184 format = QImage::Format_ARGB32;
185 }
186 // Anyway, GIMP also saves gray images with alpha in TGA format
187 } else if((info.grey) && (head.pixel_size == 16) && (numAlphaBits)) {
188 if (numAlphaBits == 8) {
189 format = QImage::Format_ARGB32;
190 }
191 } else {
192 format = QImage::Format_RGB32;
193 }
194 }
195 return format;
196}
197
198/*!
199 * \brief peekHeader
200 * Reads the header but does not change the position in the device.
201 */
202static bool peekHeader(QIODevice *device, TgaHeader &header)
203{
204 auto head = device->peek(TgaHeader::SIZE);
205 if (head.size() < TgaHeader::SIZE) {
206 return false;
207 }
208 QDataStream stream(head);
209 stream.setByteOrder(QDataStream::LittleEndian);
210 stream >> header;
211 return true;
212}
213
214static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
215{
216 img = imageAlloc(tga.width, tga.height, imageFormat(tga));
217 if (img.isNull()) {
218 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
219 return false;
220 }
221
222 TgaHeaderInfo info(tga);
223
224 const int numAlphaBits = tga.flags & 0xf;
225 uint pixel_size = (tga.pixel_size / 8);
226 qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
227
228 if (size < 1) {
229 // qDebug() << "This TGA file is broken with size " << size;
230 return false;
231 }
232
233 // Read palette.
234 static const int max_palette_size = 768;
235 char palette[max_palette_size];
236 if (info.pal) {
237 // @todo Support palettes in other formats!
238 const int palette_size = 3 * tga.colormap_length;
239 if (palette_size > max_palette_size) {
240 return false;
241 }
242 const int dataRead = s.readRawData(palette, palette_size);
243 if (dataRead < 0) {
244 return false;
245 }
246 if (dataRead < max_palette_size) {
247 memset(&palette[dataRead], 0, max_palette_size - dataRead);
248 }
249 }
250
251 // Allocate image.
252 uchar *const image = reinterpret_cast<uchar *>(malloc(size));
253 if (!image) {
254 return false;
255 }
256
257 bool valid = true;
258
259 if (info.rle) {
260 // Decode image.
261 char *dst = (char *)image;
262 char *imgEnd = dst + size;
263 qint64 num = size;
264
265 while (num > 0 && valid) {
266 if (s.atEnd()) {
267 valid = false;
268 break;
269 }
270
271 // Get packet header.
272 uchar c;
273 s >> c;
274
275 uint count = (c & 0x7f) + 1;
276 num -= count * pixel_size;
277 if (num < 0) {
278 valid = false;
279 break;
280 }
281
282 if (c & 0x80) {
283 // RLE pixels.
284 assert(pixel_size <= 8);
285 char pixel[8];
286 const int dataRead = s.readRawData(pixel, pixel_size);
287 if (dataRead < (int)pixel_size) {
288 memset(&pixel[dataRead], 0, pixel_size - dataRead);
289 }
290 do {
291 if (dst + pixel_size > imgEnd) {
292 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size));
293 valid = false;
294 break;
295 }
296
297 memcpy(dst, pixel, pixel_size);
298 dst += pixel_size;
299 } while (--count);
300 } else {
301 // Raw pixels.
302 count *= pixel_size;
303 const int dataRead = s.readRawData(dst, count);
304 if (dataRead < 0) {
305 free(image);
306 return false;
307 }
308
309 if ((uint)dataRead < count) {
310 const size_t toCopy = count - dataRead;
311 if (&dst[dataRead] + toCopy > imgEnd) {
312 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);
313 ;
314 valid = false;
315 break;
316 }
317
318 memset(&dst[dataRead], 0, toCopy);
319 }
320 dst += count;
321 }
322 }
323 } else {
324 // Read raw image.
325 const int dataRead = s.readRawData((char *)image, size);
326 if (dataRead < 0) {
327 free(image);
328 return false;
329 }
330 if (dataRead < size) {
331 memset(&image[dataRead], 0, size - dataRead);
332 }
333 }
334
335 if (!valid) {
336 free(image);
337 return false;
338 }
339
340 // Convert image to internal format.
341 int y_start;
342 int y_step;
343 int y_end;
344 if (tga.flags & TGA_ORIGIN_UPPER) {
345 y_start = 0;
346 y_step = 1;
347 y_end = tga.height;
348 } else {
349 y_start = tga.height - 1;
350 y_step = -1;
351 y_end = -1;
352 }
353
354 uchar *src = image;
355
356 for (int y = y_start; y != y_end; y += y_step) {
357 auto scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
358 if (info.pal) {
359 // Paletted.
360 for (int x = 0; x < tga.width; x++) {
361 uchar idx = *src++;
362 scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);
363 }
364 } else if (info.grey) {
365 // Greyscale.
366 for (int x = 0; x < tga.width; x++) {
367 if (tga.pixel_size == 16) {
368 scanline[x] = qRgba(*src, *src, *src, *(src + 1));
369 src += 2;
370 }
371 else {
372 scanline[x] = qRgb(*src, *src, *src);
373 src++;
374 }
375 }
376 } else {
377 // True Color.
378 if (tga.pixel_size == 16) {
379 for (int x = 0; x < tga.width; x++) {
380 Color555 c = *reinterpret_cast<Color555 *>(src);
381 scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));
382 src += 2;
383 }
384 } else if (tga.pixel_size == 24) {
385 for (int x = 0; x < tga.width; x++) {
386 scanline[x] = qRgb(src[2], src[1], src[0]);
387 src += 3;
388 }
389 } else if (tga.pixel_size == 32) {
390 for (int x = 0; x < tga.width; x++) {
391 // ### TODO: verify with images having really some alpha data
392 const uchar alpha = (src[3] << (8 - numAlphaBits));
393 scanline[x] = qRgba(src[2], src[1], src[0], alpha);
394 src += 4;
395 }
396 }
397 }
398 }
399
400 // Free image.
401 free(image);
402
403 return true;
404}
405
406} // namespace
407
408class TGAHandlerPrivate
409{
410public:
411 TGAHandlerPrivate() {}
412 ~TGAHandlerPrivate() {}
413
414 TgaHeader m_header;
415};
416
417TGAHandler::TGAHandler()
419 , d(new TGAHandlerPrivate)
420{
421}
422
423bool TGAHandler::canRead() const
424{
425 if (canRead(device())) {
426 setFormat("tga");
427 return true;
428 }
429 return false;
430}
431
432bool TGAHandler::read(QImage *outImage)
433{
434 // qDebug() << "Loading TGA file!";
435
436 auto dev = device();
437 auto&& tga = d->m_header;
438 if (!peekHeader(dev, tga) || !IsSupported(tga)) {
439 // qDebug() << "This TGA file is not valid.";
440 return false;
441 }
442
443 if (dev->isSequential()) {
444 dev->read(TgaHeader::SIZE + tga.id_length);
445 } else {
446 dev->seek(TgaHeader::SIZE + tga.id_length);
447 }
448
449 QDataStream s(dev);
451
452 // Check image file format.
453 if (s.atEnd()) {
454 // qDebug() << "This TGA file is not valid.";
455 return false;
456 }
457
458 QImage img;
459 bool result = LoadTGA(s, tga, img);
460
461 if (result == false) {
462 // qDebug() << "Error loading TGA file.";
463 return false;
464 }
465
466 *outImage = img;
467 return true;
468}
469
470bool TGAHandler::write(const QImage &image)
471{
472 QDataStream s(device());
474
475 QImage img(image);
476 const bool hasAlpha = img.hasAlphaChannel();
477 if (hasAlpha && img.format() != QImage::Format_ARGB32) {
479 } else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
481 }
482 if (img.isNull()) {
483 qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
484 return false;
485 }
486 static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
487 static constexpr quint8 alphaChannel8Bits = 0x08;
488
489 for (int i = 0; i < 12; i++) {
490 s << targaMagic[i];
491 }
492
493 // write header
494 s << quint16(img.width()); // width
495 s << quint16(img.height()); // height
496 s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
497 s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
498
499 for (int y = 0; y < img.height(); y++) {
500 auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
501 for (int x = 0; x < img.width(); x++) {
502 auto color = *(ptr + x);
503 s << quint8(qBlue(color));
504 s << quint8(qGreen(color));
505 s << quint8(qRed(color));
506 if (hasAlpha) {
507 s << quint8(qAlpha(color));
508 }
509 }
510 }
511
512 return true;
513}
514
515bool TGAHandler::supportsOption(ImageOption option) const
516{
517 if (option == QImageIOHandler::Size) {
518 return true;
519 }
520 if (option == QImageIOHandler::ImageFormat) {
521 return true;
522 }
523 return false;
524}
525
526QVariant TGAHandler::option(ImageOption option) const
527{
528 QVariant v;
529
530 if (option == QImageIOHandler::Size) {
531 auto&& header = d->m_header;
532 if (IsSupported(header)) {
533 v = QVariant::fromValue(QSize(header.width, header.height));
534 } else if (auto dev = device()) {
535 if (peekHeader(dev, header) && IsSupported(header)) {
536 v = QVariant::fromValue(QSize(header.width, header.height));
537 }
538 }
539 }
540
541 if (option == QImageIOHandler::ImageFormat) {
542 auto&& header = d->m_header;
543 if (IsSupported(header)) {
544 v = QVariant::fromValue(imageFormat(header));
545 } else if (auto dev = device()) {
546 if (peekHeader(dev, header) && IsSupported(header)) {
547 v = QVariant::fromValue(imageFormat(header));
548 }
549 }
550 }
551
552 return v;
553}
554
555bool TGAHandler::canRead(QIODevice *device)
556{
557 if (!device) {
558 qWarning("TGAHandler::canRead() called with no device");
559 return false;
560 }
561
562 TgaHeader tga;
563 if (!peekHeader(device, tga)) {
564 qWarning("TGAHandler::canRead() error while reading the header");
565 return false;
566 }
567
568 return IsSupported(tga);
569}
570
571QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
572{
573 if (format == "tga") {
574 return Capabilities(CanRead | CanWrite);
575 }
576 if (!format.isEmpty()) {
577 return {};
578 }
579 if (!device->isOpen()) {
580 return {};
581 }
582
583 Capabilities cap;
584 if (device->isReadable() && TGAHandler::canRead(device)) {
585 cap |= CanRead;
586 }
587 if (device->isWritable()) {
588 cap |= CanWrite;
589 }
590 return cap;
591}
592
593QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
594{
595 QImageIOHandler *handler = new TGAHandler;
596 handler->setDevice(device);
597 handler->setFormat(format);
598 return handler;
599}
600
601#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 Nov 22 2024 12:10:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.