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

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