KImageFormats

tga.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2003 Dominik Seichter <[email protected]>
4  SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <[email protected]>
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 
21 #include <assert.h>
22 
23 #include <QDataStream>
24 #include <QDebug>
25 #include <QImage>
26 
27 typedef quint32 uint;
28 typedef quint16 ushort;
29 typedef quint8 uchar;
30 
31 namespace // Private.
32 {
33 // Header format of saved files.
34 uchar targaMagic[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
35 
36 enum TGAType {
37  TGA_TYPE_INDEXED = 1,
38  TGA_TYPE_RGB = 2,
39  TGA_TYPE_GREY = 3,
40  TGA_TYPE_RLE_INDEXED = 9,
41  TGA_TYPE_RLE_RGB = 10,
42  TGA_TYPE_RLE_GREY = 11,
43 };
44 
45 #define TGA_INTERLEAVE_MASK 0xc0
46 #define TGA_INTERLEAVE_NONE 0x00
47 #define TGA_INTERLEAVE_2WAY 0x40
48 #define TGA_INTERLEAVE_4WAY 0x80
49 
50 #define TGA_ORIGIN_MASK 0x30
51 #define TGA_ORIGIN_LEFT 0x00
52 #define TGA_ORIGIN_RIGHT 0x10
53 #define TGA_ORIGIN_LOWER 0x00
54 #define TGA_ORIGIN_UPPER 0x20
55 
56 /** Tga Header. */
57 struct TgaHeader {
58  uchar id_length;
59  uchar colormap_type;
60  uchar image_type;
61  ushort colormap_index;
62  ushort colormap_length;
63  uchar colormap_size;
64  ushort x_origin;
65  ushort y_origin;
66  ushort width;
67  ushort height;
68  uchar pixel_size;
69  uchar flags;
70 
71  enum {
72  SIZE = 18,
73  }; // const static int SIZE = 18;
74 };
75 
76 static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
77 {
78  s >> head.id_length;
79  s >> head.colormap_type;
80  s >> head.image_type;
81  s >> head.colormap_index;
82  s >> head.colormap_length;
83  s >> head.colormap_size;
84  s >> head.x_origin;
85  s >> head.y_origin;
86  s >> head.width;
87  s >> head.height;
88  s >> head.pixel_size;
89  s >> head.flags;
90  /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
91  qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
92  qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize:
93  " << head.pixel_size << " - flags: " << head.flags;*/
94  return s;
95 }
96 
97 static bool IsSupported(const TgaHeader &head)
98 {
99  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
100  && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) {
101  return false;
102  }
103  if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
104  if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
105  return false;
106  }
107  }
108  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) {
109  if (head.colormap_type != 0) {
110  return false;
111  }
112  }
113  if (head.width == 0 || head.height == 0) {
114  return false;
115  }
116  if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
117  return false;
118  }
119  return true;
120 }
121 
122 struct Color555 {
123  ushort b : 5;
124  ushort g : 5;
125  ushort r : 5;
126 };
127 
128 struct TgaHeaderInfo {
129  bool rle;
130  bool pal;
131  bool rgb;
132  bool grey;
133 
134  TgaHeaderInfo(const TgaHeader &tga)
135  : rle(false)
136  , pal(false)
137  , rgb(false)
138  , grey(false)
139  {
140  switch (tga.image_type) {
141  case TGA_TYPE_RLE_INDEXED:
142  rle = true;
143  Q_FALLTHROUGH();
144  // no break is intended!
145  case TGA_TYPE_INDEXED:
146  pal = true;
147  break;
148 
149  case TGA_TYPE_RLE_RGB:
150  rle = true;
151  Q_FALLTHROUGH();
152  // no break is intended!
153  case TGA_TYPE_RGB:
154  rgb = true;
155  break;
156 
157  case TGA_TYPE_RLE_GREY:
158  rle = true;
159  Q_FALLTHROUGH();
160  // no break is intended!
161  case TGA_TYPE_GREY:
162  grey = true;
163  break;
164 
165  default:
166  // Error, unknown image type.
167  break;
168  }
169  }
170 };
171 
172 static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
173 {
174  // Create image.
175  img = QImage(tga.width, tga.height, QImage::Format_RGB32);
176  if (img.isNull()) {
177  qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
178  return false;
179  }
180 
181  TgaHeaderInfo info(tga);
182 
183  // Bits 0-3 are the numbers of alpha bits (can be zero!)
184  const int numAlphaBits = tga.flags & 0xf;
185  // However alpha exists only in the 32 bit format.
186  if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {
187  img = QImage(tga.width, tga.height, QImage::Format_ARGB32);
188  if (img.isNull()) {
189  qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
190  return false;
191  }
192 
193  if (numAlphaBits > 8) {
194  return false;
195  }
196  }
197 
198  uint pixel_size = (tga.pixel_size / 8);
199  qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
200 
201  if (size < 1) {
202  // qDebug() << "This TGA file is broken with size " << size;
203  return false;
204  }
205 
206  // Read palette.
207  static const int max_palette_size = 768;
208  char palette[max_palette_size];
209  if (info.pal) {
210  // @todo Support palettes in other formats!
211  const int palette_size = 3 * tga.colormap_length;
212  if (palette_size > max_palette_size) {
213  return false;
214  }
215  const int dataRead = s.readRawData(palette, palette_size);
216  if (dataRead < 0) {
217  return false;
218  }
219  if (dataRead < max_palette_size) {
220  memset(&palette[dataRead], 0, max_palette_size - dataRead);
221  }
222  }
223 
224  // Allocate image.
225  uchar *const image = reinterpret_cast<uchar *>(malloc(size));
226  if (!image) {
227  return false;
228  }
229 
230  bool valid = true;
231 
232  if (info.rle) {
233  // Decode image.
234  char *dst = (char *)image;
235  char *imgEnd = dst + size;
236  qint64 num = size;
237 
238  while (num > 0 && valid) {
239  if (s.atEnd()) {
240  valid = false;
241  break;
242  }
243 
244  // Get packet header.
245  uchar c;
246  s >> c;
247 
248  uint count = (c & 0x7f) + 1;
249  num -= count * pixel_size;
250  if (num < 0) {
251  valid = false;
252  break;
253  }
254 
255  if (c & 0x80) {
256  // RLE pixels.
257  assert(pixel_size <= 8);
258  char pixel[8];
259  const int dataRead = s.readRawData(pixel, pixel_size);
260  if (dataRead < (int)pixel_size) {
261  memset(&pixel[dataRead], 0, pixel_size - dataRead);
262  }
263  do {
264  if (dst + pixel_size > imgEnd) {
265  qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size));
266  valid = false;
267  break;
268  }
269 
270  memcpy(dst, pixel, pixel_size);
271  dst += pixel_size;
272  } while (--count);
273  } else {
274  // Raw pixels.
275  count *= pixel_size;
276  const int dataRead = s.readRawData(dst, count);
277  if (dataRead < 0) {
278  free(image);
279  return false;
280  }
281 
282  if ((uint)dataRead < count) {
283  const size_t toCopy = count - dataRead;
284  if (&dst[dataRead] + toCopy > imgEnd) {
285  qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);
286  ;
287  valid = false;
288  break;
289  }
290 
291  memset(&dst[dataRead], 0, toCopy);
292  }
293  dst += count;
294  }
295  }
296  } else {
297  // Read raw image.
298  const int dataRead = s.readRawData((char *)image, size);
299  if (dataRead < 0) {
300  free(image);
301  return false;
302  }
303  if (dataRead < size) {
304  memset(&image[dataRead], 0, size - dataRead);
305  }
306  }
307 
308  if (!valid) {
309  free(image);
310  return false;
311  }
312 
313  // Convert image to internal format.
314  int y_start;
315  int y_step;
316  int y_end;
317  if (tga.flags & TGA_ORIGIN_UPPER) {
318  y_start = 0;
319  y_step = 1;
320  y_end = tga.height;
321  } else {
322  y_start = tga.height - 1;
323  y_step = -1;
324  y_end = -1;
325  }
326 
327  uchar *src = image;
328 
329  for (int y = y_start; y != y_end; y += y_step) {
330  QRgb *scanline = (QRgb *)img.scanLine(y);
331 
332  if (info.pal) {
333  // Paletted.
334  for (int x = 0; x < tga.width; x++) {
335  uchar idx = *src++;
336  scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);
337  }
338  } else if (info.grey) {
339  // Greyscale.
340  for (int x = 0; x < tga.width; x++) {
341  scanline[x] = qRgb(*src, *src, *src);
342  src++;
343  }
344  } else {
345  // True Color.
346  if (tga.pixel_size == 16) {
347  for (int x = 0; x < tga.width; x++) {
348  Color555 c = *reinterpret_cast<Color555 *>(src);
349  scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));
350  src += 2;
351  }
352  } else if (tga.pixel_size == 24) {
353  for (int x = 0; x < tga.width; x++) {
354  scanline[x] = qRgb(src[2], src[1], src[0]);
355  src += 3;
356  }
357  } else if (tga.pixel_size == 32) {
358  for (int x = 0; x < tga.width; x++) {
359  // ### TODO: verify with images having really some alpha data
360  const uchar alpha = (src[3] << (8 - numAlphaBits));
361  scanline[x] = qRgba(src[2], src[1], src[0], alpha);
362  src += 4;
363  }
364  }
365  }
366  }
367 
368  // Free image.
369  free(image);
370 
371  return true;
372 }
373 
374 } // namespace
375 
376 TGAHandler::TGAHandler()
377 {
378 }
379 
380 bool TGAHandler::canRead() const
381 {
382  if (canRead(device())) {
383  setFormat("tga");
384  return true;
385  }
386  return false;
387 }
388 
389 bool TGAHandler::read(QImage *outImage)
390 {
391  // qDebug() << "Loading TGA file!";
392 
393  QDataStream s(device());
395 
396  // Read image header.
397  TgaHeader tga;
398  s >> tga;
399  s.device()->seek(TgaHeader::SIZE + tga.id_length);
400 
401  // Check image file format.
402  if (s.atEnd()) {
403  // qDebug() << "This TGA file is not valid.";
404  return false;
405  }
406 
407  // Check supported file types.
408  if (!IsSupported(tga)) {
409  // qDebug() << "This TGA file is not supported.";
410  return false;
411  }
412 
413  QImage img;
414  bool result = LoadTGA(s, tga, img);
415 
416  if (result == false) {
417  // qDebug() << "Error loading TGA file.";
418  return false;
419  }
420 
421  *outImage = img;
422  return true;
423 }
424 
425 bool TGAHandler::write(const QImage &image)
426 {
427  QDataStream s(device());
429 
430  const QImage &img = image;
431  const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
432  for (int i = 0; i < 12; i++) {
433  s << targaMagic[i];
434  }
435 
436  // write header
437  s << quint16(img.width()); // width
438  s << quint16(img.height()); // height
439  s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
440  s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
441 
442  for (int y = 0; y < img.height(); y++) {
443  for (int x = 0; x < img.width(); x++) {
444  const QRgb color = img.pixel(x, y);
445  s << quint8(qBlue(color));
446  s << quint8(qGreen(color));
447  s << quint8(qRed(color));
448  if (hasAlpha) {
449  s << quint8(qAlpha(color));
450  }
451  }
452  }
453 
454  return true;
455 }
456 
457 bool TGAHandler::canRead(QIODevice *device)
458 {
459  if (!device) {
460  qWarning("TGAHandler::canRead() called with no device");
461  return false;
462  }
463 
464  qint64 oldPos = device->pos();
465  QByteArray head = device->read(TgaHeader::SIZE);
466  int readBytes = head.size();
467 
468  if (device->isSequential()) {
469  for (int pos = readBytes - 1; pos >= 0; --pos) {
470  device->ungetChar(head[pos]);
471  }
472  } else {
473  device->seek(oldPos);
474  }
475 
476  if (readBytes < TgaHeader::SIZE) {
477  return false;
478  }
479 
480  QDataStream stream(head);
481  stream.setByteOrder(QDataStream::LittleEndian);
482  TgaHeader tga;
483  stream >> tga;
484  return IsSupported(tga);
485 }
486 
487 QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
488 {
489  if (format == "tga") {
490  return Capabilities(CanRead | CanWrite);
491  }
492  if (!format.isEmpty()) {
493  return {};
494  }
495  if (!device->isOpen()) {
496  return {};
497  }
498 
499  Capabilities cap;
500  if (device->isReadable() && TGAHandler::canRead(device)) {
501  cap |= CanRead;
502  }
503  if (device->isWritable()) {
504  cap |= CanWrite;
505  }
506  return cap;
507 }
508 
509 QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
510 {
511  QImageIOHandler *handler = new TGAHandler;
512  handler->setDevice(device);
513  handler->setFormat(format);
514  return handler;
515 }
uchar * scanLine(int i)
bool isWritable() const const
virtual bool seek(qint64 pos)
QDataStream & operator>>(QDataStream &in, KDateTime::Spec &spec)
bool isEmpty() const const
bool isNull() const const
bool isReadable() const const
virtual bool isSequential() const const
int readRawData(char *s, int len)
virtual qint64 pos() const const
QRgb pixel(int x, int y) const const
int width() const const
void setDevice(QIODevice *device)
qint64 read(char *data, qint64 maxSize)
bool isOpen() const const
bool atEnd() const const
void setByteOrder(QDataStream::ByteOrder bo)
typedef Capabilities
void setFormat(const QByteArray &format)
int height() const const
QIODevice * device() const const
int size() const const
void ungetChar(char c)
QImage::Format format() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 22:45:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.