File indexing completed on 2024-04-28 15:25:45
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de> 0004 SPDX-FileCopyrightText: 2004 Ignacio CastaƱo <castano@ludicon.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 /* this code supports: 0010 * reading: 0011 * uncompressed and run length encoded indexed, grey and color tga files. 0012 * image types 1, 2, 3, 9, 10 and 11. 0013 * only RGB color maps with no more than 256 colors. 0014 * pixel formats 8, 16, 24 and 32. 0015 * writing: 0016 * uncompressed true color tga files 0017 */ 0018 0019 #include "tga_p.h" 0020 #include "util_p.h" 0021 0022 #include <assert.h> 0023 0024 #include <QDataStream> 0025 #include <QDebug> 0026 #include <QImage> 0027 0028 typedef quint32 uint; 0029 typedef quint16 ushort; 0030 typedef quint8 uchar; 0031 0032 namespace // Private. 0033 { 0034 // Header format of saved files. 0035 uchar targaMagic[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 0036 0037 enum TGAType { 0038 TGA_TYPE_INDEXED = 1, 0039 TGA_TYPE_RGB = 2, 0040 TGA_TYPE_GREY = 3, 0041 TGA_TYPE_RLE_INDEXED = 9, 0042 TGA_TYPE_RLE_RGB = 10, 0043 TGA_TYPE_RLE_GREY = 11, 0044 }; 0045 0046 #define TGA_INTERLEAVE_MASK 0xc0 0047 #define TGA_INTERLEAVE_NONE 0x00 0048 #define TGA_INTERLEAVE_2WAY 0x40 0049 #define TGA_INTERLEAVE_4WAY 0x80 0050 0051 #define TGA_ORIGIN_MASK 0x30 0052 #define TGA_ORIGIN_LEFT 0x00 0053 #define TGA_ORIGIN_RIGHT 0x10 0054 #define TGA_ORIGIN_LOWER 0x00 0055 #define TGA_ORIGIN_UPPER 0x20 0056 0057 /** Tga Header. */ 0058 struct TgaHeader { 0059 uchar id_length; 0060 uchar colormap_type; 0061 uchar image_type; 0062 ushort colormap_index; 0063 ushort colormap_length; 0064 uchar colormap_size; 0065 ushort x_origin; 0066 ushort y_origin; 0067 ushort width; 0068 ushort height; 0069 uchar pixel_size; 0070 uchar flags; 0071 0072 enum { 0073 SIZE = 18, 0074 }; // const static int SIZE = 18; 0075 }; 0076 0077 static QDataStream &operator>>(QDataStream &s, TgaHeader &head) 0078 { 0079 s >> head.id_length; 0080 s >> head.colormap_type; 0081 s >> head.image_type; 0082 s >> head.colormap_index; 0083 s >> head.colormap_length; 0084 s >> head.colormap_size; 0085 s >> head.x_origin; 0086 s >> head.y_origin; 0087 s >> head.width; 0088 s >> head.height; 0089 s >> head.pixel_size; 0090 s >> head.flags; 0091 /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; 0092 qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; 0093 qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: 0094 " << head.pixel_size << " - flags: " << head.flags;*/ 0095 return s; 0096 } 0097 0098 static bool IsSupported(const TgaHeader &head) 0099 { 0100 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 0101 && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) { 0102 return false; 0103 } 0104 if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) { 0105 if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { 0106 return false; 0107 } 0108 } 0109 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) { 0110 if (head.colormap_type != 0) { 0111 return false; 0112 } 0113 } 0114 if (head.width == 0 || head.height == 0) { 0115 return false; 0116 } 0117 if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { 0118 return false; 0119 } 0120 return true; 0121 } 0122 0123 struct Color555 { 0124 ushort b : 5; 0125 ushort g : 5; 0126 ushort r : 5; 0127 }; 0128 0129 struct TgaHeaderInfo { 0130 bool rle; 0131 bool pal; 0132 bool rgb; 0133 bool grey; 0134 0135 TgaHeaderInfo(const TgaHeader &tga) 0136 : rle(false) 0137 , pal(false) 0138 , rgb(false) 0139 , grey(false) 0140 { 0141 switch (tga.image_type) { 0142 case TGA_TYPE_RLE_INDEXED: 0143 rle = true; 0144 Q_FALLTHROUGH(); 0145 // no break is intended! 0146 case TGA_TYPE_INDEXED: 0147 pal = true; 0148 break; 0149 0150 case TGA_TYPE_RLE_RGB: 0151 rle = true; 0152 Q_FALLTHROUGH(); 0153 // no break is intended! 0154 case TGA_TYPE_RGB: 0155 rgb = true; 0156 break; 0157 0158 case TGA_TYPE_RLE_GREY: 0159 rle = true; 0160 Q_FALLTHROUGH(); 0161 // no break is intended! 0162 case TGA_TYPE_GREY: 0163 grey = true; 0164 break; 0165 0166 default: 0167 // Error, unknown image type. 0168 break; 0169 } 0170 } 0171 }; 0172 0173 static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) 0174 { 0175 // Create image. 0176 img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32); 0177 if (img.isNull()) { 0178 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); 0179 return false; 0180 } 0181 0182 TgaHeaderInfo info(tga); 0183 0184 // Bits 0-3 are the numbers of alpha bits (can be zero!) 0185 const int numAlphaBits = tga.flags & 0xf; 0186 // However alpha exists only in the 32 bit format. 0187 if ((tga.pixel_size == 32) && (tga.flags & 0xf)) { 0188 img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32); 0189 if (img.isNull()) { 0190 qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); 0191 return false; 0192 } 0193 0194 if (numAlphaBits > 8) { 0195 return false; 0196 } 0197 } 0198 0199 uint pixel_size = (tga.pixel_size / 8); 0200 qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size; 0201 0202 if (size < 1) { 0203 // qDebug() << "This TGA file is broken with size " << size; 0204 return false; 0205 } 0206 0207 // Read palette. 0208 static const int max_palette_size = 768; 0209 char palette[max_palette_size]; 0210 if (info.pal) { 0211 // @todo Support palettes in other formats! 0212 const int palette_size = 3 * tga.colormap_length; 0213 if (palette_size > max_palette_size) { 0214 return false; 0215 } 0216 const int dataRead = s.readRawData(palette, palette_size); 0217 if (dataRead < 0) { 0218 return false; 0219 } 0220 if (dataRead < max_palette_size) { 0221 memset(&palette[dataRead], 0, max_palette_size - dataRead); 0222 } 0223 } 0224 0225 // Allocate image. 0226 uchar *const image = reinterpret_cast<uchar *>(malloc(size)); 0227 if (!image) { 0228 return false; 0229 } 0230 0231 bool valid = true; 0232 0233 if (info.rle) { 0234 // Decode image. 0235 char *dst = (char *)image; 0236 char *imgEnd = dst + size; 0237 qint64 num = size; 0238 0239 while (num > 0 && valid) { 0240 if (s.atEnd()) { 0241 valid = false; 0242 break; 0243 } 0244 0245 // Get packet header. 0246 uchar c; 0247 s >> c; 0248 0249 uint count = (c & 0x7f) + 1; 0250 num -= count * pixel_size; 0251 if (num < 0) { 0252 valid = false; 0253 break; 0254 } 0255 0256 if (c & 0x80) { 0257 // RLE pixels. 0258 assert(pixel_size <= 8); 0259 char pixel[8]; 0260 const int dataRead = s.readRawData(pixel, pixel_size); 0261 if (dataRead < (int)pixel_size) { 0262 memset(&pixel[dataRead], 0, pixel_size - dataRead); 0263 } 0264 do { 0265 if (dst + pixel_size > imgEnd) { 0266 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size)); 0267 valid = false; 0268 break; 0269 } 0270 0271 memcpy(dst, pixel, pixel_size); 0272 dst += pixel_size; 0273 } while (--count); 0274 } else { 0275 // Raw pixels. 0276 count *= pixel_size; 0277 const int dataRead = s.readRawData(dst, count); 0278 if (dataRead < 0) { 0279 free(image); 0280 return false; 0281 } 0282 0283 if ((uint)dataRead < count) { 0284 const size_t toCopy = count - dataRead; 0285 if (&dst[dataRead] + toCopy > imgEnd) { 0286 qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]); 0287 ; 0288 valid = false; 0289 break; 0290 } 0291 0292 memset(&dst[dataRead], 0, toCopy); 0293 } 0294 dst += count; 0295 } 0296 } 0297 } else { 0298 // Read raw image. 0299 const int dataRead = s.readRawData((char *)image, size); 0300 if (dataRead < 0) { 0301 free(image); 0302 return false; 0303 } 0304 if (dataRead < size) { 0305 memset(&image[dataRead], 0, size - dataRead); 0306 } 0307 } 0308 0309 if (!valid) { 0310 free(image); 0311 return false; 0312 } 0313 0314 // Convert image to internal format. 0315 int y_start; 0316 int y_step; 0317 int y_end; 0318 if (tga.flags & TGA_ORIGIN_UPPER) { 0319 y_start = 0; 0320 y_step = 1; 0321 y_end = tga.height; 0322 } else { 0323 y_start = tga.height - 1; 0324 y_step = -1; 0325 y_end = -1; 0326 } 0327 0328 uchar *src = image; 0329 0330 for (int y = y_start; y != y_end; y += y_step) { 0331 QRgb *scanline = (QRgb *)img.scanLine(y); 0332 0333 if (info.pal) { 0334 // Paletted. 0335 for (int x = 0; x < tga.width; x++) { 0336 uchar idx = *src++; 0337 scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]); 0338 } 0339 } else if (info.grey) { 0340 // Greyscale. 0341 for (int x = 0; x < tga.width; x++) { 0342 scanline[x] = qRgb(*src, *src, *src); 0343 src++; 0344 } 0345 } else { 0346 // True Color. 0347 if (tga.pixel_size == 16) { 0348 for (int x = 0; x < tga.width; x++) { 0349 Color555 c = *reinterpret_cast<Color555 *>(src); 0350 scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2)); 0351 src += 2; 0352 } 0353 } else if (tga.pixel_size == 24) { 0354 for (int x = 0; x < tga.width; x++) { 0355 scanline[x] = qRgb(src[2], src[1], src[0]); 0356 src += 3; 0357 } 0358 } else if (tga.pixel_size == 32) { 0359 for (int x = 0; x < tga.width; x++) { 0360 // ### TODO: verify with images having really some alpha data 0361 const uchar alpha = (src[3] << (8 - numAlphaBits)); 0362 scanline[x] = qRgba(src[2], src[1], src[0], alpha); 0363 src += 4; 0364 } 0365 } 0366 } 0367 } 0368 0369 // Free image. 0370 free(image); 0371 0372 return true; 0373 } 0374 0375 } // namespace 0376 0377 TGAHandler::TGAHandler() 0378 { 0379 } 0380 0381 bool TGAHandler::canRead() const 0382 { 0383 if (canRead(device())) { 0384 setFormat("tga"); 0385 return true; 0386 } 0387 return false; 0388 } 0389 0390 bool TGAHandler::read(QImage *outImage) 0391 { 0392 // qDebug() << "Loading TGA file!"; 0393 0394 QDataStream s(device()); 0395 s.setByteOrder(QDataStream::LittleEndian); 0396 0397 // Read image header. 0398 TgaHeader tga; 0399 s >> tga; 0400 s.device()->seek(TgaHeader::SIZE + tga.id_length); 0401 0402 // Check image file format. 0403 if (s.atEnd()) { 0404 // qDebug() << "This TGA file is not valid."; 0405 return false; 0406 } 0407 0408 // Check supported file types. 0409 if (!IsSupported(tga)) { 0410 // qDebug() << "This TGA file is not supported."; 0411 return false; 0412 } 0413 0414 QImage img; 0415 bool result = LoadTGA(s, tga, img); 0416 0417 if (result == false) { 0418 // qDebug() << "Error loading TGA file."; 0419 return false; 0420 } 0421 0422 *outImage = img; 0423 return true; 0424 } 0425 0426 bool TGAHandler::write(const QImage &image) 0427 { 0428 QDataStream s(device()); 0429 s.setByteOrder(QDataStream::LittleEndian); 0430 0431 QImage img(image); 0432 const bool hasAlpha = img.hasAlphaChannel(); 0433 if (hasAlpha && img.format() != QImage::Format_ARGB32) { 0434 img = img.convertToFormat(QImage::Format_ARGB32); 0435 } else if (!hasAlpha && img.format() != QImage::Format_RGB32) { 0436 img = img.convertToFormat(QImage::Format_RGB32); 0437 } 0438 if (img.isNull()) { 0439 qDebug() << "TGAHandler::write: image conversion to 32 bits failed!"; 0440 return false; 0441 } 0442 static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20 0443 static constexpr quint8 alphaChannel8Bits = 0x08; 0444 0445 for (int i = 0; i < 12; i++) { 0446 s << targaMagic[i]; 0447 } 0448 0449 // write header 0450 s << quint16(img.width()); // width 0451 s << quint16(img.height()); // height 0452 s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) 0453 s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8) 0454 0455 for (int y = 0; y < img.height(); y++) { 0456 auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y)); 0457 for (int x = 0; x < img.width(); x++) { 0458 auto color = *(ptr + x); 0459 s << quint8(qBlue(color)); 0460 s << quint8(qGreen(color)); 0461 s << quint8(qRed(color)); 0462 if (hasAlpha) { 0463 s << quint8(qAlpha(color)); 0464 } 0465 } 0466 } 0467 0468 return true; 0469 } 0470 0471 bool TGAHandler::canRead(QIODevice *device) 0472 { 0473 if (!device) { 0474 qWarning("TGAHandler::canRead() called with no device"); 0475 return false; 0476 } 0477 0478 qint64 oldPos = device->pos(); 0479 QByteArray head = device->read(TgaHeader::SIZE); 0480 int readBytes = head.size(); 0481 0482 if (device->isSequential()) { 0483 for (int pos = readBytes - 1; pos >= 0; --pos) { 0484 device->ungetChar(head[pos]); 0485 } 0486 } else { 0487 device->seek(oldPos); 0488 } 0489 0490 if (readBytes < TgaHeader::SIZE) { 0491 return false; 0492 } 0493 0494 QDataStream stream(head); 0495 stream.setByteOrder(QDataStream::LittleEndian); 0496 TgaHeader tga; 0497 stream >> tga; 0498 return IsSupported(tga); 0499 } 0500 0501 QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const 0502 { 0503 if (format == "tga") { 0504 return Capabilities(CanRead | CanWrite); 0505 } 0506 if (!format.isEmpty()) { 0507 return {}; 0508 } 0509 if (!device->isOpen()) { 0510 return {}; 0511 } 0512 0513 Capabilities cap; 0514 if (device->isReadable() && TGAHandler::canRead(device)) { 0515 cap |= CanRead; 0516 } 0517 if (device->isWritable()) { 0518 cap |= CanWrite; 0519 } 0520 return cap; 0521 } 0522 0523 QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const 0524 { 0525 QImageIOHandler *handler = new TGAHandler; 0526 handler->setDevice(device); 0527 handler->setFormat(format); 0528 return handler; 0529 } 0530 0531 #include "moc_tga_p.cpp"