1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "lightmapviewerhelpers.h"
5
6#include <QtQuick3DRuntimeRender/private/qssglightmapio_p.h>
7
8#include <QRgb>
9#include <QVector>
10#include <QImage>
11#include <QFile>
12#include <QJsonObject>
13#include <QDir>
14
15static QRgb numberToBBGRColor(quint32 i, quint32 N, bool useAlpha)
16{
17 if (i < 1 || i > N) {
18 return qRgba(r: 0, g: 0, b: 0, a: useAlpha ? 0 : 0xff);
19 }
20
21 int range = N - 1; // exclude 0
22 double t = static_cast<double>(i - 1) / range;
23
24 quint8 r = 0, g = 0, b = 0;
25
26 if (t < 0.5) {
27 // Blue -> Green
28 double t2 = t / 0.5; // normalize 0..1
29 g = 255 * t2;
30 b = 255 * (1.0 - t2);
31 } else {
32 // Green -> Red
33 double t2 = (t - 0.5) / 0.5;
34 r = 255 * t2;
35 g = 255 * (1.0 - t2);
36 }
37
38 return qRgba(r, g, b, a: 0xff);
39}
40
41QString LightmapViewerHelpers::lightmapTagToString(QSSGLightmapIODataTag tag)
42{
43 switch (tag) {
44 case QSSGLightmapIODataTag::Unset:
45 return QStringLiteral("Unset");
46 break;
47 case QSSGLightmapIODataTag::Mask:
48 return QStringLiteral("Mask");
49 break;
50 case QSSGLightmapIODataTag::Texture_Final:
51 return QStringLiteral("Texture_Final");
52 break;
53 case QSSGLightmapIODataTag::Texture_Direct:
54 return QStringLiteral("Texture_Direct");
55 break;
56 case QSSGLightmapIODataTag::Texture_Indirect:
57 return QStringLiteral("Texture_Indirect");
58 break;
59 case QSSGLightmapIODataTag::Metadata:
60 return QStringLiteral("Metadata");
61 break;
62 case QSSGLightmapIODataTag::Mesh:
63 return QStringLiteral("Mesh");
64 break;
65 default:
66 break;
67 }
68 return QStringLiteral("Invalid");
69}
70
71QSSGLightmapIODataTag LightmapViewerHelpers::stringToLightmapTag(const QString &tag)
72{
73 if (tag == QStringLiteral("Unset"))
74 return QSSGLightmapIODataTag::Unset;
75 if (tag == QStringLiteral("Mask"))
76 return QSSGLightmapIODataTag::Mask;
77 if (tag == QStringLiteral("Texture_Final"))
78 return QSSGLightmapIODataTag::Texture_Final;
79 if (tag == QStringLiteral("Texture_Direct"))
80 return QSSGLightmapIODataTag::Texture_Direct;
81 if (tag == QStringLiteral("Texture_Indirect"))
82 return QSSGLightmapIODataTag::Texture_Indirect;
83 if (tag == QStringLiteral("Metadata"))
84 return QSSGLightmapIODataTag::Metadata;
85 if (tag == QStringLiteral("Mesh"))
86 return QSSGLightmapIODataTag::Mesh;
87 if (tag == QStringLiteral("Unset"))
88 return QSSGLightmapIODataTag::Unset;
89
90 qWarning() << "Could not match tag for: " << tag;
91 return QSSGLightmapIODataTag::Unset;
92}
93
94void LightmapViewerHelpers::maskToBBGRColor(QByteArray &array, bool useAlpha)
95{
96 QVector<quint32> uints;
97 uints.resize(size: array.size() / sizeof(quint32));
98 memcpy(dest: uints.data(), src: array.data(), n: array.size());
99
100 quint32 maxN = 0;
101 for (quint32 v : uints) {
102 maxN = qMax(a: maxN, b: v);
103 }
104 for (quint32 &vRef : uints) {
105 vRef = numberToBBGRColor(i: vRef, N: maxN, useAlpha);
106 }
107 memcpy(dest: array.data(), src: uints.data(), n: array.size());
108}
109
110bool LightmapViewerHelpers::processLightmap(const QString &filename, bool print, bool extract)
111{
112 bool success = true;
113 QSharedPointer<QSSGLightmapLoader> loader = QSSGLightmapLoader::open(path: filename);
114
115 if (!loader) {
116 return false;
117 }
118
119 if (extract) {
120 QDir dir;
121 for (const char *path : { "meshes", "images", "images/masks", "images/direct", "images/indirect", "images/final" }) {
122 if (!dir.mkpath(dirPath: path)) {
123 qInfo() << "Failed to create folders";
124 return false;
125 }
126 }
127 }
128
129 int numImagesSaved = 0;
130 int numMeshesSaved = 0;
131
132 QList<std::pair<QString, QSSGLightmapIODataTag>> keys = loader->getKeys();
133
134 if (print)
135 qInfo() << "-- Keys --";
136
137 QVector<QString> meshKeys;
138
139 for (const auto &[key, tag] : std::as_const(t&: keys)) {
140 QString tagString = LightmapViewerHelpers::lightmapTagToString(tag);
141
142 if (print)
143 qInfo() << key << ":" << tagString;
144
145 if (tag == QSSGLightmapIODataTag::Mesh)
146 meshKeys.push_back(t: key);
147 }
148
149 if (print)
150 qInfo() << "-- Values --";
151
152 // Extract meshes
153 if (extract) {
154 for (const QString &key : meshKeys) {
155 const QByteArray meshData = loader->readData(key, tag: QSSGLightmapIODataTag::Mesh);
156 QFile meshFile(QString("meshes/" + key + ".mesh"));
157 if (meshFile.open(flags: QFile::WriteOnly)) {
158 meshFile.write(data: meshData);
159 meshFile.close();
160 ++numMeshesSaved;
161 } else {
162 success = false;
163 qInfo() << key << "->" << "FAILED TO WRITE";
164 }
165 }
166 }
167
168 for (const auto &[key, tag] : std::as_const(t&: keys)) {
169 if (tag != QSSGLightmapIODataTag::Metadata)
170 continue;
171
172 int width = 0;
173 int height = 0;
174
175 if (tag == QSSGLightmapIODataTag::Metadata) {
176 QVariantMap map = loader->readMetadata(key);
177 if (print) {
178 qInfo() << key << ":";
179 qInfo().noquote() << QJsonDocument(QJsonObject::fromVariantMap(map)).toJson(format: QJsonDocument::Indented).trimmed();
180 }
181 width = map[QStringLiteral("width")].toInt();
182 height = map[QStringLiteral("height")].toInt();
183 }
184
185 if (extract) {
186 if (keys.contains(t: std::make_pair(x: key, y: QSSGLightmapIODataTag::Mask))) {
187 QByteArray data = loader->readU32Image(key, tag: QSSGLightmapIODataTag::Mask);
188 maskToBBGRColor(array&: data);
189 QImage img = QImage(reinterpret_cast<uchar *>(data.data()), width, height, QImage::Format_RGBA8888);
190 img.save(fileName: QString("images/masks/" + key + ".png"));
191 ++numImagesSaved;
192 }
193 for (const auto &[texTag, dir] : std::array {
194 std::pair { QSSGLightmapIODataTag::Texture_Direct, QStringLiteral("direct") },
195 std::pair { QSSGLightmapIODataTag::Texture_Indirect, QStringLiteral("indirect") },
196 std::pair { QSSGLightmapIODataTag::Texture_Final, QStringLiteral("final") },
197 }) {
198 if (!keys.contains(t: std::make_pair(x: key, y: texTag)))
199 continue;
200 QByteArray data = loader->readF32Image(key, tag: texTag);
201 QImage img = QImage(reinterpret_cast<uchar *>(data.data()), width, height, QImage::Format_RGBA32FPx4);
202 img.save(fileName: QString("images/" + dir + "/" + key + ".png"));
203 ++numImagesSaved;
204 }
205 }
206 }
207
208 if (extract) {
209 qInfo() << "Saved" << numImagesSaved << "images to 'images' and " << numMeshesSaved << "meshes to 'meshes'";
210 }
211
212 return success;
213}
214

source code of qtquick3d/tools/lightmapviewer/lightmapviewerhelpers.cpp