From a6e661d25bf7ebeb8f4e58925aa9375f5ca10ef3 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 9 Dec 2019 13:44:31 +0100 Subject: Image ColorSpace bindings for Quick/QML Adds a ColorSpace type mapped to QColorSpace, and adds a property to Image nodes to read and change it, converting the image if necessary on read. Fixes: QTBUG-80616 Change-Id: Ie80c8bc045f66de01de3a5d2c4a9974f07d2871d Reviewed-by: Andy Nichols --- src/quick/items/qquickimagebase.cpp | 20 ++++++++ src/quick/items/qquickimagebase_p.h | 6 +++ src/quick/items/qquickimagebase_p_p.h | 1 + src/quick/util/qquickglobal.cpp | 60 +++++++++++++++++++++++ src/quick/util/qquickimageprovider.cpp | 18 ++++++- src/quick/util/qquickpixmapcache.cpp | 14 ++++++ src/quick/util/qquickpixmapcache_p.h | 5 ++ src/quick/util/qquickvaluetypes.cpp | 42 ++++++++++++++++ src/quick/util/qquickvaluetypes_p.h | 52 ++++++++++++++++++++ tests/auto/quick/qquickimage/data/ProPhoto.jpg | Bin 0 -> 30900 bytes tests/auto/quick/qquickimage/tst_qquickimage.cpp | 29 +++++++++++ 11 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 tests/auto/quick/qquickimage/data/ProPhoto.jpg diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp index 2118f67387..8849c2005c 100644 --- a/src/quick/items/qquickimagebase.cpp +++ b/src/quick/items/qquickimagebase.cpp @@ -396,6 +396,10 @@ void QQuickImageBase::requestFinished() d->frameCount = d->pix.frameCount(); emit frameCountChanged(); } + if (d->colorSpace != d->pix.colorSpace()) { + d->colorSpace = d->pix.colorSpace(); + emit colorSpaceChanged(); + } update(); } @@ -488,6 +492,22 @@ void QQuickImageBase::setAutoTransform(bool transform) emitAutoTransformBaseChanged(); } +QColorSpace QQuickImageBase::colorSpace() const +{ + Q_D(const QQuickImageBase); + return d->colorSpace; +} + +void QQuickImageBase::setColorSpace(const QColorSpace &colorSpace) +{ + Q_D(QQuickImageBase); + if (d->colorSpace == colorSpace) + return; + d->colorSpace = colorSpace; + d->providerOptions.setTargetColorSpace(colorSpace); + emit colorSpaceChanged(); +} + QT_END_NAMESPACE #include "moc_qquickimagebase_p.cpp" diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h index 238b31b2e5..095547a2cf 100644 --- a/src/quick/items/qquickimagebase_p.h +++ b/src/quick/items/qquickimagebase_p.h @@ -53,6 +53,7 @@ #include "qquickimplicitsizeitem_p.h" #include +#include QT_BEGIN_NAMESPACE @@ -70,6 +71,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickImageBase : public QQuickImplicitSizeItem Q_PROPERTY(bool mirror READ mirror WRITE setMirror NOTIFY mirrorChanged) Q_PROPERTY(int currentFrame READ currentFrame WRITE setCurrentFrame NOTIFY currentFrameChanged REVISION 14) Q_PROPERTY(int frameCount READ frameCount NOTIFY frameCountChanged REVISION 14) + Q_PROPERTY(QColorSpace colorSpace READ colorSpace WRITE setColorSpace NOTIFY colorSpaceChanged REVISION 15) QML_NAMED_ELEMENT(ImageBase); QML_ADDED_IN_MINOR_VERSION(14) @@ -122,6 +124,9 @@ public: virtual void setAutoTransform(bool transform); bool autoTransform() const; + QColorSpace colorSpace() const; + virtual void setColorSpace(const QColorSpace &colorSpace); + static void resolve2xLocalFile(const QUrl &url, qreal targetDevicePixelRatio, QUrl *sourceUrl, qreal *sourceDevicePixelRatio); // Use a virtual rather than a signal->signal to avoid the huge @@ -139,6 +144,7 @@ Q_SIGNALS: Q_REVISION(14) void currentFrameChanged(); Q_REVISION(14) void frameCountChanged(); Q_REVISION(15) void sourceClipRectChanged(); + Q_REVISION(15) void colorSpaceChanged(); protected: void loadEmptyUrl(); diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h index 00a9295ef0..ebb7568caf 100644 --- a/src/quick/items/qquickimagebase_p_p.h +++ b/src/quick/items/qquickimagebase_p_p.h @@ -88,6 +88,7 @@ public: qreal devicePixelRatio; QRectF sourceClipRect; QQuickImageProviderOptions providerOptions; + QColorSpace colorSpace; int currentFrame; int frameCount; bool async : 1; diff --git a/src/quick/util/qquickglobal.cpp b/src/quick/util/qquickglobal.cpp index bc5b8c4f3e..70256e202e 100644 --- a/src/quick/util/qquickglobal.cpp +++ b/src/quick/util/qquickglobal.cpp @@ -273,6 +273,52 @@ public: return QMatrix4x4(); } + static QColorSpace colorSpaceFromObject(const QV4::Value &object, QV4::ExecutionEngine *v4, bool *ok) + { + if (ok) + *ok = false; + QColorSpace retn; + QV4::Scope scope(v4); + QV4::ScopedObject obj(scope, object); + if (!obj) { + if (ok) + *ok = false; + return retn; + } + + QV4::ScopedString s(scope); + + QV4::ScopedValue vName(scope, obj->get((s = v4->newString(QStringLiteral("namedColorSpace"))))); + if (vName->isInt32()) { + if (ok) + *ok = true; + return QColorSpace((QColorSpace::NamedColorSpace)vName->toInt32()); + } + + QV4::ScopedValue vPri(scope, obj->get((s = v4->newString(QStringLiteral("primaries"))))); + QV4::ScopedValue vTra(scope, obj->get((s = v4->newString(QStringLiteral("transferFunction"))))); + if (!vPri->isInt32() || !vTra->isInt32()) { + if (ok) + *ok = false; + return retn; + } + + QColorSpace::Primaries pri = static_cast(vPri->integerValue()); + QColorSpace::TransferFunction tra = static_cast(vTra->integerValue()); + float gamma = 0.0f; + if (tra == QColorSpace::TransferFunction::Gamma) { + QV4::ScopedValue vGam(scope, obj->get((s = v4->newString(QStringLiteral("gamma"))))); + if (!vGam->isNumber()) { + if (ok) + *ok = false; + return retn; + } + gamma = vGam->toNumber(); + } + if (ok) *ok = true; + return QColorSpace(pri, tra, gamma); + } + static QFont fontFromObject(const QV4::Value &object, QV4::ExecutionEngine *v4, bool *ok) { if (ok) @@ -402,6 +448,8 @@ public: switch (type) { case QMetaType::QColor: return &QQuickColorValueType::staticMetaObject; + case QMetaType::QColorSpace: + return &QQuickColorSpaceValueType::staticMetaObject; case QMetaType::QFont: return &QQuickFontValueType::staticMetaObject; case QMetaType::QVector2D: @@ -427,6 +475,9 @@ public: case QMetaType::QColor: dst.setValue(QColor()); return true; + case QMetaType::QColorSpace: + dst.setValue(QColorSpace()); + return true; case QMetaType::QFont: dst.setValue(QFont()); return true; @@ -647,6 +698,9 @@ public: #endif bool ok = false; switch (type) { + case QMetaType::QColorSpace: + *v = QVariant::fromValue(colorSpaceFromObject(object, v4, &ok)); + break; case QMetaType::QFont: *v = QVariant::fromValue(fontFromObject(object, v4, &ok)); break; @@ -669,6 +723,8 @@ public: switch (type) { case QMetaType::QColor: return typedEqual(lhs, rhs); + case QMetaType::QColorSpace: + return typedEqual(lhs, rhs); case QMetaType::QFont: return typedEqual(lhs, rhs); case QMetaType::QVector2D: @@ -732,6 +788,8 @@ public: switch (dstType) { case QMetaType::QColor: return typedRead(src, dstType, dst); + case QMetaType::QColorSpace: + return typedRead(src, dstType, dst); case QMetaType::QFont: return typedRead(src, dstType, dst); case QMetaType::QVector2D: @@ -766,6 +824,8 @@ public: switch (type) { case QMetaType::QColor: return typedWrite(src, dst); + case QMetaType::QColorSpace: + return typedWrite(src, dst); case QMetaType::QFont: return typedWrite(src, dst); case QMetaType::QVector2D: diff --git a/src/quick/util/qquickimageprovider.cpp b/src/quick/util/qquickimageprovider.cpp index db82b2d807..80873e2ad5 100644 --- a/src/quick/util/qquickimageprovider.cpp +++ b/src/quick/util/qquickimageprovider.cpp @@ -43,6 +43,7 @@ #include "qquickpixmapcache_p.h" #include #include +#include QT_BEGIN_NAMESPACE @@ -510,6 +511,7 @@ public: { } + QColorSpace targetColorSpace; QQuickImageProviderOptions::AutoTransform autoTransform = QQuickImageProviderOptions::UsePluginDefaultTransform; bool preserveAspectRatioCrop = false; bool preserveAspectRatioFit = false; @@ -558,7 +560,8 @@ bool QQuickImageProviderOptions::operator==(const QQuickImageProviderOptions &ot { return d->autoTransform == other.d->autoTransform && d->preserveAspectRatioCrop == other.d->preserveAspectRatioCrop && - d->preserveAspectRatioFit == other.d->preserveAspectRatioFit; + d->preserveAspectRatioFit == other.d->preserveAspectRatioFit && + d->targetColorSpace == other.d->targetColorSpace; } /*! @@ -602,6 +605,19 @@ void QQuickImageProviderOptions::setPreserveAspectRatioFit(bool preserveAspectRa d->preserveAspectRatioFit = preserveAspectRatioFit; } +/*! + Returns the color space the image provider should return the image in. +*/ +QColorSpace QQuickImageProviderOptions::targetColorSpace() const +{ + return d->targetColorSpace; +} + +void QQuickImageProviderOptions::setTargetColorSpace(const QColorSpace &colorSpace) +{ + d->targetColorSpace = colorSpace; +} + QQuickImageProviderWithOptions::QQuickImageProviderWithOptions(ImageType type, Flags flags) : QQuickAsyncImageProvider() { diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp index bdd1e2c514..2ae9debbc9 100644 --- a/src/quick/util/qquickpixmapcache.cpp +++ b/src/quick/util/qquickpixmapcache.cpp @@ -318,6 +318,7 @@ public: QSize requestSize; QQuickImageProviderOptions providerOptions; QQuickImageProviderOptions::AutoTransform appliedTransform; + QColorSpace targetColorSpace; QQuickTextureFactory *textureFactory; @@ -436,6 +437,12 @@ static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *e maybeRemoveAlpha(image); if (impsize && impsize->width() < 0) *impsize = image->size(); + if (providerOptions.targetColorSpace().isValid()) { + if (image->colorSpace().isValid()) + image->convertToColorSpace(providerOptions.targetColorSpace()); + else + image->setColorSpace(providerOptions.targetColorSpace()); + } return true; } else { if (errorString) @@ -1763,6 +1770,13 @@ bool QQuickPixmap::connectDownloadProgress(QObject *object, int method) return QMetaObject::connect(d->reply, QQuickPixmapReply::downloadProgressIndex, object, method); } +QColorSpace QQuickPixmap::colorSpace() const +{ + if (!d || !d->textureFactory) + return QColorSpace(); + return d->textureFactory->image().colorSpace(); +} + QT_END_NAMESPACE #include diff --git a/src/quick/util/qquickpixmapcache_p.h b/src/quick/util/qquickpixmapcache_p.h index 01c99cdbb6..93dec63e94 100644 --- a/src/quick/util/qquickpixmapcache_p.h +++ b/src/quick/util/qquickpixmapcache_p.h @@ -117,6 +117,9 @@ public: bool preserveAspectRatioFit() const; void setPreserveAspectRatioFit(bool preserveAspectRatioFit); + QColorSpace targetColorSpace() const; + void setTargetColorSpace(const QColorSpace &colorSpace); + private: QSharedDataPointer d; }; @@ -156,6 +159,8 @@ public: void setImage(const QImage &); void setPixmap(const QQuickPixmap &other); + QColorSpace colorSpace() const; + QQuickTextureFactory *textureFactory() const; QRect rect() const; diff --git a/src/quick/util/qquickvaluetypes.cpp b/src/quick/util/qquickvaluetypes.cpp index b47e1a082d..395385fa0d 100644 --- a/src/quick/util/qquickvaluetypes.cpp +++ b/src/quick/util/qquickvaluetypes.cpp @@ -41,6 +41,7 @@ #include #include +#include #include @@ -784,6 +785,47 @@ void QQuickFontValueType::setPreferShaping(bool enable) v.setStyleStrategy(static_cast(v.styleStrategy() | QFont::PreferNoShaping)); } +QQuickColorSpaceValueType::NamedColorSpace QQuickColorSpaceValueType::namedColorSpace() const noexcept +{ + if (const auto *p = QColorSpacePrivate::get(v)) + return (QQuickColorSpaceValueType::NamedColorSpace)p->namedColorSpace; + return QQuickColorSpaceValueType::Unknown; +} +void QQuickColorSpaceValueType::setNamedColorSpace(QQuickColorSpaceValueType::NamedColorSpace namedColorSpace) +{ + v = { (QColorSpace::NamedColorSpace)namedColorSpace }; +} + +QQuickColorSpaceValueType::Primaries QQuickColorSpaceValueType::primaries() const noexcept +{ + return (QQuickColorSpaceValueType::Primaries)v.primaries(); +} + +void QQuickColorSpaceValueType::setPrimaries(QQuickColorSpaceValueType::Primaries primariesId) +{ + v.setPrimaries((QColorSpace::Primaries)primariesId); +} + +QQuickColorSpaceValueType::TransferFunction QQuickColorSpaceValueType::transferFunction() const noexcept +{ + return (QQuickColorSpaceValueType::TransferFunction)v.transferFunction(); +} + +void QQuickColorSpaceValueType::setTransferFunction(QQuickColorSpaceValueType::TransferFunction transferFunction) +{ + v.setTransferFunction((QColorSpace::TransferFunction)transferFunction, v.gamma()); +} + +float QQuickColorSpaceValueType::gamma() const noexcept +{ + return v.gamma(); +} + +void QQuickColorSpaceValueType::setGamma(float gamma) +{ + v.setTransferFunction(v.transferFunction(), gamma); +} + QT_END_NAMESPACE #include "moc_qquickvaluetypes_p.cpp" diff --git a/src/quick/util/qquickvaluetypes_p.h b/src/quick/util/qquickvaluetypes_p.h index 91e3815d5b..b6cbc37adb 100644 --- a/src/quick/util/qquickvaluetypes_p.h +++ b/src/quick/util/qquickvaluetypes_p.h @@ -56,6 +56,7 @@ #include #include +#include #include #include #include @@ -408,6 +409,57 @@ public: void setPreferShaping(bool b); }; +class QQuickColorSpaceValueType +{ + QColorSpace v; + Q_GADGET + + Q_PROPERTY(NamedColorSpace namedColorSpace READ namedColorSpace WRITE setNamedColorSpace FINAL) + Q_PROPERTY(Primaries primaries READ primaries WRITE setPrimaries FINAL) + Q_PROPERTY(TransferFunction transferFunction READ transferFunction WRITE setTransferFunction FINAL) + Q_PROPERTY(float gamma READ gamma WRITE setGamma FINAL) + + QML_NAMED_ELEMENT(ColorSpace) + Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + +public: + enum NamedColorSpace { + Unknown = 0, + SRgb, + SRgbLinear, + AdobeRgb, + DisplayP3, + ProPhotoRgb + }; + Q_ENUM(NamedColorSpace) + + enum class Primaries { + Custom = 0, + SRgb, + AdobeRgb, + DciP3D65, + ProPhotoRgb + }; + Q_ENUM(Primaries) + enum class TransferFunction { + Custom = 0, + Linear, + Gamma, + SRgb, + ProPhotoRgb + }; + Q_ENUM(TransferFunction) + + NamedColorSpace namedColorSpace() const noexcept; + void setNamedColorSpace(NamedColorSpace namedColorSpace); + Primaries primaries() const noexcept; + void setPrimaries(Primaries primariesId); + TransferFunction transferFunction() const noexcept; + void setTransferFunction(TransferFunction transferFunction); + float gamma() const noexcept; + void setGamma(float gamma); +}; + QT_END_NAMESPACE #endif // QQUICKVALUETYPES_P_H diff --git a/tests/auto/quick/qquickimage/data/ProPhoto.jpg b/tests/auto/quick/qquickimage/data/ProPhoto.jpg new file mode 100644 index 0000000000..481d35ca8e Binary files /dev/null and b/tests/auto/quick/qquickimage/data/ProPhoto.jpg differ diff --git a/tests/auto/quick/qquickimage/tst_qquickimage.cpp b/tests/auto/quick/qquickimage/tst_qquickimage.cpp index abc7cd86bd..bab1f1445d 100644 --- a/tests/auto/quick/qquickimage/tst_qquickimage.cpp +++ b/tests/auto/quick/qquickimage/tst_qquickimage.cpp @@ -99,6 +99,7 @@ private slots: void urlInterceptor(); void multiFrame_data(); void multiFrame(); + void colorSpace(); private: QQmlEngine engine; @@ -1190,6 +1191,34 @@ void tst_qquickimage::multiFrame() QVERIFY(qBlue(color) < 0xc0); } +void tst_qquickimage::colorSpace() +{ + QString componentStr1 = "import QtQuick 2.15\n" + "Image { source: srcImage; }"; + QQmlComponent component1(&engine); + component1.setData(componentStr1.toLatin1(), QUrl::fromLocalFile("")); + engine.rootContext()->setContextProperty("srcImage", testFileUrl("ProPhoto.jpg")); + + QScopedPointer object1 { qobject_cast(component1.create())}; + QVERIFY(object1); + QTRY_COMPARE(object1->status(), QQuickImageBase::Ready); + QCOMPARE(object1->colorSpace(), QColorSpace(QColorSpace::ProPhotoRgb)); + + QString componentStr2 = "import QtQuick 2.15\n" + "Image {\n" + " source: srcImage;\n" + " colorSpace.namedColorSpace: ColorSpace.SRgb;\n" + "}"; + + QQmlComponent component2(&engine); + component2.setData(componentStr2.toLatin1(), QUrl::fromLocalFile("")); + + QScopedPointer object2 { qobject_cast(component2.create())}; + QVERIFY(object2); + QTRY_COMPARE(object2->status(), QQuickImageBase::Ready); + QCOMPARE(object2->colorSpace(), QColorSpace(QColorSpace::SRgb)); +} + QTEST_MAIN(tst_qquickimage) #include "tst_qquickimage.moc" -- cgit v1.2.3