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
11struct 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
21struct 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
38struct 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
50struct 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*/
97QTgaFile::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 footer[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*/
163QTgaFile::~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*/
181QImage 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

source code of qtimageformats/src/plugins/imageformats/tga/qtgafile.cpp