| 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 | |