diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index d0ccd195fdb..1f35535bd04 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -213,6 +213,7 @@ extend_qtc_plugin(QmlDesigner selectioncontext.cpp selectioncontext.h theme.cpp theme.h zoomaction.cpp zoomaction.h + hdrimage.cpp hdrimage.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore.pri b/src/plugins/qmldesigner/components/componentcore/componentcore.pri index ef97a93343c..174ed0341a5 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore.pri +++ b/src/plugins/qmldesigner/components/componentcore/componentcore.pri @@ -19,6 +19,7 @@ SOURCES += navigation2d.cpp SOURCES += crumblebar.cpp SOURCES += qmldesignericonprovider.cpp SOURCES += zoomaction.cpp +SOURCES += hdrimage.cpp HEADERS += modelnodecontextmenu.h HEADERS += addimagesdialog.h @@ -41,6 +42,7 @@ HEADERS += actioninterface.h HEADERS += crumblebar.h HEADERS += qmldesignericonprovider.h HEADERS += zoomaction.h +HEADERS += hdrimage.h FORMS += \ $$PWD/addsignalhandlerdialog.ui diff --git a/src/plugins/qmldesigner/components/componentcore/hdrimage.cpp b/src/plugins/qmldesigner/components/componentcore/hdrimage.cpp new file mode 100644 index 00000000000..170bceb4e6b --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/hdrimage.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "hdrimage.h" + +#include +#include + +#include + +namespace QmlDesigner { + +// Hdr loading code was copied from QtQuick3D and adapted to create QImage instead of texture. + +typedef unsigned char RGBE[4]; +#define R 0 +#define G 1 +#define B 2 +#define E 3 + +struct M8E8 +{ + quint8 m; + quint8 e; + M8E8() : m(0), e(0){ + } + M8E8(const float val) { + float l2 = 1.f + std::floor(log2f(val)); + float mm = val / powf(2.f, l2); + m = quint8(mm * 255.f); + e = quint8(l2 + 128); + } + M8E8(const float val, quint8 exp) { + if (val <= 0) { + m = e = 0; + return; + } + float mm = val / powf(2.f, exp - 128); + m = quint8(mm * 255.f); + e = exp; + } +}; + +QByteArray fileToByteArray(QString const &filename) +{ + QFile file(filename); + QFileInfo info(file); + + if (info.exists() && file.open(QFile::ReadOnly)) + return file.readAll(); + + return {}; +} + +inline int calculateLine(int width, int bitdepth) { return ((width * bitdepth) + 7) / 8; } + +inline int calculatePitch(int line) { return (line + 3) & ~3; } + +float convertComponent(int exponent, int val) +{ + float v = val / (256.0f); + float d = powf(2.0f, (float)exponent - 128.0f); + return v * d; +} + +void decrunchScanline(const char *&p, const char *pEnd, RGBE *scanline, int w) +{ + scanline[0][R] = *p++; + scanline[0][G] = *p++; + scanline[0][B] = *p++; + scanline[0][E] = *p++; + + if (scanline[0][R] == 2 && scanline[0][G] == 2 && scanline[0][B] < 128) { + // new rle, the first pixel was a dummy + for (int channel = 0; channel < 4; ++channel) { + for (int x = 0; x < w && p < pEnd; ) { + unsigned char c = *p++; + if (c > 128) { // run + if (p < pEnd) { + int repCount = c & 127; + c = *p++; + while (repCount--) + scanline[x++][channel] = c; + } + } else { // not a run + while (c-- && p < pEnd) + scanline[x++][channel] = *p++; + } + } + } + } else { + // old rle + scanline[0][R] = 2; + int bitshift = 0; + int x = 1; + while (x < w && pEnd - p >= 4) { + scanline[x][R] = *p++; + scanline[x][G] = *p++; + scanline[x][B] = *p++; + scanline[x][E] = *p++; + + if (scanline[x][R] == 1 && scanline[x][G] == 1 && scanline[x][B] == 1) { // run + int repCount = scanline[x][3] << bitshift; + while (repCount--) { + memcpy(scanline[x], scanline[x - 1], 4); + ++x; + } + bitshift += 8; + } else { // not a run + ++x; + bitshift = 0; + } + } + } +} + +void decodeScanlineToImageData(RGBE *scanline, int width, void *outBuf, quint32 offset) +{ + quint8 *target = reinterpret_cast(outBuf); + target += offset; + + float rgbaF32[4]; + for (int i = 0; i < width; ++i) { + rgbaF32[R] = convertComponent(scanline[i][E], scanline[i][R]); + rgbaF32[G] = convertComponent(scanline[i][E], scanline[i][G]); + rgbaF32[B] = convertComponent(scanline[i][E], scanline[i][B]); + rgbaF32[3] = 1.0f; + + float max = qMax(rgbaF32[R], qMax(rgbaF32[G], rgbaF32[B])); + M8E8 ex(max); + M8E8 a(rgbaF32[R], ex.e); + M8E8 b(rgbaF32[G], ex.e); + M8E8 c(rgbaF32[B], ex.e); + quint8 *dst = target + i * 4; + dst[0] = c.m; + dst[1] = b.m; + dst[2] = a.m; + dst[3] = 255; + } +} + +HdrImage::HdrImage(const QString &fileName) + : m_fileName(fileName) +{ + loadHdr(); +} + +QPixmap HdrImage::toPixmap() const +{ + // Copy is used to ensure pixmap will be valid after HdrImage goes out of scope + return QPixmap::fromImage(m_image, Qt::NoFormatConversion).copy(); +} + +void HdrImage::loadHdr() +{ + QByteArray buf(fileToByteArray(m_fileName)); + + auto handleError = [this](const QString &error) { + qWarning(QStringLiteral("Failed to load HDR image '%1': %2.").arg(m_fileName, error).toUtf8()); + }; + + if (buf.isEmpty()) { + handleError("File open failed"); + return; + } + + if (!buf.startsWith(QByteArrayLiteral("#?RADIANCE\n"))) { + handleError("Non-HDR file"); + return; + } + + const char *p = buf.constData(); + const char *pEnd = p + buf.size(); + + // Process lines until the empty one. + QByteArray line; + QByteArray formatTag {"FORMAT="}; + QByteArray formatId {"32-bit_rle_rgbe"}; + while (p < pEnd) { + char c = *p++; + if (c == '\n') { + if (line.isEmpty()) + break; + if (line.startsWith(formatTag)) { + const QByteArray format = line.mid(7).trimmed(); + if (format != formatId) { + handleError(QStringLiteral("Unsupported HDR format '%1'") + .arg(QString::fromUtf8(format))); + return; + } + } + line.clear(); + } else { + line.append(c); + } + } + if (p == pEnd) { + handleError("Malformed HDR image data at property strings"); + return; + } + + // Get the resolution string. + while (p < pEnd) { + char c = *p++; + if (c == '\n') + break; + line.append(c); + } + if (p == pEnd) { + handleError("Malformed HDR image data at resolution string"); + return; + } + + int width = 0; + int height = 0; + // We only care about the standard orientation + if (!::sscanf(line.constData(), "-Y %d +X %d", &height, &width)) { + handleError(QStringLiteral("Unsupported HDR resolution string '%1'") + .arg(QString::fromUtf8(line))); + return; + } + if (width <= 0 || height <= 0) { + handleError("Invalid HDR resolution"); + return; + } + + const int bytesPerPixel = 4; + const int bitCount = bytesPerPixel * 8; + const int pitch = calculatePitch(calculateLine(width, bitCount)); + const int dataSize = int(height * pitch); + + m_buf = QByteArray {dataSize, 0}; + + // Allocate a scanline worth of RGBE data + RGBE *scanline = new RGBE[width]; + + for (int y = 0; y < height; ++y) { + quint32 byteOffset = quint32(y * width * bytesPerPixel); + if (pEnd - p < 4) { + handleError("Unexpected end of HDR data"); + delete[] scanline; + return; + } + decrunchScanline(p, pEnd, scanline, width); + decodeScanlineToImageData(scanline, width, m_buf.data(), byteOffset); + } + + delete[] scanline; + + m_image = QImage {reinterpret_cast(m_buf.constData()), width, height, pitch, + QImage::Format_ARGB32}; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/hdrimage.h b/src/plugins/qmldesigner/components/componentcore/hdrimage.h new file mode 100644 index 00000000000..498e7c3c650 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/hdrimage.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#pragma once + +#include + +#include +#include +#include + +namespace QmlDesigner { + +class QMLDESIGNERCORE_EXPORT HdrImage +{ +public: + HdrImage(const QString &fileName); + + QString fileName() const { return m_fileName; } + const QImage &image() const { return m_image; } + QPixmap toPixmap() const; + +private: + void loadHdr(); + + QImage m_image; + QString m_fileName; + QByteArray m_buf; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp index 9ceabad49d5..549c4ea4483 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/customfilesystemmodel.cpp @@ -27,6 +27,7 @@ #include #include +#include #include @@ -94,6 +95,11 @@ static QPixmap defaultPixmapForType(const QString &type, const QSize &size) return QPixmap(QStringLiteral(":/ItemLibrary/images/asset_%1_%2.png").arg(type).arg(size.width())); } +static QPixmap texturePixmap(const QString &fileName) +{ + return HdrImage{fileName}.toPixmap(); +} + QString fontFamily(const QFileInfo &info) { QRawFont font(info.absoluteFilePath(), 10); @@ -117,24 +123,26 @@ public: const QString suffix = info.suffix(); const QString filePath = info.absoluteFilePath(); + // Provide icon depending on suffix + QPixmap origPixmap; + if (supportedFontSuffixes().contains(suffix)) return generateFontIcons(filePath); + else if (supportedImageSuffixes().contains(suffix)) + origPixmap.load(filePath); + else if (supportedTexture3DSuffixes().contains(suffix)) + origPixmap = texturePixmap(filePath); for (auto iconSize : iconSizes) { - // Provide icon depending on suffix - QPixmap pixmap; - - if (supportedImageSuffixes().contains(suffix)) - pixmap.load(filePath); - else if (supportedAudioSuffixes().contains(suffix)) - pixmap = defaultPixmapForType("sound", iconSize); - else if (supportedShaderSuffixes().contains(suffix)) - pixmap = defaultPixmapForType("shader", iconSize); - else if (supportedTexture3DSuffixes().contains(suffix)) - pixmap = defaultPixmapForType("texture", iconSize); - - if (pixmap.isNull()) - return QFileIconProvider::icon(info); + QPixmap pixmap = origPixmap; + if (pixmap.isNull()) { + if (supportedAudioSuffixes().contains(suffix)) + pixmap = defaultPixmapForType("sound", iconSize); + else if (supportedShaderSuffixes().contains(suffix)) + pixmap = defaultPixmapForType("shader", iconSize); + if (pixmap.isNull()) + return QFileIconProvider::icon(info); + } if ((pixmap.width() > iconSize.width()) || (pixmap.height() > iconSize.height())) pixmap = pixmap.scaled(iconSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index ef7c17afadd..a9e73b429e9 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -86,6 +86,7 @@ #include #include #include +#include #endif #include @@ -1723,7 +1724,12 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo originalPixmap = QPixmap::fromImage(paintImage); } } else { - originalPixmap.load(imageSource); +#ifndef QMLDESIGNER_TEST + if (imageFi.suffix() == "hdr") + originalPixmap = HdrImage{imageSource}.toPixmap(); + else +#endif + originalPixmap.load(imageSource); } if (!originalPixmap.isNull()) { const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 3d8bf364e5e..e7d1192f05c 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -481,6 +481,8 @@ Project { "componentcore/componentcore.qrc", "componentcore/zoomaction.cpp", "componentcore/zoomaction.h", + "componentcore/hdrimage.cpp", + "componentcore/hdrimage.h", "texteditor/texteditorstatusbar.cpp", "texteditor/texteditorstatusbar.h", "componentcore/changestyleaction.cpp",