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

KDE's Doxygen guidelines are available online.