1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | /* |
5 | Based on "sky.cpp" from the Godot engine v3 |
6 | Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. |
7 | Copyright (c) 2014-2022 Godot Engine contributors. |
8 | */ |
9 | |
10 | #include "proceduralskytexturedata_p.h" |
11 | #include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h> |
12 | #include <QtQuick3DRuntimeRender/private/qssgrenderimage_p.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /*! |
17 | \qmltype ProceduralSkyTextureData |
18 | \inqmlmodule QtQuick3D.Helpers |
19 | \inherits TextureData |
20 | \brief Generates an HDR skybox cubemap. |
21 | |
22 | This helper type provides an easy way to generate a lightprobe/skybox texture in HDR format. Note that |
23 | generating a lightprobe is an expensive process that can take significant time on embedded hardware. |
24 | |
25 | The generated cubemap consists of three elements: the sky, the ground, and the sun. The sky and the |
26 | ground cover the top and bottom hemispheres. The position of the sun can be specified by setting |
27 | \l sunLatitude and \l sunLongitude. |
28 | |
29 | \qml |
30 | View3D { |
31 | environment: SceneEnvironment { |
32 | backgroundMode: SceneEnvironment.SkyBox |
33 | lightProbe: Texture { |
34 | textureData: ProceduralSkyTextureData { |
35 | } |
36 | } |
37 | } |
38 | } |
39 | \endqml |
40 | |
41 | \image sceneenvironment_lightprobe_proceduralsky.jpg |
42 | |
43 | \sa SceneEnvironment |
44 | */ |
45 | |
46 | /*! \qmlproperty color ProceduralSkyTextureData::skyTopColor |
47 | Specifies the sky color at the top of the skybox. The top half of the skybox has a gradient from \l skyHorizonColor to \c skyTopColor. |
48 | */ |
49 | |
50 | /*! \qmlproperty color ProceduralSkyTextureData::skyHorizonColor |
51 | Specifies the sky color at the horizon. The top half of the skybox has a gradient from \c skyHorizonColor to \l skyTopColor. |
52 | */ |
53 | |
54 | /*! \qmlproperty real ProceduralSkyTextureData::skyCurve |
55 | Modifies the curve of the sky gradient. |
56 | */ |
57 | |
58 | /*! \qmlproperty real ProceduralSkyTextureData::skyEnergy |
59 | Specifies the intensity of the top half of the skybox. The sky gradient is multiplied with this factor. |
60 | */ |
61 | |
62 | /*! \qmlproperty color ProceduralSkyTextureData::groundBottomColor |
63 | Specifies the ground color at the bottom of the skybox. The bottom half of the skybox has |
64 | a gradient from \l groundHorizonColor to \c groundBottomColor. |
65 | */ |
66 | |
67 | /*! \qmlproperty color ProceduralSkyTextureData::groundHorizonColor |
68 | Specifies the ground color at the horizon. The bottom half of the skybox has |
69 | a gradient from \c groundHorizonColor to \l groundBottomColor. |
70 | */ |
71 | |
72 | /*! \qmlproperty real ProceduralSkyTextureData::groundCurve |
73 | Modifies the curve of the ground gradient. |
74 | */ |
75 | |
76 | /*! \qmlproperty real ProceduralSkyTextureData::groundEnergy |
77 | Specifies the intensity of the bottom half of the skybox. The ground gradient is multiplied with this factor. |
78 | */ |
79 | |
80 | /*! \qmlproperty color ProceduralSkyTextureData::sunColor |
81 | Specifies the color of the sun. |
82 | */ |
83 | |
84 | /*! \qmlproperty real ProceduralSkyTextureData::sunLatitude |
85 | Specifies the angle between the horizon and the sun position. |
86 | */ |
87 | |
88 | /*! \qmlproperty real ProceduralSkyTextureData::sunLongitude |
89 | Specifies the angle between the forward direction and the sun position. |
90 | */ |
91 | |
92 | /*! \qmlproperty real ProceduralSkyTextureData::sunAngleMin |
93 | Specifies the angle from the center of the sun to where it starts to fade. |
94 | */ |
95 | |
96 | /*! \qmlproperty real ProceduralSkyTextureData::sunAngleMax |
97 | Specifies the angle from the center of the sun to where it fades out completely. |
98 | */ |
99 | |
100 | /*! \qmlproperty real ProceduralSkyTextureData::sunCurve |
101 | Modifies the curve of the sun gradient. |
102 | */ |
103 | |
104 | /*! \qmlproperty float ProceduralSkyTextureData::sunEnergy |
105 | Specifies the intensity of the sun. |
106 | */ |
107 | |
108 | /*! \qmlproperty SkyTextureQuality ProceduralSkyTextureData::textureQuality |
109 | This property sets the quality of the sky texture. Supported values are: |
110 | |
111 | \value ProceduralSkyTextureData.SkyTextureQualityLow Generate a 512x512 texture |
112 | \value ProceduralSkyTextureData.SkyTextureQualityMedium Generate a 1024x1024 texture |
113 | \value ProceduralSkyTextureData.SkyTextureQualityHigh Generate a 2048x2048 texture |
114 | \value ProceduralSkyTextureData.SkyTextureQualityVeryHigh Generate a 4096x4096 texture |
115 | */ |
116 | |
117 | ProceduralSkyTextureData::ProceduralSkyTextureData() |
118 | { |
119 | scheduleTextureUpdate(); |
120 | } |
121 | |
122 | ProceduralSkyTextureData::~ProceduralSkyTextureData() |
123 | { |
124 | } |
125 | |
126 | QColor ProceduralSkyTextureData::skyTopColor() const |
127 | { |
128 | return m_skyTopColor; |
129 | } |
130 | |
131 | QColor ProceduralSkyTextureData::skyHorizonColor() const |
132 | { |
133 | return m_skyHorizonColor; |
134 | } |
135 | |
136 | float ProceduralSkyTextureData::skyCurve() const |
137 | { |
138 | return m_skyCurve; |
139 | } |
140 | |
141 | float ProceduralSkyTextureData::skyEnergy() const |
142 | { |
143 | return m_skyEnergy; |
144 | } |
145 | |
146 | QColor ProceduralSkyTextureData::groundBottomColor() const |
147 | { |
148 | return m_groundBottomColor; |
149 | } |
150 | |
151 | QColor ProceduralSkyTextureData::groundHorizonColor() const |
152 | { |
153 | return m_groundHorizonColor; |
154 | } |
155 | |
156 | float ProceduralSkyTextureData::groundCurve() const |
157 | { |
158 | return m_groundCurve; |
159 | } |
160 | |
161 | float ProceduralSkyTextureData::groundEnergy() const |
162 | { |
163 | return m_groundEnergy; |
164 | } |
165 | |
166 | QColor ProceduralSkyTextureData::sunColor() const |
167 | { |
168 | return m_sunColor; |
169 | } |
170 | |
171 | float ProceduralSkyTextureData::sunLatitude() const |
172 | { |
173 | return m_sunLatitude; |
174 | } |
175 | |
176 | float ProceduralSkyTextureData::sunLongitude() const |
177 | { |
178 | return m_sunLongitude; |
179 | } |
180 | |
181 | float ProceduralSkyTextureData::sunAngleMin() const |
182 | { |
183 | return m_sunAngleMin; |
184 | } |
185 | |
186 | float ProceduralSkyTextureData::sunAngleMax() const |
187 | { |
188 | return m_sunAngleMax; |
189 | } |
190 | |
191 | float ProceduralSkyTextureData::sunCurve() const |
192 | { |
193 | return m_sunCurve; |
194 | } |
195 | |
196 | float ProceduralSkyTextureData::sunEnergy() const |
197 | { |
198 | return m_sunEnergy; |
199 | } |
200 | |
201 | ProceduralSkyTextureData::SkyTextureQuality ProceduralSkyTextureData::textureQuality() const |
202 | { |
203 | return m_textureQuality; |
204 | } |
205 | |
206 | void ProceduralSkyTextureData::setSkyTopColor(QColor skyTopColor) |
207 | { |
208 | if (m_skyTopColor == skyTopColor) |
209 | return; |
210 | |
211 | m_skyTopColor = skyTopColor; |
212 | emit skyTopColorChanged(skyTopColor: m_skyTopColor); |
213 | scheduleTextureUpdate(); |
214 | } |
215 | |
216 | void ProceduralSkyTextureData::setSkyHorizonColor(QColor skyHorizonColor) |
217 | { |
218 | if (m_skyHorizonColor == skyHorizonColor) |
219 | return; |
220 | |
221 | m_skyHorizonColor = skyHorizonColor; |
222 | emit skyHorizonColorChanged(skyHorizonColor: m_skyHorizonColor); |
223 | scheduleTextureUpdate(); |
224 | } |
225 | |
226 | void ProceduralSkyTextureData::setSkyCurve(float skyCurve) |
227 | { |
228 | if (qFuzzyCompare(p1: m_skyCurve, p2: skyCurve)) |
229 | return; |
230 | |
231 | m_skyCurve = skyCurve; |
232 | emit skyCurveChanged(skyCurve: m_skyCurve); |
233 | scheduleTextureUpdate(); |
234 | } |
235 | |
236 | void ProceduralSkyTextureData::setSkyEnergy(float skyEnergy) |
237 | { |
238 | if (qFuzzyCompare(p1: m_skyEnergy, p2: skyEnergy)) |
239 | return; |
240 | |
241 | m_skyEnergy = skyEnergy; |
242 | emit skyEnergyChanged(skyEnergy: m_skyEnergy); |
243 | scheduleTextureUpdate(); |
244 | } |
245 | |
246 | void ProceduralSkyTextureData::setGroundBottomColor(QColor groundBottomColor) |
247 | { |
248 | if (m_groundBottomColor == groundBottomColor) |
249 | return; |
250 | |
251 | m_groundBottomColor = groundBottomColor; |
252 | emit groundBottomColorChanged(groundBottomColor: m_groundBottomColor); |
253 | scheduleTextureUpdate(); |
254 | } |
255 | |
256 | void ProceduralSkyTextureData::setGroundHorizonColor(QColor groundHorizonColor) |
257 | { |
258 | if (m_groundHorizonColor == groundHorizonColor) |
259 | return; |
260 | |
261 | m_groundHorizonColor = groundHorizonColor; |
262 | emit groundHorizonColorChanged(groundHorizonColor: m_groundHorizonColor); |
263 | scheduleTextureUpdate(); |
264 | } |
265 | |
266 | void ProceduralSkyTextureData::setGroundCurve(float groundCurve) |
267 | { |
268 | if (qFuzzyCompare(p1: m_groundCurve, p2: groundCurve)) |
269 | return; |
270 | |
271 | m_groundCurve = groundCurve; |
272 | emit groundCurveChanged(groundCurve: m_groundCurve); |
273 | scheduleTextureUpdate(); |
274 | } |
275 | |
276 | void ProceduralSkyTextureData::setGroundEnergy(float groundEnergy) |
277 | { |
278 | if (qFuzzyCompare(p1: m_groundEnergy, p2: groundEnergy)) |
279 | return; |
280 | |
281 | m_groundEnergy = groundEnergy; |
282 | emit groundEnergyChanged(groundEnergy: m_groundEnergy); |
283 | scheduleTextureUpdate(); |
284 | } |
285 | |
286 | void ProceduralSkyTextureData::setSunColor(QColor sunColor) |
287 | { |
288 | if (m_sunColor == sunColor) |
289 | return; |
290 | |
291 | m_sunColor = sunColor; |
292 | emit sunColorChanged(sunColor: m_sunColor); |
293 | scheduleTextureUpdate(); |
294 | } |
295 | |
296 | void ProceduralSkyTextureData::setSunLatitude(float sunLatitude) |
297 | { |
298 | if (qFuzzyCompare(p1: m_sunLatitude, p2: sunLatitude)) |
299 | return; |
300 | |
301 | m_sunLatitude = sunLatitude; |
302 | emit sunLatitudeChanged(sunLatitude: m_sunLatitude); |
303 | scheduleTextureUpdate(); |
304 | } |
305 | |
306 | void ProceduralSkyTextureData::setSunLongitude(float sunLongitude) |
307 | { |
308 | if (qFuzzyCompare(p1: m_sunLongitude, p2: sunLongitude)) |
309 | return; |
310 | |
311 | m_sunLongitude = sunLongitude; |
312 | emit sunLongitudeChanged(sunLongitude: m_sunLongitude); |
313 | scheduleTextureUpdate(); |
314 | } |
315 | |
316 | void ProceduralSkyTextureData::setSunAngleMin(float sunAngleMin) |
317 | { |
318 | if (qFuzzyCompare(p1: m_sunAngleMin, p2: sunAngleMin)) |
319 | return; |
320 | |
321 | m_sunAngleMin = sunAngleMin; |
322 | emit sunAngleMinChanged(sunAngleMin: m_sunAngleMin); |
323 | scheduleTextureUpdate(); |
324 | } |
325 | |
326 | void ProceduralSkyTextureData::setSunAngleMax(float sunAngleMax) |
327 | { |
328 | if (qFuzzyCompare(p1: m_sunAngleMax, p2: sunAngleMax)) |
329 | return; |
330 | |
331 | m_sunAngleMax = sunAngleMax; |
332 | emit sunAngleMaxChanged(sunAngleMax: m_sunAngleMax); |
333 | scheduleTextureUpdate(); |
334 | } |
335 | |
336 | void ProceduralSkyTextureData::setSunCurve(float sunCurve) |
337 | { |
338 | if (qFuzzyCompare(p1: m_sunCurve, p2: sunCurve)) |
339 | return; |
340 | |
341 | m_sunCurve = sunCurve; |
342 | emit sunCurveChanged(sunCurve: m_sunCurve); |
343 | scheduleTextureUpdate(); |
344 | } |
345 | |
346 | void ProceduralSkyTextureData::setSunEnergy(float sunEnergy) |
347 | { |
348 | if (qFuzzyCompare(p1: m_sunEnergy, p2: sunEnergy)) |
349 | return; |
350 | |
351 | m_sunEnergy = sunEnergy; |
352 | emit sunEnergyChanged(sunEnergy: m_sunEnergy); |
353 | scheduleTextureUpdate(); |
354 | } |
355 | |
356 | void ProceduralSkyTextureData::setTextureQuality(ProceduralSkyTextureData::SkyTextureQuality textureQuality) |
357 | { |
358 | if (m_textureQuality == textureQuality) |
359 | return; |
360 | |
361 | m_textureQuality = textureQuality; |
362 | emit textureQualityChanged(textureQuality: m_textureQuality); |
363 | scheduleTextureUpdate(); |
364 | } |
365 | |
366 | void ProceduralSkyTextureData::generateRGBA16FTexture() |
367 | { |
368 | int size = 0; |
369 | switch (m_textureQuality) { |
370 | case SkyTextureQuality::SkyTextureQualityLow: |
371 | size = 512; |
372 | break; |
373 | case SkyTextureQuality::SkyTextureQualityMedium: |
374 | size = 1024; |
375 | break; |
376 | case SkyTextureQuality::SkyTextureQualityHigh: |
377 | size = 2048; |
378 | break; |
379 | case SkyTextureQuality::SkyTextureQualityVeryHigh: |
380 | size = 4096; |
381 | break; |
382 | } |
383 | |
384 | const int width = size; |
385 | const int height = width / 2; |
386 | setSize(QSize(width, height)); |
387 | setFormat(Format::RGBA16F); |
388 | setHasTransparency(false); |
389 | const int dataSize = width * height * 4 * 2; // 2 bytes per channel |
390 | QByteArray imageData; |
391 | imageData.resize(size: dataSize); |
392 | generateSkyTexture(width, height, imageData, isRGBE: false); |
393 | setTextureData(imageData); |
394 | } |
395 | |
396 | QByteArray ProceduralSkyTextureData::generateSkyTexture(int width, int height, QByteArray &imageData, bool isRGBE) const |
397 | { |
398 | quint32 *data = reinterpret_cast<quint32 *>(imageData.data()); |
399 | |
400 | LinearColor skyTopLinear(m_skyTopColor); |
401 | LinearColor skyHorizonLinear(m_skyHorizonColor); |
402 | LinearColor groundBottomLinear(m_groundBottomColor); |
403 | LinearColor groundHorizonLinear(m_groundHorizonColor); |
404 | LinearColor sunLinear(m_sunColor); |
405 | sunLinear.r *= m_sunEnergy; |
406 | sunLinear.g *= m_sunEnergy; |
407 | sunLinear.b *= m_sunEnergy; |
408 | |
409 | QVector3D sun(0, 0, -1); |
410 | |
411 | sun = QQuaternion::fromAxisAndAngle(axis: QVector3D(1, 0, 0), angle: m_sunLatitude) * sun; |
412 | sun = QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 1, 0), angle: m_sunLongitude) * sun; |
413 | sun.normalize(); |
414 | |
415 | auto clamp = [](float value, float min, float max) { |
416 | if (value < min) |
417 | return min; |
418 | else if (value > max) |
419 | return max; |
420 | return value; |
421 | }; |
422 | |
423 | auto ease = [](float x, float c) { |
424 | if (x < 0.0f) |
425 | x = 0.0f; |
426 | else if (x > 1.0f) |
427 | x = 1.0f; |
428 | if (c > 0.0f) { |
429 | if (c < 1.0f) { |
430 | return 1.0f - qPow(x: 1.0f - x, y: 1.0f / c); |
431 | } else { |
432 | return qPow(x, y: c); |
433 | } |
434 | } else if (c < 0.0f) { |
435 | if (x < 0.5f) { |
436 | return qPow(x: x * 2.0f, y: -c) * 0.5f; |
437 | } else { |
438 | return (1.0f - qPow(x: 1.0f - (x - 0.5f) * 2.0f, y: -c)) * 0.5f + 0.5f; |
439 | } |
440 | } else |
441 | return 0.0f; |
442 | }; |
443 | |
444 | for (int i = 0; i < width; i++) { |
445 | |
446 | float u = float(i) / (width - 1); |
447 | float phi = u * 2.0 * M_PI; |
448 | |
449 | for (int j = 0; j < height; j++) { |
450 | float v = float(j) / (height - 1); |
451 | float theta = v * M_PI; |
452 | |
453 | QVector3D normal(qSin(v: phi) * qSin(v: theta) * -1.0, |
454 | qCos(v: theta), |
455 | qCos(v: phi) * qSin(v: theta) * -1.0); |
456 | normal.normalize(); |
457 | float vAngle = qAcos(v: clamp(normal.y(), -1.0, 1.0)); |
458 | LinearColor color; |
459 | |
460 | if (normal.y() < 0) { |
461 | // Ground color |
462 | float c = (vAngle - (M_PI * 0.5f)) / (M_PI * 0.5f); |
463 | color = groundHorizonLinear.interpolate(color: groundBottomLinear, value: ease(c, m_groundCurve)); |
464 | color.r *= m_groundEnergy; |
465 | color.g *= m_groundEnergy; |
466 | color.b *= m_groundEnergy; |
467 | } else { |
468 | // Sky color |
469 | float c = vAngle / (M_PI * 0.5f); |
470 | color = skyHorizonLinear.interpolate(color: skyTopLinear, value: ease(1.0 - c, m_skyCurve)); |
471 | color.r *= m_skyEnergy; |
472 | color.g *= m_skyEnergy; |
473 | color.b *= m_skyEnergy; |
474 | |
475 | float sunAngle = qRadiansToDegrees(radians: qAcos(v: clamp(QVector3D::dotProduct(v1: sun, v2: normal), -1.0f, 1.0f))); |
476 | if (sunAngle < m_sunAngleMin) { |
477 | color = color.blend(color: sunLinear); |
478 | } else if (sunAngle < m_sunAngleMax) { |
479 | float c2 = (sunAngle - m_sunAngleMin) / (m_sunAngleMax - m_sunAngleMin); |
480 | c2 = ease(c2, m_sunCurve); |
481 | color = color.blend(color: sunLinear).interpolate(color, value: c2); |
482 | } |
483 | } |
484 | |
485 | // Write from bottom to top |
486 | if (isRGBE) { |
487 | data[(height - j - 1) * width + i] = color.toRGBE8(); |
488 | } else { |
489 | // RGBA16F |
490 | const int offset = ((height - j - 1) * width + i) * 2; |
491 | qfloat16 *fData = reinterpret_cast<qfloat16 *>(data + offset); |
492 | float pixel[4] = {color.r, color.g, color.b, color.a }; |
493 | qFloatToFloat16(fData, pixel, length: 4); |
494 | } |
495 | } |
496 | } |
497 | |
498 | return imageData; |
499 | } |
500 | |
501 | void ProceduralSkyTextureData::scheduleTextureUpdate() |
502 | { |
503 | generateRGBA16FTexture(); |
504 | } |
505 | |
506 | ProceduralSkyTextureData::LinearColor::LinearColor(const QColor &color) |
507 | { |
508 | const float red = color.redF(); |
509 | const float green = color.greenF(); |
510 | const float blue = color.blueF(); |
511 | const float alpha = color.alphaF(); |
512 | |
513 | r = red < 0.04045 ? red * (1.0 / 12.92) : qPow(x: (red + 0.055) * (1.0 / (1 + 0.055)), y: 2.4), |
514 | g = green < 0.04045 ? green * (1.0 / 12.92) : qPow(x: (green + 0.055) * (1.0 / (1 + 0.055)), y: 2.4), |
515 | b = blue < 0.04045 ? blue * (1.0 / 12.92) : qPow(x: (blue + 0.055) * (1.0 / (1 + 0.055)), y: 2.4), |
516 | a = alpha; |
517 | } |
518 | |
519 | ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::interpolate(const ProceduralSkyTextureData::LinearColor &color, float value) const |
520 | { |
521 | LinearColor copy = *this; |
522 | |
523 | copy.r += (value * (color.r - r)); |
524 | copy.g += (value * (color.g - g)); |
525 | copy.b += (value * (color.b - b)); |
526 | copy.a += (value * (color.a - a)); |
527 | |
528 | return copy; |
529 | } |
530 | |
531 | ProceduralSkyTextureData::LinearColor ProceduralSkyTextureData::LinearColor::blend(const ProceduralSkyTextureData::LinearColor &color) const |
532 | { |
533 | LinearColor copy; |
534 | float sa = 1.0 - color.a; |
535 | copy.a = a * sa + color.a; |
536 | if (copy.a == 0) { |
537 | return LinearColor(); |
538 | } else { |
539 | copy.r = (r * a * sa + color.r * color.a) / copy.a; |
540 | copy.g = (g * a * sa + color.g * color.a) / copy.a; |
541 | copy.b = (b * a * sa + color.b * color.a) / copy.a; |
542 | } |
543 | return copy; |
544 | } |
545 | |
546 | quint32 ProceduralSkyTextureData::LinearColor::toRGBA8() const |
547 | { |
548 | return (quint32(lrintf(x: r)) & 0xFF) | |
549 | ((quint32(lrintf(x: g)) & 0xFF) << 8) | |
550 | ((quint32(lrintf(x: b)) & 0xFF) << 16) | |
551 | ((quint32(lrintf(x: a)) & 0xFF) << 24); |
552 | } |
553 | |
554 | quint32 ProceduralSkyTextureData::LinearColor::toRGBE8() const |
555 | { |
556 | float v = 0.0f; |
557 | int exp = 0; |
558 | |
559 | v = r; |
560 | if (g > v) |
561 | v = g; |
562 | if (b > v) |
563 | v = b; |
564 | |
565 | v = frexp(x: v, exponent: &exp) * 256.0f / v; |
566 | quint32 result = 0; |
567 | quint8 *components = reinterpret_cast<quint8*>(&result); |
568 | components[0] = quint8(r * v); |
569 | components[1] = quint8(g * v); |
570 | components[2] = quint8(b * v); |
571 | components[3] = quint8(exp + 128); |
572 | return result; |
573 | } |
574 | |
575 | quint32 ProceduralSkyTextureData::LinearColor::toRGBE9995() const |
576 | { |
577 | const float pow2to9 = 512.0f; |
578 | const float B = 15.0f; |
579 | const float N = 9.0f; |
580 | |
581 | float sharedExp = 65408.000f; |
582 | |
583 | float cRed = qMax(a: 0.0f, b: qMin(a: sharedExp, b: r)); |
584 | float cGreen = qMax(a: 0.0f, b: qMin(a: sharedExp, b: g)); |
585 | float cBlue = qMax(a: 0.0f, b: qMin(a: sharedExp, b)); |
586 | |
587 | float cMax = qMax(a: cRed, b: qMax(a: cGreen, b: cBlue)); |
588 | |
589 | float expp = qMax(a: -B - 1.0f, b: floor(x: std::log(x: cMax) / M_LN2)) + 1.0f + B; |
590 | |
591 | float sMax = (float)floor(x: (cMax / qPow(x: 2.0f, y: expp - B - N)) + 0.5f); |
592 | |
593 | float exps = expp + 1.0f; |
594 | |
595 | if (0.0 <= sMax && sMax < pow2to9) { |
596 | exps = expp; |
597 | } |
598 | |
599 | float sRed = qFloor(v: (cRed / pow(x: 2.0f, y: exps - B - N)) + 0.5f); |
600 | float sGreen = qFloor(v: (cGreen / pow(x: 2.0f, y: exps - B - N)) + 0.5f); |
601 | float sBlue = qFloor(v: (cBlue / pow(x: 2.0f, y: exps - B - N)) + 0.5f); |
602 | |
603 | return (quint32(lrintf(x: sRed)) & 0x1FF) | |
604 | ((quint32(lrintf(x: sGreen)) & 0x1FF) << 9) | |
605 | ((quint32(lrintf(x: sBlue)) & 0x1FF) << 18) | |
606 | ((quint32(lrintf(x: exps)) & 0x1F) << 27); |
607 | } |
608 | |
609 | QT_END_NAMESPACE |
610 | |