Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qssgrenderloadedtexture.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h>
6#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
7#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrendertexturedata_p.h>
9#include <QtGui/QImageReader>
10#include <QtGui/QColorSpace>
11#include <QtMath>
12
13#include <QtQuick3DUtils/private/qssgutils_p.h>
14
15#include <private/qtexturefilereader_p.h>
16
17#define TINYEXR_IMPLEMENTATION
18#define TINYEXR_USE_MINIZ 0
19#define TINYEXR_USE_THREAD 1
20#include <zlib.h>
21#include <tinyexr.h>
22
24
25
27{
28 QFile *file = nullptr;
29 QString tryPath = inPath.startsWith(QLatin1String("qrc:/")) ? inPath.mid(3) : inPath;
30 QFileInfo fi(tryPath);
31 bool found = fi.exists();
32 if (!found && fi.isNativePath()) {
33 tryPath.prepend(QLatin1String(":/"));
34 fi.setFile(tryPath);
35 found = fi.exists();
36 }
37 if (found) {
38 QString filePath = fi.canonicalFilePath();
39 file = new QFile(filePath);
41 if (outPath)
42 *outPath = filePath;
43 } else {
44 delete file;
45 file = nullptr;
46 }
47 }
48 if (!file && !inQuiet)
49 qCWarning(WARNING, "Failed to find file: %s", qPrintable(inPath));
51}
52
54 QString *outPath, FileType *outFileType)
55{
56 static const QList<QByteArray> hdrFormats = QList<QByteArray>({ "hdr", "exr" });
57 static const QList<QByteArray> textureFormats = QTextureFileReader::supportedFileFormats();
58 static const QList<QByteArray> imageFormats = QImageReader::supportedImageFormats();
59 static const QList<QByteArray> allFormats = textureFormats + hdrFormats + imageFormats;
60
61 QString filePath;
63 QSharedPointer<QIODevice> stream = getStreamForFile(inPath, true, &filePath);
64 if (stream) {
65 ext = QFileInfo(filePath).suffix().toLatin1().toLower();
66 } else {
67 for (const QByteArray &format : allFormats) {
68 QString tryName = inPath + QLatin1Char('.') + QLatin1String(format);
69 stream = getStreamForFile(tryName, true, &filePath);
70 if (stream) {
71 ext = format;
72 break;
73 }
74 }
75 }
76 if (stream) {
77 if (outPath)
78 *outPath = filePath;
79 if (outFileType) {
81 if (hdrFormats.contains(ext))
82 type = HdrFile;
83 else if (textureFormats.contains(ext))
85 else if (imageFormats.contains(ext))
87 *outFileType = type;
88 }
89 } else if (!inQuiet) {
90 qCWarning(WARNING, "Failed to find texture file for: %s", qPrintable(inPath));
91 }
92 return stream;
93}
94
96{
97 switch (internalFormat) {
98 case 0x8229:
100 case 0x822A:
102 case 0x822D:
104 case 0x8235:
106 case 0x8236:
108 case 0x822E:
110 case 0x822B:
112 case 0x8058:
114 case 0x8051:
116 case 0x8C41:
118 case 0x8C43:
120 case 0x8D62:
122 case 0x803C:
124 case 0x8040:
126 case 0x8042:
128 case 0x8045:
130 case 0x881A:
132 case 0x822F:
134 case 0x8230:
136 case 0x8815:
138 case 0x8814:
140 case 0x8C3A:
142 case 0x8C3D:
144 case 0x8059:
146 case 0x881B:
148 case 0x8D70:
150 case 0x8D71:
152 case 0x8D76:
154 case 0x8D77:
156 case 0x8D7C:
158 case 0x8D7D:
160 case 0x8D82:
162 case 0x8D83:
164 case 0x8D88:
166 case 0x8D89:
168 case 0x8D8E:
170 case 0x8D8F:
172 case 0x83F1:
174 case 0x83F0:
176 case 0x83F2:
178 case 0x83F3:
180 case 0x9270:
182 case 0x9271:
184 case 0x9272:
186 case 0x9273:
188 case 0x9274:
190 case 0x9275:
192 case 0x9276:
194 case 0x9277:
196 case 0x9278:
198 case 0x9279:
200 case 0x93B0:
202 case 0x93B1:
204 case 0x93B2:
206 case 0x93B3:
208 case 0x93B4:
210 case 0x93B5:
212 case 0x93B6:
214 case 0x93B7:
216 case 0x93B8:
218 case 0x93B9:
220 case 0x93BA:
222 case 0x93BB:
224 case 0x93BC:
226 case 0x93BD:
228 case 0x93D0:
230 case 0x93D1:
232 case 0x93D2:
234 case 0x93D3:
236 case 0x93D4:
238 case 0x93D5:
240 case 0x93D6:
242 case 0x93D7:
244 case 0x93D8:
246 case 0x93D9:
248 case 0x93DA:
250 case 0x93DB:
252 case 0x93DC:
254 case 0x93DD:
256 case 0x81A5:
258 case 0x81A6:
260 case 0x81A7:
262 case 0x88F0:
264 default:
266 }
267}
268
269static QImage loadImage(const QString &inPath, bool flipVertical)
270{
271 QImage image(inPath);
272 if (image.isNull())
273 return image;
274 const QPixelFormat pixFormat = image.pixelFormat();
276 if (image.colorCount()) // a palleted image
277 targetFormat = QImage::Format_RGBA8888;
278 else if (pixFormat.channelCount() == 1)
279 targetFormat = QImage::Format_Grayscale8;
280 else if (pixFormat.alphaUsage() == QPixelFormat::IgnoresAlpha)
281 targetFormat = QImage::Format_RGBX8888;
282 else if (pixFormat.premultiplied() == QPixelFormat::NotPremultiplied)
283 targetFormat = QImage::Format_RGBA8888;
284
285 image.convertTo(targetFormat); // convert to a format mappable to QRhiTexture::Format
286 if (flipVertical)
287 image.mirror(); // Flip vertically to the conventional Y-up orientation
288 return image;
289}
290
292{
293 QImage image = loadImage(inPath, flipVertical);
294 if (image.isNull())
295 return nullptr;
297 retval->width = image.width();
298 retval->height = image.height();
299 retval->components = image.pixelFormat().channelCount();
300 retval->image = image;
301 retval->data = (void *)retval->image.bits();
302 retval->dataSizeInBytes = image.sizeInBytes();
303 retval->setFormatFromComponents();
304 // #TODO: This is a very crude way detect color space
305 retval->isSRGB = image.colorSpace().transferFunction() != QColorSpace::TransferFunction::Linear;
306
307 return retval;
308}
309
311{
312 QSSGLoadedTexture *retval = nullptr;
313
314 // Open File
315 QFile imageFile(inPath);
316 if (!imageFile.open(QIODevice::ReadOnly)) {
317 qWarning() << "Could not open image file: " << inPath;
318 return retval;
319 }
320 auto reader = new QTextureFileReader(&imageFile, inPath);
321
322 if (!reader->canRead()) {
323 qWarning() << "Unable to read image file: " << inPath;
324 delete reader;
325 return retval;
326 }
327 retval = new QSSGLoadedTexture;
328 retval->textureFileData = reader->read();
329
330 // Fill out what makes sense, leave the rest at the default 0 and null.
331 retval->width = retval->textureFileData.size().width();
332 retval->height = retval->textureFileData.size().height();
333 auto glFormat = retval->textureFileData.glInternalFormat()
335 : retval->textureFileData.glFormat();
336 retval->format = fromGLtoTextureFormat(glFormat);
337
338 delete reader;
339 imageFile.close();
340
341 return retval;
342
343}
344
345namespace {
346typedef unsigned char RGBE[4];
347#define R 0
348#define G 1
349#define B 2
350#define E 3
351
352#define MINELEN 8 // minimum scanline length for encoding
353#define MAXELEN 0x7fff // maximum scanline length for encoding
354
355
356
357inline int calculateLine(int width, int bitdepth) { return ((width * bitdepth) + 7) / 8; }
358
359inline int calculatePitch(int line) { return (line + 3) & ~3; }
360
361float convertComponent(int exponent, int val)
362{
363 float v = val / (256.0f);
364 float d = powf(2.0f, (float)exponent - 128.0f);
365 return v * d;
366}
367
368void decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w)
369{
370 scanline[0][R] = *p++;
371 scanline[0][G] = *p++;
372 scanline[0][B] = *p++;
373 scanline[0][E] = *p++;
374
375 if (scanline[0][R] == 2 && scanline[0][G] == 2 && scanline[0][B] < 128) {
376 // new rle, the first pixel was a dummy
377 for (int channel = 0; channel < 4; ++channel) {
378 for (int x = 0; x < w && p < pEnd; ) {
379 unsigned char c = *p++;
380 if (c > 128) { // run
381 if (p < pEnd) {
382 int repCount = c & 127;
383 c = *p++;
384 while (repCount--)
385 scanline[x++][channel] = c;
386 }
387 } else { // not a run
388 while (c-- && p < pEnd)
389 scanline[x++][channel] = *p++;
390 }
391 }
392 }
393 } else {
394 // old rle
395 scanline[0][R] = 2;
396 int bitshift = 0;
397 int x = 1;
398 while (x < w && pEnd - p >= 4) {
399 scanline[x][R] = *p++;
400 scanline[x][G] = *p++;
401 scanline[x][B] = *p++;
402 scanline[x][E] = *p++;
403
404 if (scanline[x][R] == 1 && scanline[x][G] == 1 && scanline[x][B] == 1) { // run
405 int repCount = scanline[x][3] << bitshift;
406 while (repCount--) {
407 memcpy(scanline[x], scanline[x - 1], 4);
408 ++x;
409 }
410 bitshift += 8;
411 } else { // not a run
412 ++x;
413 bitshift = 0;
414 }
415 }
416 }
417}
418
419void decodeScanlineToTexture(RGBE *scanline, int width, void *outBuf, quint32 offset, QSSGRenderTextureFormat inFormat)
420{
421 quint8 *target = reinterpret_cast<quint8 *>(outBuf);
422 target += offset;
423
424 if (inFormat == QSSGRenderTextureFormat::RGBE8) {
425 memcpy(target, scanline, size_t(width) * 4);
426 } else {
427 float rgbaF32[4];
428 for (int i = 0; i < width; ++i) {
429 rgbaF32[R] = convertComponent(scanline[i][E], scanline[i][R]);
430 rgbaF32[G] = convertComponent(scanline[i][E], scanline[i][G]);
431 rgbaF32[B] = convertComponent(scanline[i][E], scanline[i][B]);
432 rgbaF32[3] = 1.0f;
433
434 inFormat.encodeToPixel(rgbaF32, target, i * inFormat.getSizeofFormat());
435 }
436 }
437}
438
440{
441 QSSGLoadedTexture *imageData = nullptr;
442
443 char sig[256];
444 source->seek(0);
445 source->read(sig, 11);
446 if (!strncmp(sig, "#?RADIANCE\n", 11)) {
447 QByteArray buf = source->readAll();
448 const char *p = buf.constData();
449 const char *pEnd = p + buf.size();
450
451 // Process lines until the empty one.
453 while (p < pEnd) {
454 char c = *p++;
455 if (c == '\n') {
456 if (line.isEmpty())
457 break;
458 if (line.startsWith(QByteArrayLiteral("FORMAT="))) {
459 const QByteArray format = line.mid(7).trimmed();
460 if (format != QByteArrayLiteral("32-bit_rle_rgbe")) {
461 qWarning("HDR format '%s' is not supported", format.constData());
462 return imageData;
463 }
464 }
465 line.clear();
466 } else {
467 line.append(c);
468 }
469 }
470 if (p == pEnd) {
471 qWarning("Malformed HDR image data at property strings");
472 return imageData;
473 }
474
475 // Get the resolution string.
476 while (p < pEnd) {
477 char c = *p++;
478 if (c == '\n')
479 break;
480 line.append(c);
481 }
482 if (p == pEnd) {
483 qWarning("Malformed HDR image data at resolution string");
484 return imageData;
485 }
486
487 int width = 0;
488 int height = 0;
489 // We only care about the standard orientation.
490#ifdef Q_CC_MSVC
491 if (!sscanf_s(line.constData(), "-Y %d +X %d", &height, &width)) {
492#else
493 if (!sscanf(line.constData(), "-Y %d +X %d", &height, &width)) {
494#endif
495 qWarning("Unsupported HDR resolution string '%s'", line.constData());
496 return imageData;
497 }
498 if (width <= 0 || height <= 0) {
499 qWarning("Invalid HDR resolution");
500 return imageData;
501 }
502
503 const int bytesPerPixel = format.getSizeofFormat();
504 const int bitCount = bytesPerPixel * 8;
505 const int pitch = calculatePitch(calculateLine(width, bitCount));
506 const quint32 dataSize = quint32(height * pitch);
508 imageData->dataSizeInBytes = dataSize;
509 imageData->data = ::malloc(dataSize);
510 imageData->width = width;
511 imageData->height = height;
512 imageData->format = format;
513 imageData->components = format.getNumberOfComponent();
514
515 // Allocate a scanline worth of RGBE data
516 RGBE *scanline = new RGBE[width];
517
518 // Note we are writing to the data buffer from bottom to top
519 // to correct for -Y orientation
520 for (int y = 0; y < height; ++y) {
521 quint32 byteOffset = quint32((height - 1 - y) * width * bytesPerPixel);
522 if (pEnd - p < 4) {
523 qWarning("Unexpected end of HDR data");
524 delete[] scanline;
525 return imageData;
526 }
527 decrunchScanline(p, pEnd, scanline, width);
528 decodeScanlineToTexture(scanline, width, imageData->data, byteOffset, format);
529 }
530
531 delete[] scanline;
532 }
533
534 return imageData;
535}
536
538{
539 QSSGLoadedTexture *imageData = nullptr;
540
541 char versionBuffer[tinyexr::kEXRVersionSize];
542 source->seek(0);
543 auto size = source->read(versionBuffer, tinyexr::kEXRVersionSize);
544 // Check if file is big enough
545 if (size != tinyexr::kEXRVersionSize)
546 return imageData;
547 // Try to load the Version
548 EXRVersion exrVersion;
549 if (ParseEXRVersionFromMemory(&exrVersion, reinterpret_cast<unsigned char *>(versionBuffer), tinyexr::kEXRVersionSize) != TINYEXR_SUCCESS)
550 return imageData;
551
552 // Check that the file is not a multipart file
553 if (exrVersion.multipart)
554 return imageData;
555
556 // If we get here, than this is an EXR file
557 source->seek(0);
558 QByteArray buf = source->readAll();
559 const char *err = nullptr;
560 // Header
561 EXRHeader exrHeader;
562 InitEXRHeader(&exrHeader);
563 if (ParseEXRHeaderFromMemory(&exrHeader,
564 &exrVersion,
565 reinterpret_cast<const unsigned char *>(buf.constData()),
566 buf.size(),
567 &err) != TINYEXR_SUCCESS) {
568 qWarning("Failed to parse EXR Header with error: '%s'", err);
569 FreeEXRErrorMessage(err);
570 return imageData;
571 }
572
573 // Make sure we get floats instead of half floats
574 for (int i = 0; i < exrHeader.num_channels; i++) {
575 if (exrHeader.pixel_types[i] == TINYEXR_PIXELTYPE_HALF)
576 exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
577 }
578
579 // Image
580 EXRImage exrImage;
581
582 InitEXRImage(&exrImage);
583 if (LoadEXRImageFromMemory(&exrImage,
584 &exrHeader,
585 reinterpret_cast<const unsigned char *>(buf.constData()),
586 buf.size(),
587 &err) != TINYEXR_SUCCESS) {
588 qWarning("Failed to load EXR Image with error: '%s'", err);
589 FreeEXRHeader(&exrHeader);
590 FreeEXRErrorMessage(err);
591 return imageData;
592 }
593
594 // Setup Output container
595 const int bytesPerPixel = format.getSizeofFormat();
596 const int bitCount = bytesPerPixel * 8;
597 const int pitch = calculatePitch(calculateLine(exrImage.width, bitCount));
598 const quint32 dataSize = quint32(exrImage.height * pitch);
600 imageData->dataSizeInBytes = dataSize;
601 imageData->data = ::malloc(dataSize);
602 imageData->width = exrImage.width;
603 imageData->height = exrImage.height;
604 imageData->format = format;
605 imageData->components = format.getNumberOfComponent();
606 imageData->isSRGB = false;
607
608 quint8 *target = reinterpret_cast<quint8 *>(imageData->data);
609
610 // Convert data
611 // RGBA
612 int idxR = -1;
613 int idxG = -1;
614 int idxB = -1;
615 int idxA = -1;
616 for (int c = 0; c < exrHeader.num_channels; c++) {
617 if (strcmp(exrHeader.channels[c].name, "R") == 0)
618 idxR = c;
619 else if (strcmp(exrHeader.channels[c].name, "G") == 0)
620 idxG = c;
621 else if (strcmp(exrHeader.channels[c].name, "B") == 0)
622 idxB = c;
623 else if (strcmp(exrHeader.channels[c].name, "A") == 0)
624 idxA = c;
625 }
626 const bool isSingleChannel = exrHeader.num_channels == 1;
627 float rgbaF32[4];
628
629 if (exrHeader.tiled) {
630 for (int it = 0; it < exrImage.num_tiles; it++) {
631 for (int j = 0; j < exrHeader.tile_size_y; j++)
632 for (int i = 0; i < exrHeader.tile_size_x; i++) {
633 const int ii =
634 exrImage.tiles[it].offset_x * exrHeader.tile_size_x + i;
635 const int jj =
636 exrImage.tiles[it].offset_y * exrHeader.tile_size_y + j;
637 const int inverseJJ = std::abs(jj - (exrImage.height - 1));
638 const int idx = ii + inverseJJ * exrImage.width;
639
640 // out of region check.
641 if (ii >= exrImage.width) {
642 continue;
643 }
644 if (jj >= exrImage.height) {
645 continue;
646 }
647 const int srcIdx = i + j * exrHeader.tile_size_x;
648 unsigned char **src = exrImage.tiles[it].images;
649 if (isSingleChannel) {
650 rgbaF32[R] = reinterpret_cast<float **>(src)[0][srcIdx];
651 rgbaF32[G] = rgbaF32[R];
652 rgbaF32[B] = rgbaF32[R];
653 rgbaF32[3] = rgbaF32[R];
654 } else {
655 rgbaF32[R] = reinterpret_cast<float **>(src)[idxR][srcIdx];
656 rgbaF32[G] = reinterpret_cast<float **>(src)[idxG][srcIdx];
657 rgbaF32[B] = reinterpret_cast<float **>(src)[idxB][srcIdx];
658 if (idxA != -1)
659 rgbaF32[3] = reinterpret_cast<float **>(src)[idxA][srcIdx];
660 else
661 rgbaF32[3] = 1.0f;
662 }
663 format.encodeToPixel(rgbaF32, target, idx * bytesPerPixel);
664 }
665 }
666 } else {
667 int idx = 0;
668 for (int y = exrImage.height - 1; y >= 0; --y) {
669 for (int x = 0; x < exrImage.width; x++) {
670 const int i = y * exrImage.width + x;
671 if (isSingleChannel) {
672 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[0][i];
673 rgbaF32[G] = rgbaF32[R];
674 rgbaF32[B] = rgbaF32[R];
675 rgbaF32[3] = rgbaF32[R];
676 } else {
677 rgbaF32[R] = reinterpret_cast<float **>(exrImage.images)[idxR][i];
678 rgbaF32[G] = reinterpret_cast<float **>(exrImage.images)[idxG][i];
679 rgbaF32[B] = reinterpret_cast<float **>(exrImage.images)[idxB][i];
680 if (idxA != -1)
681 rgbaF32[3] = reinterpret_cast<float **>(exrImage.images)[idxA][i];
682 else
683 rgbaF32[3] = 1.0f;
684 }
685 format.encodeToPixel(rgbaF32, target, idx * bytesPerPixel);
686 ++idx;
687 }
688 }
689 }
690
691 // Cleanup
692 FreeEXRImage(&exrImage);
693 FreeEXRHeader(&exrHeader);
694
695 return imageData;
696
697}
698}
699
701{
702 QSSGLoadedTexture *imageData = nullptr;
703 // We need to do a sanity check on the inFormat
706 // Loading HDR images for use outside of lightProbes will end up here
707 // The renderer doesn't understand RGBE8 textures outside of lightProbes
708 // So this needs to be a "real" format
709 // TODO: This is a fallback, but there is no way of telling here what formats are supported
711 }
712
713 // .hdr Files
714 imageData = loadRadianceHdr(source, format);
715
716 // .exr Files
717 if (!imageData)
718 imageData = loadExr(source, format);
719
720 return imageData;
721}
722
724{
726
727 if (!textureData->format().isCompressedTextureFormat()) {
728 const int bytesPerPixel = textureData->format().getSizeofFormat();
729 const int bitCount = bytesPerPixel * 8;
730 const int pitch = calculatePitch(calculateLine(textureData->size().width(), bitCount));
731 quint32 dataSize = quint32(textureData->size().height() * pitch);
732 if (textureData->depth() > 0)
733 dataSize *= textureData->depth();
734 imageData->dataSizeInBytes = dataSize;
735 // We won't modifiy the data, but that is a nasty cast...
736 imageData->data = const_cast<void*>(reinterpret_cast<const void*>(textureData->textureData().data()));
737 imageData->width = textureData->size().width();
738 imageData->height = textureData->size().height();
739 imageData->depth = textureData->depth();
740 imageData->format = textureData->format();
741 imageData->components = textureData->format().getNumberOfComponent();
742 } else {
743 // Compressed Textures work a bit differently
744 // Fill out what makes sense, leave the rest at the default 0 and null.
745 imageData->data = const_cast<void*>(reinterpret_cast<const void*>(textureData->textureData().data()));
746 imageData->dataSizeInBytes = textureData->textureData().size();
747 // When we use depth we need to do slicing per layer for the uploads, but right now there it is non-trivial
748 // to determine the size of each "pixel" for compressed formats, so we don't support it for now.
749 // TODO: We need to force depth to 0 for now, as we don't support compressed 3D textures from texureData
750 imageData->width = textureData->size().width();
751 imageData->height = textureData->size().height();
752 imageData->format = textureData->format();
753 }
754
755 // #TODO: add an API to make this explicit
756 // For now we assume HDR formats are linear and everything else
757 // is sRGB, which is not ideal but so far this is only used by
758 // the environment mapper code
763 imageData->isSRGB = false;
764 else
765 imageData->isSRGB = true;
766
767 return imageData;
768}
769
770namespace {
771
772bool scanImageForAlpha(const void *inData, quint32 inWidth, quint32 inHeight, quint32 inPixelSizeInBytes, quint8 inAlphaSizeInBits)
773{
774 const quint8 *rowPtr = reinterpret_cast<const quint8 *>(inData);
775 bool hasAlpha = false;
776 if (inAlphaSizeInBits == 0)
777 return hasAlpha;
778 if (inPixelSizeInBytes != 2 && inPixelSizeInBytes != 4) {
779 Q_ASSERT(false);
780 return false;
781 }
782 if (inAlphaSizeInBits > 8) {
783 Q_ASSERT(false);
784 return false;
785 }
786
787 quint32 alphaRightShift = inPixelSizeInBytes * 8 - inAlphaSizeInBits;
788 quint32 maxAlphaValue = (1 << inAlphaSizeInBits) - 1;
789
790 for (quint32 rowIdx = 0; rowIdx < inHeight && !hasAlpha; ++rowIdx) {
791 for (quint32 idx = 0; idx < inWidth && !hasAlpha; ++idx, rowPtr += inPixelSizeInBytes) {
792 quint32 pixelValue = 0;
793 if (inPixelSizeInBytes == 2)
794 pixelValue = *(reinterpret_cast<const quint16 *>(rowPtr));
795 else
796 pixelValue = *(reinterpret_cast<const quint32 *>(rowPtr));
797 pixelValue = pixelValue >> alphaRightShift;
798 if (pixelValue < maxAlphaValue)
799 hasAlpha = true;
800 }
801 }
802 return hasAlpha;
803}
804}
805
807{
808 if (data && image.sizeInBytes() <= 0 && ownsData)
809 ::free(data);
810}
811
813{
814 switch (format.format) {
817 if (!data) // dds
818 return true;
819
820 return scanImageForAlpha(data, width, height, 4, 8);
821 // Scan the image.
825 return false;
827 return false;
829 if (!data) { // dds
830 return true;
831 } else {
832 return scanImageForAlpha(data, width, height, 2, 1);
833 }
835 return true;
839 return false;
841 if (!data) // dds
842 return true;
843
844 return scanImageForAlpha(data, width, height, 2, 8);
846 return false;
850 return false;
852 return false;
857 // TODO : For now, since IBL will be the main consumer, we'll just
858 // pretend there's no alpha. Need to do a proper scan down the line,
859 // but doing it for floats is a little different from integer scans.
860 return false;
861 default:
862 break;
863 }
864 Q_ASSERT(false);
865 return false;
866}
867
868static bool isCompatible(const QImage &img1, const QImage &img2)
869{
870 if (img1.size() != img2.size())
871 return false;
872 if (img1.pixelFormat().channelCount() != img2.pixelFormat().channelCount())
873 return false;
874
875 return true;
876}
877
878static QSSGLoadedTexture *loadCubeMap(const QString &inPath, bool flipY)
879{
881 if (inPath.contains(QStringLiteral("%p"))) {
882 fileNames.reserve(6);
883 const char *faces[6] = { "posx", "negx", "posy", "negy", "posz", "negz" };
884 for (const auto face : faces) {
885 QString fileName = inPath;
888 }
889
890 } else if (inPath.contains(QStringLiteral(";"))) {
891 fileNames = inPath.split(QChar(u';'));
892 }
893 if (fileNames.size() != 6)
894 return nullptr; // TODO: allow sparse cube maps (with some faces missing)
895 std::unique_ptr<QTextureFileData> textureFileData = std::make_unique<QTextureFileData>(QTextureFileData::ImageMode);
896 textureFileData->setNumFaces(6);
897 textureFileData->setNumLevels(1);
898 textureFileData->setLogName(inPath.toUtf8());
899 QImage prevImage;
900 for (int i = 0; i < 6; ++i) {
901 QString searchName = fileNames[i];
902 QString filePath;
903 auto stream = QSSGInputUtil::getStreamForFile(searchName, true, &filePath);
904 if (!stream)
905 return nullptr;
906
907 QImage face = loadImage(filePath, !flipY); // Cube maps are flipped the other way
908 if (face.isNull() || (!prevImage.isNull() && !isCompatible(prevImage, face))) {
909 return nullptr;
910 }
911 textureFileData->setData(face, 0, i);
912 textureFileData->setSize(face.size());
913 prevImage = face;
914 }
915
917
918 retval->textureFileData = *textureFileData;
919
920 retval->width = prevImage.width();
921 retval->height = prevImage.height();
922 retval->components = prevImage.pixelFormat().channelCount();
923 retval->image = prevImage;
924 retval->data = (void *)retval->image.bits();
925 retval->dataSizeInBytes = prevImage.sizeInBytes();
926 retval->setFormatFromComponents();
927 // #TODO: This is a very crude way detect color space
929
930 return retval;
931}
932
934 const QSSGRenderTextureFormat &inFormat,
935 bool inFlipY)
936{
937 if (inPath.isEmpty())
938 return nullptr;
939
940 QSSGLoadedTexture *theLoadedImage = nullptr;
943 QSharedPointer<QIODevice> theStream =
945
946 if (theStream) {
947 switch (fileType) {
949 // inFormat is a suggestion that's only relevant for HDR images
950 // (tells if we want want RGBA16F or RGBE-on-RGBA8)
951 theLoadedImage = loadHdrImage(theStream, inFormat);
952 break;
954 theLoadedImage = loadCompressedImage(fileName); // no choice but to ignore inFlipY here
955 break;
956 default:
957 theLoadedImage = loadQImage(fileName, inFlipY);
958 break;
959 }
960 } else {
961 // Check to see if we can find a cubemap
962 return loadCubeMap(inPath, inFlipY);
963 }
964 return theLoadedImage;
965}
966
IOBluetoothL2CAPChannel * channel
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:534
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
QByteArray toLower() const &
Definition qbytearray.h:190
\inmodule QtCore
Definition qchar.h:48
TransferFunction transferFunction() const noexcept
Returns the predefined transfer function of the color space or TransferFunction::Custom if it doesn't...
void close() override
Calls QFileDevice::flush() and closes the file.
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
bool isNativePath() const
QString suffix() const
Returns the suffix (extension) of the file.
void setFile(const QString &file)
Sets the file that the QFileInfo provides information about to file.
QString canonicalFilePath() const
Returns the canonical path including the file name, i.e.
bool exists() const
Returns true if the file exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:881
bool canRead() const
Returns true if an image can be read for the device (i.e., the image format is supported,...
static QList< QByteArray > supportedImageFormats()
Returns the list of image formats supported by QImageReader.
QImage read()
Reads an image from the device.
\inmodule QtGui
Definition qimage.h:37
QColorSpace colorSpace() const
Definition qimage.cpp:5039
qsizetype sizeInBytes() const
Definition qimage.cpp:1526
QSize size() const
Returns the size of the image, i.e.
int width() const
Returns the width of the image.
uchar * bits()
Returns a pointer to the first pixel data.
Definition qimage.cpp:1677
bool isNull() const
Returns true if it is a null image, otherwise returns false.
Definition qimage.cpp:1197
int height() const
Returns the height of the image.
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_RGBA8888
Definition qimage.h:59
@ Format_RGBA8888_Premultiplied
Definition qimage.h:60
@ Format_RGBX8888
Definition qimage.h:58
@ Format_Grayscale8
Definition qimage.h:66
QPixelFormat pixelFormat() const noexcept
Returns the QImage::Format as a QPixelFormat.
Definition qimage.cpp:5713
Definition qlist.h:74
\inmodule QtGui
constexpr AlphaUsage alphaUsage() const noexcept
Accessor function for alphaUsage.
constexpr AlphaPremultiplied premultiplied() const noexcept
Accessor function for the AlphaPremultiplied enum.
constexpr uchar channelCount() const noexcept
Accessor function for getting the channelCount.
const QByteArray & textureData() const
QSSGRenderTextureFormat format() const
\inmodule QtCore
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QByteArray toLatin1() const &
Definition qstring.h:559
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5299
QStringList split(const QString &sep, Qt::SplitBehavior behavior=Qt::KeepEmptyParts, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Splits the string into substrings wherever sep occurs, and returns the list of those strings.
Definition qstring.cpp:7956
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
const QChar * constData() const
Returns a pointer to the data stored in the QString.
Definition qstring.h:1101
QString mid(qsizetype position, qsizetype n=-1) const
Returns a string that contains n characters of this string, starting at the specified position index.
Definition qstring.cpp:5204
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
bool contains(QChar c, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Definition qstring.h:1217
QString & append(QChar c)
Definition qstring.cpp:3227
QString trimmed() const &
Definition qstring.h:380
QByteArray toUtf8() const &
Definition qstring.h:563
QString & prepend(QChar c)
Definition qstring.h:411
quint32 glInternalFormat() const
quint32 glFormat() const
static QList< QByteArray > supportedFileFormats()
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Definition image.cpp:4
#define QByteArrayLiteral(str)
Definition qbytearray.h:52
EGLStreamKHR stream
#define qWarning
Definition qlogging.h:162
#define qCWarning(category,...)
GLsizei const GLfloat * v
[13]
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLsizei dataSize
GLenum face
GLenum src
GLint GLsizei width
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint GLintptr offset
GLint GLsizei GLsizei GLenum format
GLint y
GLsizei GLenum internalFormat
GLsizei GLsizei GLchar * source
const GLubyte * c
GLuint GLfloat * val
GLint * exponent
GLfloat GLfloat p
[1]
static bool hasAlpha(const QImage &image)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static bool isCompatible(const QImage &img1, const QImage &img2)
static QSSGRenderTextureFormat fromGLtoTextureFormat(quint32 internalFormat)
static QImage loadImage(const QString &inPath, bool flipVertical)
static QSSGLoadedTexture * loadCubeMap(const QString &inPath, bool flipY)
#define qPrintable(string)
Definition qstring.h:1391
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define QStringLiteral(str)
static FileType fileType(const QFileInfo &fi)
unsigned int quint32
Definition qtypes.h:45
unsigned short quint16
Definition qtypes.h:43
int qint32
Definition qtypes.h:44
unsigned char quint8
Definition qtypes.h:41
QFile file
[0]
QFileInfo fi("c:/temp/foo")
[newstuff]
QByteArray imageData
[15]
QStringList fileNames
[4]
\inmodule QtCore \reentrant
Definition qchar.h:17
bool contains(const AT &t) const noexcept
Definition qlist.h:44
static QSharedPointer< QIODevice > getStreamForTextureFile(const QString &inPath, bool inQuiet=false, QString *outPath=nullptr, FileType *outFileType=nullptr)
static QSharedPointer< QIODevice > getStreamForFile(const QString &inPath, bool inQuiet=false, QString *outPath=nullptr)
QTextureFileData textureFileData
static QSSGLoadedTexture * loadHdrImage(const QSharedPointer< QIODevice > &source, const QSSGRenderTextureFormat &inFormat)
static QSSGLoadedTexture * loadCompressedImage(const QString &inPath)
QSSGRenderTextureFormat format
static QSSGLoadedTexture * load(const QString &inPath, const QSSGRenderTextureFormat &inFormat, bool inFlipY=true)
static QSSGLoadedTexture * loadTextureData(QSSGRenderTextureData *textureData)
static QSSGLoadedTexture * loadQImage(const QString &inPath, qint32 flipVertical)
void encodeToPixel(float *inPtr, void *outPtr, qint32 byteOfs) const
constexpr bool isCompressedTextureFormat() const noexcept