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"