1 | // Copyright (C) 2018 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 "QtGui/qimage.h" |
5 | #include "qtexturefiledata_p.h" |
6 | #include <QtCore/qsize.h> |
7 | #include <QtCore/qvarlengtharray.h> |
8 | #include <QtCore/qmap.h> |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | Q_LOGGING_CATEGORY(lcQtGuiTextureIO, "qt.gui.textureio" ); |
13 | |
14 | constexpr size_t MAX_FACES = 6; |
15 | |
16 | class QTextureFileDataPrivate : public QSharedData |
17 | { |
18 | public: |
19 | QTextureFileDataPrivate() |
20 | { |
21 | } |
22 | |
23 | QTextureFileDataPrivate(const QTextureFileDataPrivate &other) |
24 | : QSharedData(other), |
25 | mode(other.mode), |
26 | logName(other.logName), |
27 | data(other.data), |
28 | offsets(other.offsets), |
29 | lengths(other.lengths), |
30 | images(other.images), |
31 | size(other.size), |
32 | format(other.format), |
33 | numFaces(other.numFaces), |
34 | numLevels(other.numLevels), |
35 | keyValues(other.keyValues) |
36 | { |
37 | } |
38 | |
39 | ~QTextureFileDataPrivate() |
40 | { |
41 | } |
42 | |
43 | void ensureSize(int levels, int faces, bool force = false) |
44 | { |
45 | numLevels = force ? levels : qMax(a: numLevels, b: levels); |
46 | numFaces = force ? faces : qMax(a: numFaces, b: faces); |
47 | if (mode == QTextureFileData::ByteArrayMode) { |
48 | offsets.resize(sz: numFaces); |
49 | lengths.resize(sz: numFaces); |
50 | |
51 | for (auto faceList : { &offsets, &lengths }) |
52 | for (auto &levelList : *faceList) |
53 | levelList.resize(size: numLevels); |
54 | } else { |
55 | images.resize(sz: numFaces); |
56 | for (auto &levelList : images) |
57 | levelList.resize(size: numLevels); |
58 | } |
59 | } |
60 | |
61 | bool isValid(int level, int face) const { return level < numLevels && face < numFaces; } |
62 | |
63 | int getOffset(int level, int face) const { return offsets[face][level]; } |
64 | void setOffset(int value, int level, int face) { offsets[face][level] = value; } |
65 | int getLength(int level, int face) const { return lengths[face][level]; } |
66 | void setLength(int value, int level, int face) { lengths[face][level] = value; } |
67 | |
68 | QTextureFileData::Mode mode = QTextureFileData::ByteArrayMode; |
69 | QByteArray logName; |
70 | QByteArray data; |
71 | QVarLengthArray<QList<int>, MAX_FACES> offsets; // [Face][Level] = offset |
72 | QVarLengthArray<QList<int>, MAX_FACES> lengths; // [Face][Level] = length |
73 | QVarLengthArray<QList<QImage>, MAX_FACES> images; // [Face][Level] = length |
74 | QSize size; |
75 | quint32 format = 0; |
76 | quint32 internalFormat = 0; |
77 | quint32 baseInternalFormat = 0; |
78 | int numFaces = 0; |
79 | int numLevels = 0; |
80 | QMap<QByteArray, QByteArray> keyValues; |
81 | }; |
82 | |
83 | QTextureFileData::QTextureFileData(Mode mode) |
84 | { |
85 | d = new QTextureFileDataPrivate; |
86 | d->mode = mode; |
87 | } |
88 | |
89 | QTextureFileData::QTextureFileData(const QTextureFileData &other) |
90 | : d(other.d) |
91 | { |
92 | } |
93 | |
94 | QTextureFileData &QTextureFileData::operator=(const QTextureFileData &other) |
95 | { |
96 | d = other.d; |
97 | return *this; |
98 | } |
99 | |
100 | QTextureFileData::~QTextureFileData() |
101 | { |
102 | } |
103 | |
104 | bool QTextureFileData::isNull() const |
105 | { |
106 | return !d; |
107 | } |
108 | |
109 | bool QTextureFileData::isValid() const |
110 | { |
111 | if (!d) |
112 | return false; |
113 | |
114 | if (d->mode == ImageMode) |
115 | return true; // Manually populated: the caller needs to do verification at that time. |
116 | |
117 | if (d->data.isEmpty() || d->size.isEmpty() || (!d->format && !d->internalFormat)) |
118 | return false; |
119 | |
120 | const int numFacesOffset = d->offsets.size(); |
121 | const int numFacesLength = d->lengths.size(); |
122 | if (numFacesOffset == 0 || numFacesLength == 0 || d->numFaces != numFacesOffset |
123 | || d->numFaces != numFacesLength) |
124 | return false; |
125 | |
126 | const qint64 dataSize = d->data.size(); |
127 | |
128 | // Go through all faces and levels and check that the range is inside the data size. |
129 | for (int face = 0; face < d->numFaces; face++) { |
130 | const int numLevelsOffset = d->offsets.at(idx: face).size(); |
131 | const int numLevelsLength = d->lengths.at(idx: face).size(); |
132 | if (numLevelsOffset == 0 || numLevelsLength == 0 || d->numLevels != numLevelsOffset |
133 | || d->numLevels != numLevelsLength) |
134 | return false; |
135 | |
136 | for (int level = 0; level < d->numLevels; level++) { |
137 | const qint64 offset = d->getOffset(level, face); |
138 | const qint64 length = d->getLength(level, face); |
139 | if (offset < 0 || offset >= dataSize || length <= 0 || (offset + length > dataSize)) |
140 | return false; |
141 | } |
142 | } |
143 | return true; |
144 | } |
145 | |
146 | void QTextureFileData::clear() |
147 | { |
148 | d = nullptr; |
149 | } |
150 | |
151 | QByteArray QTextureFileData::data() const |
152 | { |
153 | return d ? d->data : QByteArray(); |
154 | } |
155 | |
156 | void QTextureFileData::setData(const QByteArray &data) |
157 | { |
158 | Q_ASSERT(d->mode == ByteArrayMode); |
159 | d->data = data; |
160 | } |
161 | |
162 | void QTextureFileData::setData(const QImage &image, int level, int face) |
163 | { |
164 | Q_ASSERT(d->mode == ImageMode); |
165 | d->ensureSize(levels: level + 1, faces: face + 1); |
166 | d->images[face][level] = image; |
167 | } |
168 | |
169 | int QTextureFileData::dataOffset(int level, int face) const |
170 | { |
171 | Q_ASSERT(d->mode == ByteArrayMode); |
172 | return (d && d->isValid(level, face)) ? d->getOffset(level, face) : 0; |
173 | } |
174 | |
175 | void QTextureFileData::setDataOffset(int offset, int level, int face) |
176 | { |
177 | Q_ASSERT(d->mode == ByteArrayMode); |
178 | if (d.constData() && level >= 0) { |
179 | d->ensureSize(levels: level + 1, faces: face + 1); |
180 | d->setOffset(value: offset, level, face); |
181 | } |
182 | } |
183 | |
184 | int QTextureFileData::dataLength(int level, int face) const |
185 | { |
186 | Q_ASSERT(d->mode == ByteArrayMode); |
187 | return (d && d->isValid(level, face)) ? d->getLength(level, face) : 0; |
188 | } |
189 | |
190 | QByteArrayView QTextureFileData::getDataView(int level, int face) const |
191 | { |
192 | if (d->mode == ByteArrayMode) { |
193 | const int dataLength = this->dataLength(level, face); |
194 | const int dataOffset = this->dataOffset(level, face); |
195 | |
196 | if (d == nullptr || dataLength == 0) |
197 | return QByteArrayView(); |
198 | |
199 | return QByteArrayView(d->data.constData() + dataOffset, dataLength); |
200 | } else { |
201 | if (!d->isValid(level, face)) |
202 | return QByteArrayView(); |
203 | const QImage &img = d->images[face][level]; |
204 | return img.isNull() ? QByteArrayView() : QByteArrayView(img.constBits(), img.sizeInBytes()); |
205 | } |
206 | } |
207 | |
208 | void QTextureFileData::setDataLength(int length, int level, int face) |
209 | { |
210 | Q_ASSERT(d->mode == ByteArrayMode); |
211 | if (d.constData() && level >= 0) { |
212 | d->ensureSize(levels: level + 1, faces: face + 1); |
213 | d->setLength(value: length, level, face); |
214 | } |
215 | } |
216 | |
217 | int QTextureFileData::numLevels() const |
218 | { |
219 | return d ? d->numLevels : 0; |
220 | } |
221 | |
222 | void QTextureFileData::setNumLevels(int numLevels) |
223 | { |
224 | if (d && numLevels >= 0) |
225 | d->ensureSize(levels: numLevels, faces: d->numFaces, force: true); |
226 | } |
227 | |
228 | int QTextureFileData::numFaces() const |
229 | { |
230 | return d ? d->numFaces : 0; |
231 | } |
232 | |
233 | void QTextureFileData::setNumFaces(int numFaces) |
234 | { |
235 | if (d && numFaces >= 0) |
236 | d->ensureSize(levels: d->numLevels, faces: numFaces, force: true); |
237 | } |
238 | |
239 | QSize QTextureFileData::size() const |
240 | { |
241 | return d ? d->size : QSize(); |
242 | } |
243 | |
244 | void QTextureFileData::setSize(const QSize &size) |
245 | { |
246 | if (d.constData()) |
247 | d->size = size; |
248 | } |
249 | |
250 | quint32 QTextureFileData::glFormat() const |
251 | { |
252 | return d ? d->format : 0; |
253 | } |
254 | |
255 | void QTextureFileData::setGLFormat(quint32 format) |
256 | { |
257 | if (d.constData()) |
258 | d->format = format; |
259 | } |
260 | |
261 | quint32 QTextureFileData::glInternalFormat() const |
262 | { |
263 | return d ? d->internalFormat : 0; |
264 | } |
265 | |
266 | void QTextureFileData::setGLInternalFormat(quint32 format) |
267 | { |
268 | if (d.constData()) |
269 | d->internalFormat = format; |
270 | } |
271 | |
272 | quint32 QTextureFileData::glBaseInternalFormat() const |
273 | { |
274 | return d ? d->baseInternalFormat : 0; |
275 | } |
276 | |
277 | void QTextureFileData::setGLBaseInternalFormat(quint32 format) |
278 | { |
279 | if (d.constData()) |
280 | d->baseInternalFormat = format; |
281 | } |
282 | |
283 | QByteArray QTextureFileData::logName() const |
284 | { |
285 | return d ? d->logName : QByteArray(); |
286 | } |
287 | |
288 | void QTextureFileData::setLogName(const QByteArray &name) |
289 | { |
290 | if (d.constData()) |
291 | d->logName = name; |
292 | } |
293 | |
294 | QMap<QByteArray, QByteArray> QTextureFileData::keyValueMetadata() const |
295 | { |
296 | return d ? d->keyValues : QMap<QByteArray, QByteArray>(); |
297 | } |
298 | |
299 | void QTextureFileData::setKeyValueMetadata(const QMap<QByteArray, QByteArray> &keyValues) |
300 | { |
301 | if (d) |
302 | d->keyValues = keyValues; |
303 | } |
304 | |
305 | static QByteArray glFormatName(quint32 fmt) |
306 | { |
307 | return QByteArray("0x" + QByteArray::number(fmt, base: 16).rightJustified(width: 4, fill: '0')); |
308 | } |
309 | |
310 | QDebug operator<<(QDebug dbg, const QTextureFileData &d) |
311 | { |
312 | QDebugStateSaver saver(dbg); |
313 | |
314 | dbg.nospace() << "QTextureFileData(" ; |
315 | if (!d.isNull()) { |
316 | dbg.space() << d.logName() << d.size(); |
317 | dbg << "glFormat:" << glFormatName(fmt: d.glFormat()); |
318 | dbg << "glInternalFormat:" << glFormatName(fmt: d.glInternalFormat()); |
319 | dbg << "glBaseInternalFormat:" << glFormatName(fmt: d.glBaseInternalFormat()); |
320 | dbg.nospace() << "Levels: " << d.numLevels(); |
321 | dbg.nospace() << "Faces: " << d.numFaces(); |
322 | if (!d.isValid()) |
323 | dbg << " {Invalid}" ; |
324 | dbg << ")" ; |
325 | dbg << (d.d->mode ? "[bytearray-based]" : "[image-based]" ); |
326 | } else { |
327 | dbg << "null)" ; |
328 | } |
329 | |
330 | return dbg; |
331 | } |
332 | |
333 | QT_END_NAMESPACE |
334 | |