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

KDE's Doxygen guidelines are available online.