| 1 | // Copyright (C) 2016 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "qtgafile.h" | 
| 5 |  | 
| 6 | #include <QtCore/QIODevice> | 
| 7 | #include <QtCore/QDebug> | 
| 8 | #include <QtCore/QDateTime> | 
| 9 | #include <QtGui/QImageIOHandler> | 
| 10 |  | 
| 11 | struct TgaReader | 
| 12 | { | 
| 13 |     Q_DISABLE_COPY(TgaReader) | 
| 14 |  | 
| 15 |     TgaReader() = default; | 
| 16 |  | 
| 17 |     virtual ~TgaReader() {} | 
| 18 |     virtual QRgb operator()(QIODevice *s) const = 0; | 
| 19 | }; | 
| 20 |  | 
| 21 | struct Tga16Reader : public TgaReader | 
| 22 | { | 
| 23 |     ~Tga16Reader() {} | 
| 24 |     QRgb operator()(QIODevice *s) const override | 
| 25 |     { | 
| 26 |         char ch1, ch2; | 
| 27 |         if (s->getChar(c: &ch1) && s->getChar(c: &ch2)) { | 
| 28 |             quint16 d = (int(ch1) & 0xFF) | ((int(ch2) & 0xFF) << 8); | 
| 29 |             QRgb result = (d & 0x8000) ? 0xFF000000 : 0x00000000; | 
| 30 |             result |= ((d & 0x7C00) << 6) | ((d & 0x03E0) << 3) | (d & 0x001F); | 
| 31 |             return result; | 
| 32 |         } else { | 
| 33 |             return 0; | 
| 34 |         } | 
| 35 |     } | 
| 36 | }; | 
| 37 |  | 
| 38 | struct Tga24Reader : public TgaReader | 
| 39 | { | 
| 40 |     QRgb operator()(QIODevice *s) const override | 
| 41 |     { | 
| 42 |         char r, g, b; | 
| 43 |         if (s->getChar(c: &b) && s->getChar(c: &g) && s->getChar(c: &r)) | 
| 44 |             return qRgb(r: uchar(r), g: uchar(g), b: uchar(b)); | 
| 45 |         else | 
| 46 |             return 0; | 
| 47 |     } | 
| 48 | }; | 
| 49 |  | 
| 50 | struct Tga32Reader : public TgaReader | 
| 51 | { | 
| 52 |     QRgb operator()(QIODevice *s) const override | 
| 53 |     { | 
| 54 |         char r, g, b, a; | 
| 55 |         if (s->getChar(c: &b) && s->getChar(c: &g) && s->getChar(c: &r) && s->getChar(c: &a)) | 
| 56 |             return qRgba(r: uchar(r), g: uchar(g), b: uchar(b), a: uchar(a)); | 
| 57 |         else | 
| 58 |             return 0; | 
| 59 |     } | 
| 60 | }; | 
| 61 |  | 
| 62 | /*! | 
| 63 |     \class QTgaFile | 
| 64 |     \since 4.8 | 
| 65 |     \internal | 
| 66 |  | 
| 67 |     File data container for a TrueVision Graphics format file. | 
| 68 |  | 
| 69 |     Format is as described here: | 
| 70 |     http://local.wasp.uwa.edu.au/~pbourke/dataformats/tga/ | 
| 71 |     http://netghost.narod.ru/gff2/graphics/summary/tga.htm | 
| 72 |  | 
| 73 |     Usage is: | 
| 74 |     \code | 
| 75 |     QTgaFile tga(myFile); | 
| 76 |     QImage tgaImage; | 
| 77 |     if (tga.isValid()) | 
| 78 |         tgaImage = tga.readImage(); | 
| 79 |     \endcode | 
| 80 |  | 
| 81 |     The class is designed to handle sequential and non-sequential | 
| 82 |     sources, so during construction the mHeader is read.  Then during | 
| 83 |     the readImage() call the rest of the data is read. | 
| 84 |  | 
| 85 |     After passing myFile to the constructor, if the QIODevice *myFile | 
| 86 |     is read, or has seek() called, the results are undefined - so don't | 
| 87 |     do that. | 
| 88 | */ | 
| 89 |  | 
| 90 | /*! | 
| 91 |     Construct a new QTgaFile object getting data from \a device. | 
| 92 |  | 
| 93 |     The object does not take ownership of the \a device, but until the | 
| 94 |     object is destroyed do not do any non-const operations, eg seek or | 
| 95 |     read on the device. | 
| 96 | */ | 
| 97 | QTgaFile::QTgaFile(QIODevice *device) | 
| 98 |     : mDevice(device) | 
| 99 | { | 
| 100 |     ::memset(s: mHeader, c: 0, n: HeaderSize); | 
| 101 |     if (!mDevice->isReadable()) | 
| 102 |     { | 
| 103 |         mErrorMessage = tr(sourceText: "Could not read image data" ); | 
| 104 |         return; | 
| 105 |     } | 
| 106 |     if (mDevice->isSequential()) | 
| 107 |     { | 
| 108 |         mErrorMessage = tr(sourceText: "Sequential device (eg socket) for image read not supported" ); | 
| 109 |         return; | 
| 110 |     } | 
| 111 |     if (!mDevice->seek(pos: 0)) | 
| 112 |     { | 
| 113 |         mErrorMessage = tr(sourceText: "Seek file/device for image read failed" ); | 
| 114 |         return; | 
| 115 |     } | 
| 116 |     if (device->read(data: reinterpret_cast<char*>(mHeader), maxlen: HeaderSize) != HeaderSize) { | 
| 117 |         mErrorMessage = tr(sourceText: "Image header read failed" ); | 
| 118 |         return; | 
| 119 |     } | 
| 120 |     if (mHeader[ImageType] != 2) | 
| 121 |     { | 
| 122 |         // TODO: should support other image types | 
| 123 |         mErrorMessage = tr(sourceText: "Image type not supported" ); | 
| 124 |         return; | 
| 125 |     } | 
| 126 |     int bitsPerPixel = mHeader[PixelDepth]; | 
| 127 |     bool validDepth = (bitsPerPixel == 16 || bitsPerPixel == 24 || bitsPerPixel == 32); | 
| 128 |     if (!validDepth) | 
| 129 |     { | 
| 130 |         mErrorMessage = tr(sourceText: "Image depth not valid" ); | 
| 131 |         return; | 
| 132 |     } | 
| 133 |     if (quint64(width()) * quint64(height()) > (8192 * 8192)) | 
| 134 |     { | 
| 135 |         mErrorMessage = tr(sourceText: "Image size exceeds limit" ); | 
| 136 |         return; | 
| 137 |     } | 
| 138 |     int curPos = mDevice->pos(); | 
| 139 |     int fileBytes = mDevice->size(); | 
| 140 |     if (!mDevice->seek(pos: fileBytes - FooterSize)) | 
| 141 |     { | 
| 142 |         mErrorMessage = tr(sourceText: "Could not seek to image read footer" ); | 
| 143 |         return; | 
| 144 |     } | 
| 145 |     char [FooterSize]; | 
| 146 |     if (mDevice->read(data: reinterpret_cast<char*>(footer), maxlen: FooterSize) != FooterSize) { | 
| 147 |         mErrorMessage = tr(sourceText: "Could not read footer" ); | 
| 148 |     } | 
| 149 |     if (qstrncmp(str1: &footer[SignatureOffset], str2: "TRUEVISION-XFILE" , len: 16) != 0) | 
| 150 |     { | 
| 151 |         mErrorMessage = tr(sourceText: "Image type (non-TrueVision 2.0) not supported" ); | 
| 152 |     } | 
| 153 |     if (!mDevice->seek(pos: curPos)) | 
| 154 |     { | 
| 155 |         mErrorMessage = tr(sourceText: "Could not reset to read data" ); | 
| 156 |     } | 
| 157 | } | 
| 158 |  | 
| 159 | /*! | 
| 160 |     \internal | 
| 161 |     Destroy the device, recovering any resources. | 
| 162 | */ | 
| 163 | QTgaFile::~QTgaFile() | 
| 164 | { | 
| 165 | } | 
| 166 |  | 
| 167 | /*! | 
| 168 |     \internal | 
| 169 |     Reads an image file from the QTgaFile's device, and returns it. | 
| 170 |  | 
| 171 |     This method seeks to the absolute position of the image data in the file, | 
| 172 |     so no assumptions are made about where the devices read pointer is when this | 
| 173 |     method is called.  For this reason only random access devices are supported. | 
| 174 |  | 
| 175 |     If the constructor completed successfully, such that isValid() returns true, | 
| 176 |     then this method is likely to succeed, unless the file is somehow corrupted. | 
| 177 |  | 
| 178 |     In the case that the read fails, the QImage returned will be null, such that | 
| 179 |     QImage::isNull() will be true. | 
| 180 | */ | 
| 181 | QImage QTgaFile::readImage() | 
| 182 | { | 
| 183 |     if (!isValid()) | 
| 184 |         return QImage(); | 
| 185 |  | 
| 186 |     int offset = mHeader[IdLength];  // Mostly always zero | 
| 187 |  | 
| 188 |     // Even in TrueColor files a color palette may be present so we have to check it here | 
| 189 |     // even we only support image type 2 (= uncompressed true-color image) | 
| 190 |     if (mHeader[ColorMapType] == 1) { | 
| 191 |         int cmapDepth = mHeader[CMapDepth]; | 
| 192 |         if (cmapDepth == 15)    // 15 bit is stored as 16 bit + ignoring the highest bit (no alpha) | 
| 193 |             cmapDepth = 16; | 
| 194 |         if (cmapDepth != 16 && cmapDepth != 24 && cmapDepth != 32) { | 
| 195 |             mErrorMessage = tr(sourceText: "Invalid color map depth (%1)" ).arg(a: cmapDepth); | 
| 196 |             return {}; | 
| 197 |         } | 
| 198 |         offset += littleEndianInt(d: &mHeader[CMapLength]) * cmapDepth / 8; | 
| 199 |     } | 
| 200 |  | 
| 201 |     mDevice->seek(pos: HeaderSize + offset); | 
| 202 |  | 
| 203 |     char dummy; | 
| 204 |     for (int i = 0; i < offset; ++i) | 
| 205 |         mDevice->getChar(c: &dummy); | 
| 206 |  | 
| 207 |     int bitsPerPixel = mHeader[PixelDepth]; | 
| 208 |     int imageWidth = width(); | 
| 209 |     int imageHeight = height(); | 
| 210 |  | 
| 211 |     unsigned char desc = mHeader[ImageDescriptor]; | 
| 212 |     //unsigned char xCorner = desc & 0x10; // 0 = left, 1 = right | 
| 213 |     unsigned char yCorner = desc & 0x20; // 0 = lower, 1 = upper | 
| 214 |  | 
| 215 |     QImage im; | 
| 216 |     if (!QImageIOHandler::allocateImage(size: QSize(imageWidth, imageHeight), format: QImage::Format_ARGB32, image: &im)) | 
| 217 |         return QImage(); | 
| 218 |     TgaReader *reader = 0; | 
| 219 |     if (bitsPerPixel == 16) | 
| 220 |         reader = new Tga16Reader(); | 
| 221 |     else if (bitsPerPixel == 24) | 
| 222 |         reader = new Tga24Reader(); | 
| 223 |     else if (bitsPerPixel == 32) | 
| 224 |         reader = new Tga32Reader(); | 
| 225 |     else | 
| 226 |         return QImage(); | 
| 227 |     TgaReader &read = *reader; | 
| 228 |  | 
| 229 |     // For now only deal with yCorner, since no one uses xCorner == 1 | 
| 230 |     // Also this is upside down, since Qt has the origin flipped | 
| 231 |     if (yCorner) | 
| 232 |     { | 
| 233 |         for (int y = 0; y < imageHeight; ++y) | 
| 234 |             for (int x = 0; x < imageWidth; ++x) | 
| 235 |                 im.setPixel(x, y, index_or_rgb: read(mDevice)); | 
| 236 |     } | 
| 237 |     else | 
| 238 |     { | 
| 239 |         for (int y = imageHeight - 1; y >= 0; --y) | 
| 240 |             for (int x = 0; x < imageWidth; ++x) | 
| 241 |                 im.setPixel(x, y, index_or_rgb: read(mDevice)); | 
| 242 |     } | 
| 243 |  | 
| 244 |     delete reader; | 
| 245 |  | 
| 246 |     // TODO: add processing of TGA extension information - ie TGA 2.0 files | 
| 247 |     return im; | 
| 248 | } | 
| 249 |  |