summaryrefslogtreecommitdiffstats
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt28
-rw-r--r--src/gui/accessible/linux/atspiadaptor.cpp100
-rw-r--r--src/gui/accessible/linux/atspiadaptor_p.h10
-rw-r--r--src/gui/accessible/linux/dbusconnection.cpp16
-rw-r--r--src/gui/accessible/linux/dbusconnection_p.h4
-rw-r--r--src/gui/accessible/linux/qspiaccessiblebridge.cpp2
-rw-r--r--src/gui/accessible/linux/qspiaccessiblebridge_p.h4
-rw-r--r--src/gui/accessible/qaccessible.cpp108
-rw-r--r--src/gui/accessible/qaccessible.h32
-rw-r--r--src/gui/accessible/qaccessible_base.h9
-rw-r--r--src/gui/accessible/qaccessiblebridge.cpp2
-rw-r--r--src/gui/accessible/qaccessiblebridge.h4
-rw-r--r--src/gui/accessible/qaccessiblebridgeutils.cpp22
-rw-r--r--src/gui/accessible/qaccessiblebridgeutils_p.h2
-rw-r--r--src/gui/accessible/qaccessiblecache.cpp4
-rw-r--r--src/gui/accessible/qaccessiblecache_mac.mm10
-rw-r--r--src/gui/accessible/qaccessiblecache_p.h8
-rw-r--r--src/gui/compat/removed_api.cpp11
-rw-r--r--src/gui/configure.cmake2
-rw-r--r--src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc4
-rw-r--r--src/gui/image/qbitmap.cpp1
-rw-r--r--src/gui/image/qicon.cpp154
-rw-r--r--src/gui/image/qicon_p.h39
-rw-r--r--src/gui/image/qiconengine.cpp7
-rw-r--r--src/gui/image/qiconengine.h1
-rw-r--r--src/gui/image/qiconloader.cpp32
-rw-r--r--src/gui/image/qiconloader_p.h4
-rw-r--r--src/gui/image/qimage.cpp90
-rw-r--r--src/gui/image/qimage_conversions.cpp8
-rw-r--r--src/gui/image/qimage_p.h26
-rw-r--r--src/gui/image/qimagereader.cpp18
-rw-r--r--src/gui/image/qimagewriter.cpp4
-rw-r--r--src/gui/image/qmovie.cpp7
-rw-r--r--src/gui/image/qpicture.cpp1
-rw-r--r--src/gui/image/qpixmap.cpp9
-rw-r--r--src/gui/image/qpixmap_blitter.cpp4
-rw-r--r--src/gui/image/qpixmap_raster.cpp4
-rw-r--r--src/gui/image/qpixmapcache.cpp11
-rw-r--r--src/gui/kernel/qaction.cpp2
-rw-r--r--src/gui/kernel/qguiapplication.cpp148
-rw-r--r--src/gui/kernel/qguiapplication.h2
-rw-r--r--src/gui/kernel/qguiapplication_p.h13
-rw-r--r--src/gui/kernel/qguiapplication_platform.h31
-rw-r--r--src/gui/kernel/qguivariant.cpp2
-rw-r--r--src/gui/kernel/qhighdpiscaling_p.h4
-rw-r--r--src/gui/kernel/qpaintdevicewindow.cpp5
-rw-r--r--src/gui/kernel/qpalette.cpp5
-rw-r--r--src/gui/kernel/qplatformtheme.cpp5
-rw-r--r--src/gui/kernel/qplatformtheme.h1
-rw-r--r--src/gui/kernel/qplatformwindow_p.h2
-rw-r--r--src/gui/kernel/qstylehints.cpp83
-rw-r--r--src/gui/kernel/qstylehints.h10
-rw-r--r--src/gui/kernel/qstylehints_p.h1
-rw-r--r--src/gui/kernel/qwindow.cpp73
-rw-r--r--src/gui/kernel/qwindow_p.h4
-rw-r--r--src/gui/painting/qbackingstore.cpp29
-rw-r--r--src/gui/painting/qbackingstorerhisupport.cpp4
-rw-r--r--src/gui/painting/qcmyk_p.h6
-rw-r--r--src/gui/painting/qcolormatrix_p.h6
-rw-r--r--src/gui/painting/qcolorspace.cpp103
-rw-r--r--src/gui/painting/qcolorspace.h13
-rw-r--r--src/gui/painting/qcolortransferfunction_p.h5
-rw-r--r--src/gui/painting/qcolortransfergeneric_p.h109
-rw-r--r--src/gui/painting/qcolortransform.cpp509
-rw-r--r--src/gui/painting/qcolortransform_p.h12
-rw-r--r--src/gui/painting/qcolortrc_p.h101
-rw-r--r--src/gui/painting/qcolortrclut.cpp67
-rw-r--r--src/gui/painting/qcolortrclut_p.h11
-rw-r--r--src/gui/painting/qdatabuffer_p.h3
-rw-r--r--src/gui/painting/qdrawhelper.cpp8
-rw-r--r--src/gui/painting/qicc.cpp895
-rw-r--r--src/gui/painting/qimagescale.cpp2
-rw-r--r--src/gui/painting/qpagelayout.h2
-rw-r--r--src/gui/painting/qpagesize.h2
-rw-r--r--src/gui/painting/qpaintdevice.cpp35
-rw-r--r--src/gui/painting/qpaintdevice.h16
-rw-r--r--src/gui/painting/qpaintdevice.qdoc20
-rw-r--r--src/gui/painting/qpdf.cpp70
-rw-r--r--src/gui/painting/qpdf_p.h4
-rw-r--r--src/gui/painting/qplatformbackingstore.cpp65
-rw-r--r--src/gui/painting/qplatformbackingstore.h9
-rw-r--r--src/gui/painting/qregion.cpp64
-rw-r--r--src/gui/painting/qregion.h4
-rw-r--r--src/gui/painting/qrhibackingstore.cpp23
-rw-r--r--src/gui/platform/darwin/qmetallayer.mm73
-rw-r--r--src/gui/platform/darwin/qmetallayer_p.h41
-rw-r--r--src/gui/platform/ios/qiosnativeinterface.cpp26
-rw-r--r--src/gui/platform/unix/qgenericunixthemes.cpp291
-rw-r--r--src/gui/qt_cmdline.cmake3
-rw-r--r--src/gui/rhi/qrhigles2.cpp18
-rw-r--r--src/gui/rhi/qrhigles2_p.h6
-rw-r--r--src/gui/rhi/qrhimetal.mm157
-rw-r--r--src/gui/rhi/qrhivulkan.cpp24
-rw-r--r--src/gui/rhi/qrhivulkan_p.h4
-rw-r--r--src/gui/text/coretext/qfontengine_coretext.mm5
-rw-r--r--src/gui/text/qcssparser.cpp62
-rw-r--r--src/gui/text/qcssparser_p.h17
-rw-r--r--src/gui/text/qfont.cpp21
-rw-r--r--src/gui/text/qfont.h27
-rw-r--r--src/gui/text/qfontdatabase.cpp31
-rw-r--r--src/gui/text/qfontengine.cpp7
-rw-r--r--src/gui/text/qfontengine_p.h1
-rw-r--r--src/gui/text/qtextdocument.cpp65
-rw-r--r--src/gui/text/qtextdocument.h6
-rw-r--r--src/gui/text/qtextengine.cpp6
-rw-r--r--src/gui/text/qtextformat.cpp24
-rw-r--r--src/gui/text/qtextformat.h8
-rw-r--r--src/gui/text/qtexthtmlparser.cpp62
-rw-r--r--src/gui/text/qtextimagehandler.cpp26
-rw-r--r--src/gui/util/qdesktopservices.cpp44
110 files changed, 3264 insertions, 1181 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index cef71318d8..94cc686724 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -7,7 +7,9 @@ qt_find_package(WrapPNG PROVIDED_TARGETS WrapPNG::WrapPNG)
qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype)
if (QT_FEATURE_gui)
- if(WIN32)
+ if(QT_QPA_PLATFORMS)
+ list(GET QT_QPA_PLATFORMS 0 _default_platform)
+ elseif(WIN32)
set(_default_platform "windows")
elseif(ANDROID)
set(_default_platform "android")
@@ -30,6 +32,11 @@ if (QT_FEATURE_gui)
endif()
set(QT_QPA_DEFAULT_PLATFORM "${_default_platform}" CACHE STRING "QPA default platform")
+ if(NOT "${QT_QPA_DEFAULT_PLATFORM}" IN_LIST QT_QPA_PLATFORMS)
+ list(APPEND QT_QPA_PLATFORMS "${QT_QPA_DEFAULT_PLATFORM}")
+ set(QT_QPA_PLATFORMS "${QT_QPA_PLATFORMS}" CACHE STRING
+ "QPA platforms deployed by default" FORCE)
+ endif()
endif()
# Silence warnings in 3rdparty code
@@ -164,6 +171,7 @@ qt_internal_add_module(Gui
painting/qcolormatrix_p.h
painting/qcolorspace.cpp painting/qcolorspace.h painting/qcolorspace_p.h
painting/qcolortransferfunction_p.h
+ painting/qcolortransfergeneric_p.h
painting/qcolortransfertable_p.h
painting/qcolortransform.cpp painting/qcolortransform.h painting/qcolortransform_p.h
painting/qcolortrc_p.h
@@ -376,6 +384,11 @@ qt_internal_extend_target(Gui CONDITION MACOS
${FWAppKit}
)
+qt_internal_extend_target(Gui CONDITION UIKIT
+ SOURCES
+ platform/ios/qiosnativeinterface.cpp
+)
+
qt_internal_extend_target(Gui CONDITION WASM
SOURCES
platform/wasm/qwasmnativeinterface.cpp
@@ -402,6 +415,19 @@ qt_internal_extend_target(Gui CONDITION APPLE
${FWImageIO}
)
+qt_internal_extend_target(Gui CONDITION APPLE AND QT_FEATURE_metal
+ SOURCES
+ platform/darwin/qmetallayer.mm platform/darwin/qmetallayer_p.h
+ LIBRARIES
+ ${FWQuartzCore}
+)
+
+qt_internal_extend_target(Gui CONDITION QNX
+ SOURCES
+ painting/qrasterbackingstore.cpp painting/qrasterbackingstore_p.h
+ painting/qrhibackingstore.cpp painting/qrhibackingstore_p.h
+)
+
qt_internal_extend_target(Gui CONDITION QT_FEATURE_animation
SOURCES
animation/qguivariantanimation.cpp
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp
index b3269a2a95..722723207a 100644
--- a/src/gui/accessible/linux/atspiadaptor.cpp
+++ b/src/gui/accessible/linux/atspiadaptor.cpp
@@ -35,6 +35,13 @@
#define ATSPI_COORD_TYPE_PARENT 2
#endif
+// ATSPI_*_VERSION defines were added in libatspi 2.50,
+// as was the AtspiLive enum; define values here for older versions
+#if !defined(ATSPI_MAJOR_VERSION) || !defined(ATSPI_MINOR_VERSION) || ATSPI_MAJOR_VERSION < 2 || ATSPI_MINOR_VERSION < 50
+#define ATSPI_LIVE_POLITE 1
+#define ATSPI_LIVE_ASSERTIVE 2
+#endif
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -42,11 +49,12 @@ using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcAccessibilityAtspi, "qt.accessibility.atspi")
Q_LOGGING_CATEGORY(lcAccessibilityAtspiCreation, "qt.accessibility.atspi.creation")
-AtSpiAdaptor::AtSpiAdaptor(DBusConnection *connection, QObject *parent)
+AtSpiAdaptor::AtSpiAdaptor(QAtSpiDBusConnection *connection, QObject *parent)
: QDBusVirtualObject(parent), m_dbus(connection)
, sendFocus(0)
, sendObject(0)
, sendObject_active_descendant_changed(0)
+ , sendObject_announcement(0)
, sendObject_attributes_changed(0)
, sendObject_bounds_changed(0)
, sendObject_children_changed(0)
@@ -127,6 +135,7 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <interface name=\"org.a11y.atspi.Accessible\">\n"
" <property access=\"read\" type=\"s\" name=\"Name\"/>\n"
" <property access=\"read\" type=\"s\" name=\"Description\"/>\n"
+ " <property access=\"read\" type=\"s\" name=\"HelpText\"/>\n"
" <property access=\"read\" type=\"(so)\" name=\"Parent\">\n"
" <annotation value=\"QSpiObjectReference\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n"
" </property>\n"
@@ -678,6 +687,8 @@ void AtSpiAdaptor::setBitFlag(const QString &flag)
if (false) {
} else if (right.startsWith("ActiveDescendantChanged"_L1)) {
sendObject_active_descendant_changed = 1;
+ } else if (right.startsWith("Announcement"_L1)) {
+ sendObject_announcement = 1;
} else if (right.startsWith("AttributesChanged"_L1)) {
sendObject_attributes_changed = 1;
} else if (right.startsWith("BoundsChanged"_L1)) {
@@ -929,6 +940,26 @@ void AtSpiAdaptor::notifyStateChange(QAccessibleInterface *interface, const QStr
sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "StateChanged"_L1, stateArgs);
}
+void AtSpiAdaptor::sendAnnouncement(QAccessibleAnnouncementEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ if (!iface) {
+ qCWarning(lcAccessibilityAtspi, "Announcement event has no accessible set.");
+ return;
+ }
+ if (!iface->isValid()) {
+ qCWarning(lcAccessibilityAtspi) << "Announcement event with invalid accessible: " << iface;
+ return;
+ }
+
+ const QString path = pathForInterface(iface);
+ const QString message = event->message();
+ const QAccessible::AnnouncementPriority prio = event->priority();
+ const int politeness = (prio == QAccessible::AnnouncementPriority::Assertive) ? ATSPI_LIVE_ASSERTIVE : ATSPI_LIVE_POLITE;
+
+ const QVariantList args = packDBusSignalArguments(QString(), politeness, 0, QVariant::fromValue(QDBusVariant(message)));
+ sendDBusSignal(path, ATSPI_DBUS_INTERFACE_EVENT_OBJECT ""_L1, "Announcement"_L1, args);
+}
/*!
This function gets called when Qt notifies about accessibility updates.
@@ -1003,6 +1034,14 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event)
sendFocusChanged(event->accessibleInterface());
break;
}
+
+ case QAccessible::Announcement: {
+ if (sendObject || sendObject_announcement) {
+ QAccessibleAnnouncementEvent *announcementEvent = static_cast<QAccessibleAnnouncementEvent*>(event);
+ sendAnnouncement(announcementEvent);
+ }
+ break;
+ }
case QAccessible::TextInserted:
case QAccessible::TextRemoved:
case QAccessible::TextUpdated: {
@@ -1313,6 +1352,7 @@ void AtSpiAdaptor::notify(QAccessibleEvent *event)
case QAccessible::HelpChanged:
case QAccessible::DefaultActionChanged:
case QAccessible::AcceleratorChanged:
+ case QAccessible::IdentifierChanged:
case QAccessible::InvalidEvent:
break;
}
@@ -1516,26 +1556,6 @@ void AtSpiAdaptor::registerApplication()
delete registry;
}
-namespace {
-QString accessibleIdForAccessible(QAccessibleInterface *accessible)
-{
- QString result;
- while (accessible) {
- if (!result.isEmpty())
- result.prepend(u'.');
- if (auto obj = accessible->object()) {
- const QString name = obj->objectName();
- if (!name.isEmpty())
- result.prepend(name);
- else
- result.prepend(QString::fromUtf8(obj->metaObject()->className()));
- }
- accessible = accessible->parent();
- }
- return result;
-}
-} // namespace
-
// Accessible
bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QString &function, const QDBusMessage &message, const QDBusConnection &connection)
{
@@ -1586,6 +1606,8 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS
sendReply(connection, message, accessibleInterfaces(interface));
} else if (function == "GetDescription"_L1) {
sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Description))));
+ } else if (function == "GetHelpText"_L1) {
+ sendReply(connection, message, QVariant::fromValue(QDBusVariant(interface->text(QAccessible::Help))));
} else if (function == "GetState"_L1) {
quint64 spiState = spiStatesFromQState(interface->state());
if (interface->tableInterface()) {
@@ -1606,7 +1628,7 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS
sendReply(connection, message,
QVariant::fromValue(spiStateSetFromSpiStates(spiState)));
} else if (function == "GetAttributes"_L1) {
- sendReply(connection, message, QVariant::fromValue(QSpiAttributeSet()));
+ sendReply(connection, message, QVariant::fromValue(getAttributes(interface)));
} else if (function == "GetRelationSet"_L1) {
sendReply(connection, message, QVariant::fromValue(relationSet(interface, connection)));
} else if (function == "GetApplication"_L1) {
@@ -1624,7 +1646,7 @@ bool AtSpiAdaptor::accessibleInterface(QAccessibleInterface *interface, const QS
connection.send(message.createReply(QVariant::fromValue(children)));
} else if (function == "GetAccessibleId"_L1) {
sendReply(connection, message,
- QVariant::fromValue(QDBusVariant(accessibleIdForAccessible(interface))));
+ QVariant::fromValue(QDBusVariant(QAccessibleBridgeUtils::accessibleId(interface))));
} else {
qCWarning(lcAccessibilityAtspi) << "AtSpiAdaptor::accessibleInterface does not implement" << function << message.path();
return false;
@@ -2264,6 +2286,38 @@ namespace
}
}
+QSpiAttributeSet AtSpiAdaptor::getAttributes(QAccessibleInterface *interface) const
+{
+ QSpiAttributeSet set;
+ QAccessibleAttributesInterface *attributesIface = interface->attributesInterface();
+ if (!attributesIface)
+ return set;
+
+ const QList<QAccessible::Attribute> attrKeys = attributesIface->attributeKeys();
+ for (QAccessible::Attribute key : attrKeys) {
+ const QVariant value = attributesIface->attributeValue(key);
+ // see "Core Accessibility API Mappings" spec: https://www.w3.org/TR/core-aam-1.2/
+ switch (key) {
+ case QAccessible::Attribute::Custom:
+ {
+ // forward custom attributes to AT-SPI as-is
+ Q_ASSERT((value.canConvert<QHash<QString, QString>>()));
+ const QHash<QString, QString> attrMap = value.value<QHash<QString, QString>>();
+ for (auto [name, val] : attrMap.asKeyValueRange())
+ set.insert(name, val);
+ break;
+ }
+ case QAccessible::Attribute::Level:
+ Q_ASSERT(value.canConvert<int>());
+ set.insert(QStringLiteral("level"), QString::number(value.toInt()));
+ break;
+ default:
+ break;
+ }
+ }
+ return set;
+}
+
// FIXME all attribute methods below should share code
QVariantList AtSpiAdaptor::getAttributes(QAccessibleInterface *interface, int offset, bool includeDefaults) const
{
diff --git a/src/gui/accessible/linux/atspiadaptor_p.h b/src/gui/accessible/linux/atspiadaptor_p.h
index 3a890f3d7d..fe7f6c477c 100644
--- a/src/gui/accessible/linux/atspiadaptor_p.h
+++ b/src/gui/accessible/linux/atspiadaptor_p.h
@@ -16,7 +16,7 @@
// We mean it.
//
-#include <atspi/atspi-constants.h>
+#include <atspi/atspi.h>
#include <QtGui/private/qtguiglobal_p.h>
#include <QtDBus/qdbusvirtualobject.h>
@@ -38,7 +38,7 @@ class AtSpiAdaptor :public QDBusVirtualObject
Q_OBJECT
public:
- explicit AtSpiAdaptor(DBusConnection *connection, QObject *parent = nullptr);
+ explicit AtSpiAdaptor(QAtSpiDBusConnection *connection, QObject *parent = nullptr);
~AtSpiAdaptor();
void registerApplication();
@@ -85,8 +85,11 @@ private:
void notifyStateChange(QAccessibleInterface *interface, const QString& state, int value);
+ void sendAnnouncement(QAccessibleAnnouncementEvent *event);
+
// accessible helper functions
AtspiRole getRole(QAccessibleInterface *interface) const;
+ QSpiAttributeSet getAttributes(QAccessibleInterface *) const;
QSpiRelationArray relationSet(QAccessibleInterface *interface, const QDBusConnection &connection) const;
QStringList accessibleInterfaces(QAccessibleInterface *interface) const;
@@ -111,7 +114,7 @@ private:
// private vars
QSpiObjectReference accessibilityRegistry;
- DBusConnection *m_dbus;
+ QAtSpiDBusConnection *m_dbus;
QSpiApplicationAdaptor *m_applicationAdaptor;
/// Assigned from the accessibility registry.
@@ -130,6 +133,7 @@ private:
// all of object
uint sendObject : 1;
uint sendObject_active_descendant_changed : 1;
+ uint sendObject_announcement : 1;
uint sendObject_attributes_changed : 1;
uint sendObject_bounds_changed : 1;
uint sendObject_children_changed : 1;
diff --git a/src/gui/accessible/linux/dbusconnection.cpp b/src/gui/accessible/linux/dbusconnection.cpp
index 10bd10927e..620575cc98 100644
--- a/src/gui/accessible/linux/dbusconnection.cpp
+++ b/src/gui/accessible/linux/dbusconnection.cpp
@@ -26,13 +26,13 @@ using namespace Qt::StringLiterals;
#define A11Y_PATH "/org/a11y/bus"_L1
/*!
- \class DBusConnection
+ \class QAtSpiDBusConnection
\internal
\brief Connects to the accessibility dbus.
This is usually a different bus from the session bus.
*/
-DBusConnection::DBusConnection(QObject *parent)
+QAtSpiDBusConnection::QAtSpiDBusConnection(QObject *parent)
: QObject(parent), m_a11yConnection(QString()), m_enabled(false)
{
// If the bus is explicitly set via env var it overrides everything else.
@@ -66,7 +66,7 @@ DBusConnection::DBusConnection(QObject *parent)
}
}
-QString DBusConnection::getAddressFromXCB()
+QString QAtSpiDBusConnection::getAddressFromXCB()
{
QGuiApplication *app = qobject_cast<QGuiApplication *>(QCoreApplication::instance());
if (!app)
@@ -85,7 +85,7 @@ QString DBusConnection::getAddressFromXCB()
// We have the a11y registry on the session bus.
// Subscribe to updates about a11y enabled state.
// Find out the bus address
-void DBusConnection::serviceRegistered()
+void QAtSpiDBusConnection::serviceRegistered()
{
// listen to enabled changes
QDBusConnection c = QDBusConnection::sessionBus();
@@ -113,12 +113,12 @@ void DBusConnection::serviceRegistered()
// connect(a11yStatus, ); QtDbus doesn't support notifications for property changes yet
}
-void DBusConnection::serviceUnregistered()
+void QAtSpiDBusConnection::serviceUnregistered()
{
emit enabledChanged(false);
}
-void DBusConnection::connectA11yBus(const QString &address)
+void QAtSpiDBusConnection::connectA11yBus(const QString &address)
{
if (address.isEmpty()) {
qWarning("Could not find Accessibility DBus address.");
@@ -130,7 +130,7 @@ void DBusConnection::connectA11yBus(const QString &address)
emit enabledChanged(true);
}
-void DBusConnection::dbusError(const QDBusError &error)
+void QAtSpiDBusConnection::dbusError(const QDBusError &error)
{
qWarning() << "Accessibility encountered a DBus error:" << error;
}
@@ -139,7 +139,7 @@ void DBusConnection::dbusError(const QDBusError &error)
Returns the DBus connection that got established.
Or an invalid connection if not yet connected.
*/
-QDBusConnection DBusConnection::connection() const
+QDBusConnection QAtSpiDBusConnection::connection() const
{
return m_a11yConnection;
}
diff --git a/src/gui/accessible/linux/dbusconnection_p.h b/src/gui/accessible/linux/dbusconnection_p.h
index 8b231c5ad0..98bd02741f 100644
--- a/src/gui/accessible/linux/dbusconnection_p.h
+++ b/src/gui/accessible/linux/dbusconnection_p.h
@@ -27,12 +27,12 @@ QT_BEGIN_NAMESPACE
class QDBusServiceWatcher;
-class DBusConnection : public QObject
+class QAtSpiDBusConnection : public QObject
{
Q_OBJECT
public:
- DBusConnection(QObject *parent = nullptr);
+ QAtSpiDBusConnection(QObject *parent = nullptr);
QDBusConnection connection() const;
bool isEnabled() const { return m_enabled; }
diff --git a/src/gui/accessible/linux/qspiaccessiblebridge.cpp b/src/gui/accessible/linux/qspiaccessiblebridge.cpp
index de2e7d5fc0..0d18589ac5 100644
--- a/src/gui/accessible/linux/qspiaccessiblebridge.cpp
+++ b/src/gui/accessible/linux/qspiaccessiblebridge.cpp
@@ -31,7 +31,7 @@ using namespace Qt::StringLiterals;
QSpiAccessibleBridge::QSpiAccessibleBridge()
: cache(nullptr), dec(nullptr), dbusAdaptor(nullptr)
{
- dbusConnection = new DBusConnection();
+ dbusConnection = new QAtSpiDBusConnection();
connect(dbusConnection, SIGNAL(enabledChanged(bool)), this, SLOT(enabledChanged(bool)));
// Now that we have connected the signal, make sure we didn't miss a change,
// e.g. when running as root or when AT_SPI_BUS_ADDRESS is set by hand.
diff --git a/src/gui/accessible/linux/qspiaccessiblebridge_p.h b/src/gui/accessible/linux/qspiaccessiblebridge_p.h
index 5ad48c5cc4..69c4feb7bf 100644
--- a/src/gui/accessible/linux/qspiaccessiblebridge_p.h
+++ b/src/gui/accessible/linux/qspiaccessiblebridge_p.h
@@ -27,7 +27,7 @@ QT_REQUIRE_CONFIG(accessibility);
QT_BEGIN_NAMESPACE
-class DBusConnection;
+class QAtSpiDBusConnection;
class QSpiDBusCache;
class AtSpiAdaptor;
struct RoleNames;
@@ -60,7 +60,7 @@ private:
QSpiDBusCache *cache;
DeviceEventControllerAdaptor *dec;
AtSpiAdaptor *dbusAdaptor;
- DBusConnection* dbusConnection;
+ QAtSpiDBusConnection* dbusConnection;
SpiRoleMapping m_spiRoleMapping;
};
diff --git a/src/gui/accessible/qaccessible.cpp b/src/gui/accessible/qaccessible.cpp
index 46bca16dad..b75712c3e8 100644
--- a/src/gui/accessible/qaccessible.cpp
+++ b/src/gui/accessible/qaccessible.cpp
@@ -173,6 +173,7 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\value ActionChanged An action has been changed.
\value ActiveDescendantChanged
\value Alert A system alert (e.g., a message from a QMessageBox)
+ \value [since 6.8] Announcement The announcement of a message is requested.
\value AttributeChanged
\value ContextHelpEnd Context help (QWhatsThis) for an object is finished.
\value ContextHelpStart Context help (QWhatsThis) for an object is initiated.
@@ -205,6 +206,7 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
clicked or via a key press.
\value HypertextLinkSelected A hypertext link has been selected.
\value HypertextNLinksChanged
+ \value [since 6.8] IdentifierChanged The identifier of an object has changed.
\value LocationChanged An object's location on the screen has changed.
\value MenuCommand A menu item is triggered.
\value MenuEnd A menu has been closed (Qt uses PopupMenuEnd for all
@@ -388,14 +390,15 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
This enum specifies string information that an accessible object
returns.
- \value Name The name of the object. This can be used both
- as an identifier or a short description by
- accessible clients.
- \value Description A short text describing the object.
- \value Value The value of the object.
- \value Help A longer text giving information about how to use the object.
- \value Accelerator The keyboard shortcut that executes the object's default action.
- \value UserText The first value to be used for user defined text.
+ \value Name The name of the object. This can be used both
+ as an identifier or a short description by
+ accessible clients.
+ \value Description A short text describing the object.
+ \value Value The value of the object.
+ \value Help A longer text giving information about how to use the object.
+ \value Accelerator The keyboard shortcut that executes the object's default action.
+ \value UserText The first value to be used for user defined text.
+ \value [since 6.8] Identifier An identifier for the object for e.g. UI tests.
\omitvalue DebugDescription
*/
@@ -449,6 +452,30 @@ Q_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core");
\sa QAccessibleAttributesInterface
*/
+/*! \enum QAccessible::AnnouncementPriority
+ This enum describes the priority for announcements used by the
+ \l QAccessibleAnnouncementEvent.
+ \since 6.8
+
+ With \a QAccessible::AnouncementPriority::Polite, assistive technologies
+ should announce the message at the next graceful opportunity such as at the
+ end of speaking the current sentence or when the user pauses typing.
+
+ When specifying \a QAccessible::AnouncementPriority::Assertive, assistive
+ technologies should notify the user immediately.
+
+ Because an interruption might disorient users or cause them to not complete
+ their current task, \a QAccessible::AnouncementPriority::Assertive should
+ not be used unless the interruption is imperative.
+
+ \value Polite The announcement has normal priority.
+ \value Assertive The announcement has high priority and should notify
+ the user immediately, even if that means interrupting the user's
+ current task.
+
+ \sa QAccessibleAnnouncementEvent
+*/
+
/*!
\enum QAccessible::InterfaceType
@@ -1224,6 +1251,13 @@ QAccessibleInterface *QAccessibleInterface::focusChild() const
tool buttons also have shortcut keys and usually display them in
their tooltip.
+ The \l QAccessible::Identifier can be explicitly set to provide an
+ ID to assistive technologies. This can be especially useful for UI tests.
+ If no identifier has been explicitly set, the identifier is set by the
+ respective interface to an ID based on \l QObject::objectName or its
+ class name and \l QObject::objectName or class name of the parents
+ in its parents chain.
+
All objects provide a string for \l QAccessible::Name.
\sa role(), state()
@@ -1778,7 +1812,56 @@ QAccessibleTextSelectionEvent::~QAccessibleTextSelectionEvent()
{
}
+/*!
+ \since 6.8
+ \class QAccessibleAnnouncementEvent
+ \ingroup accessibility
+ \inmodule QtGui
+
+ \brief The QAccessibleAnnouncementEvent is used to request the announcement
+ of a given message by assistive technologies.
+
+ This class is used with \l QAccessible::updateAccessibility().
+*/
+
+/*! \fn QAccessibleAnnouncementEvent::QAccessibleAnnouncementEvent(QObject *object, const QString &message)
+
+ Constructs a new QAccessibleAnnouncementEvent event for \a object
+ to request the announcement of \a message with priority \l QAccessible::AnnouncementPriority::Polite.
+
+ \l QAccessibleAnnouncementEvent::setPriority can be used to adjust the priority.
+*/
+/*! \fn QAccessibleAnnouncementEvent::QAccessibleAnnouncementEvent(QAccessibleInterface *iface, const QString &message)
+
+ Constructs a new QAccessibleAnnouncementEvent event for \a iface
+ to request the announcement of \a message with priority \l QAccessible::AnnouncementPriority::Polite.
+
+ \l QAccessibleAnnouncementEvent::setPriority can be used to adjust the priority.
+*/
+
+/*! \fn QString QAccessibleAnnouncementEvent::message() const
+
+ Returns the message.
+*/
+
+/*! \fn QAccessible::AnnouncementPriority QAccessibleAnnouncementEvent::priority() const
+
+ Returns the priority.
+*/
+
+/*! \fn void QAccessibleAnnouncementEvent::setPriority(QAccessible::AnnouncementPriority priority)
+
+ Sets the priority with which the announcement will be requested to \a priority.
+*/
+
+
+/*!
+ \internal
+*/
+QAccessibleAnnouncementEvent::~QAccessibleAnnouncementEvent()
+{
+}
/*!
Returns the QAccessibleInterface associated with the event.
@@ -3118,7 +3201,7 @@ bool QAccessibleSelectionInterface::isSelected(QAccessibleInterface *childItem)
Attributes are key-value pairs. Values are stored in \l QVariant.
- The \a QAccessible::Attributes enumeration describes the available keys and
+ The \l QAccessible::Attribute enumeration describes the available keys and
documents which type to use for the value of each key.
While the text-specific attributes handled by \l QAccessibleTextInterface::attributes
@@ -3151,8 +3234,11 @@ QAccessibleAttributesInterface::~QAccessibleAttributesInterface()
Returns the value of the attribute \a key of this object.
- If the specificed attribute is not set for this object, an invalid
- \l QVariant is returned.
+ If the attribute is set for this object, a value of the type documented for the
+ given key in the documentation of the \l QAccessible::Attribute enumeration is
+ returned in the \l QVariant.
+
+ Otherwise, an invalid \l QVariant is returned.
*/
/*! \internal */
diff --git a/src/gui/accessible/qaccessible.h b/src/gui/accessible/qaccessible.h
index 0a92e76c73..3d8daa4b3c 100644
--- a/src/gui/accessible/qaccessible.h
+++ b/src/gui/accessible/qaccessible.h
@@ -316,6 +316,7 @@ public:
Q_ASSERT(m_type != QAccessible::TextRemoved);
Q_ASSERT(m_type != QAccessible::TextUpdated);
Q_ASSERT(m_type != QAccessible::TableModelChanged);
+ Q_ASSERT(m_type != QAccessible::Announcement);
}
inline QAccessibleEvent(QAccessibleInterface *iface, QAccessible::Event typ)
@@ -330,6 +331,7 @@ public:
Q_ASSERT(m_type != QAccessible::TextRemoved);
Q_ASSERT(m_type != QAccessible::TextUpdated);
Q_ASSERT(m_type != QAccessible::TableModelChanged);
+ Q_ASSERT(m_type != QAccessible::Announcement);
m_uniqueId = QAccessible::uniqueId(iface);
m_object = iface->object();
}
@@ -605,6 +607,36 @@ protected:
int m_lastColumn;
};
+class Q_GUI_EXPORT QAccessibleAnnouncementEvent : public QAccessibleEvent
+{
+public:
+ inline QAccessibleAnnouncementEvent(QObject *object, const QString &message)
+ : QAccessibleEvent(object, QAccessible::InvalidEvent)
+ , m_message(message)
+ , m_priority(QAccessible::AnnouncementPriority::Polite)
+ {
+ m_type = QAccessible::Announcement;
+ }
+
+ inline QAccessibleAnnouncementEvent(QAccessibleInterface *iface, const QString &message)
+ : QAccessibleEvent(iface, QAccessible::InvalidEvent)
+ , m_message(message)
+ , m_priority(QAccessible::AnnouncementPriority::Polite)
+ {
+ m_type = QAccessible::Announcement;
+ }
+
+ ~QAccessibleAnnouncementEvent();
+
+ QString message() const { return m_message; }
+ QAccessible::AnnouncementPriority priority() const { return m_priority; }
+ void setPriority(QAccessible::AnnouncementPriority priority) { m_priority = priority; };
+
+protected:
+ QString m_message;
+ QAccessible::AnnouncementPriority m_priority;
+};
+
#ifndef Q_QDOC
#define QAccessibleInterface_iid "org.qt-project.Qt.QAccessibleInterface"
Q_DECLARE_INTERFACE(QAccessibleInterface, QAccessibleInterface_iid)
diff --git a/src/gui/accessible/qaccessible_base.h b/src/gui/accessible/qaccessible_base.h
index 2d2b1de316..e164a2ad6b 100644
--- a/src/gui/accessible/qaccessible_base.h
+++ b/src/gui/accessible/qaccessible_base.h
@@ -101,6 +101,8 @@ public:
HelpChanged = 0x80A0,
DefaultActionChanged = 0x80B0,
AcceleratorChanged = 0x80C0,
+ Announcement = 0x80D0,
+ IdentifierChanged = 0x80E0,
InvalidEvent
};
@@ -323,6 +325,7 @@ public:
Help,
Accelerator,
DebugDescription,
+ Identifier,
UserText = 0x0000ffff
};
@@ -367,6 +370,12 @@ public:
Level,
};
+ enum class AnnouncementPriority {
+ Polite,
+ Assertive
+ };
+ Q_ENUM(AnnouncementPriority)
+
typedef QAccessibleInterface*(*InterfaceFactory)(const QString &key, QObject*);
typedef void(*UpdateHandler)(QAccessibleEvent *event);
typedef void(*RootObjectHandler)(QObject*);
diff --git a/src/gui/accessible/qaccessiblebridge.cpp b/src/gui/accessible/qaccessiblebridge.cpp
index a8b54b0c68..0651c516ea 100644
--- a/src/gui/accessible/qaccessiblebridge.cpp
+++ b/src/gui/accessible/qaccessiblebridge.cpp
@@ -33,6 +33,8 @@ QT_BEGIN_NAMESPACE
Destroys the accessibility bridge object.
*/
+QAccessibleBridge::~QAccessibleBridge()
+ = default;
/*!
\fn void QAccessibleBridge::setRootObject(QAccessibleInterface *object)
diff --git a/src/gui/accessible/qaccessiblebridge.h b/src/gui/accessible/qaccessiblebridge.h
index 4d817c8e58..753f59597a 100644
--- a/src/gui/accessible/qaccessiblebridge.h
+++ b/src/gui/accessible/qaccessiblebridge.h
@@ -16,10 +16,10 @@ QT_BEGIN_NAMESPACE
class QAccessibleInterface;
class QAccessibleEvent;
-class QAccessibleBridge
+class Q_GUI_EXPORT QAccessibleBridge
{
public:
- virtual ~QAccessibleBridge() {}
+ virtual ~QAccessibleBridge();
virtual void setRootObject(QAccessibleInterface *) = 0;
virtual void notifyAccessibilityUpdate(QAccessibleEvent *event) = 0;
};
diff --git a/src/gui/accessible/qaccessiblebridgeutils.cpp b/src/gui/accessible/qaccessiblebridgeutils.cpp
index 994f95fee9..0f91927d4c 100644
--- a/src/gui/accessible/qaccessiblebridgeutils.cpp
+++ b/src/gui/accessible/qaccessiblebridgeutils.cpp
@@ -72,6 +72,28 @@ bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionNa
return true;
}
+QString accessibleId(QAccessibleInterface *accessible) {
+ QString result;
+ if (!accessible)
+ return result;
+ result = accessible->text(QAccessible::Identifier);
+ if (!result.isEmpty())
+ return result;
+ while (accessible) {
+ if (!result.isEmpty())
+ result.prepend(u'.');
+ if (auto obj = accessible->object()) {
+ const QString name = obj->objectName();
+ if (!name.isEmpty())
+ result.prepend(name);
+ else
+ result.prepend(QString::fromUtf8(obj->metaObject()->className()));
+ }
+ accessible = accessible->parent();
+ }
+ return result;
+}
+
} //namespace
QT_END_NAMESPACE
diff --git a/src/gui/accessible/qaccessiblebridgeutils_p.h b/src/gui/accessible/qaccessiblebridgeutils_p.h
index b65a4d0b6c..d7b88eaa58 100644
--- a/src/gui/accessible/qaccessiblebridgeutils_p.h
+++ b/src/gui/accessible/qaccessiblebridgeutils_p.h
@@ -17,6 +17,7 @@
#include <QtGui/private/qtguiglobal_p.h>
+#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
#include <QtGui/qaccessible.h>
@@ -27,6 +28,7 @@ QT_BEGIN_NAMESPACE
namespace QAccessibleBridgeUtils {
Q_GUI_EXPORT QStringList effectiveActionNames(QAccessibleInterface *iface);
Q_GUI_EXPORT bool performEffectiveAction(QAccessibleInterface *iface, const QString &actionName);
+ Q_GUI_EXPORT QString accessibleId(QAccessibleInterface *accessible);
}
QT_END_NAMESPACE
diff --git a/src/gui/accessible/qaccessiblecache.cpp b/src/gui/accessible/qaccessiblecache.cpp
index b41a2481f9..5fff9338e4 100644
--- a/src/gui/accessible/qaccessiblecache.cpp
+++ b/src/gui/accessible/qaccessiblecache.cpp
@@ -172,8 +172,8 @@ void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
objectToId.remove(obj);
delete iface;
-#ifdef Q_OS_MAC
- removeCocoaElement(id);
+#ifdef Q_OS_APPLE
+ removeAccessibleElement(id);
#endif
}
diff --git a/src/gui/accessible/qaccessiblecache_mac.mm b/src/gui/accessible/qaccessiblecache_mac.mm
index 77a9613d38..1c87539194 100644
--- a/src/gui/accessible/qaccessiblecache_mac.mm
+++ b/src/gui/accessible/qaccessiblecache_mac.mm
@@ -3,7 +3,7 @@
#include "qaccessiblecache_p.h"
-// qcocoaaccessibilityelement.h in Cocoa platform plugin
+// qcocoaaccessibilityelement.h in platform plugin
@interface QT_MANGLE_NAMESPACE(QMacAccessibilityElement)
- (void)invalidate;
@end
@@ -12,19 +12,19 @@ QT_BEGIN_NAMESPACE
void QAccessibleCache::insertElement(QAccessible::Id axid, QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *element) const
{
- cocoaElements[axid] = element;
+ accessibleElements[axid] = element;
}
-void QAccessibleCache::removeCocoaElement(QAccessible::Id axid)
+void QAccessibleCache::removeAccessibleElement(QAccessible::Id axid)
{
QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *element = elementForId(axid);
[element invalidate];
- cocoaElements.remove(axid);
+ accessibleElements.remove(axid);
}
QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *QAccessibleCache::elementForId(QAccessible::Id axid) const
{
- return cocoaElements.value(axid);
+ return accessibleElements.value(axid);
}
QT_END_NAMESPACE
diff --git a/src/gui/accessible/qaccessiblecache_p.h b/src/gui/accessible/qaccessiblecache_p.h
index d1abca0f5d..6356969827 100644
--- a/src/gui/accessible/qaccessiblecache_p.h
+++ b/src/gui/accessible/qaccessiblecache_p.h
@@ -41,7 +41,7 @@ public:
QAccessible::Id insert(QObject *object, QAccessibleInterface *iface) const;
void deleteInterface(QAccessible::Id id, QObject *obj = nullptr);
-#ifdef Q_OS_MAC
+#ifdef Q_OS_APPLE
QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *elementForId(QAccessible::Id axid) const;
void insertElement(QAccessible::Id axid, QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *element) const;
#endif
@@ -56,9 +56,9 @@ private:
mutable QHash<QAccessibleInterface *, QAccessible::Id> interfaceToId;
mutable QMultiHash<QObject *, QPair<QAccessible::Id, const QMetaObject*>> objectToId;
-#ifdef Q_OS_MAC
- void removeCocoaElement(QAccessible::Id axid);
- mutable QHash<QAccessible::Id, QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *> cocoaElements;
+#ifdef Q_OS_APPLE
+ void removeAccessibleElement(QAccessible::Id axid);
+ mutable QHash<QAccessible::Id, QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *> accessibleElements;
#endif
friend class QAccessible;
diff --git a/src/gui/compat/removed_api.cpp b/src/gui/compat/removed_api.cpp
index 659a27ecb6..a642c33c42 100644
--- a/src/gui/compat/removed_api.cpp
+++ b/src/gui/compat/removed_api.cpp
@@ -7,6 +7,17 @@
QT_USE_NAMESPACE
+#if QT_GUI_REMOVED_SINCE(6, 4)
+
+#include "qpagesize.h" // removed duplicate declaration of op==
+ // (still caused an symbol on some platforms)
+
+// #include "qotherheader.h"
+// // implement removed functions from qotherheader.h
+// order sections alphabetically
+
+#endif // QT_GUI_REMOVED_SINCE(6, 4)
+
#if QT_GUI_REMOVED_SINCE(6, 6)
#include "qpixmapcache.h" // inlined API
diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake
index e1d8efb292..da08863ac6 100644
--- a/src/gui/configure.cmake
+++ b/src/gui/configure.cmake
@@ -893,7 +893,7 @@ qt_feature("jpeg" PRIVATE
CONDITION QT_FEATURE_imageformatplugin
DISABLE INPUT_libjpeg STREQUAL 'no'
)
-qt_feature_definition("jpeg" "QT_NO_IMAGEFORMAT_JPEG" NEGATE)
+qt_feature_definition("jpeg" "QT_NO_IMAGEFORMAT_JPEG" NEGATE VALUE "1")
qt_feature("system-jpeg" PRIVATE
LABEL " Using system libjpeg"
CONDITION QT_FEATURE_jpeg AND JPEG_FOUND
diff --git a/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc b/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc
index b17d2bd66f..8b7e333df8 100644
--- a/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc
+++ b/src/gui/doc/src/includes/qiconengine-virtualhookhelper.qdocinc
@@ -1,3 +1,3 @@
-\note This is a helper method and the actual work is done by the
-virtual_hook() method, hence this method depends on icon engine support
+\note If the icon engine does not reimplement this function, the actual work is
+done by the virtual_hook() method, hence this method depends on icon engine support
and may not work with all icon engines.
diff --git a/src/gui/image/qbitmap.cpp b/src/gui/image/qbitmap.cpp
index 2208cca1be..315c847c91 100644
--- a/src/gui/image/qbitmap.cpp
+++ b/src/gui/image/qbitmap.cpp
@@ -129,7 +129,6 @@ QBitmap::QBitmap(const QString& fileName, const char *format)
/*!
\fn void QBitmap::swap(QBitmap &other)
- \since 4.8
Swaps bitmap \a other with this bitmap. This operation is very
fast and never fails.
diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp
index 086ac37a07..07e646efa5 100644
--- a/src/gui/image/qicon.cpp
+++ b/src/gui/image/qicon.cpp
@@ -32,6 +32,33 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+// Convenience class providing a bool read() function.
+namespace {
+class ImageReader
+{
+public:
+ ImageReader(const QString &fileName) : m_reader(fileName), m_atEnd(false) { }
+
+ QByteArray format() const { return m_reader.format(); }
+
+ bool read(QImage *image)
+ {
+ if (m_atEnd)
+ return false;
+ *image = m_reader.read();
+ if (!image->size().isValid()) {
+ m_atEnd = true;
+ return false;
+ }
+ m_atEnd = !m_reader.jumpToNextImage();
+ return true;
+ }
+
+private:
+ QImageReader m_reader;
+ bool m_atEnd;
+};
+} // namespace
/*!
\enum QIcon::Mode
@@ -150,7 +177,7 @@ void QPixmapIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode
painter->drawPixmap(rect, px);
}
-static inline int area(const QSize &s) { return s.width() * s.height(); }
+static inline qint64 area(const QSize &s) { return qint64(s.width()) * s.height(); }
// Returns the smallest of the two that is still larger than or equal to size.
// Pixmaps at the correct scale are preferred, pixmaps at lower scale are
@@ -160,15 +187,16 @@ static inline int area(const QSize &s) { return s.width() * s.height(); }
// the 2x pixmaps then.)
static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale, QPixmapIconEngineEntry *pa, QPixmapIconEngineEntry *pb)
{
-
+ const auto scaleA = pa->pixmap.devicePixelRatio();
+ const auto scaleB = pb->pixmap.devicePixelRatio();
// scale: we can only differentiate on scale if the scale differs
- if (pa->scale != pb->scale) {
+ if (scaleA != scaleB) {
// Score the pixmaps: 0 is an exact scale match, positive
// scores have more detail than requested, negative scores
// have less detail than requested.
- qreal ascore = pa->scale - scale;
- qreal bscore = pb->scale - scale;
+ qreal ascore = scaleA - scale;
+ qreal bscore = scaleB - scale;
// always prefer positive scores to prevent upscaling
if ((ascore < 0) != (bscore < 0))
@@ -177,18 +205,18 @@ static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale
return (qAbs(ascore) < qAbs(bscore)) ? pa : pb;
}
- int s = area(size);
+ qint64 s = area(size);
if (pa->size == QSize() && pa->pixmap.isNull()) {
pa->pixmap = QPixmap(pa->fileName);
pa->size = pa->pixmap.size();
}
- int a = area(pa->size);
+ qint64 a = area(pa->size);
if (pb->size == QSize() && pb->pixmap.isNull()) {
pb->pixmap = QPixmap(pb->fileName);
pb->size = pb->pixmap.size();
}
- int b = area(pb->size);
- int res = a;
+ qint64 b = area(pb->size);
+ qint64 res = a;
if (qMin(a,b) >= s)
res = qMin(a,b);
else
@@ -201,13 +229,14 @@ static QPixmapIconEngineEntry *bestSizeScaleMatch(const QSize &size, qreal scale
QPixmapIconEngineEntry *QPixmapIconEngine::tryMatch(const QSize &size, qreal scale, QIcon::Mode mode, QIcon::State state)
{
QPixmapIconEngineEntry *pe = nullptr;
- for (int i = 0; i < pixmaps.size(); ++i)
- if (pixmaps.at(i).mode == mode && pixmaps.at(i).state == state) {
+ for (auto &entry : pixmaps) {
+ if (entry.mode == mode && entry.state == state) {
if (pe)
- pe = bestSizeScaleMatch(size, scale, &pixmaps[i], pe);
+ pe = bestSizeScaleMatch(size, scale, &entry, pe);
else
- pe = &pixmaps[i];
+ pe = &entry;
}
+ }
return pe;
}
@@ -271,41 +300,32 @@ QPixmap QPixmapIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::St
QPixmap QPixmapIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
{
-
QPixmap pm;
QPixmapIconEngineEntry *pe = bestMatch(size, scale, mode, state, false);
if (pe)
pm = pe->pixmap;
+ else
+ return pm;
if (pm.isNull()) {
- int idx = pixmaps.size();
- while (--idx >= 0) {
- if (pe == &pixmaps.at(idx)) {
- pixmaps.remove(idx);
- break;
- }
- }
+ removePixmapEntry(pe);
if (pixmaps.isEmpty())
return pm;
- else
- return pixmap(size, mode, state);
+ return scaledPixmap(size, mode, state, scale);
}
- QSize actualSize = pm.size();
- if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
- actualSize.scale(size, Qt::KeepAspectRatio);
-
+ const auto actualSize = adjustSize(size, pm.size());
QString key = "qt_"_L1
% HexString<quint64>(pm.cacheKey())
- % HexString<uint>(pe ? pe->mode : QIcon::Normal)
+ % HexString<quint8>(pe->mode)
% HexString<quint64>(QGuiApplication::palette().cacheKey())
% HexString<uint>(actualSize.width())
% HexString<uint>(actualSize.height());
if (mode == QIcon::Active) {
- if (QPixmapCache::find(key % HexString<uint>(mode), &pm))
+ if (QPixmapCache::find(key % HexString<quint8>(mode), &pm))
return pm; // horray
- if (QPixmapCache::find(key % HexString<uint>(QIcon::Normal), &pm)) {
+ if (QPixmapCache::find(key % HexString<quint8>(QIcon::Normal), &pm)) {
QPixmap active = pm;
if (QGuiApplication *guiApp = qobject_cast<QGuiApplication *>(qApp))
active = static_cast<QGuiApplicationPrivate*>(QObjectPrivate::get(guiApp))->applyQIconStyleHelper(QIcon::Active, pm);
@@ -314,7 +334,7 @@ QPixmap QPixmapIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIc
}
}
- if (!QPixmapCache::find(key % HexString<uint>(mode), &pm)) {
+ if (!QPixmapCache::find(key % HexString<quint8>(mode), &pm)) {
if (pm.size() != actualSize)
pm = pm.scaled(actualSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
if (pe->mode != mode && mode != QIcon::Normal) {
@@ -324,7 +344,7 @@ QPixmap QPixmapIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIc
if (!generated.isNull())
pm = generated;
}
- QPixmapCache::insert(key % HexString<uint>(mode), pm);
+ QPixmapCache::insert(key % HexString<quint8>(mode), pm);
}
return pm;
}
@@ -341,12 +361,7 @@ QSize QPixmapIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::
if (QPixmapIconEngineEntry *pe = bestMatch(size, scale, mode, state, true))
actualSize = pe->size;
- if (actualSize.isNull())
- return actualSize;
-
- if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
- actualSize.scale(size, Qt::KeepAspectRatio);
- return actualSize;
+ return adjustSize(size, actualSize);
}
QList<QSize> QPixmapIconEngine::availableSizes(QIcon::Mode mode, QIcon::State state)
@@ -369,7 +384,7 @@ void QPixmapIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon
{
if (!pixmap.isNull()) {
QPixmapIconEngineEntry *pe = tryMatch(pixmap.size(), pixmap.devicePixelRatio(), mode, state);
- if (pe && pe->size == pixmap.size() && pe->scale == pixmap.devicePixelRatio()) {
+ if (pe && pe->size == pixmap.size() && pe->pixmap.devicePixelRatio() == pixmap.devicePixelRatio()) {
pe->pixmap = pixmap;
pe->fileName.clear();
} else {
@@ -387,41 +402,13 @@ static inline int origIcoDepth(const QImage &image)
static inline int findBySize(const QList<QImage> &images, const QSize &size)
{
- for (int i = 0; i < images.size(); ++i) {
+ for (qsizetype i = 0; i < images.size(); ++i) {
if (images.at(i).size() == size)
return i;
}
return -1;
}
-// Convenience class providing a bool read() function.
-namespace {
-class ImageReader
-{
-public:
- ImageReader(const QString &fileName) : m_reader(fileName), m_atEnd(false) {}
-
- QByteArray format() const { return m_reader.format(); }
-
- bool read(QImage *image)
- {
- if (m_atEnd)
- return false;
- *image = m_reader.read();
- if (!image->size().isValid()) {
- m_atEnd = true;
- return false;
- }
- m_atEnd = !m_reader.jumpToNextImage();
- return true;
- }
-
-private:
- QImageReader m_reader;
- bool m_atEnd;
-};
-} // namespace
-
void QPixmapIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
{
if (fileName.isEmpty())
@@ -787,7 +774,6 @@ QIcon &QIcon::operator=(const QIcon &other)
/*!
\fn void QIcon::swap(QIcon &other)
- \since 4.8
Swaps icon \a other with this icon. This operation is very
fast and never fails.
@@ -805,7 +791,6 @@ QIcon::operator QVariant() const
Returns a number that identifies the contents of this QIcon
object. Distinct QIcon objects can have the same key if
they refer to the same contents.
- \since 4.3
The cacheKey() will change when the icon is altered via
addPixmap() or addFile().
@@ -862,7 +847,9 @@ QPixmap QIcon::pixmap(const QSize &size, Mode mode, State state) const
\since 6.0
Returns a pixmap with the requested \a size, \a devicePixelRatio, \a mode, and \a
- state, generating one if necessary.
+ state, generating one with the given \a mode and \a state if necessary. The pixmap
+ might be smaller than requested, but never larger, unless the device-pixel ratio
+ of the returned pixmap is larger than 1.
\sa actualSize(), paint()
*/
@@ -1113,6 +1100,7 @@ void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State
if (fileName.isEmpty())
return;
detach();
+ bool alreadyAdded = false;
if (!d) {
QFileInfo info(fileName);
@@ -1122,10 +1110,12 @@ void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State
suffix = QMimeDatabase().mimeTypeForFile(info).preferredSuffix(); // determination from contents
#endif // mimetype
QIconEngine *engine = iconEngineFromSuffix(fileName, suffix);
+ if (engine)
+ alreadyAdded = !engine->isNull();
d = new QIconPrivate(engine ? engine : new QPixmapIconEngine);
}
-
- d->engine->addFile(fileName, size, mode, state);
+ if (!alreadyAdded)
+ d->engine->addFile(fileName, size, mode, state);
// Check if a "@Nx" file exists and add it.
QString atNxFileName = qt_findAtNxFile(fileName, qApp->devicePixelRatio());
@@ -1134,8 +1124,6 @@ void QIcon::addFile(const QString &fileName, const QSize &size, Mode mode, State
}
/*!
- \since 4.5
-
Returns a list of available icon sizes for the specified \a mode and
\a state.
*/
@@ -1147,8 +1135,6 @@ QList<QSize> QIcon::availableSizes(Mode mode, State state) const
}
/*!
- \since 4.7
-
Returns the name used to create the icon, if available.
Depending on the way the icon was created, it may have an associated
@@ -1164,8 +1150,6 @@ QString QIcon::name() const
}
/*!
- \since 4.6
-
Sets the search paths for icon themes to \a paths.
The content of \a paths should follow the theme format
@@ -1179,8 +1163,6 @@ void QIcon::setThemeSearchPaths(const QStringList &paths)
}
/*!
- \since 4.6
-
Returns the search paths for icon themes.
The default search paths will be defined by the platform.
@@ -1235,8 +1217,6 @@ void QIcon::setFallbackSearchPaths(const QStringList &paths)
}
/*!
- \since 4.6
-
Sets the current icon theme to \a name.
The theme will be will be looked up in themeSearchPaths().
@@ -1255,8 +1235,6 @@ void QIcon::setThemeName(const QString &name)
}
/*!
- \since 4.6
-
Returns the name of the current icon theme.
If not set, the current icon theme will be defined by the
@@ -1317,8 +1295,6 @@ void QIcon::setFallbackThemeName(const QString &name)
}
/*!
- \since 4.6
-
Returns the QIcon corresponding to \a name in the
\l{themeName()}{current icon theme}.
@@ -1388,8 +1364,6 @@ QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback)
}
/*!
- \since 4.6
-
Returns \c true if there is an icon available for \a name in the
current icon theme or any of the fallbacks, as described by
fromTheme(), otherwise returns \c false.
@@ -1836,7 +1810,6 @@ bool QIcon::isMask() const
/*!
\fn QDataStream &operator<<(QDataStream &stream, const QIcon &icon)
\relates QIcon
- \since 4.2
Writes the given \a icon to the given \a stream as a PNG
image. If the icon contains more than one image, all images will
@@ -1877,7 +1850,6 @@ QDataStream &operator<<(QDataStream &s, const QIcon &icon)
/*!
\fn QDataStream &operator>>(QDataStream &stream, QIcon &icon)
\relates QIcon
- \since 4.2
Reads an image, or a set of images, from the given \a stream into
the given \a icon.
diff --git a/src/gui/image/qicon_p.h b/src/gui/image/qicon_p.h
index c5bf120620..8050d76435 100644
--- a/src/gui/image/qicon_p.h
+++ b/src/gui/image/qicon_p.h
@@ -34,7 +34,7 @@ public:
delete engine;
}
- qreal pixmapDevicePixelRatio(qreal displayDevicePixelRatio, const QSize &requestedSize, const QSize &actualSize);
+ static qreal pixmapDevicePixelRatio(qreal displayDevicePixelRatio, const QSize &requestedSize, const QSize &actualSize);
QIconEngine *engine;
@@ -49,24 +49,22 @@ public:
struct QPixmapIconEngineEntry
{
- QPixmapIconEngineEntry():scale(1), mode(QIcon::Normal), state(QIcon::Off){}
- QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off)
- :pixmap(pm), size(pm.size()), scale(pm.devicePixelRatio()), mode(m), state(s){}
- QPixmapIconEngineEntry(const QString &file, const QSize &sz = QSize(), QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off)
- :fileName(file), size(sz), scale(1), mode(m), state(s){}
- QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m = QIcon::Normal, QIcon::State s = QIcon::Off);
+ QPixmapIconEngineEntry() = default;
+ QPixmapIconEngineEntry(const QPixmap &pm, QIcon::Mode m, QIcon::State s)
+ : pixmap(pm), size(pm.size()), mode(m), state(s) {}
+ QPixmapIconEngineEntry(const QString &file, const QSize &sz, QIcon::Mode m, QIcon::State s)
+ : fileName(file), size(sz), mode(m), state(s) {}
+ QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m, QIcon::State s);
QPixmap pixmap;
QString fileName;
QSize size;
- qreal scale;
- QIcon::Mode mode;
- QIcon::State state;
- bool isNull() const {return (fileName.isEmpty() && pixmap.isNull()); }
+ QIcon::Mode mode = QIcon::Normal;
+ QIcon::State state = QIcon::Off;
};
Q_DECLARE_TYPEINFO(QPixmapIconEngineEntry, Q_RELOCATABLE_TYPE);
inline QPixmapIconEngineEntry::QPixmapIconEngineEntry(const QString &file, const QImage &image, QIcon::Mode m, QIcon::State s)
- : fileName(file), size(image.size()), scale(image.devicePixelRatio()), mode(m), state(s)
+ : fileName(file), size(image.size()), mode(m), state(s)
{
pixmap.convertFromImage(image);
}
@@ -91,7 +89,24 @@ public:
bool read(QDataStream &in) override;
bool write(QDataStream &out) const override;
+ static inline QSize adjustSize(const QSize &expectedSize, QSize size)
+ {
+ if (!size.isNull() && (size.width() > expectedSize.width() || size.height() > expectedSize.height()))
+ size.scale(expectedSize, Qt::KeepAspectRatio);
+ return size;
+ }
+
private:
+ void removePixmapEntry(QPixmapIconEngineEntry *pe)
+ {
+ auto idx = pixmaps.size();
+ while (--idx >= 0) {
+ if (pe == &pixmaps.at(idx)) {
+ pixmaps.remove(idx);
+ return;
+ }
+ }
+ }
QPixmapIconEngineEntry *tryMatch(const QSize &size, qreal scale, QIcon::Mode mode, QIcon::State state);
QList<QPixmapIconEngineEntry> pixmaps;
diff --git a/src/gui/image/qiconengine.cpp b/src/gui/image/qiconengine.cpp
index 78273bdeb3..efcc3824ba 100644
--- a/src/gui/image/qiconengine.cpp
+++ b/src/gui/image/qiconengine.cpp
@@ -114,7 +114,6 @@ void QIconEngine::addFile(const QString &/*fileName*/, const QSize &/*size*/, QI
/*!
\enum QIconEngine::IconEngineHook
- \since 4.5
These enum values are used for virtual_hook() to allow additional
queries to icon engine without breaking binary compatibility.
@@ -224,8 +223,6 @@ bool QIconEngine::write(QDataStream &) const
}
/*!
- \since 4.5
-
Additional method to allow extending QIconEngine without
adding new virtual methods (and without breaking binary compatibility).
The actual action and format of \a data depends on \a id argument
@@ -249,8 +246,6 @@ void QIconEngine::virtual_hook(int id, void *data)
}
/*!
- \since 4.5
-
Returns sizes of all images that are contained in the engine for the
specific \a mode and \a state.
*/
@@ -260,8 +255,6 @@ QList<QSize> QIconEngine::availableSizes(QIcon::Mode /*mode*/, QIcon::State /*st
}
/*!
- \since 4.7
-
Returns the name used to create the engine, if available.
*/
QString QIconEngine::iconName()
diff --git a/src/gui/image/qiconengine.h b/src/gui/image/qiconengine.h
index 61411b0660..f5c5184608 100644
--- a/src/gui/image/qiconengine.h
+++ b/src/gui/image/qiconengine.h
@@ -18,6 +18,7 @@ public:
virtual ~QIconEngine();
virtual void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) = 0;
virtual QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state);
+ // ### Qt7: add qreal scale argument and remove scaledPixmap
virtual QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state);
virtual void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state);
diff --git a/src/gui/image/qiconloader.cpp b/src/gui/image/qiconloader.cpp
index 982b9a26b4..97735e4af6 100644
--- a/src/gui/image/qiconloader.cpp
+++ b/src/gui/image/qiconloader.cpp
@@ -137,6 +137,10 @@ void QIconLoader::invalidateKey()
// recreating the actual engine the next time the icon is used.
// We don't need to clear the QIcon cache itself.
m_themeKey++;
+
+ // invalidating the factory results in us looking once for
+ // a plugin that provides icon for the new themeName()
+ m_factory = std::nullopt;
}
QString QIconLoader::themeName() const
@@ -650,7 +654,18 @@ QIconEngine *QIconLoader::iconEngine(const QString &iconName) const
qCDebug(lcIconLoader) << "Resolving icon engine for icon" << iconName;
std::unique_ptr<QIconEngine> iconEngine;
- if (hasUserTheme())
+
+ if (!m_factory) {
+ qCDebug(lcIconLoader) << "Finding a plugin for theme" << themeName();
+ // try to find a plugin that supports the current theme
+ const int factoryIndex = qt_iconEngineFactoryLoader()->indexOf(themeName());
+ if (factoryIndex >= 0)
+ m_factory = qobject_cast<QIconEnginePlugin *>(qt_iconEngineFactoryLoader()->instance(factoryIndex));
+ }
+ if (m_factory && *m_factory)
+ iconEngine.reset(m_factory.value()->create(iconName));
+
+ if (hasUserTheme() && (!iconEngine || iconEngine->isNull()))
iconEngine.reset(new QIconLoaderEngine(iconName));
if (!iconEngine || iconEngine->isNull()) {
qCDebug(lcIconLoader) << "Icon is not available from theme or fallback theme.";
@@ -894,18 +909,15 @@ QPixmap PixmapEntry::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State st
if (basePixmap.isNull())
basePixmap.load(filename);
- QSize actualSize = basePixmap.size();
// If the size of the best match we have (basePixmap) is larger than the
// requested size, we downscale it to match.
- if (!actualSize.isNull() && (actualSize.width() > size.width() || actualSize.height() > size.height()))
- actualSize.scale(size, Qt::KeepAspectRatio);
-
+ const auto actualSize = QPixmapIconEngine::adjustSize(size, basePixmap.size());
QString key = "$qt_theme_"_L1
- % HexString<qint64>(basePixmap.cacheKey())
- % HexString<int>(mode)
- % HexString<qint64>(QGuiApplication::palette().cacheKey())
- % HexString<int>(actualSize.width())
- % HexString<int>(actualSize.height());
+ % HexString<quint64>(basePixmap.cacheKey())
+ % HexString<quint8>(mode)
+ % HexString<quint64>(QGuiApplication::palette().cacheKey())
+ % HexString<uint>(actualSize.width())
+ % HexString<uint>(actualSize.height());
QPixmap cachedPixmap;
if (QPixmapCache::find(key, &cachedPixmap)) {
diff --git a/src/gui/image/qiconloader_p.h b/src/gui/image/qiconloader_p.h
index 3cfa9381d1..4a8079a3e9 100644
--- a/src/gui/image/qiconloader_p.h
+++ b/src/gui/image/qiconloader_p.h
@@ -30,6 +30,7 @@
#include <vector>
#include <memory>
+#include <optional>
QT_BEGIN_NAMESPACE
@@ -161,6 +162,8 @@ public:
QList<QSharedPointer<QIconCacheGtkReader>> m_gtkCaches;
};
+class QIconEnginePlugin;
+
class Q_GUI_EXPORT QIconLoader
{
public:
@@ -195,6 +198,7 @@ private:
QThemeIconInfo lookupFallbackIcon(const QString &iconName) const;
uint m_themeKey;
+ mutable std::optional<QIconEnginePlugin *> m_factory;
bool m_supportsSvg;
bool m_initialized;
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp
index 3bbf21320e..175ff8cc49 100644
--- a/src/gui/image/qimage.cpp
+++ b/src/gui/image/qimage.cpp
@@ -1093,7 +1093,6 @@ QImage &QImage::operator=(const QImage &image)
/*!
\fn void QImage::swap(QImage &other)
- \since 4.8
Swaps image \a other with this image. This operation is very
fast and never fails.
@@ -1412,7 +1411,6 @@ int QImage::depth() const
}
/*!
- \since 4.6
\fn int QImage::colorCount() const
Returns the size of the color table for the image.
@@ -1676,7 +1674,6 @@ const uchar *QImage::scanLine(int i) const
shared pixel data, because the returned data is const.
\sa scanLine(), constBits()
- \since 4.7
*/
const uchar *QImage::constScanLine(int i) const
{
@@ -1732,7 +1729,6 @@ const uchar *QImage::bits() const
shared pixel data, because the returned data is const.
\sa bits(), constScanLine()
- \since 4.7
*/
const uchar *QImage::constBits() const
{
@@ -1839,7 +1835,6 @@ void QImage::fill(uint pixel)
/*!
\fn void QImage::fill(Qt::GlobalColor color)
\overload
- \since 4.8
Fills the image with the given \a color, described as a standard global
color.
@@ -1865,8 +1860,6 @@ void QImage::fill(Qt::GlobalColor color)
If the depth of the image is 8, the image will be filled with the
index corresponding the \a color in the color table if present; it
will otherwise be filled with 0.
-
- \since 4.8
*/
void QImage::fill(const QColor &color)
@@ -2123,7 +2116,6 @@ void QImage::invertPixels(InvertMode mode)
#endif
/*!
- \since 4.6
Resizes the color table to contain \a colorCount entries.
If the color table is expanded, all the extra colors will be set to
@@ -4331,6 +4323,12 @@ int QImage::metric(PaintDeviceMetric metric) const
return d->devicePixelRatio * QPaintDevice::devicePixelRatioFScale();
break;
+ case PdmDevicePixelRatioF_EncodedA:
+ Q_FALLTHROUGH();
+ case PdmDevicePixelRatioF_EncodedB:
+ return QPaintDevice::encodeMetricF(metric, d->devicePixelRatio);
+ break;
+
default:
qWarning("QImage::metric(): Unhandled metric type %d", metric);
break;
@@ -4607,7 +4605,6 @@ bool QImage::hasAlphaChannel() const
}
/*!
- \since 4.7
Returns the number of bit planes in the image.
The number of bit planes is the number of bits of color and
@@ -4702,6 +4699,8 @@ QImage QImage::smoothScaled(int w, int h) const
src.convertTo(QImage::Format_RGBA32FPx4_Premultiplied);
break;
#endif
+ case QImage::Format_CMYK8888:
+ break;
default:
if (src.hasAlphaChannel())
src.convertTo(QImage::Format_ARGB32_Premultiplied);
@@ -4844,6 +4843,9 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
// with scaling smoothly more than 2x down.
if (hd * 2 < hs || wd * 2 < ws)
nonpaintable_scale_xform = true;
+ // We cannot paint on a CMYK image, so don't try to do so
+ if (format() == QImage::Format_CMYK8888)
+ nonpaintable_scale_xform = true;
} else {
if (mat.type() <= QTransform::TxRotate && mat.m11() == 0 && mat.m22() == 0) {
if (mat.m12() == 1. && mat.m21() == -1.)
@@ -4875,6 +4877,7 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
case QImage::Format_RGBX64:
case QImage::Format_RGBA64_Premultiplied:
#endif
+ case QImage::Format_CMYK8888:
// Use smoothScaled for scaling when we can do so without conversion.
if (mat.m11() > 0.0F && mat.m22() > 0.0F)
return smoothScaled(wd, hd);
@@ -4945,7 +4948,7 @@ QImage Q_TRACE_INSTRUMENT(qtgui) QImage::transformed(const QTransform &matrix, Q
} else
memset(dImage.bits(), 0x00, dImage.d->nbytes);
- if (target_format >= QImage::Format_RGB32) {
+ if (target_format >= QImage::Format_RGB32 && target_format != QImage::Format_CMYK8888) {
// Prevent QPainter from applying devicePixelRatio corrections
QImage sImage = (devicePixelRatio() != 1) ? QImage(constBits(), width(), height(), format()) : *this;
if (sImage.d != d
@@ -5024,7 +5027,7 @@ void QImage::setColorSpace(const QColorSpace &colorSpace)
return;
if (d->colorSpace == colorSpace)
return;
- if (colorSpace.isValid() && !qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel()))
+ if (colorSpace.isValid() && !qt_compatibleColorModelSource(pixelFormat().colorModel(), colorSpace.colorModel()))
return;
detachMetadata(false);
@@ -5054,7 +5057,7 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace)
}
if (d->colorSpace == colorSpace)
return;
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), colorSpace.colorModel())) {
+ if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) {
*this = convertedToColorSpace(colorSpace);
return;
}
@@ -5085,7 +5088,7 @@ void QImage::convertToColorSpace(const QColorSpace &colorSpace, QImage::Format f
qWarning() << "QImage::convertToColorSpace: Output colorspace is not valid";
return;
}
- if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ if (!qt_compatibleColorModelTarget(toPixelFormat(format).colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) {
qWarning() << "QImage::convertToColorSpace: Color space is not compatible with format";
return;
}
@@ -5145,7 +5148,7 @@ QImage QImage::convertedToColorSpace(const QColorSpace &colorSpace, QImage::Form
qWarning() << "QImage::convertedToColorSpace: Output colorspace is not valid";
return QImage();
}
- if (!qt_compatibleColorModel(toPixelFormat(format).colorModel(), colorSpace.colorModel())) {
+ if (!qt_compatibleColorModelTarget(toPixelFormat(format).colorModel(), colorSpace.colorModel(), colorSpace.transformModel())) {
qWarning() << "QImage::convertedToColorSpace: Color space is not compatible with format";
return QImage();
}
@@ -5178,8 +5181,9 @@ void QImage::applyColorTransform(const QColorTransform &transform)
if (transform.isIdentity())
return;
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) ||
- !qt_compatibleColorModel(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel)) {
+ if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel) ||
+ !qt_compatibleColorModelTarget(pixelFormat().colorModel(), QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel,
+ QColorTransformPrivate::get(transform)->colorSpaceOut->transformModel)) {
qWarning() << "QImage::applyColorTransform can not apply format switching transform without switching format";
return;
}
@@ -5239,14 +5243,14 @@ void QImage::applyColorTransform(const QColorTransform &transform)
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
uint8_t *scanline = reinterpret_cast<uint8_t *>(d->data + y * d->bytes_per_line);
- QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
}
};
} else if (format() == Format_Grayscale16) {
transformSegment = [&](int yStart, int yEnd) {
for (int y = yStart; y < yEnd; ++y) {
uint16_t *scanline = reinterpret_cast<uint16_t *>(d->data + y * d->bytes_per_line);
- QColorTransformPrivate::get(transform)->applyGray(scanline, scanline, width(), flags);
+ QColorTransformPrivate::get(transform)->apply(scanline, scanline, width(), flags);
}
};
} else if (qt_fpColorPrecision(format())) {
@@ -5340,15 +5344,15 @@ QImage QImage::colorTransformed(const QColorTransform &transform) const &
if (transform.isIdentity())
return *this;
- QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
- QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData();
+ const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData();
+ if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) {
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
return QImage();
}
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) {
// All model switching transforms are opaque in at least one end.
- switch (outColorModel) {
+ switch (outColorSpace->colorModel) {
case QColorSpace::ColorModel::Rgb:
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
case QColorSpace::ColorModel::Gray:
@@ -5427,13 +5431,13 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
if (transform.isIdentity())
return convertedTo(toFormat, flags);
- QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
- QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData();
+ const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData();
+ if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) {
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
return QImage();
}
- if (!qt_compatibleColorModel(toPixelFormat(toFormat).colorModel(), outColorModel)) {
+ if (!qt_compatibleColorModelTarget(toPixelFormat(toFormat).colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) {
qWarning() << "QImage::colorTransformed: Invalid output color space for transform";
return QImage();
}
@@ -5530,7 +5534,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
QRgb *out_scanline = reinterpret_cast<QRgb *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
@@ -5538,7 +5542,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
QRgba64 *out_scanline = reinterpret_cast<QRgba64 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
@@ -5549,7 +5553,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(d->data + y * d->bytes_per_line);
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
@@ -5557,7 +5561,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(d->data + y * d->bytes_per_line);
QCmyk32 *out_scanline = reinterpret_cast<QCmyk32 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
@@ -5569,7 +5573,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const QRgb *in_scanline = reinterpret_cast<const QRgb *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
@@ -5578,7 +5582,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const QRgba64 *in_scanline = reinterpret_cast<const QRgba64 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
@@ -5589,7 +5593,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
} else {
@@ -5597,7 +5601,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const QCmyk32 *in_scanline = reinterpret_cast<const QCmyk32 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyReturnGray(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), QColorTransformPrivate::InputOpaque);
}
};
}
@@ -5701,11 +5705,11 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
const quint8 *in_scanline = reinterpret_cast<const quint8 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
if (tmpFormat == Format_Grayscale8) {
quint8 *out_scanline = reinterpret_cast<quint8 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
} else {
Q_ASSERT(tmpFormat == Format_Grayscale16);
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
}
};
@@ -5714,7 +5718,7 @@ QImage QImage::colorTransformed(const QColorTransform &transform, QImage::Format
for (int y = yStart; y < yEnd; ++y) {
const quint16 *in_scanline = reinterpret_cast<const quint16 *>(fromImage.constBits() + y * fromImage.bytesPerLine());
quint16 *out_scanline = reinterpret_cast<quint16 *>(toImage.d->data + y * toImage.bytesPerLine());
- QColorTransformPrivate::get(transform)->applyGray(out_scanline, in_scanline, width(), transFlags);
+ QColorTransformPrivate::get(transform)->apply(out_scanline, in_scanline, width(), transFlags);
}
};
} else if (fromImage.format() == Format_CMYK8888) {
@@ -5808,15 +5812,15 @@ QImage QImage::colorTransformed(const QColorTransform &transform) &&
if (!d)
return QImage();
- QColorSpace::ColorModel inColorModel = QColorTransformPrivate::get(transform)->colorSpaceIn->colorModel;
- QColorSpace::ColorModel outColorModel = QColorTransformPrivate::get(transform)->colorSpaceOut->colorModel;
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), inColorModel)) {
+ const QColorSpacePrivate *inColorSpace = QColorTransformPrivate::get(transform)->colorSpaceIn.constData();
+ const QColorSpacePrivate *outColorSpace = QColorTransformPrivate::get(transform)->colorSpaceOut.constData();
+ if (!qt_compatibleColorModelSource(pixelFormat().colorModel(), inColorSpace->colorModel)) {
qWarning() << "QImage::colorTransformed: Invalid input color space for transform";
return QImage();
}
- if (!qt_compatibleColorModel(pixelFormat().colorModel(), outColorModel)) {
+ if (!qt_compatibleColorModelTarget(pixelFormat().colorModel(), outColorSpace->colorModel, outColorSpace->transformModel)) {
// There is currently no inplace conversion of both colorspace and format, so just use the normal version.
- switch (outColorModel) {
+ switch (outColorSpace->colorModel) {
case QColorSpace::ColorModel::Rgb:
return colorTransformed(transform, qt_highColorPrecision(format(), true) ? QImage::Format_RGBX64 : QImage::Format_RGB32);
case QColorSpace::ColorModel::Gray:
diff --git a/src/gui/image/qimage_conversions.cpp b/src/gui/image/qimage_conversions.cpp
index a806954df2..8d9e0b87e2 100644
--- a/src/gui/image/qimage_conversions.cpp
+++ b/src/gui/image/qimage_conversions.cpp
@@ -1424,7 +1424,7 @@ static void convert_ARGB_to_gray8(QImageData *dest, const QImageData *src, Qt::I
for (int i = 0; i < src->height; ++i) {
const QRgb *src_line = reinterpret_cast<const QRgb *>(src_data);
- tfd->applyReturnGray(dest_data, src_line, src->width, flags);
+ tfd->apply(dest_data, src_line, src->width, flags);
src_data += sbpl;
dest_data += dbpl;
}
@@ -1461,7 +1461,7 @@ static void convert_ARGB_to_gray16(QImageData *dest, const QImageData *src, Qt::
const int len = std::min(src->width - j, BufferSize);
for (int k = 0; k < len; ++k)
tmp_line[k] = QRgba64::fromArgb32(src_line[j + k]);
- tfd->applyReturnGray(dest_line + j, tmp_line, len, flags);
+ tfd->apply(dest_line + j, tmp_line, len, flags);
j += len;
}
src_data += sbpl;
@@ -1498,7 +1498,7 @@ static void convert_RGBA64_to_gray8(QImageData *dest, const QImageData *src, Qt:
int j = 0;
while (j < src->width) {
const int len = std::min(src->width - j, BufferSize);
- tfd->applyReturnGray(gray_line, src_line + j, len, flags);
+ tfd->apply(gray_line, src_line + j, len, flags);
for (int k = 0; k < len; ++k)
dest_line[j + k] = qt_div_257(gray_line[k]);
j += len;
@@ -1533,7 +1533,7 @@ static void convert_RGBA64_to_gray16(QImageData *dest, const QImageData *src, Qt
for (int i = 0; i < src->height; ++i) {
const QRgba64 *src_line = reinterpret_cast<const QRgba64 *>(src_data);
quint16 *dest_line = reinterpret_cast<quint16 *>(dest_data);
- tfd->applyReturnGray(dest_line, src_line, src->width, flags);
+ tfd->apply(dest_line, src_line, src->width, flags);
src_data += sbpl;
dest_data += dbpl;
}
diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h
index 0d42f94253..43b03ada7c 100644
--- a/src/gui/image/qimage_p.h
+++ b/src/gui/image/qimage_p.h
@@ -456,7 +456,7 @@ inline QColorSpace::ColorModel qt_csColorData(QPixelFormat::ColorModel format)
return QColorSpace::ColorModel::Undefined;
}
-inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
+inline bool qt_compatibleColorModelBase(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
{
QColorSpace::ColorModel dataCs = qt_csColorData(data);
@@ -466,11 +466,27 @@ inline bool qt_compatibleColorModel(QPixelFormat::ColorModel data, QColorSpace::
if (cs == QColorSpace::ColorModel::Undefined || dataCs == QColorSpace::ColorModel::Undefined)
return false;
- if (dataCs == cs)
- return true; // Matching color models
+ return (dataCs == cs); // Matching color models
+}
+
+inline bool qt_compatibleColorModelSource(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs)
+{
+ if (qt_compatibleColorModelBase(data, cs))
+ return true;
+
+ if (data == QPixelFormat::ColorModel::Grayscale && cs == QColorSpace::ColorModel::Rgb)
+ return true; // Can apply Rgb CS to Gray input data
+
+ return false;
+}
+
+inline bool qt_compatibleColorModelTarget(QPixelFormat::ColorModel data, QColorSpace::ColorModel cs, QColorSpace::TransformModel tm)
+{
+ if (qt_compatibleColorModelBase(data, cs))
+ return true;
- if (dataCs == QColorSpace::ColorModel::Gray)
- return true; // Can apply any CS with white point to Gray data
+ if (data == QPixelFormat::ColorModel::Grayscale && tm == QColorSpace::TransformModel::ThreeComponentMatrix)
+ return true; // Can apply three-component matrix CS to gray output
return false;
}
diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp
index 9366e9cbb1..1f85fef3bd 100644
--- a/src/gui/image/qimagereader.cpp
+++ b/src/gui/image/qimagereader.cpp
@@ -800,8 +800,6 @@ QString QImageReader::fileName() const
}
/*!
- \since 4.2
-
Sets the quality setting of the image format to \a quality.
Some image formats, in particular lossy ones, entail a tradeoff between a)
@@ -825,8 +823,6 @@ void QImageReader::setQuality(int quality)
}
/*!
- \since 4.2
-
Returns the quality setting of the image format.
\sa setQuality()
@@ -856,8 +852,6 @@ QSize QImageReader::size() const
}
/*!
- \since 4.5
-
Returns the format of the image, without actually reading the image
contents. The format describes the image format \l QImageReader::read()
returns, not the format of the actual image.
@@ -876,8 +870,6 @@ QImage::Format QImageReader::imageFormat() const
}
/*!
- \since 4.1
-
Returns the text keys for this image. You can use
these keys with text() to list the image text for
a certain key.
@@ -894,8 +886,6 @@ QStringList QImageReader::textKeys() const
}
/*!
- \since 4.1
-
Returns the image text associated with \a key.
Support for this option is implemented through
@@ -985,8 +975,6 @@ QRect QImageReader::scaledClipRect() const
}
/*!
- \since 4.1
-
Sets the background color to \a color.
Image formats that support this operation are expected to
initialize the background to \a color before reading an image.
@@ -1000,8 +988,6 @@ void QImageReader::setBackgroundColor(const QColor &color)
}
/*!
- \since 4.1
-
Returns the background color that's used when reading an image.
If the image format does not support setting the background color
an invalid color is returned.
@@ -1016,8 +1002,6 @@ QColor QImageReader::backgroundColor() const
}
/*!
- \since 4.1
-
Returns \c true if the image format supports animation;
otherwise, false is returned.
@@ -1432,8 +1416,6 @@ QString QImageReader::errorString() const
}
/*!
- \since 4.2
-
Returns \c true if the reader supports \a option; otherwise returns
false.
diff --git a/src/gui/image/qimagewriter.cpp b/src/gui/image/qimagewriter.cpp
index d2176e4189..0fcc783e6d 100644
--- a/src/gui/image/qimagewriter.cpp
+++ b/src/gui/image/qimagewriter.cpp
@@ -588,8 +588,6 @@ QImageIOHandler::Transformations QImageWriter::transformation() const
}
/*!
- \since 4.1
-
Sets the image text associated with the key \a key to
\a text. This is useful for storing copyright information
or other information about the image. Example:
@@ -710,8 +708,6 @@ QString QImageWriter::errorString() const
}
/*!
- \since 4.2
-
Returns \c true if the writer supports \a option; otherwise returns
false.
diff --git a/src/gui/image/qmovie.cpp b/src/gui/image/qmovie.cpp
index 0d13639d35..8fd62c2361 100644
--- a/src/gui/image/qmovie.cpp
+++ b/src/gui/image/qmovie.cpp
@@ -103,7 +103,6 @@
*/
/*! \fn void QMovie::frameChanged(int frameNumber)
- \since 4.1
This signal is emitted when the frame number has changed to
\a frameNumber. You can call currentImage() or currentPixmap() to get a
@@ -973,8 +972,6 @@ void QMovie::stop()
}
/*!
- \since 4.1
-
Returns the scaled size of frames.
\sa QImageReader::scaledSize()
@@ -986,8 +983,6 @@ QSize QMovie::scaledSize()
}
/*!
- \since 4.1
-
Sets the scaled frame size to \a size.
\sa QImageReader::setScaledSize()
@@ -999,8 +994,6 @@ void QMovie::setScaledSize(const QSize &size)
}
/*!
- \since 4.1
-
Returns the list of image formats supported by QMovie.
\sa QImageReader::supportedImageFormats()
diff --git a/src/gui/image/qpicture.cpp b/src/gui/image/qpicture.cpp
index da8c5ef1e5..0bc21a3e9f 100644
--- a/src/gui/image/qpicture.cpp
+++ b/src/gui/image/qpicture.cpp
@@ -925,7 +925,6 @@ QPicture& QPicture::operator=(const QPicture &p)
/*!
\fn void QPicture::swap(QPicture &other)
- \since 4.8
Swaps picture \a other with this picture. This operation is very
fast and never fails.
diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp
index 89b8d5303b..afef16f867 100644
--- a/src/gui/image/qpixmap.cpp
+++ b/src/gui/image/qpixmap.cpp
@@ -289,7 +289,6 @@ QPixmap QPixmap::copy(const QRect &rect) const
/*!
\fn QPixmap::scroll(int dx, int dy, int x, int y, int width, int height, QRegion *exposed)
- \since 4.6
This convenience function is equivalent to calling QPixmap::scroll(\a dx,
\a dy, QRect(\a x, \a y, \a width, \a height), \a exposed).
@@ -298,8 +297,6 @@ QPixmap QPixmap::copy(const QRect &rect) const
*/
/*!
- \since 4.6
-
Scrolls the area \a rect of this pixmap by (\a dx, \a dy). The exposed
region is left unchanged. You can optionally pass a pointer to an empty
QRegion to get the region that is \a exposed by the scroll operation.
@@ -371,7 +368,6 @@ QPixmap &QPixmap::operator=(const QPixmap &pixmap)
/*!
\fn void QPixmap::swap(QPixmap &other)
- \since 4.8
Swaps pixmap \a other with this pixmap. This operation is very
fast and never fails.
@@ -970,12 +966,7 @@ bool QPixmap::isDetached() const
Passing 0 for \a flags sets all the default options. Returns \c true
if the result is that this pixmap is not null.
- Note: this function was part of Qt 3 support in Qt 4.6 and earlier.
- It has been promoted to official API status in 4.7 to support updating
- the pixmap's image without creating a new QPixmap as fromImage() would.
-
\sa fromImage()
- \since 4.7
*/
bool QPixmap::convertFromImage(const QImage &image, Qt::ImageConversionFlags flags)
{
diff --git a/src/gui/image/qpixmap_blitter.cpp b/src/gui/image/qpixmap_blitter.cpp
index 41563bf380..925ac37be4 100644
--- a/src/gui/image/qpixmap_blitter.cpp
+++ b/src/gui/image/qpixmap_blitter.cpp
@@ -88,6 +88,10 @@ int QBlittablePlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) con
return devicePixelRatio();
case QPaintDevice::PdmDevicePixelRatioScaled:
return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale();
+ case QPaintDevice::PdmDevicePixelRatioF_EncodedA:
+ Q_FALLTHROUGH();
+ case QPaintDevice::PdmDevicePixelRatioF_EncodedB:
+ return QPaintDevice::encodeMetricF(metric, devicePixelRatio());
default:
qWarning("QRasterPlatformPixmap::metric(): Unhandled metric type %d", metric);
break;
diff --git a/src/gui/image/qpixmap_raster.cpp b/src/gui/image/qpixmap_raster.cpp
index 6e10bdd562..a261bb7b5c 100644
--- a/src/gui/image/qpixmap_raster.cpp
+++ b/src/gui/image/qpixmap_raster.cpp
@@ -253,6 +253,10 @@ int QRasterPlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const
return image.devicePixelRatio();
case QPaintDevice::PdmDevicePixelRatioScaled:
return image.devicePixelRatio() * QPaintDevice::devicePixelRatioFScale();
+ case QPaintDevice::PdmDevicePixelRatioF_EncodedA:
+ Q_FALLTHROUGH();
+ case QPaintDevice::PdmDevicePixelRatioF_EncodedB:
+ return QPaintDevice::encodeMetricF(metric, image.devicePixelRatio());
default:
qWarning("QRasterPlatformPixmap::metric(): Unhandled metric type %d", metric);
diff --git a/src/gui/image/qpixmapcache.cpp b/src/gui/image/qpixmapcache.cpp
index 45c9743f93..b0db1f48cb 100644
--- a/src/gui/image/qpixmapcache.cpp
+++ b/src/gui/image/qpixmapcache.cpp
@@ -81,7 +81,6 @@ static inline bool qt_pixmapcache_thread_test()
\brief The QPixmapCache::Key class can be used for efficient access
to the QPixmapCache.
\inmodule QtGui
- \since 4.6
Use QPixmapCache::insert() to receive an instance of Key generated
by the pixmap cache. You can store the key in your own objects for
@@ -421,8 +420,6 @@ QPixmapCacheEntry::~QPixmapCacheEntry()
If the pixmap is found, the function sets \a pixmap to that pixmap and
returns \c true; otherwise it leaves \a pixmap alone and returns \c false.
- \since 4.6
-
Example:
\snippet code/src_gui_image_qpixmapcache.cpp 1
*/
@@ -443,8 +440,6 @@ bool QPixmapCache::find(const QString &key, QPixmap *pixmap)
returns \c true; otherwise it leaves \a pixmap alone and returns \c false. If
the pixmap is not found, it means that the \a key is no longer valid,
so it will be released for the next insertion.
-
- \since 4.6
*/
bool QPixmapCache::find(const Key &key, QPixmap *pixmap)
{
@@ -498,8 +493,6 @@ bool QPixmapCache::insert(const QString &key, const QPixmap &pixmap)
deleted when more space is needed.
\sa setCacheLimit(), replace()
-
- \since 4.6
*/
QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
{
@@ -523,8 +516,6 @@ QPixmapCache::Key QPixmapCache::insert(const QPixmap &pixmap)
the cache by this function.
\sa setCacheLimit(), insert()
-
- \since 4.6
*/
#endif // QT_DEPRECATED_SINCE(6, 6)
@@ -571,8 +562,6 @@ void QPixmapCache::remove(const QString &key)
/*!
Removes the pixmap associated with \a key from the cache and releases
the key for a future insertion.
-
- \since 4.6
*/
void QPixmapCache::remove(const Key &key)
{
diff --git a/src/gui/kernel/qaction.cpp b/src/gui/kernel/qaction.cpp
index c67cfd53b1..35f7ec12bf 100644
--- a/src/gui/kernel/qaction.cpp
+++ b/src/gui/kernel/qaction.cpp
@@ -1315,8 +1315,6 @@ Q_GUI_EXPORT QDebug operator<<(QDebug d, const QAction *action)
QtDebugUtils::formatQEnum(d, action->menuRole());
d << " enabled=" << action->isEnabled();
d << " visible=" << action->isVisible();
- } else {
- d << '0';
}
d << ')';
return d;
diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp
index 1d7cd37f67..1630103afe 100644
--- a/src/gui/kernel/qguiapplication.cpp
+++ b/src/gui/kernel/qguiapplication.cpp
@@ -107,6 +107,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcPopup, "qt.gui.popup");
+
using namespace Qt::StringLiterals;
using namespace QtMiscUtils;
@@ -180,12 +182,15 @@ Q_CONSTINIT QClipboard *QGuiApplicationPrivate::qt_clipboard = nullptr;
Q_CONSTINIT QList<QScreen *> QGuiApplicationPrivate::screen_list;
Q_CONSTINIT QWindowList QGuiApplicationPrivate::window_list;
+Q_CONSTINIT QWindowList QGuiApplicationPrivate::popup_list;
+Q_CONSTINIT const QWindow *QGuiApplicationPrivate::active_popup_on_press = nullptr;
Q_CONSTINIT QWindow *QGuiApplicationPrivate::focus_window = nullptr;
Q_CONSTINIT static QBasicMutex applicationFontMutex;
Q_CONSTINIT QFont *QGuiApplicationPrivate::app_font = nullptr;
Q_CONSTINIT QStyleHints *QGuiApplicationPrivate::styleHints = nullptr;
Q_CONSTINIT bool QGuiApplicationPrivate::obey_desktop_settings = true;
+Q_CONSTINIT bool QGuiApplicationPrivate::popup_closed_on_press = false;
Q_CONSTINIT QInputDeviceManager *QGuiApplicationPrivate::m_inputDeviceManager = nullptr;
@@ -960,6 +965,43 @@ bool QGuiApplicationPrivate::isWindowBlocked(QWindow *window, QWindow **blocking
return false;
}
+QWindow *QGuiApplicationPrivate::activePopupWindow()
+{
+ // might be the same as focusWindow() if that's a popup
+ return QGuiApplicationPrivate::popup_list.isEmpty() ?
+ nullptr : QGuiApplicationPrivate::popup_list.constLast();
+}
+
+void QGuiApplicationPrivate::activatePopup(QWindow *popup)
+{
+ if (!popup->isVisible())
+ return;
+ popup_list.removeOne(popup); // ensure that there's only one entry, and it's the last
+ qCDebug(lcPopup) << "appending popup" << popup << "to existing" << popup_list;
+ popup_list.append(popup);
+}
+
+bool QGuiApplicationPrivate::closePopup(QWindow *popup)
+{
+ const auto removed = QGuiApplicationPrivate::popup_list.removeAll(popup);
+ qCDebug(lcPopup) << "removed?" << removed << "popup" << popup << "; remaining" << popup_list;
+ return removed; // >= 1 if something was removed
+}
+
+/*!
+ Returns \c true if there are no more open popups.
+*/
+bool QGuiApplicationPrivate::closeAllPopups()
+{
+ // Close all popups: In case some popup refuses to close,
+ // we give up after 1024 attempts (to avoid an infinite loop).
+ int maxiter = 1024;
+ QWindow *popup;
+ while ((popup = activePopupWindow()) && maxiter--)
+ popup->close(); // this will call QApplicationPrivate::closePopup
+ return QGuiApplicationPrivate::popup_list.isEmpty();
+}
+
/*!
Returns the QWindow that receives events tied to focus,
such as key events.
@@ -1785,6 +1827,7 @@ QGuiApplicationPrivate::~QGuiApplicationPrivate()
platform_integration = nullptr;
window_list.clear();
+ popup_list.clear();
screen_list.clear();
self = nullptr;
@@ -2306,6 +2349,14 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
}
#endif
+ const auto *activePopup = activePopupWindow();
+ if (type == QEvent::MouseButtonPress)
+ active_popup_on_press = activePopup;
+ if (window->d_func()->blockedByModalWindow && !activePopup) {
+ // a modal window is blocking this window, don't allow mouse events through
+ return;
+ }
+
QMouseEvent ev(type, localPoint, localPoint, globalPoint, button, e->buttons, e->modifiers, e->source, device);
Q_ASSERT(devPriv->pointById(0) == persistentEPD); // we don't expect reallocation in QPlatformCursor::pointerEvenmt()
// restore globalLastPosition to avoid invalidating the velocity calculations,
@@ -2314,9 +2365,15 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
persistentEPD = nullptr; // incoming and synth events can cause reallocation during delivery, so don't use this again
// ev now contains a detached copy of the QEventPoint from QPointingDevicePrivate::activePoints
ev.setTimestamp(e->timestamp);
- if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
- // a modal window is blocking this window, don't allow mouse events through
- return;
+
+ if (activePopup && activePopup != window && (!popup_closed_on_press || type == QEvent::MouseButtonRelease)) {
+ // If the popup handles the event, we're done.
+ auto *handlingPopup = window->d_func()->forwardToPopup(&ev, active_popup_on_press);
+ if (handlingPopup) {
+ if (type == QEvent::MouseButtonPress)
+ active_popup_on_press = handlingPopup;
+ return;
+ }
}
if (doubleClick && (ev.type() == QEvent::MouseButtonPress)) {
@@ -2368,6 +2425,7 @@ void QGuiApplicationPrivate::processMouseEvent(QWindowSystemInterfacePrivate::Mo
}
}
if (type == QEvent::MouseButtonRelease && e->buttons == Qt::NoButton) {
+ popup_closed_on_press = false;
if (auto *persistentEPD = devPriv->queryPointById(0)) {
ev.setExclusiveGrabber(persistentEPD->eventPoint, nullptr);
ev.clearPassiveGrabbers(persistentEPD->eventPoint);
@@ -2422,6 +2480,11 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE
window = QGuiApplication::focusWindow();
}
+ if (!window) {
+ e->eventAccepted = false;
+ return;
+ }
+
#if defined(Q_OS_ANDROID)
static bool backKeyPressAccepted = false;
static bool menuKeyPressAccepted = false;
@@ -2430,7 +2493,7 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE
#if !defined(Q_OS_MACOS)
// FIXME: Include OS X in this code path by passing the key event through
// QPlatformInputContext::filterEvent().
- if (e->keyType == QEvent::KeyPress && window) {
+ if (e->keyType == QEvent::KeyPress) {
if (QWindowSystemInterface::handleShortcutEvent(window, e->timestamp, e->key, e->modifiers,
e->nativeScanCode, e->nativeVirtualKey, e->nativeModifiers, e->unicode, e->repeat, e->repeatCount)) {
#if defined(Q_OS_ANDROID)
@@ -2447,9 +2510,16 @@ void QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyE
e->unicode, e->repeat, e->repeatCount);
ev.setTimestamp(e->timestamp);
+ const auto *activePopup = activePopupWindow();
+ if (activePopup && activePopup != window) {
+ // If the popup handles the event, we're done.
+ if (window->d_func()->forwardToPopup(&ev, active_popup_on_press))
+ return;
+ }
+
// only deliver key events when we have a window, and no modal window is blocking this window
- if (window && !window->d_func()->blockedByModalWindow)
+ if (!window->d_func()->blockedByModalWindow)
QGuiApplication::sendSpontaneousEvent(window, &ev);
#ifdef Q_OS_ANDROID
else
@@ -2520,10 +2590,15 @@ void QGuiApplicationPrivate::processFocusWindowEvent(QWindowSystemInterfacePriva
if (previous == newFocus)
return;
- if (newFocus)
+ bool activatedPopup = false;
+ if (newFocus) {
if (QPlatformWindow *platformWindow = newFocus->handle())
if (platformWindow->isAlertState())
platformWindow->setAlertState(false);
+ activatedPopup = (newFocus->flags() & Qt::WindowType_Mask) == Qt::Popup;
+ if (activatedPopup)
+ activatePopup(newFocus);
+ }
QObject *previousFocusObject = previous ? previous->focusObject() : nullptr;
@@ -2538,8 +2613,7 @@ void QGuiApplicationPrivate::processFocusWindowEvent(QWindowSystemInterfacePriva
if (previous) {
Qt::FocusReason r = e->reason;
- if ((r == Qt::OtherFocusReason || r == Qt::ActiveWindowFocusReason) &&
- newFocus && (newFocus->flags() & Qt::Popup) == Qt::Popup)
+ if ((r == Qt::OtherFocusReason || r == Qt::ActiveWindowFocusReason) && activatedPopup)
r = Qt::PopupFocusReason;
QFocusEvent focusOut(QEvent::FocusOut, r);
QCoreApplication::sendSpontaneousEvent(previous, &focusOut);
@@ -2643,28 +2717,18 @@ void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate::
QIconPrivate::clearIconCache();
- QStyleHintsPrivate::get(QGuiApplication::styleHints())->updateColorScheme(colorScheme());
-
QEvent themeChangeEvent(QEvent::ThemeChange);
const QWindowList windows = tce->window ? QWindowList{tce->window} : window_list;
for (auto *window : windows)
QGuiApplication::sendSpontaneousEvent(window, &themeChangeEvent);
}
-/*!
- \internal
- \brief QGuiApplicationPrivate::colorScheme
- \return the platform theme's color scheme
- or Qt::ColorScheme::Unknown if a platform theme cannot be established
- */
-Qt::ColorScheme QGuiApplicationPrivate::colorScheme()
-{
- return platformTheme() ? platformTheme()->colorScheme()
- : Qt::ColorScheme::Unknown;
-}
-
void QGuiApplicationPrivate::handleThemeChanged()
{
+ const auto newColorScheme = platformTheme() ? platformTheme()->colorScheme()
+ : Qt::ColorScheme::Unknown;
+ QStyleHintsPrivate::get(QGuiApplication::styleHints())->updateColorScheme(newColorScheme);
+
updatePalette();
QIconLoader::instance()->updateSystemTheme();
@@ -2792,6 +2856,7 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T
}
if (!window)
return;
+ active_popup_on_press = activePopupWindow();
pointData.target = window;
} else {
if (e->nullWindow()) {
@@ -2819,12 +2884,25 @@ void QGuiApplicationPrivate::processTabletEvent(QWindowSystemInterfacePrivate::T
}
}
+ const auto *activePopup = activePopupWindow();
+ if (window->d_func()->blockedByModalWindow && !activePopup) {
+ // a modal window is blocking this window, don't allow events through
+ return;
+ }
+
QTabletEvent tabletEvent(type, device, local, e->global,
e->pressure, e->xTilt, e->yTilt,
e->tangentialPressure, e->rotation, e->z,
e->modifiers, button, e->buttons);
tabletEvent.setAccepted(false);
tabletEvent.setTimestamp(e->timestamp);
+
+ if (activePopup && activePopup != window) {
+ // If the popup handles the event, we're done.
+ if (window->d_func()->forwardToPopup(&tabletEvent, active_popup_on_press))
+ return;
+ }
+
QGuiApplication::sendSpontaneousEvent(window, &tabletEvent);
pointData.state = e->buttons;
if (!tabletEvent.isAccepted()
@@ -2997,6 +3075,7 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
if (!window)
window = QGuiApplication::topLevelAt(tempPt.globalPosition().toPoint());
QMutableEventPoint::setWindow(ep, window);
+ active_popup_on_press = activePopupWindow();
break;
case QEventPoint::State::Released:
@@ -3067,7 +3146,8 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
break;
}
- if (window->d_func()->blockedByModalWindow && !qApp->d_func()->popupActive()) {
+ const auto *activePopup = activePopupWindow();
+ if (window->d_func()->blockedByModalWindow && !activePopup) {
// a modal window is blocking this window, don't allow touch events through
// QTBUG-37371 temporary fix; TODO: revisit when we have a forwarding solution
@@ -3081,6 +3161,12 @@ void QGuiApplicationPrivate::processTouchEvent(QWindowSystemInterfacePrivate::To
continue;
}
+ if (activePopup && activePopup != window) {
+ // If the popup handles the event, we're done.
+ if (window->d_func()->forwardToPopup(&touchEvent, active_popup_on_press))
+ return;
+ }
+
// Note: after the call to sendSpontaneousEvent, touchEvent.position() will have
// changed to reflect the local position inside the last (random) widget it tried
// to deliver the touch event to, and will therefore be invalid afterwards.
@@ -3457,6 +3543,15 @@ void QGuiApplicationPrivate::updatePalette()
}
}
+QEvent::Type QGuiApplicationPrivate::contextMenuEventType()
+{
+ switch (QGuiApplication::styleHints()->contextMenuTrigger()) {
+ case Qt::ContextMenuTrigger::Press: return QEvent::MouseButtonPress;
+ case Qt::ContextMenuTrigger::Release: return QEvent::MouseButtonRelease;
+ }
+ return QEvent::None;
+}
+
void QGuiApplicationPrivate::clearPalette()
{
delete app_pal;
@@ -4326,7 +4421,7 @@ const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA8Text()
{
#ifdef Q_OS_WIN
if (!m_a8ColorProfile)
- m_a8ColorProfile = QColorTrcLut::fromGamma(2.31); // This is a hard-coded thing for Windows text rendering
+ m_a8ColorProfile = QColorTrcLut::fromGamma(2.31f); // This is a hard-coded thing for Windows text rendering
return m_a8ColorProfile.get();
#else
return colorProfileForA32Text();
@@ -4336,7 +4431,7 @@ const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA8Text()
const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA32Text()
{
if (!m_a32ColorProfile)
- m_a32ColorProfile = QColorTrcLut::fromGamma(fontSmoothingGamma);
+ m_a32ColorProfile = QColorTrcLut::fromGamma(float(fontSmoothingGamma));
return m_a32ColorProfile.get();
}
@@ -4402,6 +4497,9 @@ void *QGuiApplication::resolveInterface(const char *name, int revision) const
#if QT_CONFIG(wayland)
QT_NATIVE_INTERFACE_RETURN_IF(QWaylandApplication, platformNativeInterface());
#endif
+#if defined(Q_OS_VISIONOS)
+ QT_NATIVE_INTERFACE_RETURN_IF(QVisionOSApplication, platformIntegration);
+#endif
return QCoreApplication::resolveInterface(name, revision);
}
diff --git a/src/gui/kernel/qguiapplication.h b/src/gui/kernel/qguiapplication.h
index 14bce88c62..23d7fb3d65 100644
--- a/src/gui/kernel/qguiapplication.h
+++ b/src/gui/kernel/qguiapplication.h
@@ -42,7 +42,7 @@ class Q_GUI_EXPORT QGuiApplication : public QCoreApplication
Q_PROPERTY(QString desktopFileName READ desktopFileName WRITE setDesktopFileName)
Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection
NOTIFY layoutDirectionChanged)
- Q_PROPERTY(QString platformName READ platformName STORED false)
+ Q_PROPERTY(QString platformName READ platformName STORED false CONSTANT)
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed
WRITE setQuitOnLastWindowClosed)
Q_PROPERTY(QScreen *primaryScreen READ primaryScreen NOTIFY primaryScreenChanged STORED false)
diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h
index 2bfcd87f3f..81f616e773 100644
--- a/src/gui/kernel/qguiapplication_p.h
+++ b/src/gui/kernel/qguiapplication_p.h
@@ -196,8 +196,11 @@ public:
virtual Qt::WindowModality defaultModality() const;
virtual bool windowNeverBlocked(QWindow *window) const;
bool isWindowBlocked(QWindow *window, QWindow **blockingWindow = nullptr) const;
- virtual bool popupActive() { return false; }
- virtual bool closeAllPopups() { return false; }
+ static qsizetype popupCount() { return QGuiApplicationPrivate::popup_list.size(); }
+ static QWindow *activePopupWindow();
+ static void activatePopup(QWindow *popup);
+ static bool closePopup(QWindow *popup);
+ static bool closeAllPopups();
static Qt::MouseButton mousePressButton;
static struct QLastCursorPosition {
@@ -258,6 +261,8 @@ public:
static QPalette *app_pal;
static QWindowList window_list;
+ static QWindowList popup_list;
+ static const QWindow *active_popup_on_press;
static QWindow *focus_window;
#ifndef QT_NO_CURSOR
@@ -270,6 +275,7 @@ public:
static QString styleOverride;
static QStyleHints *styleHints;
static bool obey_desktop_settings;
+ static bool popup_closed_on_press;
QInputMethod *inputMethod;
QString firstWindowTitle;
@@ -323,7 +329,7 @@ public:
static void updatePalette();
- static Qt::ColorScheme colorScheme();
+ static QEvent::Type contextMenuEventType();
protected:
virtual void handleThemeChanged();
@@ -340,6 +346,7 @@ private:
static void clearPalette();
friend class QDragManager;
+ friend class QWindowPrivate;
static QGuiApplicationPrivate *self;
static int m_fakeMouseSourcePointId;
diff --git a/src/gui/kernel/qguiapplication_platform.h b/src/gui/kernel/qguiapplication_platform.h
index 545bf75c84..98e19427ae 100644
--- a/src/gui/kernel/qguiapplication_platform.h
+++ b/src/gui/kernel/qguiapplication_platform.h
@@ -32,6 +32,22 @@ struct wl_pointer;
struct wl_touch;
#endif
+#if defined(Q_OS_VISIONOS) || defined(Q_QDOC)
+# ifdef __OBJC__
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer_capabilities);
+typedef CP_OBJECT_cp_layer_renderer_capabilities *cp_layer_renderer_capabilities_t;
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer_configuration);
+typedef CP_OBJECT_cp_layer_renderer_configuration *cp_layer_renderer_configuration_t;
+Q_FORWARD_DECLARE_OBJC_CLASS(CP_OBJECT_cp_layer_renderer);
+typedef CP_OBJECT_cp_layer_renderer *cp_layer_renderer_t;
+# else
+typedef struct cp_layer_renderer_capabilities_s *cp_layer_renderer_capabilities_t;
+typedef struct cp_layer_renderer_configuration_s *cp_layer_renderer_configuration_t;
+typedef struct cp_layer_renderer_s *cp_layer_renderer_t;
+# endif
+#endif
+
+
QT_BEGIN_NAMESPACE
namespace QNativeInterface
@@ -61,6 +77,21 @@ struct Q_GUI_EXPORT QWaylandApplication
};
#endif
+#if defined(Q_OS_VISIONOS) || defined(Q_QDOC)
+struct Q_GUI_EXPORT QVisionOSApplication
+{
+ QT_DECLARE_NATIVE_INTERFACE(QVisionOSApplication, 1, QGuiApplication)
+ struct ImmersiveSpaceCompositorLayer {
+ virtual void configure(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t) const {}
+ virtual void render(cp_layer_renderer_t) = 0;
+ virtual void handleSpatialEvents(const QJsonObject &) {};
+ };
+ virtual void setImmersiveSpaceCompositorLayer(ImmersiveSpaceCompositorLayer *layer) = 0;
+ virtual void openImmersiveSpace() = 0;
+ virtual void dismissImmersiveSpace() = 0;
+};
+#endif
+
} // QNativeInterface
QT_END_NAMESPACE
diff --git a/src/gui/kernel/qguivariant.cpp b/src/gui/kernel/qguivariant.cpp
index fe72e7782f..78a1660355 100644
--- a/src/gui/kernel/qguivariant.cpp
+++ b/src/gui/kernel/qguivariant.cpp
@@ -78,7 +78,9 @@ static constexpr struct : QMetaTypeModuleHelper
// either two nullptrs from canConvert, or two valid pointers
Q_ASSERT(onlyCheck || (bool(from) && bool(to)));
+#if QT_CONFIG(shortcut)
using Int = int;
+#endif
switch (makePair(toTypeId, fromTypeId)) {
QMETATYPE_CONVERTER(QByteArray, QColor,
result = source.name(source.alpha() != 255 ?
diff --git a/src/gui/kernel/qhighdpiscaling_p.h b/src/gui/kernel/qhighdpiscaling_p.h
index 189f31fd0a..d6deb8a72a 100644
--- a/src/gui/kernel/qhighdpiscaling_p.h
+++ b/src/gui/kernel/qhighdpiscaling_p.h
@@ -172,7 +172,7 @@ inline QMargins scale(const QMargins &margins, qreal scaleFactor, QPoint origin
template<typename T>
QList<T> scale(const QList<T> &list, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- if (!QHighDpiScaling::isActive())
+ if (qFuzzyCompare(scaleFactor, qreal(1)))
return list;
QList<T> scaled;
@@ -184,7 +184,7 @@ QList<T> scale(const QList<T> &list, qreal scaleFactor, QPoint origin = QPoint(0
inline QRegion scale(const QRegion &region, qreal scaleFactor, QPoint origin = QPoint(0, 0))
{
- if (!QHighDpiScaling::isActive())
+ if (qFuzzyCompare(scaleFactor, qreal(1)))
return region;
QRegion scaled = region.translated(-origin);
diff --git a/src/gui/kernel/qpaintdevicewindow.cpp b/src/gui/kernel/qpaintdevicewindow.cpp
index 9e8c6ae5a8..8531aeed51 100644
--- a/src/gui/kernel/qpaintdevicewindow.cpp
+++ b/src/gui/kernel/qpaintdevicewindow.cpp
@@ -136,6 +136,11 @@ int QPaintDeviceWindow::metric(PaintDeviceMetric metric) const
case PdmDevicePixelRatioScaled:
return int(QWindow::devicePixelRatio() * devicePixelRatioFScale());
break;
+ case PdmDevicePixelRatioF_EncodedA:
+ Q_FALLTHROUGH();
+ case PdmDevicePixelRatioF_EncodedB:
+ return QPaintDevice::encodeMetricF(metric, QWindow::devicePixelRatio());
+ break;
default:
break;
}
diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp
index 256ea52f01..e308b796ab 100644
--- a/src/gui/kernel/qpalette.cpp
+++ b/src/gui/kernel/qpalette.cpp
@@ -958,7 +958,10 @@ static constexpr QPalette::ResolveMask allResolveMask()
/*!
Returns a new QPalette that is a union of this instance and \a other.
- Color roles set in this instance take precedence.
+ Color roles set in this instance take precedence. Roles that are not
+ set in this instance will be taken from \a other.
+
+ \sa isBrushSet
*/
QPalette QPalette::resolve(const QPalette &other) const
{
diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp
index 48978b849a..3d1319615e 100644
--- a/src/gui/kernel/qplatformtheme.cpp
+++ b/src/gui/kernel/qplatformtheme.cpp
@@ -447,6 +447,11 @@ Qt::ColorScheme QPlatformTheme::colorScheme() const
return Qt::ColorScheme::Unknown;
}
+void QPlatformTheme::requestColorScheme(Qt::ColorScheme scheme)
+{
+ Q_UNUSED(scheme);
+}
+
const QPalette *QPlatformTheme::palette(Palette type) const
{
Q_D(const QPlatformTheme);
diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h
index c0193947b9..d007a19675 100644
--- a/src/gui/kernel/qplatformtheme.h
+++ b/src/gui/kernel/qplatformtheme.h
@@ -295,6 +295,7 @@ public:
#endif
virtual Qt::ColorScheme colorScheme() const;
+ virtual void requestColorScheme(Qt::ColorScheme scheme);
virtual const QPalette *palette(Palette type = SystemPalette) const;
diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h
index 2bbdfd5bf9..0bbddea722 100644
--- a/src/gui/kernel/qplatformwindow_p.h
+++ b/src/gui/kernel/qplatformwindow_p.h
@@ -58,6 +58,8 @@ struct Q_GUI_EXPORT QCocoaWindow
QT_DECLARE_NATIVE_INTERFACE(QCocoaWindow, 1, QWindow)
virtual void setContentBorderEnabled(bool enable) = 0;
virtual QPoint bottomLeftClippedByNSWindowOffset() const = 0;
+
+ virtual bool inLiveResize() const = 0;
};
#endif
diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp
index 5029701f24..73c6199733 100644
--- a/src/gui/kernel/qstylehints.cpp
+++ b/src/gui/kernel/qstylehints.cpp
@@ -123,8 +123,29 @@ int QStyleHints::touchDoubleTapDistance() const
/*!
\property QStyleHints::colorScheme
- \brief the color scheme of the platform theme.
- \sa Qt::ColorScheme
+ \brief the color scheme used by the application.
+
+ By default, this follows the system's default color scheme (also known as appearance),
+ and changes when the system color scheme changes (e.g. during dusk or dawn).
+ Setting the color scheme to an explicit value will override the system setting and
+ ignore any changes to the system's color scheme. However, doing so is a hint to the
+ system, and overriding the color scheme is not supported on all platforms.
+
+ Resetting this property, or setting it to \l{Qt::ColorScheme::Unknown}, will remove
+ the override and make the application follow the system default again. The property
+ value will change to the color scheme the system currently has.
+
+ When this property changes, Qt will read the system palette and update the default
+ palette, but won't overwrite palette entries that have been explicitly set by the
+ application. When the colorSchemeChange() signal gets emitted, the old palette is
+ still in effect.
+
+ Application-specific colors should be selected to work well with the effective
+ palette, taking the current color scheme into account. To update application-
+ specific colors when the effective palette changes, handle
+ \l{QEvent::}{PaletteChange} or \l{QEvent::}{ApplicationPaletteChange} events.
+
+ \sa Qt::ColorScheme, QGuiApplication::palette(), QEvent::PaletteChange
\since 6.5
*/
Qt::ColorScheme QStyleHints::colorScheme() const
@@ -134,6 +155,30 @@ Qt::ColorScheme QStyleHints::colorScheme() const
}
/*!
+ \since 6.8
+
+ Sets the color scheme used by the application to an explicit \a scheme, or
+ revert to the system's current color scheme if \a scheme is Qt::ColorScheme::Unknown.
+*/
+void QStyleHints::setColorScheme(Qt::ColorScheme scheme)
+{
+ if (!QCoreApplication::instance()) {
+ qWarning("Must construct a QGuiApplication before accessing a platform theme hint.");
+ return;
+ }
+ if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
+ theme->requestColorScheme(scheme);
+}
+
+/*!
+ \fn void QStyleHints::unsetColorScheme()
+ \since 6.8
+
+ Restores the color scheme to the system's current color scheme.
+*/
+
+
+/*!
Sets the \a mousePressAndHoldInterval.
\internal
\sa mousePressAndHoldInterval()
@@ -398,6 +443,40 @@ void QStyleHints::setShowShortcutsInContextMenus(bool s)
}
/*!
+ \property QStyleHints::contextMenuTrigger
+ \since 6.8
+ \brief mouse event used to trigger a context menu event.
+
+ The default on UNIX systems is to show context menu on mouse button press event, while on
+ Windows it is the mouse button release event. This property can be used to override the default
+ platform behavior.
+
+ \note Developers must use this property with great care, as it changes the default interaction
+ mode that their users will expect on the platform that they are running on.
+
+ \sa Qt::ContextMenuTrigger
+*/
+Qt::ContextMenuTrigger QStyleHints::contextMenuTrigger() const
+{
+ Q_D(const QStyleHints);
+ if (d->m_contextMenuTrigger == -1) {
+ return themeableHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool()
+ ? Qt::ContextMenuTrigger::Release
+ : Qt::ContextMenuTrigger::Press;
+ }
+ return Qt::ContextMenuTrigger(d->m_contextMenuTrigger);
+}
+
+void QStyleHints::setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger)
+{
+ Q_D(QStyleHints);
+ const Qt::ContextMenuTrigger currentTrigger = this->contextMenuTrigger();
+ d->m_contextMenuTrigger = int(contextMenuTrigger);
+ if (currentTrigger != contextMenuTrigger)
+ emit contextMenuTriggerChanged(contextMenuTrigger);
+}
+
+/*!
\property QStyleHints::passwordMaskDelay
\brief the time, in milliseconds, a typed letter is displayed unshrouded
in a text input field in password mode.
diff --git a/src/gui/kernel/qstylehints.h b/src/gui/kernel/qstylehints.h
index 969bec4b35..97ef59f3cf 100644
--- a/src/gui/kernel/qstylehints.h
+++ b/src/gui/kernel/qstylehints.h
@@ -36,6 +36,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(bool showIsMaximized READ showIsMaximized STORED false CONSTANT FINAL)
Q_PROPERTY(bool showShortcutsInContextMenus READ showShortcutsInContextMenus
WRITE setShowShortcutsInContextMenus NOTIFY showShortcutsInContextMenusChanged FINAL)
+ Q_PROPERTY(Qt::ContextMenuTrigger contextMenuTrigger READ contextMenuTrigger WRITE
+ setContextMenuTrigger NOTIFY contextMenuTriggerChanged FINAL)
Q_PROPERTY(int startDragDistance READ startDragDistance NOTIFY startDragDistanceChanged FINAL)
Q_PROPERTY(int startDragTime READ startDragTime NOTIFY startDragTimeChanged FINAL)
Q_PROPERTY(int startDragVelocity READ startDragVelocity STORED false CONSTANT FINAL)
@@ -52,7 +54,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(int mouseDoubleClickDistance READ mouseDoubleClickDistance STORED false CONSTANT
FINAL)
Q_PROPERTY(int touchDoubleTapDistance READ touchDoubleTapDistance STORED false CONSTANT FINAL)
- Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme NOTIFY colorSchemeChanged FINAL)
+ Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme WRITE setColorScheme
+ RESET unsetColorScheme NOTIFY colorSchemeChanged FINAL)
public:
void setMouseDoubleClickInterval(int mouseDoubleClickInterval);
@@ -79,6 +82,8 @@ public:
bool showIsMaximized() const;
bool showShortcutsInContextMenus() const;
void setShowShortcutsInContextMenus(bool showShortcutsInContextMenus);
+ Qt::ContextMenuTrigger contextMenuTrigger() const;
+ void setContextMenuTrigger(Qt::ContextMenuTrigger contextMenuTrigger);
int passwordMaskDelay() const;
QChar passwordMaskCharacter() const;
qreal fontSmoothingGamma() const;
@@ -94,6 +99,8 @@ public:
void setMouseQuickSelectionThreshold(int threshold);
int mouseQuickSelectionThreshold() const;
Qt::ColorScheme colorScheme() const;
+ void setColorScheme(Qt::ColorScheme scheme);
+ void unsetColorScheme() { setColorScheme(Qt::ColorScheme::Unknown); }
Q_SIGNALS:
void cursorFlashTimeChanged(int cursorFlashTime);
@@ -105,6 +112,7 @@ Q_SIGNALS:
void tabFocusBehaviorChanged(Qt::TabFocusBehavior tabFocusBehavior);
void useHoverEffectsChanged(bool useHoverEffects);
void showShortcutsInContextMenusChanged(bool);
+ void contextMenuTriggerChanged(Qt::ContextMenuTrigger contextMenuTrigger);
void wheelScrollLinesChanged(int scrollLines);
void mouseQuickSelectionThresholdChanged(int threshold);
void colorSchemeChanged(Qt::ColorScheme colorScheme);
diff --git a/src/gui/kernel/qstylehints_p.h b/src/gui/kernel/qstylehints_p.h
index 2b3979512a..497bf95cbf 100644
--- a/src/gui/kernel/qstylehints_p.h
+++ b/src/gui/kernel/qstylehints_p.h
@@ -35,6 +35,7 @@ public:
int m_tabFocusBehavior = -1;
int m_uiEffects = -1;
int m_showShortcutsInContextMenus = -1;
+ int m_contextMenuTrigger = -1;
int m_wheelScrollLines = -1;
int m_mouseQuickSelectionThreshold = -1;
int m_mouseDoubleClickDistance = -1;
diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp
index b40fd7e8e8..cd3af8598d 100644
--- a/src/gui/kernel/qwindow.cpp
+++ b/src/gui/kernel/qwindow.cpp
@@ -27,6 +27,8 @@
#endif // QT_CONFIG(draganddrop)
#include <private/qevent_p.h>
+#include <private/qeventpoint_p.h>
+#include <private/qguiapplication_p.h>
#include <QtCore/QTimer>
#include <QtCore/QDebug>
@@ -37,6 +39,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcPopup)
+
/*!
\class QWindow
\inmodule QtGui
@@ -186,6 +190,7 @@ QWindow::~QWindow()
// Decouple from parent before window goes under
setParent(nullptr);
QGuiApplicationPrivate::window_list.removeAll(this);
+ QGuiApplicationPrivate::popup_list.removeAll(this);
if (!QGuiApplicationPrivate::is_app_closing)
QGuiApplicationPrivate::instance()->modalWindowList.removeOne(this);
@@ -411,6 +416,13 @@ void QWindowPrivate::setVisible(bool visible)
QGuiApplicationPrivate::updateBlockedStatus(q);
}
+ if (q->type() == Qt::Popup) {
+ if (visible)
+ QGuiApplicationPrivate::activatePopup(q);
+ else
+ QGuiApplicationPrivate::closePopup(q);
+ }
+
#ifndef QT_NO_CURSOR
if (visible && (hasCursor || QGuiApplication::overrideCursor()))
applyCursor();
@@ -2361,8 +2373,13 @@ bool QWindow::close()
if (!isTopLevel())
return false;
- if (!d->platformWindow)
+ if (!d->platformWindow) {
+ // dock widgets can transition back and forth to being popups;
+ // avoid getting stuck
+ if (QGuiApplicationPrivate::activePopupWindow() == this)
+ QGuiApplicationPrivate::closePopup(this);
return true;
+ }
// The window might be deleted during close,
// as a result of delivering the close event.
@@ -2402,6 +2419,52 @@ bool QWindowPrivate::treatAsVisible() const
return q->isVisible();
}
+/*! \internal
+ Returns the popup window that has consumed \a event, if any.
+ \a activePopupOnPress is the window that we have observed previously handling the press.
+*/
+const QWindow *QWindowPrivate::forwardToPopup(QEvent *event, const QWindow */*activePopupOnPress*/)
+{
+ Q_Q(const QWindow);
+ qCDebug(lcPopup) << "checking for popup alternative to" << q << "for" << event
+ << "active popup?" << QGuiApplicationPrivate::activePopupWindow();
+ QWindow *ret = nullptr;
+ if (QWindow *popupWindow = QGuiApplicationPrivate::activePopupWindow()) {
+ if (q == popupWindow)
+ return nullptr; // avoid infinite recursion: we're already handling it
+ if (event->isPointerEvent()) {
+ // detach eventPoints before modifying them
+ QScopedPointer<QPointerEvent> pointerEvent(static_cast<QPointerEvent *>(event)->clone());
+ for (int i = 0; i < pointerEvent->pointCount(); ++i) {
+ QEventPoint &eventPoint = pointerEvent->point(i);
+ const QPoint globalPos = eventPoint.globalPosition().toPoint();
+ const QPointF mapped = popupWindow->mapFromGlobal(globalPos);
+ QMutableEventPoint::setPosition(eventPoint, mapped);
+ QMutableEventPoint::setScenePosition(eventPoint, mapped);
+ }
+
+ /* Popups are expected to be able to directly handle the
+ drag-release sequence after pressing to open, as well as
+ any other mouse events that occur within the popup's bounds. */
+ if (QCoreApplication::sendEvent(popupWindow, pointerEvent.get())) {
+ event->setAccepted(pointerEvent->isAccepted());
+ if (pointerEvent->isAccepted())
+ ret = popupWindow;
+ }
+ qCDebug(lcPopup) << q << "forwarded" << event->type() << "to popup" << popupWindow
+ << "handled?" << (ret != nullptr) << event->isAccepted();
+ return ret;
+ } else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
+ if (QCoreApplication::sendEvent(popupWindow, event))
+ ret = popupWindow;
+ qCDebug(lcPopup) << q << "forwarded" << event->type() << "to popup" << popupWindow
+ << "handled?" << (ret != nullptr) << event->isAccepted();
+ return ret;
+ }
+ }
+ return ret;
+}
+
/*!
The expose event (\a ev) is sent by the window system when a window moves
between the un-exposed and exposed states.
@@ -2654,16 +2717,14 @@ bool QWindow::event(QEvent *ev)
This logic could be simplified by always synthesizing events in
QGuiApplicationPrivate, or perhaps even in each QPA plugin. See QTBUG-93486.
*/
- static const QEvent::Type contextMenuTrigger =
- QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ContextMenuOnMouseRelease).toBool() ?
- QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
auto asMouseEvent = [](QEvent *ev) {
const auto t = ev->type();
return t == QEvent::MouseButtonPress || t == QEvent::MouseButtonRelease
? static_cast<QMouseEvent *>(ev) : nullptr ;
};
- if (QMouseEvent *me = asMouseEvent(ev); me &&
- ev->type() == contextMenuTrigger && me->button() == Qt::RightButton) {
+ if (QMouseEvent *me = asMouseEvent(ev);
+ me && ev->type() == QGuiApplicationPrivate::contextMenuEventType()
+ && me->button() == Qt::RightButton) {
QContextMenuEvent e(QContextMenuEvent::Mouse, me->position().toPoint(),
me->globalPosition().toPoint(), me->modifiers());
QGuiApplication::sendEvent(this, &e);
diff --git a/src/gui/kernel/qwindow_p.h b/src/gui/kernel/qwindow_p.h
index f6c8aee9f6..b3722a6ed8 100644
--- a/src/gui/kernel/qwindow_p.h
+++ b/src/gui/kernel/qwindow_p.h
@@ -84,7 +84,7 @@ public:
Next,
Prev
};
- virtual void setFocusToTarget(QWindowPrivate::FocusTarget) {}
+ virtual void setFocusToTarget(FocusTarget, Qt::FocusReason) {}
virtual QRectF closestAcceptableGeometry(const QRectF &rect) const;
@@ -97,6 +97,8 @@ public:
virtual bool participatesInLastWindowClosed() const;
virtual bool treatAsVisible() const;
+ const QWindow *forwardToPopup(QEvent *event, const QWindow *activePopupOnPress);
+
bool isPopup() const { return (windowFlags & Qt::WindowType_Mask) == Qt::Popup; }
void setAutomaticPositionAndResizeEnabled(bool a)
{ positionAutomatic = resizeAutomatic = a; }
diff --git a/src/gui/painting/qbackingstore.cpp b/src/gui/painting/qbackingstore.cpp
index 2304ee2256..3b709ec77b 100644
--- a/src/gui/painting/qbackingstore.cpp
+++ b/src/gui/painting/qbackingstore.cpp
@@ -50,6 +50,7 @@ public:
QScopedPointer<QImage> highDpiBackingstore;
QRegion staticContents;
QSize size;
+ QSize nativeSize;
bool downscale = qEnvironmentVariableIntValue("QT_WIDGETS_HIGHDPI_DOWNSCALE") > 0;
};
@@ -115,14 +116,13 @@ QWindow* QBackingStore::window() const
void QBackingStore::beginPaint(const QRegion &region)
{
- const qreal dpr = d_ptr->backingStoreDevicePixelRatio();
+ const qreal toNativeFactor = d_ptr->deviceIndependentToNativeFactor();
- if (d_ptr->highDpiBackingstore &&
- d_ptr->highDpiBackingstore->devicePixelRatio() != dpr)
+ if (d_ptr->nativeSize != QHighDpi::scale(size(), toNativeFactor))
resize(size());
QPlatformBackingStore *platformBackingStore = handle();
- platformBackingStore->beginPaint(QHighDpi::scale(region, d_ptr->deviceIndependentToNativeFactor()));
+ platformBackingStore->beginPaint(QHighDpi::scale(region, toNativeFactor));
// When QtGui is applying a high-dpi scale factor the backing store
// creates a "large" backing store image. This image needs to be
@@ -131,18 +131,20 @@ void QBackingStore::beginPaint(const QRegion &region)
// the image data to avoid having the new devicePixelRatio be propagated
// back to the platform plugin.
QPaintDevice *device = platformBackingStore->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image) {
+ if (!qFuzzyCompare(toNativeFactor, qreal(1)) && device->devType() == QInternal::Image) {
QImage *source = static_cast<QImage *>(device);
const bool needsNewImage = d_ptr->highDpiBackingstore.isNull()
- || source->data_ptr() != d_ptr->highDpiBackingstore->data_ptr()
+ || source->constBits() != d_ptr->highDpiBackingstore->constBits()
|| source->size() != d_ptr->highDpiBackingstore->size()
- || source->devicePixelRatio() != d_ptr->highDpiBackingstore->devicePixelRatio();
- if (needsNewImage) {
+ || source->bytesPerLine() != d_ptr->highDpiBackingstore->bytesPerLine()
+ || source->format() != d_ptr->highDpiBackingstore->format();
+ if (needsNewImage)
d_ptr->highDpiBackingstore.reset(
new QImage(source->bits(), source->width(), source->height(), source->bytesPerLine(), source->format()));
- d_ptr->highDpiBackingstore->setDevicePixelRatio(dpr);
- }
+ d_ptr->highDpiBackingstore->setDevicePixelRatio(d_ptr->backingStoreDevicePixelRatio());
+ } else {
+ d_ptr->highDpiBackingstore.reset();
}
}
@@ -156,7 +158,7 @@ QPaintDevice *QBackingStore::paintDevice()
{
QPaintDevice *device = handle()->paintDevice();
- if (QHighDpiScaling::isActive() && device->devType() == QInternal::Image)
+ if (!qFuzzyCompare(d_ptr->deviceIndependentToNativeFactor(), qreal(1)) && device->devType() == QInternal::Image)
return d_ptr->highDpiBackingstore.data();
return device;
@@ -229,9 +231,10 @@ void QBackingStore::flush(const QRegion &region, QWindow *window, const QPoint &
*/
void QBackingStore::resize(const QSize &size)
{
- d_ptr->size = size;
const qreal factor = d_ptr->deviceIndependentToNativeFactor();
- handle()->resize(QHighDpi::scale(size, factor), QHighDpi::scale(d_ptr->staticContents, factor));
+ d_ptr->size = size;
+ d_ptr->nativeSize = QHighDpi::scale(size, factor);
+ handle()->resize(d_ptr->nativeSize, QHighDpi::scale(d_ptr->staticContents, factor));
}
/*!
diff --git a/src/gui/painting/qbackingstorerhisupport.cpp b/src/gui/painting/qbackingstorerhisupport.cpp
index fe5589dc2d..37c52155eb 100644
--- a/src/gui/painting/qbackingstorerhisupport.cpp
+++ b/src/gui/painting/qbackingstorerhisupport.cpp
@@ -114,8 +114,8 @@ bool QBackingStoreRhiSupport::create()
if (QRhi::probe(QRhi::Metal, &params)) {
rhi = QRhi::create(QRhi::Metal, &params, flags);
} else {
- qCDebug(lcQpaBackingStore, "Metal does not seem to be supported. Falling back to OpenGL.");
- rhi = QRhi::create(QRhi::OpenGLES2, &params, flags);
+ qCDebug(lcQpaBackingStore, "Metal does not seem to be supported");
+ return false;
}
}
#endif
diff --git a/src/gui/painting/qcmyk_p.h b/src/gui/painting/qcmyk_p.h
index d00a4b5a6e..1294a18244 100644
--- a/src/gui/painting/qcmyk_p.h
+++ b/src/gui/painting/qcmyk_p.h
@@ -24,6 +24,10 @@ class QCmyk32
{
private:
uint m_cmyk = 0;
+ friend constexpr bool comparesEqual(const QCmyk32 &lhs, const QCmyk32 &rhs) noexcept
+ {
+ return lhs.m_cmyk == rhs.m_cmyk;
+ }
public:
QCmyk32() = default;
@@ -77,6 +81,8 @@ public:
QColor c = color.toCmyk();
return QCmyk32(c.cyan(), c.magenta(), c.yellow(), c.black());
}
+
+ Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(QCmyk32)
};
static_assert(sizeof(QCmyk32) == sizeof(int));
diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h
index 6a9b9f4f9a..0c85222a9a 100644
--- a/src/gui/painting/qcolormatrix_p.h
+++ b/src/gui/painting/qcolormatrix_p.h
@@ -336,6 +336,12 @@ public:
{ 0.1351922452f, 0.7118769884f, 0.0000000000f },
{ 0.0313525312f, 0.0000856627f, 0.8251883388f } };
}
+ static QColorMatrix toXyzFromBt2020()
+ {
+ return QColorMatrix { { 0.673447f, 0.279037f, -0.00192261f },
+ { 0.165665f, 0.675339f, 0.0299835f },
+ { 0.125092f, 0.0456238f, 0.797134f } };
+ }
friend inline bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs);
Q_DECLARE_EQUALITY_COMPARABLE(QColorMatrix);
};
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp
index 7a1d34a408..3d763d577c 100644
--- a/src/gui/painting/qcolorspace.cpp
+++ b/src/gui/painting/qcolorspace.cpp
@@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE
Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock;
-Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {};
+Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::Bt2100Hlg] = {};
static void cleanupPredefinedColorspaces()
{
for (QAtomicPointer<QColorSpacePrivate> &ptr : s_predefinedColorspacePrivates) {
@@ -60,6 +60,12 @@ QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries)
bluePoint = QPointF(0.0366, 0.0001);
whitePoint = QColorVector::D50Chromaticity();
break;
+ case QColorSpace::Primaries::Bt2020:
+ redPoint = QPointF(0.708, 0.292);
+ greenPoint = QPointF(0.170, 0.797);
+ bluePoint = QPointF(0.131, 0.046);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
default:
Q_UNREACHABLE();
}
@@ -132,6 +138,21 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp
transferFunction = QColorSpace::TransferFunction::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
break;
+ case QColorSpace::Bt2020:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::Bt2020;
+ description = QStringLiteral("BT.2020");
+ break;
+ case QColorSpace::Bt2100Pq:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::St2084;
+ description = QStringLiteral("BT.2100(PQ)");
+ break;
+ case QColorSpace::Bt2100Hlg:
+ primaries = QColorSpace::Primaries::Bt2020;
+ transferFunction = QColorSpace::TransferFunction::Hlg;
+ description = QStringLiteral("BT.2100(HLG)");
+ break;
default:
Q_UNREACHABLE();
}
@@ -292,6 +313,26 @@ void QColorSpacePrivate::identifyColorSpace()
}
}
break;
+ case QColorSpace::Primaries::Bt2020:
+ if (transferFunction == QColorSpace::TransferFunction::Bt2020) {
+ namedColorSpace = QColorSpace::Bt2020;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2020");
+ return;
+ }
+ if (transferFunction == QColorSpace::TransferFunction::St2084) {
+ namedColorSpace = QColorSpace::Bt2100Pq;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2100(PQ)");
+ return;
+ }
+ if (transferFunction == QColorSpace::TransferFunction::Hlg) {
+ namedColorSpace = QColorSpace::Bt2100Hlg;
+ if (description.isEmpty())
+ description = QStringLiteral("BT.2100(HLG)");
+ return;
+ }
+ break;
default:
break;
}
@@ -337,7 +378,7 @@ void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transfe
} else if (curve.isSRgb()) {
transferFunction = QColorSpace::TransferFunction::SRgb;
}
- trc[0].m_type = QColorTrc::Type::Function;
+ trc[0].m_type = QColorTrc::Type::ParameterizedFunction;
trc[0].m_fun = curve;
} else {
trc[0].m_type = QColorTrc::Type::Table;
@@ -363,21 +404,21 @@ void QColorSpacePrivate::setTransferFunctionTables(const QList<uint16_t> &redTra
transferFunction = QColorSpace::TransferFunction::Custom;
QColorTransferFunction curve;
if (redTable.asColorTransferFunction(&curve)) {
- trc[0].m_type = QColorTrc::Type::Function;
+ trc[0].m_type = QColorTrc::Type::ParameterizedFunction;
trc[0].m_fun = curve;
} else {
trc[0].m_type = QColorTrc::Type::Table;
trc[0].m_table = redTable;
}
if (greenTable.asColorTransferFunction(&curve)) {
- trc[1].m_type = QColorTrc::Type::Function;
+ trc[1].m_type = QColorTrc::Type::ParameterizedFunction;
trc[1].m_fun = curve;
} else {
trc[1].m_type = QColorTrc::Type::Table;
trc[1].m_table = greenTable;
}
if (blueTable.asColorTransferFunction(&curve)) {
- trc[2].m_type = QColorTrc::Type::Function;
+ trc[2].m_type = QColorTrc::Type::ParameterizedFunction;
trc[2].m_fun = curve;
} else {
trc[2].m_type = QColorTrc::Type::Table;
@@ -390,27 +431,34 @@ void QColorSpacePrivate::setTransferFunction()
{
switch (transferFunction) {
case QColorSpace::TransferFunction::Linear:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction();
+ trc[0] = QColorTransferFunction();
if (qFuzzyIsNull(gamma))
gamma = 1.0f;
break;
case QColorSpace::TransferFunction::Gamma:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromGamma(gamma);
+ trc[0] = QColorTransferFunction::fromGamma(gamma);
break;
case QColorSpace::TransferFunction::SRgb:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromSRgb();
+ trc[0] = QColorTransferFunction::fromSRgb();
if (qFuzzyIsNull(gamma))
gamma = 2.31f;
break;
case QColorSpace::TransferFunction::ProPhotoRgb:
- trc[0].m_type = QColorTrc::Type::Function;
- trc[0].m_fun = QColorTransferFunction::fromProPhotoRgb();
+ trc[0] = QColorTransferFunction::fromProPhotoRgb();
if (qFuzzyIsNull(gamma))
gamma = 1.8f;
break;
+ case QColorSpace::TransferFunction::Bt2020:
+ trc[0] = QColorTransferFunction::fromBt2020();
+ if (qFuzzyIsNull(gamma))
+ gamma = 2.1f;
+ break;
+ case QColorSpace::TransferFunction::St2084:
+ trc[0] = QColorTransferGenericFunction::pq();
+ break;
+ case QColorSpace::TransferFunction::Hlg:
+ trc[0] = QColorTransferGenericFunction::hlg();
+ break;
case QColorSpace::TransferFunction::Custom:
break;
default:
@@ -448,11 +496,13 @@ QColorTransform QColorSpacePrivate::transformationToXYZ() const
transform.d = ptr;
ptr->colorSpaceIn = this;
ptr->colorSpaceOut = this;
- // Convert to XYZ relative to our white point, not the regular D50 white point.
if (isThreeComponentMatrix())
- ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * toXyz;
+ ptr->colorMatrix = toXyz;
else
ptr->colorMatrix = QColorMatrix::identity();
+ // Convert to XYZ relative to our white point, not the regular D50 white point.
+ if (!chad.isNull())
+ ptr->colorMatrix = chad.inverted() * ptr->colorMatrix;
return transform;
}
@@ -526,6 +576,13 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\l{http://www.color.org/chardata/rgb/DCIP3.xalter}{ICC registration of DCI-P3}
\value ProPhotoRgb The Pro Photo RGB color space, also known as ROMM RGB is a very wide gamut color space.
\l{http://www.color.org/chardata/rgb/rommrgb.xalter}{ICC registration of ROMM RGB}
+ \value [since 6.8] Bt2020 BT.2020, also known as Rec.2020 is a basic colorspace of HDR TVs.
+ \l{http://www.color.org/chardata/rgb/BT2020.xalter}{ICC registration of BT.2020}
+ \value [since 6.8] Bt2100Pq BT.2100(PQ), also known as Rec.2100 or HDR10 is an HDR encoding with the same
+ primaries as Bt2020 but using the Perceptual Quantizer transfer function.
+ \l{http://www.color.org/chardata/rgb/BT2100.xalter}{ICC registration of BT.2100}
+ \value [since 6.8] Bt2100Hlg BT.2100 (HLG) is an HDR encoding with the same
+ primaries as Bt2020 but using the Hybrid Log-Gamma transfer function.
*/
/*!
@@ -538,6 +595,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\value AdobeRgb The Adobe RGB primaries
\value DciP3D65 The DCI-P3 primaries with the D65 whitepoint
\value ProPhotoRgb The ProPhoto RGB primaries with the D50 whitepoint
+ \value [since 6.8] Bt2020 The BT.2020 primaries with a D65 whitepoint
*/
/*!
@@ -550,6 +608,10 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
\value Gamma A transfer function that is a real gamma curve based on the value of gamma()
\value SRgb The sRGB transfer function, composed of linear and gamma parts
\value ProPhotoRgb The ProPhoto RGB transfer function, composed of linear and gamma parts
+ \value [since 6.8] Bt2020 The BT.2020 transfer function, composited of linear and gamma parts
+ \value [since 6.8] St2084 The SMPTE ST 2084 transfer function, also known Perceptual Quantizer(PQ).
+ \value [since 6.8] Hlg The Hybrid log-gamma transfer function.
+
*/
/*!
@@ -592,7 +654,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit()
*/
QColorSpace::QColorSpace(NamedColorSpace namedColorSpace)
{
- if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::ProPhotoRgb) {
+ if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::Bt2100Hlg) {
qWarning() << "QColorSpace attempted constructed from invalid QColorSpace::NamedColorSpace: " << int(namedColorSpace);
return;
}
@@ -1129,9 +1191,11 @@ bool QColorSpacePrivate::isValid() const noexcept
if (colorModel == QColorSpace::ColorModel::Gray) {
if (!trc[0].isValid())
return false;
- } else {
+ } else if (colorModel == QColorSpace::ColorModel::Rgb){
if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid())
return false;
+ } else {
+ return false;
}
return true;
}
@@ -1419,13 +1483,16 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
if (colorSpace.d_ptr) {
if (colorSpace.d_ptr->namedColorSpace)
dbg << colorSpace.d_ptr->namedColorSpace << ", ";
+ else
+ dbg << colorSpace.colorModel() << ", ";
if (!colorSpace.isValid()) {
dbg << "Invalid";
if (!colorSpace.d_ptr->iccProfile.isEmpty())
dbg << " with profile data";
} else if (colorSpace.d_ptr->isThreeComponentMatrix()) {
dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
- dbg << ", gamma=" << colorSpace.gamma();
+ if (colorSpace.transferFunction() == QColorSpace::TransferFunction::Gamma)
+ dbg << "=" << colorSpace.gamma();
} else {
if (colorSpace.d_ptr->isPcsLab)
dbg << "PCSLab, ";
diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h
index 488cbd6a53..ecf6614561 100644
--- a/src/gui/painting/qcolorspace.h
+++ b/src/gui/painting/qcolorspace.h
@@ -26,7 +26,10 @@ public:
SRgbLinear,
AdobeRgb,
DisplayP3,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
+ Bt2100Pq,
+ Bt2100Hlg,
};
Q_ENUM(NamedColorSpace)
enum class Primaries {
@@ -34,7 +37,8 @@ public:
SRgb,
AdobeRgb,
DciP3D65,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
};
Q_ENUM(Primaries)
enum class TransferFunction {
@@ -42,7 +46,10 @@ public:
Linear,
Gamma,
SRgb,
- ProPhotoRgb
+ ProPhotoRgb,
+ Bt2020,
+ St2084,
+ Hlg,
};
Q_ENUM(TransferFunction)
enum class TransformModel : uint8_t {
diff --git a/src/gui/painting/qcolortransferfunction_p.h b/src/gui/painting/qcolortransferfunction_p.h
index 484cc69114..b9a09b4646 100644
--- a/src/gui/painting/qcolortransferfunction_p.h
+++ b/src/gui/painting/qcolortransferfunction_p.h
@@ -114,6 +114,11 @@ public:
return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f,
Hints(Hint::Calculated));
}
+ static QColorTransferFunction fromBt2020()
+ {
+ return QColorTransferFunction(1.0f / 1.0993f, 0.0993f / 1.0993f, 1.0f / 4.5f, 0.08145f, 0.0f, 0.0f, 2.2f,
+ Hints(Hint::Calculated));
+ }
bool matches(const QColorTransferFunction &o) const
{
return paramCompare(m_a, o.m_a) && paramCompare(m_b, o.m_b)
diff --git a/src/gui/painting/qcolortransfergeneric_p.h b/src/gui/painting/qcolortransfergeneric_p.h
new file mode 100644
index 0000000000..b4e6e8dec7
--- /dev/null
+++ b/src/gui/painting/qcolortransfergeneric_p.h
@@ -0,0 +1,109 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOLORTRANSFERGENERIC_P_H
+#define QCOLORTRANSFERGENERIC_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/private/qtguiglobal_p.h>
+
+#include <cmath>
+
+QT_BEGIN_NAMESPACE
+
+// Defines the a generic transfer function for our HDR functions
+class QColorTransferGenericFunction
+{
+public:
+ using ConverterPtr = float (*)(float);
+ constexpr QColorTransferGenericFunction(ConverterPtr toLinear = nullptr, ConverterPtr fromLinear = nullptr) noexcept
+ : m_toLinear(toLinear), m_fromLinear(fromLinear)
+ {}
+
+ static QColorTransferGenericFunction hlg()
+ {
+ return QColorTransferGenericFunction(hlgToLinear, hlgFromLinear);
+ }
+ static QColorTransferGenericFunction pq()
+ {
+ return QColorTransferGenericFunction(pqToLinear, pqFromLinear);
+ }
+
+ float apply(float x) const
+ {
+ return m_toLinear(x);
+ }
+
+ float applyInverse(float x) const
+ {
+ return m_fromLinear(x);
+ }
+
+ bool operator==(const QColorTransferGenericFunction &o) const noexcept
+ {
+ return m_toLinear == o.m_toLinear && m_fromLinear == o.m_fromLinear;
+ }
+ bool operator!=(const QColorTransferGenericFunction &o) const noexcept
+ {
+ return m_toLinear != o.m_toLinear || m_fromLinear != o.m_fromLinear;
+ }
+
+private:
+ ConverterPtr m_toLinear = nullptr;
+ ConverterPtr m_fromLinear = nullptr;
+
+ // HLG from linear [0-12] -> [0-1]
+ static float hlgFromLinear(float x)
+ {
+ if (x > 1.f)
+ return m_hlg_a * std::log(x - m_hlg_b) + m_hlg_c;
+ return std::sqrt(x * 0.25f);
+ }
+
+ // HLG to linear [0-1] -> [0-12]
+ static float hlgToLinear(float x)
+ {
+ if (x < 0.5f)
+ return (x * x) * 4.f;
+ return std::exp((x - m_hlg_c) / m_hlg_a) + m_hlg_b;
+ }
+
+ constexpr static float m_hlg_a = 0.17883277f;
+ constexpr static float m_hlg_b = 1.f - (4.f * m_hlg_a);
+ constexpr static float m_hlg_c = 0.55991073f; // 0.5 - a * ln(4 * a)
+
+ // PQ from linear [0-64] -> [0-1]
+ static float pqFromLinear(float x)
+ {
+ x = std::pow(x * (1.f / m_pq_f), (1.f / m_pq_m1));
+ return std::pow((m_pq_c1 - x) / (m_pq_c3 * x - m_pq_c2), (1.f / m_pq_m2));
+ }
+
+ // PQ from linear [0-1] -> [0-64]
+ static float pqToLinear(float x)
+ {
+ x = std::pow(x, m_pq_m1);
+ return std::pow((m_pq_c1 + m_pq_c2 * x) / (1.f + m_pq_c3 * x), m_pq_m2) * m_pq_f;
+ }
+
+ constexpr static float m_pq_c1 = 107.f / 128.f; // c3 - c2 + 1
+ constexpr static float m_pq_c2 = 2413.f / 128.f;
+ constexpr static float m_pq_c3 = 2392.f / 128.f;
+ constexpr static float m_pq_m1 = 1305.f / 8192.f;
+ constexpr static float m_pq_m2 = 2523.f / 32.f;
+ constexpr static float m_pq_f = 64.f; // This might need to be set based on scene metadata
+};
+
+QT_END_NAMESPACE
+
+#endif // QCOLORTRANSFERGENERIC_P_H
diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp
index aac07bdc09..f54dac9f26 100644
--- a/src/gui/painting/qcolortransform.cpp
+++ b/src/gui/painting/qcolortransform.cpp
@@ -22,16 +22,6 @@
QT_BEGIN_NAMESPACE
-std::shared_ptr<QColorTrcLut> lutFromTrc(const QColorTrc &trc)
-{
- if (trc.m_type == QColorTrc::Type::Table)
- return QColorTrcLut::fromTransferTable(trc.m_table);
- if (trc.m_type == QColorTrc::Type::Function)
- return QColorTrcLut::fromTransferFunction(trc.m_fun);
- qWarning() << "TRC uninitialized";
- return nullptr;
-}
-
void QColorTransformPrivate::updateLutsIn() const
{
if (colorSpaceIn->lut.generated.loadAcquire())
@@ -46,12 +36,12 @@ void QColorTransformPrivate::updateLutsIn() const
}
if (colorSpaceIn->trc[0] == colorSpaceIn->trc[1] && colorSpaceIn->trc[0] == colorSpaceIn->trc[2]) {
- colorSpaceIn->lut[0] = lutFromTrc(colorSpaceIn->trc[0]);
+ colorSpaceIn->lut[0] = QColorTrcLut::fromTrc(colorSpaceIn->trc[0]);
colorSpaceIn->lut[1] = colorSpaceIn->lut[0];
colorSpaceIn->lut[2] = colorSpaceIn->lut[0];
} else {
for (int i = 0; i < 3; ++i)
- colorSpaceIn->lut[i] = lutFromTrc(colorSpaceIn->trc[i]);
+ colorSpaceIn->lut[i] = QColorTrcLut::fromTrc(colorSpaceIn->trc[i]);
}
colorSpaceIn->lut.generated.storeRelease(1);
@@ -70,12 +60,12 @@ void QColorTransformPrivate::updateLutsOut() const
}
if (colorSpaceOut->trc[0] == colorSpaceOut->trc[1] && colorSpaceOut->trc[0] == colorSpaceOut->trc[2]) {
- colorSpaceOut->lut[0] = lutFromTrc(colorSpaceOut->trc[0]);
+ colorSpaceOut->lut[0] = QColorTrcLut::fromTrc(colorSpaceOut->trc[0]);
colorSpaceOut->lut[1] = colorSpaceOut->lut[0];
colorSpaceOut->lut[2] = colorSpaceOut->lut[0];
} else {
for (int i = 0; i < 3; ++i)
- colorSpaceOut->lut[i] = lutFromTrc(colorSpaceOut->trc[i]);
+ colorSpaceOut->lut[i] = QColorTrcLut::fromTrc(colorSpaceOut->trc[i]);
}
colorSpaceOut->lut.generated.storeRelease(1);
@@ -390,6 +380,14 @@ template<>
inline int getAlpha<QRgba64>(const QRgba64 &p)
{ return p.alpha(); }
+template<typename T>
+static inline constexpr int getFactor();
+template<>
+inline constexpr int getFactor<QRgb>()
+{ return 255; }
+template<>
+inline constexpr int getFactor<QRgba64>()
+{ return 65535; }
#endif
template<typename T>
@@ -823,8 +821,9 @@ static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer,
const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
const __m128 iFF00 = _mm_set1_ps(1.0f / (255 * 256));
constexpr bool isARGB = isArgb<D>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<S>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
__m128 vf = _mm_loadu_ps(&buffer[i].x);
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
__m128 va = _mm_mul_ps(_mm_set1_ps(a), iFF00);
@@ -905,8 +904,9 @@ static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffe
{
const __m128 vTrcRes = _mm_set1_ps(float(QColorTrcLut::Resolution));
constexpr bool isARGB = isArgb<D>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<S>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
__m128 vf = _mm_loadu_ps(&buffer[i].x);
__m128i v = _mm_cvtps_epi32(_mm_mul_ps(vf, vTrcRes));
const int ridx = _mm_extract_epi16(v, 0);
@@ -1029,8 +1029,9 @@ static void storePremultiplied(D *dst, const S *src, const QColorVector *buffer,
{
const float iFF00 = 1.0f / (255 * 256);
constexpr bool isARGB = isArgb<D>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<S>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
float32x4_t vf = vld1q_f32(&buffer[i].x);
uint32x4_t v = vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f)));
const int ridx = vgetq_lane_u32(v, 0);
@@ -1073,8 +1074,9 @@ static void storeUnpremultiplied(D *dst, const S *src, const QColorVector *buffe
const QColorTransformPrivate *d_ptr)
{
constexpr bool isARGB = isArgb<D>();
+ static_assert(getFactor<D>() >= getFactor<S>());
for (qsizetype i = 0; i < len; ++i) {
- const int a = getAlpha<S>(src[i]);
+ const int a = getAlpha<S>(src[i]) * (getFactor<D>() / getFactor<S>());
float32x4_t vf = vld1q_f32(&buffer[i].x);
uint16x4_t v = vmovn_u32(vcvtq_u32_f32(vaddq_f32(vmulq_n_f32(vf, float(QColorTrcLut::Resolution)), vdupq_n_f32(0.5f))));
const int ridx = vget_lane_u16(v, 0);
@@ -1219,17 +1221,41 @@ static void storeOpaque(QRgbaFloat32 *dst, const QColorVector *buffer, const qsi
static void loadGray(QColorVector *buffer, const quint8 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
- for (qsizetype i = 0; i < len; ++i) {
- const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
- buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray ||
+ (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] &&
+ d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) {
+ for (qsizetype i = 0; i < len; ++i) {
+ const float y = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ }
+ } else {
+ for (qsizetype i = 0; i < len; ++i) {
+ QColorVector v;
+ v.x = d_ptr->colorSpaceIn->lut[0]->u8ToLinearF32(src[i]);
+ v.y = d_ptr->colorSpaceIn->lut[1]->u8ToLinearF32(src[i]);
+ v.z = d_ptr->colorSpaceIn->lut[2]->u8ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v);
+ }
}
}
static void loadGray(QColorVector *buffer, const quint16 *src, const qsizetype len, const QColorTransformPrivate *d_ptr)
{
- for (qsizetype i = 0; i < len; ++i) {
- const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
- buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ if (d_ptr->colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray ||
+ (d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[1] &&
+ d_ptr->colorSpaceIn->lut[0] == d_ptr->colorSpaceIn->lut[2])) {
+ for (qsizetype i = 0; i < len; ++i) {
+ const float y = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->whitePoint * y;
+ }
+ } else {
+ for (qsizetype i = 0; i < len; ++i) {
+ QColorVector v;
+ v.x = d_ptr->colorSpaceIn->lut[0]->u16ToLinearF32(src[i]);
+ v.y = d_ptr->colorSpaceIn->lut[1]->u16ToLinearF32(src[i]);
+ v.z = d_ptr->colorSpaceIn->lut[2]->u16ToLinearF32(src[i]);
+ buffer[i] = d_ptr->colorSpaceIn->toXyz.map(v);
+ }
}
}
@@ -1258,6 +1284,28 @@ private:
alignas(T) char data[sizeof(T) * Count];
};
+void loadUnpremultipliedLUT(QColorVector *buffer, const uchar *src, const qsizetype len)
+{
+ const float f = 1.0f / 255.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const float p = src[i] * f;
+ buffer[i].x = p;
+ buffer[i].y = p;
+ buffer[i].z = p;
+ }
+}
+
+void loadUnpremultipliedLUT(QColorVector *buffer, const quint16 *src, const qsizetype len)
+{
+ const float f = 1.0f / 65535.f;
+ for (qsizetype i = 0; i < len; ++i) {
+ const float p = src[i] * f;
+ buffer[i].x = p;
+ buffer[i].y = p;
+ buffer[i].z = p;
+ }
+}
+
void loadUnpremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
{
const float f = 1.0f / 255.f;
@@ -1300,6 +1348,16 @@ void loadUnpremultipliedLUT(QColorVector *buffer, const QRgbaFloat32 *src, const
}
}
+void loadPremultipliedLUT(QColorVector *, const uchar *, const qsizetype)
+{
+ Q_UNREACHABLE();
+}
+
+void loadPremultipliedLUT(QColorVector *, const quint16 *, const qsizetype)
+{
+ Q_UNREACHABLE();
+}
+
void loadPremultipliedLUT(QColorVector *buffer, const QRgb *src, const qsizetype len)
{
for (qsizetype i = 0; i < len; ++i) {
@@ -1420,7 +1478,15 @@ static void storeUnpremultipliedLUT(QRgbaFloat32 *dst, const T *src,
}
template<typename T>
-static void storePremultipliedLUT(QRgb *, const T *, const QColorVector *, const qsizetype);
+static void storePremultipliedLUT(QRgb *dst, const T *, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 255.f;
+ const int g = buffer[i].y * 255.f;
+ const int b = buffer[i].z * 255.f;
+ dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
+ }
+}
template<>
void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
@@ -1434,18 +1500,6 @@ void storePremultipliedLUT(QRgb *dst, const QRgb *src, const QColorVector *buffe
}
}
-template<>
-void storePremultipliedLUT(QRgb *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len)
-{
- for (qsizetype i = 0; i < len; ++i) {
- const int r = buffer[i].x * 255.f;
- const int g = buffer[i].y * 255.f;
- const int b = buffer[i].z * 255.f;
- dst[i] = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
- }
-}
-
-
template<typename T>
static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector *buffer, const qsizetype len)
{
@@ -1453,7 +1507,15 @@ static void storePremultipliedLUT(QCmyk32 *dst, const T *src, const QColorVector
}
template<typename T>
-static void storePremultipliedLUT(QRgba64 *, const T *, const QColorVector *, const qsizetype);
+static void storePremultipliedLUT(QRgba64 *dst, const T *, const QColorVector *buffer, const qsizetype len)
+{
+ for (qsizetype i = 0; i < len; ++i) {
+ const int r = buffer[i].x * 65535.f;
+ const int g = buffer[i].y * 65535.f;
+ const int b = buffer[i].z * 65535.f;
+ dst[i] = qRgba64(r, g, b, 65535);
+ }
+}
template<>
void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *buffer, const qsizetype len)
@@ -1468,17 +1530,6 @@ void storePremultipliedLUT(QRgba64 *dst, const QRgb *src, const QColorVector *bu
}
template<>
-void storePremultipliedLUT(QRgba64 *dst, const QCmyk32 *, const QColorVector *buffer, const qsizetype len)
-{
- for (qsizetype i = 0; i < len; ++i) {
- const int r = buffer[i].x * 65535.f;
- const int g = buffer[i].y * 65535.f;
- const int b = buffer[i].z * 65535.f;
- dst[i] = qRgba64(r, g, b, 65535);
- }
-}
-
-template<>
void storePremultipliedLUT(QRgba64 *dst, const QRgba64 *src, const QColorVector *buffer, const qsizetype len)
{
for (qsizetype i = 0; i < len; ++i) {
@@ -1628,11 +1679,43 @@ QColorVector QColorTransformPrivate::mapExtended(QColorVector c) const
return c;
}
+template<typename T>
+constexpr bool IsGrayscale = std::is_same_v<T, uchar> || std::is_same_v<T, quint16>;
+template<typename T>
+constexpr bool IsAlwaysOpaque = std::is_same_v<T, QCmyk32> || IsGrayscale<T>;
+template<typename T>
+constexpr bool CanUseThreeComponent = !std::is_same_v<T, QCmyk32>;
+template<typename T>
+constexpr bool UnclampedValues = std::is_same_v<T, QRgbaFloat16> || std::is_same_v<T, QRgbaFloat32>;
+
+// Possible combos for data and color spaces:
+// DataCM ColorSpaceCM ColorSpacePM Notes
+// Gray Gray ThreeMatrix
+// Gray Rgb ThreeMatrix Invalid colorMatrix
+// Rgb Rgb ThreeMatrix
+// Rgb Rgb ElementProc
+// Gray Rgb ElementProc Only possible for input data
+// Cmyk Cmyk ElementProc
+//
+// Gray data can be uchar, quint16, and is always Opaque
+// Rgb data can be QRgb, QRgba64, or QRgbaFloat32, and is Unpremultiplied, Premultiplied, or Opaque
+// Cmyk data can be Cmyk32, and is always Opaque
+//
+// colorMatrix as setup for Gray on Gray or Rgb on Rgb, but not Gray data on Rgb colorspace.
+
template<typename S>
void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
- // Avoid compiling this part for S=QCmyk32:
- if constexpr (!std::is_same_v<S, QCmyk32>) {
+ if constexpr (IsGrayscale<S>) {
+ if (colorSpaceIn->isThreeComponentMatrix()) {
+ loadGray(buffer, src, len, this);
+ if (!colorSpaceOut->isThreeComponentMatrix() || colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray) {
+ if (!colorSpaceIn->chad.isNull())
+ applyMatrix<DoClamp>(buffer, len, colorSpaceIn->chad);
+ }
+ return;
+ }
+ } else if constexpr (CanUseThreeComponent<S>) {
if (colorSpaceIn->isThreeComponentMatrix()) {
if (flags & InputPremultiplied)
loadPremultiplied(buffer, src, len, this);
@@ -1640,7 +1723,7 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer,
loadUnpremultiplied(buffer, src, len, this);
if (!colorSpaceOut->isThreeComponentMatrix())
- applyMatrix<DoClamp>(buffer, len, colorMatrix); // colorMatrix should have the first half only.
+ applyMatrix<DoClamp>(buffer, len, colorMatrix);
return;
}
}
@@ -1651,9 +1734,6 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer,
else
loadUnpremultipliedLUT(buffer, src, len);
- if constexpr (std::is_same_v<S, QRgbaFloat16> || std::is_same_v<S, QRgbaFloat32>)
- clampIfNeeded<DoClamp>(buffer, len);
-
// Do element based conversion
for (auto &&element : colorSpaceIn->mAB)
std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
@@ -1662,13 +1742,44 @@ void QColorTransformPrivate::applyConvertIn(const S *src, QColorVector *buffer,
template<typename D, typename S>
void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const
{
- constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
- // Avoid compiling this part for D=QCmyk32:
- if constexpr (!std::is_same_v<D, QCmyk32>) {
+ constexpr ApplyMatrixForm doClamp = UnclampedValues<D> ? DoNotClamp : DoClamp;
+ if constexpr (IsGrayscale<D>) {
+ Q_UNUSED(src); // dealing with buggy warnings in gcc 9
+ Q_UNUSED(flags);
+ // Calculate the matrix for grayscale conversion
+ QColorMatrix grayMatrix;
+ if (colorSpaceIn == colorSpaceOut ||
+ (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray &&
+ colorSpaceOut->colorModel == QColorSpace::ColorModel::Gray)) {
+ // colorMatrix already has the right form
+ grayMatrix = colorMatrix;
+ } else {
+ if constexpr (IsGrayscale<S>) {
+ if (colorSpaceIn->colorModel == QColorSpace::ColorModel::Gray)
+ grayMatrix = colorSpaceIn->chad;
+ else
+ grayMatrix = QColorMatrix::identity(); // Otherwise already handled in applyConvertIn
+ } else {
+ if (colorSpaceIn->isThreeComponentMatrix())
+ grayMatrix = colorSpaceIn->toXyz;
+ else
+ grayMatrix = QColorMatrix::identity();
+ }
+ if (!colorSpaceOut->chad.isNull())
+ grayMatrix = colorSpaceOut->chad.inverted() * grayMatrix;
+ }
+
+ applyMatrix<doClamp>(buffer, len, grayMatrix);
+ storeOpaque(dst, buffer, len, this);
+ return;
+ } else if constexpr (CanUseThreeComponent<D>) {
if (colorSpaceOut->isThreeComponentMatrix()) {
- applyMatrix<doClamp>(buffer, len, colorMatrix); // colorMatrix should have the latter half only.
+ if (IsGrayscale<S> && colorSpaceIn->colorModel != QColorSpace::ColorModel::Gray)
+ applyMatrix<doClamp>(buffer, len, colorSpaceOut->toXyz.inverted()); // colorMatrix wasnt prepared for gray input
+ else
+ applyMatrix<doClamp>(buffer, len, colorMatrix);
- if constexpr (std::is_same_v<S, QCmyk32>) {
+ if constexpr (IsAlwaysOpaque<S>) {
storeOpaque(dst, buffer, len, this);
} else {
if (flags & InputOpaque)
@@ -1681,91 +1792,37 @@ void QColorTransformPrivate::applyConvertOut(D *dst, const S *src, QColorVector
return;
}
}
- Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix());
+ if constexpr (!IsGrayscale<D>) {
+ Q_ASSERT(!colorSpaceOut->isThreeComponentMatrix());
- // Do element based conversion
- for (auto &&element : colorSpaceOut->mBA)
- std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
-
- clampIfNeeded<doClamp>(buffer, len);
-
- if (flags & OutputPremultiplied)
- storePremultipliedLUT(dst, src, buffer, len);
- else
- storeUnpremultipliedLUT(dst, src, buffer, len);
-}
-
-template<typename D, typename S>
-void QColorTransformPrivate::applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const
-{
- Q_ASSERT(!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix());
-
- if (!colorMatrix.isValid())
- return;
-
- if (colorSpaceIn->isThreeComponentMatrix())
- updateLutsIn();
- if (colorSpaceOut->isThreeComponentMatrix())
- updateLutsOut();
-
- QUninitialized<QColorVector, WorkBlockSize> buffer;
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
-
- applyConvertIn(src + i, buffer, len, flags);
-
- // Match Profile Connection Spaces (PCS):
- if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].xyzToLab();
- } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].labToXyz();
- }
+ // Do element based conversion
+ for (auto &&element : colorSpaceOut->mBA)
+ std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
- applyConvertOut(dst + i, src + i, buffer, len, flags);
+ clampIfNeeded<doClamp>(buffer, len);
- i += len;
+ if (flags & OutputPremultiplied)
+ storePremultipliedLUT(dst, src, buffer, len);
+ else
+ storeUnpremultipliedLUT(dst, src, buffer, len);
+ } else {
+ Q_UNREACHABLE();
}
}
-template<typename D, typename S>
-void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const
+/*!
+ \internal
+ Adapt Profile Connection Spaces.
+*/
+void QColorTransformPrivate::pcsAdapt(QColorVector *buffer, qsizetype count) const
{
- Q_ASSERT(colorSpaceIn->isThreeComponentMatrix() && colorSpaceOut->isThreeComponentMatrix());
-
- if (!colorMatrix.isValid())
- return;
-
- updateLutsIn();
- updateLutsOut();
-
- bool doApplyMatrix = !colorMatrix.isIdentity();
- constexpr ApplyMatrixForm doClamp = (std::is_same_v<D, QRgbaFloat16> || std::is_same_v<D, QRgbaFloat32>) ? DoNotClamp : DoClamp;
-
- QUninitialized<QColorVector, WorkBlockSize> buffer;
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- if (flags & InputPremultiplied)
- loadPremultiplied(buffer, src + i, len, this);
- else
- loadUnpremultiplied(buffer, src + i, len, this);
-
- if (doApplyMatrix)
- applyMatrix<doClamp>(buffer, len, colorMatrix);
- else
- clampIfNeeded<doClamp>(buffer, len);
-
- if (flags & InputOpaque)
- storeOpaque(dst + i, buffer, len, this);
- else if (flags & OutputPremultiplied)
- storePremultiplied(dst + i, src + i, buffer, len, this);
- else
- storeUnpremultiplied(dst + i, src + i, buffer, len, this);
-
- i += len;
+ // Match Profile Connection Spaces (PCS):
+ if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].xyzToLab();
+ } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
+ for (qsizetype j = 0; j < count; ++j)
+ buffer[j] = buffer[j].labToXyz();
}
}
@@ -1781,137 +1838,23 @@ void QColorTransformPrivate::applyThreeComponentMatrix(D *dst, const S *src, qsi
template<typename D, typename S>
void QColorTransformPrivate::apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const
{
- if constexpr (!std::is_same_v<D, QCmyk32> && !std::is_same_v<S, QCmyk32>) {
- if (isThreeComponentMatrix())
- return applyThreeComponentMatrix<D, S>(dst, src, count, flags);
- }
- applyElementListTransform<D, S>(dst, src, count, flags);
-}
-
-/*!
- \internal
- Is to be called on a color-transform to XYZ, returns only luminance values.
-
- */
-template<typename D, typename S>
-void QColorTransformPrivate::applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const
-{
- Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
- updateLutsOut();
- if (!colorSpaceIn->isThreeComponentMatrix()) {
- QUninitialized<QColorVector, WorkBlockSize> buffer;
-
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
-
- applyConvertIn(src, buffer, len, flags);
-
- // Match Profile Connection Spaces (PCS):
- if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].xyzToLab();
- } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].labToXyz();
- }
-
- applyMatrix<DoClamp>(buffer, len, colorMatrix);
- storeOpaque(dst + i, buffer, len, this);
-
- i += len;
- }
- return;
- }
- if constexpr (!std::is_same_v<S, QCmyk32>) {
- if (!colorMatrix.isValid())
- return;
-
+ if (colorSpaceIn->isThreeComponentMatrix())
updateLutsIn();
-
- QUninitialized<QColorVector, WorkBlockSize> buffer;
-
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- if (flags & InputPremultiplied)
- loadPremultiplied(buffer, src + i, len, this);
- else
- loadUnpremultiplied(buffer, src + i, len, this);
-
- applyMatrix<DoClamp>(buffer, len, colorMatrix);
-
- storeOpaque(dst + i, buffer, len, this);
-
- i += len;
- }
- } else {
- Q_UNREACHABLE();
- }
-}
-
-/*!
- \internal
-*/
-template<typename D, typename S>
-void QColorTransformPrivate::applyGray(D *dst, const S *src, qsizetype count, TransformFlags) const
-{
- Q_ASSERT(colorSpaceIn->isThreeComponentMatrix());
- updateLutsIn();
- if constexpr (std::is_same_v<D, QRgb> || std::is_same_v<D, QRgba64> || std::is_same_v<D, QRgbaFloat32> || std::is_same_v<D, QCmyk32>) {
- if (!colorSpaceOut->isThreeComponentMatrix()) {
- QUninitialized<QColorVector, WorkBlockSize> buffer;
-
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- loadGray(buffer, src + i, len, this);
-
- applyMatrix<DoClamp>(buffer, len, colorMatrix);
-
- // Match Profile Connection Spaces (PCS):
- if (colorSpaceOut->isPcsLab && !colorSpaceIn->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].xyzToLab();
- } else if (colorSpaceIn->isPcsLab && !colorSpaceOut->isPcsLab) {
- for (qsizetype j = 0; j < len; ++j)
- buffer[j] = buffer[j].labToXyz();
- }
-
- // Do element based conversion
- for (auto &&element : colorSpaceOut->mBA)
- std::visit([&buffer, len](auto &&elm) { visitElement(elm, buffer, len); }, element);
-
- clampIfNeeded<DoClamp>(buffer, len);
-
- storeUnpremultipliedLUT(dst, src, buffer, len); // input is always opaque
-
- i += len;
- }
- return;
- }
- }
- Q_ASSERT(colorSpaceOut->isThreeComponentMatrix());
- if constexpr (!std::is_same_v<D, QCmyk32>) {
- if (!colorMatrix.isValid())
- return;
-
+ if (colorSpaceOut->isThreeComponentMatrix())
updateLutsOut();
- QUninitialized<QColorVector, WorkBlockSize> buffer;
+ QUninitialized<QColorVector, WorkBlockSize> buffer;
+ qsizetype i = 0;
+ while (i < count) {
+ const qsizetype len = qMin(count - i, WorkBlockSize);
- qsizetype i = 0;
- while (i < count) {
- const qsizetype len = qMin(count - i, WorkBlockSize);
- loadGray(buffer, src + i, len, this);
+ applyConvertIn(src + i, buffer, len, flags);
- applyMatrix<DoClamp>(buffer, len, colorMatrix);
+ pcsAdapt(buffer, len);
- storeOpaque(dst + i, buffer, len, this);
- i += len;
- }
- } else {
- Q_UNREACHABLE();
+ applyConvertOut(dst + i, src + i, buffer, len, flags);
+
+ i += len;
}
}
@@ -1943,25 +1886,24 @@ void QColorTransformPrivate::prepare()
updateLutsOut();
}
-// Only allow versions increasing precision
-template void QColorTransformPrivate::applyReturnGray<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyReturnGray<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyReturnGray<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyReturnGray<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<QCmyk32, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
-template void QColorTransformPrivate::applyGray<QRgba64, quint16>(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
-
+// Only some versions increasing precision 14/36 combos
+template void QColorTransformPrivate::apply<quint8, quint8>(quint8 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint8, QRgb>(quint8 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint8, QCmyk32>(quint8 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, quint8>(quint16 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, quint16>(quint16 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, QCmyk32>(quint16 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<quint16, QRgba64>(quint16 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgb, quint8>(QRgb *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgb, QRgb>(QRgb *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgb, QCmyk32>(QRgb *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, quint8>(QCmyk32 *dst, const quint8 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QCmyk32, quint16>(QCmyk32 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QCmyk32, QRgb>(QCmyk32 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QCmyk32, QCmyk32>(QCmyk32 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QCmyk32, QRgba64>(QCmyk32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QCmyk32, QRgbaFloat32>(QCmyk32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const;
+template void QColorTransformPrivate::apply<QRgba64, quint16>(QRgba64 *dst, const quint16 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgba64, QRgb>(QRgba64 *dst, const QRgb *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgba64, QCmyk32>(QRgba64 *dst, const QCmyk32 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgba64, QRgba64>(QRgba64 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
@@ -1970,15 +1912,6 @@ template void QColorTransformPrivate::apply<QRgbaFloat32, QCmyk32>(QRgbaFloat32
template void QColorTransformPrivate::apply<QRgbaFloat32, QRgba64>(QRgbaFloat32 *dst, const QRgba64 *src, qsizetype count, TransformFlags flags) const;
template void QColorTransformPrivate::apply<QRgbaFloat32, QRgbaFloat32>(QRgbaFloat32 *dst, const QRgbaFloat32 *src, qsizetype count, TransformFlags flags) const;
-bool QColorTransformPrivate::isThreeComponentMatrix() const
-{
- if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix())
- return false;
- if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix())
- return false;
- return true;
-}
-
/*!
\internal
*/
@@ -1991,7 +1924,7 @@ bool QColorTransformPrivate::isIdentity() const
if (colorSpaceIn && colorSpaceOut) {
if (colorSpaceIn->equals(colorSpaceOut.constData()))
return true;
- if (!isThreeComponentMatrix())
+ if (!colorSpaceIn->isThreeComponentMatrix() || !colorSpaceOut->isThreeComponentMatrix())
return false;
if (colorSpaceIn->transferFunction != colorSpaceOut->transferFunction)
return false;
@@ -2001,7 +1934,9 @@ bool QColorTransformPrivate::isIdentity() const
&& colorSpaceIn->trc[2] == colorSpaceOut->trc[2];
}
} else {
- if (!isThreeComponentMatrix())
+ if (colorSpaceIn && !colorSpaceIn->isThreeComponentMatrix())
+ return false;
+ if (colorSpaceOut && !colorSpaceOut->isThreeComponentMatrix())
return false;
if (colorSpaceIn && colorSpaceIn->transferFunction != QColorSpace::TransferFunction::Linear)
return false;
diff --git a/src/gui/painting/qcolortransform_p.h b/src/gui/painting/qcolortransform_p.h
index 59ea6a2405..3f11603420 100644
--- a/src/gui/painting/qcolortransform_p.h
+++ b/src/gui/painting/qcolortransform_p.h
@@ -27,7 +27,7 @@ class QCmyk32;
class QColorTransformPrivate : public QSharedData
{
public:
- QColorMatrix colorMatrix;
+ QColorMatrix colorMatrix; // Combined colorSpaceIn->toXyz and colorSpaceOut->toXyz.inverted()
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceIn;
QExplicitlySharedDataPointer<const QColorSpacePrivate> colorSpaceOut;
@@ -37,7 +37,6 @@ public:
void updateLutsIn() const;
void updateLutsOut() const;
bool isIdentity() const;
- bool isThreeComponentMatrix() const;
Q_GUI_EXPORT void prepare();
enum TransformFlag {
@@ -54,20 +53,13 @@ public:
template<typename D, typename S>
void apply(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
- template<typename D, typename S>
- void applyGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
- template<typename D, typename S>
- void applyReturnGray(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
private:
+ void pcsAdapt(QColorVector *buffer, qsizetype len) const;
template<typename S>
void applyConvertIn(const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
template<typename D, typename S>
void applyConvertOut(D *dst, const S *src, QColorVector *buffer, qsizetype len, TransformFlags flags) const;
- template<typename D, typename S>
- void applyElementListTransform(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
- template<typename D, typename S>
- void applyThreeComponentMatrix(D *dst, const S *src, qsizetype count, TransformFlags flags) const;
};
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrc_p.h b/src/gui/painting/qcolortrc_p.h
index d1ad1987df..dbca91c7f6 100644
--- a/src/gui/painting/qcolortrc_p.h
+++ b/src/gui/painting/qcolortrc_p.h
@@ -17,6 +17,7 @@
#include <QtGui/private/qtguiglobal_p.h>
#include "qcolortransferfunction_p.h"
+#include "qcolortransfergeneric_p.h"
#include "qcolortransfertable_p.h"
QT_BEGIN_NAMESPACE
@@ -26,20 +27,23 @@ class Q_GUI_EXPORT QColorTrc
{
public:
QColorTrc() noexcept : m_type(Type::Uninitialized) { }
- QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun) { }
+ QColorTrc(const QColorTransferFunction &fun) : m_type(Type::ParameterizedFunction), m_fun(fun) { }
QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table) { }
- QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::Function), m_fun(std::move(fun)) { }
+ QColorTrc(const QColorTransferGenericFunction &hdr) : m_type(Type::GenericFunction), m_hdr(hdr) { }
+ QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::ParameterizedFunction), m_fun(std::move(fun)) { }
QColorTrc(QColorTransferTable &&table) noexcept : m_type(Type::Table), m_table(std::move(table)) { }
+ QColorTrc(QColorTransferGenericFunction &&hdr) noexcept : m_type(Type::GenericFunction), m_hdr(std::move(hdr)) { }
enum class Type {
Uninitialized,
- Function,
- Table
+ ParameterizedFunction,
+ GenericFunction,
+ Table,
};
bool isIdentity() const
{
- return (m_type == Type::Function && m_fun.isIdentity())
+ return (m_type == Type::ParameterizedFunction && m_fun.isIdentity())
|| (m_type == Type::Table && m_table.isIdentity());
}
bool isValid() const
@@ -48,62 +52,87 @@ public:
}
float apply(float x) const
{
- if (m_type == Type::Table)
- return m_table.apply(x);
- if (m_type == Type::Function)
- return m_fun.apply(x);
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return fun().apply(x);
+ case Type::GenericFunction:
+ return hdr().apply(x);
+ case Type::Table:
+ return table().apply(x);
+ default:
+ break;
+ }
return x;
}
float applyExtended(float x) const
{
- if (x >= 0.0f && x <= 1.0f)
- return apply(x);
- if (m_type == Type::Function)
- return std::copysign(m_fun.apply(std::abs(x)), x);
- if (m_type == Type::Table)
- return x < 0.0f ? 0.0f : 1.0f;
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return std::copysign(fun().apply(std::abs(x)), x);
+ case Type::GenericFunction:
+ return hdr().apply(x);
+ case Type::Table:
+ return table().apply(x);
+ default:
+ break;
+ }
return x;
}
float applyInverse(float x) const
{
- if (m_type == Type::Table)
- return m_table.applyInverse(x);
- if (m_type == Type::Function)
- return m_fun.inverted().apply(x);
+ switch (m_type) {
+ case Type::ParameterizedFunction:
+ return fun().inverted().apply(x);
+ case Type::GenericFunction:
+ return hdr().applyInverse(x);
+ case Type::Table:
+ return table().applyInverse(x);
+ default:
+ break;
+ }
return x;
}
float applyInverseExtended(float x) const
{
- if (x >= 0.0f && x <= 1.0f)
- return applyInverse(x);
- if (m_type == Type::Function)
+ switch (m_type) {
+ case Type::ParameterizedFunction:
return std::copysign(applyInverse(std::abs(x)), x);
- if (m_type == Type::Table)
- return x < 0.0f ? 0.0f : 1.0f;
+ case Type::GenericFunction:
+ return hdr().applyInverse(x);
+ case Type::Table:
+ return table().applyInverse(x);
+ default:
+ break;
+ }
return x;
}
- friend inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2);
- friend inline bool operator==(const QColorTrc &o1, const QColorTrc &o2);
+ const QColorTransferTable &table() const { return m_table; }
+ const QColorTransferFunction &fun() const{ return m_fun; }
+ const QColorTransferGenericFunction &hdr() const { return m_hdr; }
+ Type type() const noexcept { return m_type; }
Type m_type;
+
+ friend inline bool comparesEqual(const QColorTrc &lhs, const QColorTrc &rhs);
+ Q_DECLARE_EQUALITY_COMPARABLE(QColorTrc);
+
QColorTransferFunction m_fun;
QColorTransferTable m_table;
+ QColorTransferGenericFunction m_hdr;
};
-inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2)
+inline bool comparesEqual(const QColorTrc &o1, const QColorTrc &o2)
{
if (o1.m_type != o2.m_type)
- return true;
- if (o1.m_type == QColorTrc::Type::Function)
- return o1.m_fun != o2.m_fun;
+ return false;
+ if (o1.m_type == QColorTrc::Type::ParameterizedFunction)
+ return o1.m_fun == o2.m_fun;
if (o1.m_type == QColorTrc::Type::Table)
- return o1.m_table != o2.m_table;
- return false;
-}
-inline bool operator==(const QColorTrc &o1, const QColorTrc &o2)
-{
- return !(o1 != o2);
+ return o1.m_table == o2.m_table;
+ if (o1.m_type == QColorTrc::Type::GenericFunction)
+ return o1.m_hdr == o2.m_hdr;
+ return true;
}
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrclut.cpp b/src/gui/painting/qcolortrclut.cpp
index 8a7673bc00..1357aa41a6 100644
--- a/src/gui/painting/qcolortrclut.cpp
+++ b/src/gui/painting/qcolortrclut.cpp
@@ -3,7 +3,9 @@
#include "qcolortrclut_p.h"
#include "qcolortransferfunction_p.h"
+#include "qcolortransfergeneric_p.h"
#include "qcolortransfertable_p.h"
+#include "qcolortrc_p.h"
#include <qmath.h>
QT_BEGIN_NAMESPACE
@@ -13,69 +15,86 @@ std::shared_ptr<QColorTrcLut> QColorTrcLut::create()
return std::make_shared<Access>();
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(qreal gamma, Direction dir)
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromGamma(float gamma, Direction dir)
{
auto cp = create();
cp->setFromGamma(gamma, dir);
return cp;
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun, Direction dir)
+std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTrc(const QColorTrc &trc, Direction dir)
{
+ if (!trc.isValid())
+ return nullptr;
auto cp = create();
- cp->setFromTransferFunction(fun, dir);
+ cp->setFromTrc(trc, dir);
return cp;
}
-std::shared_ptr<QColorTrcLut> QColorTrcLut::fromTransferTable(const QColorTransferTable &table, Direction dir)
+void QColorTrcLut::setFromGamma(float gamma, Direction dir)
{
- auto cp = create();
- cp->setFromTransferTable(table, dir);
- return cp;
+ constexpr float iRes = 1.f / float(Resolution);
+ if (dir & ToLinear) {
+ if (!m_toLinear)
+ m_toLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_toLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, gamma), 1.f) * (255 * 256)));
+ }
+
+ if (dir & FromLinear) {
+ const float iGamma = 1.f / gamma;
+ if (!m_fromLinear)
+ m_fromLinear.reset(new ushort[Resolution + 1]);
+ for (int i = 0; i <= Resolution; ++i)
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, iGamma), 1.f) * (255 * 256)));
+ }
}
-void QColorTrcLut::setFromGamma(qreal gamma, Direction dir)
+void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Direction dir)
{
+ constexpr float iRes = 1.f / float(Resolution);
if (dir & ToLinear) {
if (!m_toLinear)
m_toLinear.reset(new ushort[Resolution + 1]);
for (int i = 0; i <= Resolution; ++i)
- m_toLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), gamma) * (255 * 256)));
+ m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256)));
}
if (dir & FromLinear) {
if (!m_fromLinear)
m_fromLinear.reset(new ushort[Resolution + 1]);
+ QColorTransferFunction inv = fun.inverted();
for (int i = 0; i <= Resolution; ++i)
- m_fromLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), qreal(1) / gamma) * (255 * 256)));
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, inv.apply(i * iRes), 1.f) * (255 * 256)));
}
}
-void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Direction dir)
+void QColorTrcLut::setFromTransferGenericFunction(const QColorTransferGenericFunction &fun, Direction dir)
{
+ constexpr float iRes = 1.f / float(Resolution);
if (dir & ToLinear) {
if (!m_toLinear)
m_toLinear.reset(new ushort[Resolution + 1]);
for (int i = 0; i <= Resolution; ++i)
- m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(Resolution)) * (255 * 256)));
+ m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256)));
}
if (dir & FromLinear) {
if (!m_fromLinear)
m_fromLinear.reset(new ushort[Resolution + 1]);
- QColorTransferFunction inv = fun.inverted();
for (int i = 0; i <= Resolution; ++i)
- m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(Resolution)) * (255 * 256)));
+ m_fromLinear[i] = ushort(qRound(qBound(0.f, fun.applyInverse(i * iRes), 1.f) * (255 * 256)));
}
}
void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direction dir)
{
+ constexpr float iRes = 1.f / float(Resolution);
if (dir & ToLinear) {
if (!m_toLinear)
m_toLinear.reset(new ushort[Resolution + 1]);
for (int i = 0; i <= Resolution; ++i)
- m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(Resolution)) * (255 * 256)), 65280));
+ m_toLinear[i] = ushort(qRound(table.apply(i * iRes) * (255 * 256)));
}
if (dir & FromLinear) {
@@ -83,10 +102,24 @@ void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direct
m_fromLinear.reset(new ushort[Resolution + 1]);
float minInverse = 0.0f;
for (int i = 0; i <= Resolution; ++i) {
- minInverse = table.applyInverse(i / qreal(Resolution), minInverse);
- m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280));
+ minInverse = table.applyInverse(i * iRes, minInverse);
+ m_fromLinear[i] = ushort(qRound(minInverse * (255 * 256)));
}
}
}
+void QColorTrcLut::setFromTrc(const QColorTrc &trc, Direction dir)
+{
+ switch (trc.m_type) {
+ case QColorTrc::Type::ParameterizedFunction:
+ return setFromTransferFunction(trc.fun(), dir);
+ case QColorTrc::Type::Table:
+ return setFromTransferTable(trc.table(), dir);
+ case QColorTrc::Type::GenericFunction:
+ return setFromTransferGenericFunction(trc.hdr(), dir);
+ case QColorTrc::Type::Uninitialized:
+ break;
+ }
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/painting/qcolortrclut_p.h b/src/gui/painting/qcolortrclut_p.h
index 3ebab42809..15f7348836 100644
--- a/src/gui/painting/qcolortrclut_p.h
+++ b/src/gui/painting/qcolortrclut_p.h
@@ -30,8 +30,10 @@
QT_BEGIN_NAMESPACE
+class QColorTransferGenericFunction;
class QColorTransferFunction;
class QColorTransferTable;
+class QColorTrc;
class Q_GUI_EXPORT QColorTrcLut
{
@@ -46,12 +48,13 @@ public:
BiLinear = ToLinear | FromLinear
};
- static std::shared_ptr<QColorTrcLut> fromGamma(qreal gamma, Direction dir = BiLinear);
- static std::shared_ptr<QColorTrcLut> fromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear);
- static std::shared_ptr<QColorTrcLut> fromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear);
- void setFromGamma(qreal gamma, Direction dir = BiLinear);
+ static std::shared_ptr<QColorTrcLut> fromGamma(float gamma, Direction dir = BiLinear);
+ static std::shared_ptr<QColorTrcLut> fromTrc(const QColorTrc &trc, Direction dir = BiLinear);
+ void setFromGamma(float gamma, Direction dir = BiLinear);
void setFromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear);
void setFromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear);
+ void setFromTransferGenericFunction(const QColorTransferGenericFunction &transfn, Direction dir);
+ void setFromTrc(const QColorTrc &trc, Direction dir);
// The following methods all convert opaque or unpremultiplied colors:
diff --git a/src/gui/painting/qdatabuffer_p.h b/src/gui/painting/qdatabuffer_p.h
index 8f467afe4e..aa8335542d 100644
--- a/src/gui/painting/qdatabuffer_p.h
+++ b/src/gui/painting/qdatabuffer_p.h
@@ -16,7 +16,9 @@
//
#include <QtGui/private/qtguiglobal_p.h>
+
#include "QtCore/qbytearray.h"
+#include "QtCore/qtypeinfo.h"
#include <stdlib.h>
@@ -43,6 +45,7 @@ public:
~QDataBuffer()
{
+ static_assert(!QTypeInfo<Type>::isComplex);
if (buffer)
free(buffer);
}
diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp
index b7a943be38..aaf57e19d1 100644
--- a/src/gui/painting/qdrawhelper.cpp
+++ b/src/gui/painting/qdrawhelper.cpp
@@ -644,7 +644,7 @@ static void QT_FASTCALL destStoreGray8(QRasterBuffer *rasterBuffer, int x, int y
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
@@ -668,7 +668,7 @@ static void QT_FASTCALL destStoreGray16(QRasterBuffer *rasterBuffer, int x, int
QRgba64 tmp_line[BufferSize];
for (int k = 0; k < length; ++k)
tmp_line[k] = QRgba64::fromArgb32(buffer[k]);
- tfd->applyReturnGray(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, tmp_line, length, QColorTransformPrivate::InputPremultiplied);
}
}
@@ -749,7 +749,7 @@ static void QT_FASTCALL destStore64Gray8(QRasterBuffer *rasterBuffer, int x, int
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
quint16 gray_line[BufferSize];
- tfd->applyReturnGray(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(gray_line, buffer, length, QColorTransformPrivate::InputPremultiplied);
for (int k = 0; k < length; ++k)
data[k] = qt_div_257(gray_line[k]);
}
@@ -771,7 +771,7 @@ static void QT_FASTCALL destStore64Gray16(QRasterBuffer *rasterBuffer, int x, in
QColorSpace fromCS = rasterBuffer->colorSpace.isValid() ? rasterBuffer->colorSpace : QColorSpace::SRgb;
QColorTransform tf = QColorSpacePrivate::get(fromCS)->transformationToXYZ();
QColorTransformPrivate *tfd = QColorTransformPrivate::get(tf);
- tfd->applyReturnGray(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
+ tfd->apply(data, buffer, length, QColorTransformPrivate::InputPremultiplied);
}
}
diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp
index c01fa433ea..70bdf28b21 100644
--- a/src/gui/painting/qicc.cpp
+++ b/src/gui/painting/qicc.cpp
@@ -116,6 +116,7 @@ enum class Tag : quint32 {
mAB_ = IccTag('m', 'A', 'B', ' '),
mBA_ = IccTag('m', 'B', 'A', ' '),
chad = IccTag('c', 'h', 'a', 'd'),
+ cicp = IccTag('c', 'i', 'c', 'p'),
gamt = IccTag('g', 'a', 'm', 't'),
sf32 = IccTag('s', 'f', '3', '2'),
@@ -247,6 +248,13 @@ struct Sf32TagData : GenericTagData {
quint32_be value[9];
};
+struct CicpTagData : GenericTagData {
+ quint8 colorPrimaries;
+ quint8 transferCharacteristics;
+ quint8 matrixCoefficients;
+ quint8 videoFullRangeFlag;
+};
+
struct MatrixElement {
qint32_be e0;
qint32_be e1;
@@ -264,7 +272,11 @@ struct MatrixElement {
static int toFixedS1516(float x)
{
- return int(x * 65536.0f + 0.5f);
+ if (x < float(SHRT_MIN))
+ return INT_MIN;
+ if (x > float(SHRT_MAX))
+ return INT_MAX;
+ return qRound(x * 65536.0f);
}
static float fromFixedS1516(int x)
@@ -327,7 +339,7 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
return 12;
}
- if (trc.m_type == QColorTrc::Type::Function) {
+ if (trc.m_type == QColorTrc::Type::ParameterizedFunction) {
const QColorTransferFunction &fun = trc.m_fun;
stream << uint(Tag::para) << uint(0);
if (fun.isGamma()) {
@@ -348,6 +360,14 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
stream << toFixedS1516(fun.m_f);
return 12 + 7 * 4;
}
+ if (trc.m_type != QColorTrc::Type::Table) {
+ stream << uint(Tag::curv) << uint(0);
+ stream << uint(16);
+ for (uint i = 0; i < 16; ++i) {
+ stream << ushort(qBound(0, qRound(trc.apply(i / 15.f) * 65535.f), 65535));
+ }
+ return 12 + 16 * 2;
+ }
Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
stream << uint(Tag::curv) << uint(0);
@@ -368,31 +388,442 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
return 12 + 2 * trc.m_table.m_tableSize;
}
+// very simple version for small values (<=4) of exp.
+static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
+{
+ return (exp <= 1) ? x : x * intPow(x, exp - 1);
+}
+
+struct ElementCombo
+{
+ const QColorMatrix *inMatrix = nullptr;
+ const QColorSpacePrivate::TransferElement *inTable = nullptr;
+ const QColorCLUT *clut = nullptr;
+ const QColorSpacePrivate::TransferElement *midTable = nullptr;
+ const QColorMatrix *midMatrix = nullptr;
+ const QColorVector *midOffset = nullptr;
+ const QColorSpacePrivate::TransferElement *outTable = nullptr;
+};
+
+static void visitElement(ElementCombo &combo, const QColorSpacePrivate::TransferElement &element, int number,
+ const QList<QColorSpacePrivate::Element> &list)
+{
+ if (number == 0)
+ combo.inTable = &element;
+ else if (number == list.size() - 1)
+ combo.outTable = &element;
+ else if (number == 1 && combo.inMatrix)
+ combo.inTable = &element;
+ else
+ combo.midTable = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorMatrix &element, int number,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ if (number == 0)
+ combo.inMatrix = &element;
+ else
+ combo.midMatrix = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorVector &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.midOffset = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorCLUT &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.clut = &element;
+}
+
+static bool isTableTrc(const QColorSpacePrivate::TransferElement *transfer)
+{
+ int i = 0;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].m_type != QColorTrc::Type::Table)
+ return false;
+ i++;
+ }
+ return i > 0;
+}
+
+static bool isTableTrcSingleSize(const QColorSpacePrivate::TransferElement *transfer)
+{
+ Q_ASSERT(transfer->trc[0].m_type == QColorTrc::Type::Table);
+ int i = 1;
+ const uint32_t size = transfer->trc[0].table().m_tableSize;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].table().m_tableSize != size)
+ return false;
+ i++;
+ }
+ return true;
+}
+
+static int writeMab(QDataStream &stream, const QList<QColorSpacePrivate::Element> &abList, bool isAb, bool pcsLab, bool isCmyk)
+{
+ int number = 0;
+ ElementCombo combo;
+ for (auto &&element : abList)
+ std::visit([&](auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
+
+ Q_ASSERT(!(combo.inMatrix && combo.midMatrix));
+
+ // qWarning() << Q_FUNC_INFO << bool(combo.inMatrix) << bool(combo.inTable) << bool(combo.clut) << bool(combo.midTable) << bool(combo.midMatrix) << bool(combo.midOffset) << bool(combo.outTable);
+ bool lut16 = true;
+ if (combo.midMatrix || combo.midTable || combo.midOffset)
+ lut16 = false;
+ if (combo.clut && (combo.clut->gridPointsX != combo.clut->gridPointsY ||
+ combo.clut->gridPointsX != combo.clut->gridPointsZ ||
+ (combo.clut->gridPointsW > 1 && combo.clut->gridPointsX != combo.clut->gridPointsW)))
+ lut16 = false;
+ if (lut16 && combo.inTable)
+ lut16 = isTableTrc(combo.inTable) && isTableTrcSingleSize(combo.inTable);
+ if (lut16 && combo.outTable)
+ lut16 = isTableTrc(combo.outTable) && isTableTrcSingleSize(combo.outTable);
+
+ if (!lut16) {
+ if (combo.inMatrix)
+ qSwap(combo.inMatrix, combo.midMatrix);
+ if (isAb)
+ stream << uint(Tag::mAB_) << uint(0);
+ else
+ stream << uint(Tag::mBA_) << uint(0);
+ } else {
+ stream << uint(Tag::mft2) << uint(0);
+ }
+
+ const int inChannels = (isCmyk && isAb) ? 4 : 3;
+ const int outChannels = (isCmyk && !isAb) ? 4 : 3;
+ stream << uchar(inChannels) << uchar(outChannels);
+ qsizetype gridPointsLut16 = 0;
+ if (lut16 && combo.clut)
+ gridPointsLut16 = combo.clut->gridPointsX;
+ if (lut16)
+ stream << uchar(gridPointsLut16) << uchar(0);
+ else
+ stream << quint16(0);
+ if (lut16) {
+ if (combo.inMatrix) {
+ stream << toFixedS1516(combo.inMatrix->r.x);
+ stream << toFixedS1516(combo.inMatrix->g.x);
+ stream << toFixedS1516(combo.inMatrix->b.x);
+ stream << toFixedS1516(combo.inMatrix->r.y);
+ stream << toFixedS1516(combo.inMatrix->g.y);
+ stream << toFixedS1516(combo.inMatrix->b.y);
+ stream << toFixedS1516(combo.inMatrix->r.z);
+ stream << toFixedS1516(combo.inMatrix->g.z);
+ stream << toFixedS1516(combo.inMatrix->b.z);
+ } else {
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ }
+ int inputEntries = 0, outputEntries = 0;
+ if (combo.inTable)
+ inputEntries = combo.inTable->trc[0].table().m_tableSize;
+ else
+ inputEntries = 2;
+ if (combo.outTable)
+ outputEntries = combo.outTable->trc[0].table().m_tableSize;
+ else
+ outputEntries = 2;
+ stream << quint16(inputEntries);
+ stream << quint16(outputEntries);
+ auto writeTable = [&](const QColorSpacePrivate::TransferElement *table, int entries, int channels) {
+ if (table) {
+ for (int j = 0; j < channels; ++j) {
+ if (!table->trc[j].table().m_table16.isEmpty()) {
+ for (int i = 0; i < entries; ++i)
+ stream << table->trc[j].table().m_table16[i];
+ } else {
+ for (int i = 0; i < entries; ++i)
+ stream << quint16(table->trc[j].table().m_table8[i] * 257);
+ }
+ }
+ } else {
+ for (int j = 0; j < channels; ++j)
+ stream << quint16(0) << quint16(65535);
+ }
+ };
+
+ writeTable(combo.inTable, inputEntries, inChannels);
+
+ if (combo.clut) {
+ if (isAb && pcsLab) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65280.0f + 0.5f);
+ stream << quint16(v.y * 65280.0f + 0.5f);
+ stream << quint16(v.z * 65280.0f + 0.5f);
+ }
+ } else {
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ stream << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ }
+ }
+
+ writeTable(combo.outTable, outputEntries, outChannels);
+
+ qsizetype offset = sizeof(Lut16TagData) + 2 * inChannels * inputEntries
+ + 2 * outChannels * outputEntries
+ + 2 * outChannels * intPow(gridPointsLut16, inChannels);
+ if (offset & 0x2) {
+ stream << quint16(0);
+ offset += 2;
+ }
+ return offset;
+ } else {
+ // mAB/mBA tag:
+ if (isAb) {
+ if (!combo.clut && combo.inTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.inTable, combo.midTable);
+ } else {
+ if (!combo.clut && combo.outTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.outTable, combo.midTable);
+ }
+ quint32 offset = sizeof(mABTagData);
+ QBuffer buffer2;
+ buffer2.open(QIODevice::WriteOnly);
+ QDataStream stream2(&buffer2);
+ quint32 bOffset = offset;
+ quint32 matrixOffset = 0;
+ quint32 mOffset = 0;
+ quint32 clutOffset = 0;
+ quint32 aOffset = 0;
+ // Tags must start on 4 byte offsets, but sampled curves might have sizes 2-byte aligned
+ auto alignTag = [&]() {
+ if (offset & 0x2) {
+ stream2 << quint16(0);
+ offset += 2;
+ }
+ };
+
+ const QColorSpacePrivate::TransferElement *aCurve, *bCurve;
+ int aChannels;
+ if (isAb) {
+ aCurve = combo.inTable;
+ aChannels = inChannels;
+ bCurve = combo.outTable;
+ Q_ASSERT(outChannels == 3);
+ } else {
+ aCurve = combo.outTable;
+ aChannels = outChannels;
+ bCurve = combo.inTable;
+ Q_ASSERT(inChannels == 3);
+ }
+ if (bCurve) {
+ offset += writeColorTrc(stream2, bCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ if (combo.midMatrix || combo.midOffset || combo.midTable) {
+ matrixOffset = offset;
+ if (combo.midMatrix) {
+ stream2 << toFixedS1516(combo.midMatrix->r.x);
+ stream2 << toFixedS1516(combo.midMatrix->g.x);
+ stream2 << toFixedS1516(combo.midMatrix->b.x);
+ stream2 << toFixedS1516(combo.midMatrix->r.y);
+ stream2 << toFixedS1516(combo.midMatrix->g.y);
+ stream2 << toFixedS1516(combo.midMatrix->b.y);
+ stream2 << toFixedS1516(combo.midMatrix->r.z);
+ stream2 << toFixedS1516(combo.midMatrix->g.z);
+ stream2 << toFixedS1516(combo.midMatrix->b.z);
+ } else {
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ }
+ if (combo.midOffset) {
+ stream2 << toFixedS1516(combo.midOffset->x);
+ stream2 << toFixedS1516(combo.midOffset->y);
+ stream2 << toFixedS1516(combo.midOffset->z);
+ } else {
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ }
+ offset += 12 * 4;
+ mOffset = offset;
+ if (combo.midTable) {
+ offset += writeColorTrc(stream2, combo.midTable->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ }
+ if (combo.clut || aCurve) {
+ clutOffset = offset;
+ if (combo.clut) {
+ stream2 << uchar(combo.clut->gridPointsX);
+ stream2 << uchar(combo.clut->gridPointsY);
+ stream2 << uchar(combo.clut->gridPointsZ);
+ if (inChannels == 4)
+ stream2 << uchar(combo.clut->gridPointsW);
+ else
+ stream2 << uchar(0);
+ for (int i = 0; i < 12; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ stream2 << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ offset += 2 * outChannels * combo.clut->table.size();
+ alignTag();
+ } else {
+ for (int i = 0; i < 16; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ }
+ aOffset = offset;
+ if (aCurve) {
+ offset += writeColorTrc(stream2, aCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[2]);
+ alignTag();
+ if (aChannels == 4) {
+ offset += writeColorTrc(stream2, aCurve->trc[3]);
+ alignTag();
+ }
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ if (aChannels == 4)
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * aChannels;
+ }
+ }
+ buffer2.close();
+ QByteArray tagData = buffer2.buffer();
+ stream << quint32(bOffset);
+ stream << quint32(matrixOffset);
+ stream << quint32(mOffset);
+ stream << quint32(clutOffset);
+ stream << quint32(aOffset);
+ stream.writeRawData(tagData.data(), tagData.size());
+
+ return int(sizeof(mABTagData) + tagData.size());
+ }
+}
+
QByteArray toIccProfile(const QColorSpace &space)
{
if (!space.isValid())
return QByteArray();
const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
- // This should catch anything not three component matrix based as we can only get that from parsed ICC
if (!spaceDPtr->iccProfile.isEmpty())
return spaceDPtr->iccProfile;
- Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
int fixedLengthTagCount = 5;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ fixedLengthTagCount = 2;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ fixedLengthTagCount = 2;
bool writeChad = false;
- if (!spaceDPtr->whitePoint.isNull() && spaceDPtr->whitePoint != QColorVector::D50()) {
+ bool writeB2a = true;
+ bool writeCicp = false;
+ if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) {
writeChad = true;
fixedLengthTagCount++;
}
+ int varLengthTagCount = 4;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ varLengthTagCount = 3;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ varLengthTagCount = 2;
+
+ if (!space.isValidTarget()) {
+ writeB2a = false;
+ Q_ASSERT(!spaceDPtr->isThreeComponentMatrix());
+ varLengthTagCount--;
+ }
+ switch (spaceDPtr->transferFunction) {
+ case QColorSpace::TransferFunction::St2084:
+ case QColorSpace::TransferFunction::Hlg:
+ writeCicp = true;
+ fixedLengthTagCount++;
+ break;
+ default:
+ break;
+ }
- const int tagCount = fixedLengthTagCount + 4;
+ const int tagCount = fixedLengthTagCount + varLengthTagCount;
const uint profileDataOffset = 128 + 4 + 12 * tagCount;
- const uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
+ uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
+
uint currentOffset = 0;
- uint rTrcOffset, gTrcOffset, bTrcOffset;
- uint rTrcSize, gTrcSize, bTrcSize;
- uint descOffset, descSize;
+ uint rTrcOffset = 0;
+ uint gTrcOffset = 0;
+ uint bTrcOffset = 0;
+ uint kTrcOffset = 0;
+ uint rTrcSize = 0;
+ uint gTrcSize = 0;
+ uint bTrcSize = 0;
+ uint kTrcSize = 0;
+ uint descOffset = 0;
+ uint descSize = 0;
+ uint mA2bOffset = 0;
+ uint mB2aOffset = 0;
+ uint mA2bSize = 0;
+ uint mB2aSize = 0;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
@@ -403,13 +834,25 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0);
stream << uint(0x04400000); // Version 4.4
stream << uint(ProfileClass::Display);
- stream << uint(Tag::RGB_);
+ switch (spaceDPtr->colorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ stream << uint(ColorSpaceType::Rgb);
+ break;
+ case QColorSpace::ColorModel::Gray:
+ stream << uint(ColorSpaceType::Gray);
+ break;
+ case QColorSpace::ColorModel::Cmyk:
+ stream << uint(ColorSpaceType::Cmyk);
+ break;
+ case QColorSpace::ColorModel::Undefined:
+ Q_UNREACHABLE();
+ }
stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0);
- stream << uint(1); // Rendering intent
+ stream << uint(0); // Rendering intent
stream << uint(0x0000f6d6); // D50 X
stream << uint(0x00010000); // D50 Y
stream << uint(0x0000d32d); // D50 Z
@@ -417,81 +860,148 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0) << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
- // Tag table:
currentOffset = profileDataOffset;
- stream << uint(tagCount);
- stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
- stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
- stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
- stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
- stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(34);
- currentOffset += 20 + 20 + 20 + 20 + 34 + 2;
- if (writeChad) {
- stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
- currentOffset += 44;
- }
- // From here the offset and size will be updated later:
- stream << uint(Tag::rTRC) << uint(0) << uint(0);
- stream << uint(Tag::gTRC) << uint(0) << uint(0);
- stream << uint(Tag::bTRC) << uint(0) << uint(0);
- stream << uint(Tag::desc) << uint(0) << uint(0);
-
- // Tag data:
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.r.x);
- stream << toFixedS1516(spaceDPtr->toXyz.r.y);
- stream << toFixedS1516(spaceDPtr->toXyz.r.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.g.x);
- stream << toFixedS1516(spaceDPtr->toXyz.g.y);
- stream << toFixedS1516(spaceDPtr->toXyz.g.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.b.x);
- stream << toFixedS1516(spaceDPtr->toXyz.b.y);
- stream << toFixedS1516(spaceDPtr->toXyz.b.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->whitePoint.x);
- stream << toFixedS1516(spaceDPtr->whitePoint.y);
- stream << toFixedS1516(spaceDPtr->whitePoint.z);
- stream << uint(Tag::mluc) << uint(0);
- stream << uint(1) << uint(12);
- stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
- stream << uint(6) << uint(28);
- stream << ushort('N') << ushort('/') << ushort('A');
- stream << ushort(0); // 4-byte alignment
- if (writeChad) {
- QColorMatrix chad = QColorMatrix::chromaticAdaptation(spaceDPtr->whitePoint);
- stream << uint(Tag::sf32) << uint(0);
- stream << toFixedS1516(chad.r.x);
- stream << toFixedS1516(chad.g.x);
- stream << toFixedS1516(chad.b.x);
- stream << toFixedS1516(chad.r.y);
- stream << toFixedS1516(chad.g.y);
- stream << toFixedS1516(chad.b.y);
- stream << toFixedS1516(chad.r.z);
- stream << toFixedS1516(chad.g.z);
- stream << toFixedS1516(chad.b.z);
- }
-
- // From now on the data is variable sized:
- rTrcOffset = currentOffset;
- rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
- currentOffset += rTrcSize;
- if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
- gTrcOffset = rTrcOffset;
- gTrcSize = rTrcSize;
- } else {
- gTrcOffset = currentOffset;
- gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
- currentOffset += gTrcSize;
- }
- if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
- bTrcOffset = rTrcOffset;
- bTrcSize = rTrcSize;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ // Tag table:
+ stream << uint(tagCount);
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
+ stream << uint(Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
+ currentOffset += 20 + 20 + 20;
+ }
+ stream << uint(Tag::wtpt) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(currentOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ if (writeChad) {
+ stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
+ currentOffset += 44;
+ }
+ if (writeCicp) {
+ stream << uint(Tag::cicp) << uint(currentOffset) << uint(12);
+ currentOffset += 12;
+ }
+ // From here the offset and size will be updated later:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::rTRC) << uint(0) << uint(0);
+ stream << uint(Tag::gTRC) << uint(0) << uint(0);
+ stream << uint(Tag::bTRC) << uint(0) << uint(0);
+ } else {
+ stream << uint(Tag::kTRC) << uint(0) << uint(0);
+ }
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Tag data:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.z);
+ }
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+ if (writeChad) {
+ const QColorMatrix &chad = spaceDPtr->chad;
+ stream << uint(Tag::sf32) << uint(0);
+ stream << toFixedS1516(chad.r.x);
+ stream << toFixedS1516(chad.g.x);
+ stream << toFixedS1516(chad.b.x);
+ stream << toFixedS1516(chad.r.y);
+ stream << toFixedS1516(chad.g.y);
+ stream << toFixedS1516(chad.b.y);
+ stream << toFixedS1516(chad.r.z);
+ stream << toFixedS1516(chad.g.z);
+ stream << toFixedS1516(chad.b.z);
+ }
+ if (writeCicp) {
+ stream << uint(Tag::cicp) << uint(0);
+ stream << uchar(2); // Unspecified primaries
+ if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084)
+ stream << uchar(16);
+ else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg)
+ stream << uchar(18);
+ else
+ Q_UNREACHABLE();
+ stream << uchar(0); // Only for YCbCr, otherwise 0
+ stream << uchar(1); // Video full range
+ }
+
+ // From now on the data is variable sized:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ rTrcOffset = currentOffset;
+ rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += rTrcSize;
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
+ gTrcOffset = rTrcOffset;
+ gTrcSize = rTrcSize;
+ } else {
+ gTrcOffset = currentOffset;
+ gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
+ currentOffset += gTrcSize;
+ }
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
+ bTrcOffset = rTrcOffset;
+ bTrcSize = rTrcSize;
+ } else {
+ bTrcOffset = currentOffset;
+ bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
+ currentOffset += bTrcSize;
+ }
+ } else {
+ Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
+ kTrcOffset = currentOffset;
+ kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += kTrcSize;
+ }
} else {
- bTrcOffset = currentOffset;
- bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
- currentOffset += bTrcSize;
+ // Tag table:
+ stream << uint(tagCount);
+ stream << uint(Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ // From here the offset and size will be updated later:
+ stream << uint(Tag::A2B0) << uint(0) << uint(0);
+ if (writeB2a)
+ stream << uint(Tag::B2A0) << uint(0) << uint(0);
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Fixed tag data
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+
+ // From now on the data is variable sized:
+ mA2bOffset = currentOffset;
+ mA2bSize = writeMab(stream, spaceDPtr->mAB, true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mA2bSize;
+ if (writeB2a) {
+ mB2aOffset = currentOffset;
+ mB2aSize = writeMab(stream, spaceDPtr->mBA, false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mB2aSize;
+ }
}
// Writing description
@@ -515,14 +1025,34 @@ QByteArray toIccProfile(const QColorSpace &space)
// Now write final size
*(quint32_be *)iccProfile.data() = iccProfile.size();
// And the final indices and sizes of variable size tags:
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
+ }
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
+ variableTagTableOffsets += 12;
+ if (writeB2a) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
+ variableTagTableOffsets += 12;
+ }
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
+ }
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
@@ -559,21 +1089,27 @@ static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColo
static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type = QColorTransferTable::TwoWay)
{
+ if (tagData.size() < 12)
+ return 0;
const GenericTagData trcData = qFromUnaligned<GenericTagData>(tagData.constData());
if (trcData.type == quint32(Tag::curv)) {
Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
- if (curv.valueCount > (1 << 16))
+ if (curv.valueCount > (1 << 16)) {
+ qCWarning(lcIcc) << "Invalid count in curv table";
return 0;
- if (tagData.size() < qsizetype(12 + 2 * curv.valueCount))
+ }
+ if (tagData.size() < qsizetype(12 + 2 * curv.valueCount)) {
+ qCWarning(lcIcc) << "Truncated curv table";
return 0;
+ }
const auto valueOffset = sizeof(CurvTagData);
if (curv.valueCount == 0) {
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(); // Linear
} else if (curv.valueCount == 1) {
const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
} else {
QList<quint16> tabl;
@@ -591,7 +1127,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
gamma.m_table = table;
} else {
qCDebug(lcIcc) << "Detected curv table as function";
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = curve;
}
}
@@ -608,7 +1144,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
return 0;
qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
float g = fromFixedS1516(parameters[0]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
return 12 + 1 * 4;
}
@@ -622,7 +1158,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float a = fromFixedS1516(parameters[1]);
float b = fromFixedS1516(parameters[2]);
float d = -b / a;
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
return 12 + 3 * 4;
}
@@ -637,7 +1173,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float b = fromFixedS1516(parameters[2]);
float c = fromFixedS1516(parameters[3]);
float d = -b / a;
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
return 12 + 4 * 4;
}
@@ -650,7 +1186,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float b = fromFixedS1516(parameters[2]);
float c = fromFixedS1516(parameters[3]);
float d = fromFixedS1516(parameters[4]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
return 12 + 5 * 4;
}
@@ -665,7 +1201,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
float d = fromFixedS1516(parameters[4]);
float e = fromFixedS1516(parameters[5]);
float f = fromFixedS1516(parameters[6]);
- gamma.m_type = QColorTrc::Type::Function;
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
return 12 + 7 * 4;
}
@@ -700,12 +1236,6 @@ static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar
}
}
-// very simple version for small values (<=4) of exp.
-static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
-{
- return (exp <= 1) ? x : x * intPow(x, exp - 1);
-}
-
// Parses lut8 and lut16 type elements
template<typename T>
static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
@@ -761,20 +1291,23 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
- if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) {
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (lut.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false;
}
- if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) {
+ if (lut.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false;
}
- const qsizetype clutTableSize = intPow(lut.clutGridPoints, lut.inputChannels);
- if (tagEntry.size < (sizeof(T) + precision * lut.inputChannels * inputTableEntries
- + precision * lut.outputChannels * outputTableEntries
- + precision * lut.outputChannels * clutTableSize)) {
+ const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
+ if (tagEntry.size < (sizeof(T) + precision * inputChannels * inputTableEntries
+ + precision * outputChannels * outputTableEntries
+ + precision * outputChannels * clutTableSize)) {
qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
return false;
}
@@ -785,7 +1318,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
- for (int j = 0; j < lut.inputChannels; ++j) {
+ for (int j = 0; j < inputChannels; ++j) {
QList<S> input(inputTableEntries);
qFromBigEndian<S>(tableData, inputTableEntries, input.data());
QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
@@ -801,22 +1334,22 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
clutElement.table.resize(clutTableSize);
clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
- if (lut.inputChannels == 4)
+ if (inputChannels == 4)
clutElement.gridPointsW = lut.clutGridPoints;
if constexpr (std::is_same_v<T, Lut8TagData>) {
- parseCLUT(tableData, 1.f / 255.f, &clutElement, lut.outputChannels);
+ parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
} else {
float f = 1.0f / 65535.f;
if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
f = 1.0f / 65280.f;
- QList<S> clutTable(clutTableSize * lut.outputChannels);
+ QList<S> clutTable(clutTableSize * outputChannels);
qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
- parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
+ parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
}
- tableData += clutTableSize * lut.outputChannels * precision;
+ tableData += clutTableSize * outputChannels * precision;
- for (int j = 0; j < lut.outputChannels; ++j) {
+ for (int j = 0; j < outputChannels; ++j) {
QList<S> output(outputTableEntries);
qFromBigEndian<S>(tableData, outputTableEntries, output.data());
QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
@@ -868,12 +1401,15 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
- if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) {
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (mab.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false;
}
- if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) {
+ if (mab.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
return false;
}
@@ -923,7 +1459,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
// B Curves
- if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? mab.outputChannels : mab.inputChannels)) {
+ if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
qCWarning(lcIcc) << "Invalid B curves";
return false;
} else {
@@ -932,7 +1468,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// A Curves
if (mab.aCurvesOffset) {
- if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? mab.inputChannels : mab.outputChannels)) {
+ if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
qCWarning(lcIcc) << "Invalid A curves";
return false;
} else {
@@ -987,19 +1523,19 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
- if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
+ if ((mab.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
qCWarning(lcIcc) << "CLUT oversized for tag";
return false;
}
clutElement.table.resize(clutTableSize);
if (precision == 2) {
- QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
+ QList<uint16_t> clutTable(clutTableSize * outputChannels);
qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
- parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, mab.outputChannels);
+ parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
} else {
const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
- parseCLUT(clutTable, (1.f/255.f), &clutElement, mab.outputChannels);
+ parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
}
} else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
@@ -1013,7 +1549,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (!clutElement.isEmpty())
colorSpacePrivate->mAB.append(std::move(clutElement));
}
- if (mab.mCurvesOffset && mab.outputChannels == 3) {
+ if (mab.mCurvesOffset && outputChannels == 3) {
if (!mCurvesAreLinear)
colorSpacePrivate->mAB.append(std::move(mTableElement));
if (!matrixElement.isIdentity())
@@ -1026,7 +1562,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
} else {
if (!bCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(bTableElement));
- if (mab.mCurvesOffset && mab.inputChannels == 3) {
+ if (mab.mCurvesOffset && inputChannels == 3) {
if (!matrixElement.isIdentity())
colorSpacePrivate->mBA.append(std::move(matrixElement));
if (!offsetElement.isNull())
@@ -1067,6 +1603,8 @@ static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString
// Either 'desc' (ICCv2) or 'mluc' (ICCv4)
if (tag.type == quint32(Tag::desc)) {
+ if (tagEntry.size < sizeof(DescTagData))
+ return false;
Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
const quint32 len = desc.asciiDescriptionLength;
@@ -1134,10 +1672,12 @@ static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &t
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
- }
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromBt2020()) {
+ qCDebug(lcIcc) << "fromIccProfile: BT.2020 primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
}
return true;
}
@@ -1225,12 +1765,12 @@ static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagInd
colorspaceDPtr->trc[0] = QColorTransferFunction();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
colorspaceDPtr->gamma = 1.0f;
- } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isGamma()) {
+ } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) {
qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
colorspaceDPtr->gamma = rCurve.m_fun.m_g;
- } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isSRgb()) {
+ } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) {
qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
@@ -1249,6 +1789,63 @@ static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagInd
return true;
}
+static bool parseCicp(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
+{
+ if (colorspaceDPtr->isPcsLab)
+ return false;
+ if (tagEntry.size < sizeof(CicpTagData) || qsizetype(tagEntry.size) > data.size())
+ return false;
+ const CicpTagData cicp = qFromUnaligned<CicpTagData>(data.constData() + tagEntry.offset);
+ if (cicp.type != uint32_t(Tag::cicp)) {
+ qCWarning(lcIcc, "fromIccProfile: bad cicp data type");
+ return false;
+ }
+ switch (cicp.transferCharacteristics) {
+ case 4:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
+ colorspaceDPtr->gamma = 2.2f;
+ break;
+ case 5:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
+ colorspaceDPtr->gamma = 2.8f;
+ break;
+ case 8:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
+ break;
+ case 13:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
+ break;
+ case 1:
+ case 6:
+ case 14:
+ case 15:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Bt2020;
+ break;
+ case 16:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::St2084;
+ break;
+ case 18:
+ colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Hlg;
+ break;
+ default:
+ return false;
+ }
+ switch (cicp.colorPrimaries) {
+ case 1:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
+ break;
+ case 9:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
+ break;
+ case 12:
+ colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
{
if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
@@ -1287,7 +1884,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
return false;
}
- if (tagTable.size < 12) {
+ if (tagTable.size < 8) {
qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
return false;
}
@@ -1337,12 +1934,22 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorSpace->detach();
QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace);
+ colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
+ if (tagIndex.contains(Tag::cicp)) {
+ // Let cicp override nLut profiles if we fully recognize it.
+ if (parseCicp(data, tagIndex[Tag::cicp], colorspaceDPtr))
+ threeComponentMatrix = true;
+ if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
+ colorspaceDPtr->setToXyzMatrix();
+ if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom)
+ colorspaceDPtr->setTransferFunction();
+ }
+
if (threeComponentMatrix) {
- colorspaceDPtr->isPcsLab = false;
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
- if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr))
+ if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
return false;
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
} else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
@@ -1365,10 +1972,10 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
colorspaceDPtr->setToXyzMatrix();
- if (!parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
+ if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
+ !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
return false;
} else {
- colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
@@ -1387,6 +1994,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
return false;
}
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
+ return false;
+ } else if (!colorspaceDPtr->whitePoint.isNull()) {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
+ }
}
if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) {
diff --git a/src/gui/painting/qimagescale.cpp b/src/gui/painting/qimagescale.cpp
index a636635fd5..eb5f12389f 100644
--- a/src/gui/painting/qimagescale.cpp
+++ b/src/gui/painting/qimagescale.cpp
@@ -1208,7 +1208,7 @@ QImage qSmoothScaleImage(const QImage &src, int dw, int dh)
dw, dh, dw, src.bytesPerLine() / 8);
else
#endif
- if (src.hasAlphaChannel())
+ if (src.hasAlphaChannel() || src.format() == QImage::Format_CMYK8888)
qt_qimageScaleAARGBA(scaleinfo, (unsigned int *)buffer.scanLine(0),
dw, dh, dw, src.bytesPerLine() / 4);
else
diff --git a/src/gui/painting/qpagelayout.h b/src/gui/painting/qpagelayout.h
index 1e340b6d75..c812f92625 100644
--- a/src/gui/painting/qpagelayout.h
+++ b/src/gui/painting/qpagelayout.h
@@ -42,7 +42,7 @@ public:
enum class OutOfBoundsPolicy {
Reject,
- Clamp
+ Clamp,
};
QPageLayout();
diff --git a/src/gui/painting/qpagesize.h b/src/gui/painting/qpagesize.h
index b3629bb8d2..221863aad7 100644
--- a/src/gui/painting/qpagesize.h
+++ b/src/gui/painting/qpagesize.h
@@ -203,7 +203,9 @@ public:
void swap(QPageSize &other) noexcept { d.swap(other.d); }
+#if QT_GUI_REMOVED_SINCE(6, 4)
friend Q_GUI_EXPORT bool operator==(const QPageSize &lhs, const QPageSize &rhs);
+#endif
bool isEquivalentTo(const QPageSize &other) const;
bool isValid() const;
diff --git a/src/gui/painting/qpaintdevice.cpp b/src/gui/painting/qpaintdevice.cpp
index 14e2d7cd8e..de21901e23 100644
--- a/src/gui/painting/qpaintdevice.cpp
+++ b/src/gui/painting/qpaintdevice.cpp
@@ -17,6 +17,39 @@ QPaintDevice::~QPaintDevice()
"painted");
}
+/*!
+ \internal
+*/
+// ### Qt 7: Replace this workaround mechanism: virtual devicePixelRatio() and virtual metricF()
+inline double QPaintDevice::getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const
+{
+ qint32 buf[2];
+ // The Encoded metric enum values come in pairs of one odd and one even value.
+ // We map those to the 0 and 1 indexes of buf by taking just the least significant bit.
+ // Same mapping here as in the encodeMetricF() function, to ensure correct order.
+ buf[metricA & 1] = metric(metricA);
+ buf[metricB & 1] = metric(metricB);
+ double res;
+ memcpy(&res, buf, sizeof(res));
+ return res;
+}
+
+qreal QPaintDevice::devicePixelRatio() const
+{
+ Q_STATIC_ASSERT((PdmDevicePixelRatioF_EncodedA & 1) != (PdmDevicePixelRatioF_EncodedB & 1));
+ double res;
+ int scaledDpr = metric(PdmDevicePixelRatioScaled);
+ if (scaledDpr == int(devicePixelRatioFScale())) {
+ res = 1; // Shortcut for common case
+ } else if (scaledDpr == 2 * int(devicePixelRatioFScale())) {
+ res = 2; // Shortcut for common case
+ } else {
+ res = getDecodedMetricF(PdmDevicePixelRatioF_EncodedA, PdmDevicePixelRatioF_EncodedB);
+ if (res <= 0) // These metrics not implemented, fall back to PdmDevicePixelRatioScaled
+ res = scaledDpr / devicePixelRatioFScale();
+ }
+ return res;
+}
/*!
\internal
@@ -64,6 +97,8 @@ int QPaintDevice::metric(PaintDeviceMetric m) const
return 256;
} else if (m == PdmDevicePixelRatio) {
return 1;
+ } else if (m == PdmDevicePixelRatioF_EncodedA || m == PdmDevicePixelRatioF_EncodedB) {
+ return 0;
} else {
qDebug("Unrecognised metric %d!",m);
return 0;
diff --git a/src/gui/painting/qpaintdevice.h b/src/gui/painting/qpaintdevice.h
index ceccac5e7e..7011684df7 100644
--- a/src/gui/painting/qpaintdevice.h
+++ b/src/gui/painting/qpaintdevice.h
@@ -29,7 +29,9 @@ public:
PdmPhysicalDpiX,
PdmPhysicalDpiY,
PdmDevicePixelRatio,
- PdmDevicePixelRatioScaled
+ PdmDevicePixelRatioScaled,
+ PdmDevicePixelRatioF_EncodedA,
+ PdmDevicePixelRatioF_EncodedB,
};
virtual ~QPaintDevice();
@@ -46,18 +48,20 @@ public:
int logicalDpiY() const { return metric(PdmDpiY); }
int physicalDpiX() const { return metric(PdmPhysicalDpiX); }
int physicalDpiY() const { return metric(PdmPhysicalDpiY); }
- qreal devicePixelRatio() const { return metric(PdmDevicePixelRatioScaled) / devicePixelRatioFScale(); }
+ qreal devicePixelRatio() const;
qreal devicePixelRatioF() const { return devicePixelRatio(); }
int colorCount() const { return metric(PdmNumColors); }
int depth() const { return metric(PdmDepth); }
static inline qreal devicePixelRatioFScale() { return 0x10000; }
+ static inline int encodeMetricF(PaintDeviceMetric metric, double value);
protected:
QPaintDevice() noexcept;
virtual int metric(PaintDeviceMetric metric) const;
virtual void initPainter(QPainter *painter) const;
virtual QPaintDevice *redirected(QPoint *offset) const;
virtual QPainter *sharedPainter() const;
+ double getDecodedMetricF(PaintDeviceMetric metricA, PaintDeviceMetric metricB) const;
ushort painters; // refcount
private:
@@ -80,6 +84,14 @@ inline int QPaintDevice::devType() const
inline bool QPaintDevice::paintingActive() const
{ return painters != 0; }
+inline int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
+{
+ qint32 buf[2];
+ Q_STATIC_ASSERT(sizeof(buf) == sizeof(double));
+ memcpy(buf, &value, sizeof(buf));
+ return buf[metric & 1];
+}
+
QT_END_NAMESPACE
#endif // QPAINTDEVICE_H
diff --git a/src/gui/painting/qpaintdevice.qdoc b/src/gui/painting/qpaintdevice.qdoc
index d63fdcb92b..9b9a6facb8 100644
--- a/src/gui/painting/qpaintdevice.qdoc
+++ b/src/gui/painting/qpaintdevice.qdoc
@@ -99,6 +99,14 @@
The constant scaling factor used is devicePixelRatioFScale(). This enum value
has been introduced in Qt 5.6.
+ \value [since 6.8] PdmDevicePixelRatioF_EncodedA This enum item, together with the
+ corresponding \c B item, are used together for the device pixel ratio of the device, as an
+ encoded \c double floating point value. A QPaintDevice subclass that supports \e fractional DPR
+ values should implement support for these two enum items in its override of the metric()
+ function. The return value is expected to be the result of the encodeMetricF() function.
+
+ \value [since 6.8] PdmDevicePixelRatioF_EncodedB See PdmDevicePixelRatioF_EncodedA.
+
\sa metric(), devicePixelRatio()
*/
@@ -288,3 +296,15 @@
\since 5.6
*/
+
+/*!
+ \fn int QPaintDevice::encodeMetricF(PaintDeviceMetric metric, double value)
+
+ \internal
+
+ Returns \a value encoded for the metric \a metric. Subclasses implementing metric() should use
+ this function to encode \value as an integer return value when the query metric specifies an
+ encoded floating-point value.
+
+ \since 6.8
+*/
diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp
index 38f2a9b803..716cf35ee6 100644
--- a/src/gui/painting/qpdf.cpp
+++ b/src/gui/painting/qpdf.cpp
@@ -20,8 +20,11 @@
#include <qimagewriter.h>
#include <qnumeric.h>
#include <qtemporaryfile.h>
+#include <qtimezone.h>
#include <quuid.h>
+#include <map>
+
#ifndef QT_NO_COMPRESS
#include <zlib.h>
#endif
@@ -1035,6 +1038,7 @@ void QPdfEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
*d->currentPage << "Q\n";
}
+// Used by QtWebKit
void QPdfEngine::drawHyperlink(const QRectF &r, const QUrl &url)
{
Q_D(QPdfEngine);
@@ -1564,7 +1568,7 @@ void QPdfEnginePrivate::writeHeader()
int metaDataObj = -1;
int outputIntentObj = -1;
if (pdfVersion == QPdfEngine::Version_A1b || !xmpDocumentMetadata.isEmpty()) {
- metaDataObj = writeXmpDcumentMetaData();
+ metaDataObj = writeXmpDocumentMetaData();
}
if (pdfVersion == QPdfEngine::Version_A1b) {
outputIntentObj = writeOutputIntent();
@@ -1740,11 +1744,12 @@ void QPdfEnginePrivate::writeInfo()
xprintf("+%02d'%02d')\n", hours , mins);
else
xprintf("Z)\n");
+ xprintf("/Trapped /False\n");
xprintf(">>\n"
"endobj\n");
}
-int QPdfEnginePrivate::writeXmpDcumentMetaData()
+int QPdfEnginePrivate::writeXmpDocumentMetaData()
{
const int metaDataObj = addXrefEntry(-1);
QByteArray metaDataContent;
@@ -1752,26 +1757,12 @@ int QPdfEnginePrivate::writeXmpDcumentMetaData()
if (xmpDocumentMetadata.isEmpty()) {
const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR));
- const QDateTime now = QDateTime::currentDateTime();
- const QDate date = now.date();
- const QTime time = now.time();
- const QString timeStr =
- QString::asprintf("%d-%02d-%02dT%02d:%02d:%02d",
- date.year(), date.month(), date.day(),
- time.hour(), time.minute(), time.second());
-
- const int offset = now.offsetFromUtc();
- const int hours = (offset / 60) / 60;
- const int mins = (offset / 60) % 60;
- QString tzStr;
- if (offset < 0)
- tzStr = QString::asprintf("-%02d:%02d", -hours, -mins);
- else if (offset > 0)
- tzStr = QString::asprintf("+%02d:%02d", hours , mins);
- else
- tzStr = "Z"_L1;
-
- const QString metaDataDate = timeStr + tzStr;
+#if QT_CONFIG(timezone)
+ const QDateTime now = QDateTime::currentDateTime(QTimeZone::systemTimeZone());
+#else
+ const QDateTime now = QDateTime::currentDateTimeUtc();
+#endif
+ const QString metaDataDate = now.toString(Qt::ISODate);
QFile metaDataFile(":/qpdf/qpdfa_metadata.xml"_L1);
bool ok = metaDataFile.open(QIODevice::ReadOnly);
@@ -1813,7 +1804,8 @@ int QPdfEnginePrivate::writeOutputIntent()
s << "/N 3\n";
s << "/Alternate /DeviceRGB\n";
s << "/Length " << length_object << "0 R\n";
- s << "/Filter /FlateDecode\n";
+ if (do_compress)
+ s << "/Filter /FlateDecode\n";
s << ">>\n";
s << "stream\n";
write(data);
@@ -1867,7 +1859,7 @@ void QPdfEnginePrivate::writeDestsRoot()
if (destCache.isEmpty())
return;
- QHash<QString, int> destObjects;
+ std::map<QString, int> destObjects;
QByteArray xs, ys;
for (const DestInfo &destInfo : std::as_const(destCache)) {
int destObj = addXrefEntry(-1);
@@ -1875,21 +1867,19 @@ void QPdfEnginePrivate::writeDestsRoot()
ys.setNum(static_cast<double>(destInfo.coords.y()), 'f');
xprintf("[%d 0 R /XYZ %s %s 0]\n", destInfo.pageObj, xs.constData(), ys.constData());
xprintf("endobj\n");
- destObjects.insert(destInfo.anchor, destObj);
+ destObjects.insert_or_assign(destInfo.anchor, destObj);
}
// names
destsRoot = addXrefEntry(-1);
- QStringList anchors = destObjects.keys();
- anchors.sort();
xprintf("<<\n/Limits [");
- printString(anchors.constFirst());
+ printString(destObjects.begin()->first);
xprintf(" ");
- printString(anchors.constLast());
+ printString(destObjects.rbegin()->first);
xprintf("]\n/Names [\n");
- for (const QString &anchor : std::as_const(anchors)) {
+ for (const auto &[anchor, destObject] : destObjects) {
printString(anchor);
- xprintf(" %d 0 R\n", destObjects[anchor]);
+ xprintf(" %d 0 R\n", destObject);
}
xprintf("]\n>>\n"
"endobj\n");
@@ -1922,7 +1912,8 @@ void QPdfEnginePrivate::writeAttachmentRoot()
attachments.push_back(addXrefEntry(-1));
xprintf("<<\n"
- "/F (%s)", attachment.fileName.toLatin1().constData());
+ "/F ");
+ printString(attachment.fileName);
xprintf("\n/EF <</F %d 0 R>>\n"
"/Type/Filespec\n"
@@ -2135,17 +2126,24 @@ void QPdfEnginePrivate::writePage()
qreal userUnit = calcUserUnit();
addXrefEntry(pages.constLast());
+
+ // make sure we use the pagesize from when we started the page, since the user may have changed it
+ const QByteArray formattedPageWidth = QByteArray::number(currentPage->pageSize.width() / userUnit, 'f');
+ const QByteArray formattedPageHeight = QByteArray::number(currentPage->pageSize.height() / userUnit, 'f');
+
xprintf("<<\n"
"/Type /Page\n"
"/Parent %d 0 R\n"
"/Contents %d 0 R\n"
"/Resources %d 0 R\n"
"/Annots %d 0 R\n"
- "/MediaBox [0 0 %s %s]\n",
+ "/MediaBox [0 0 %s %s]\n"
+ "/TrimBox [0 0 %s %s]\n",
pageRoot, pageStream, resources, annots,
- // make sure we use the pagesize from when we started the page, since the user may have changed it
- QByteArray::number(currentPage->pageSize.width() / userUnit, 'f').constData(),
- QByteArray::number(currentPage->pageSize.height() / userUnit, 'f').constData());
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData(),
+ formattedPageWidth.constData(),
+ formattedPageHeight.constData());
if (pdfVersion >= QPdfEngine::Version_1_6)
xprintf("/UserUnit %s\n", QByteArray::number(userUnit, 'f').constData());
diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h
index 3c34e0bd7a..54d37b00a8 100644
--- a/src/gui/painting/qpdf_p.h
+++ b/src/gui/painting/qpdf_p.h
@@ -290,7 +290,7 @@ private:
QPdfEngine::ColorModel colorModelForColor(const QColor &color) const;
void writeColor(ColorDomain domain, const QColor &color);
void writeInfo();
- int writeXmpDcumentMetaData();
+ int writeXmpDocumentMetaData();
int writeOutputIntent();
void writePageRoot();
void writeDestsRoot();
@@ -319,7 +319,7 @@ private:
int addXrefEntry(int object, bool printostr = true);
void printString(QStringView string);
void xprintf(const char* fmt, ...);
- inline void write(const QByteArray &data) {
+ inline void write(QByteArrayView data) {
stream->writeRawData(data.constData(), data.size());
streampos += data.size();
}
diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp
index f7c4df40c8..21e89d67fd 100644
--- a/src/gui/painting/qplatformbackingstore.cpp
+++ b/src/gui/painting/qplatformbackingstore.cpp
@@ -10,6 +10,8 @@
#include <QtCore/private/qobject_p.h>
+#include <unordered_map>
+
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaBackingStore, "qt.qpa.backingstore", QtWarningMsg);
@@ -26,11 +28,14 @@ public:
QWindow *window;
QBackingStore *backingStore;
- // The order matters. if it needs to be rearranged in the future, call
- // reset() explicitly from the dtor in the correct order.
- // (first the compositor, then the rhiSupport)
- QBackingStoreRhiSupport rhiSupport;
- QBackingStoreDefaultCompositor compositor;
+ struct SurfaceSupport {
+ // The order matters. if it needs to be rearranged in the future, call
+ // reset() explicitly from the dtor in the correct order.
+ // (first the compositor, then the rhiSupport)
+ QBackingStoreRhiSupport rhiSupport;
+ QBackingStoreDefaultCompositor compositor;
+ };
+ std::unordered_map<QSurface::SurfaceType, SurfaceSupport> surfaceSupport;
};
struct QBackingstoreTextureInfo
@@ -210,8 +215,12 @@ QPlatformBackingStore::FlushResult QPlatformBackingStore::rhiFlush(QWindow *wind
QPlatformTextureList *textures,
bool translucentBackground)
{
- return d_ptr->compositor.flush(this, d_ptr->rhiSupport.rhi(), d_ptr->rhiSupport.swapChainForWindow(window),
- window, sourceDevicePixelRatio, region, offset, textures, translucentBackground);
+ auto &surfaceSupport = d_ptr->surfaceSupport[window->surfaceType()];
+ return surfaceSupport.compositor.flush(this,
+ surfaceSupport.rhiSupport.rhi(),
+ surfaceSupport.rhiSupport.swapChainForWindow(window),
+ window, sourceDevicePixelRatio, region, offset, textures,
+ translucentBackground);
}
/*!
@@ -261,7 +270,10 @@ QRhiTexture *QPlatformBackingStore::toTexture(QRhiResourceUpdateBatch *resourceU
const QRegion &dirtyRegion,
TextureFlags *flags) const
{
- return d_ptr->compositor.toTexture(this, d_ptr->rhiSupport.rhi(), resourceUpdates, dirtyRegion, flags);
+ auto &surfaceSupport = d_ptr->surfaceSupport[window()->surfaceType()];
+ return surfaceSupport.compositor.toTexture(this,
+ surfaceSupport.rhiSupport.rhi(), resourceUpdates,
+ dirtyRegion, flags);
}
/*!
@@ -356,33 +368,44 @@ bool QPlatformBackingStore::scroll(const QRegion &area, int dx, int dy)
return false;
}
-void QPlatformBackingStore::setRhiConfig(const QPlatformBackingStoreRhiConfig &config)
+void QPlatformBackingStore::createRhi(QWindow *window, QPlatformBackingStoreRhiConfig config)
{
if (!config.isEnabled())
return;
- d_ptr->rhiSupport.setConfig(config);
- d_ptr->rhiSupport.setWindow(d_ptr->window);
- d_ptr->rhiSupport.setFormat(d_ptr->window->format());
- d_ptr->rhiSupport.create();
+ qCDebug(lcQpaBackingStore) << "Setting up RHI support in" << this
+ << "for" << window << "with" << window->surfaceType()
+ << "and requested API" << config.api();
+
+ auto &support = d_ptr->surfaceSupport[window->surfaceType()];
+ if (!support.rhiSupport.rhi()) {
+ support.rhiSupport.setConfig(config);
+ support.rhiSupport.setWindow(window);
+ support.rhiSupport.setFormat(window->format());
+ support.rhiSupport.create();
+ } else {
+ qCDebug(lcQpaBackingStore) << "Window already has RHI support"
+ << "with backend" << support.rhiSupport.rhi()->backendName();
+ }
}
-QRhi *QPlatformBackingStore::rhi() const
+QRhi *QPlatformBackingStore::rhi(QWindow *window) const
{
// Returning null is valid, and means this is not a QRhi-capable backingstore.
- return d_ptr->rhiSupport.rhi();
+ return d_ptr->surfaceSupport[window->surfaceType()].rhiSupport.rhi();
}
-void QPlatformBackingStore::graphicsDeviceReportedLost()
+void QPlatformBackingStore::graphicsDeviceReportedLost(QWindow *window)
{
- if (!d_ptr->rhiSupport.rhi())
+ auto &surfaceSupport = d_ptr->surfaceSupport[window->surfaceType()];
+ if (!surfaceSupport.rhiSupport.rhi())
return;
qWarning("Rhi backingstore: graphics device lost, attempting to reinitialize");
- d_ptr->compositor.reset();
- d_ptr->rhiSupport.reset();
- d_ptr->rhiSupport.create();
- if (!d_ptr->rhiSupport.rhi())
+ surfaceSupport.compositor.reset();
+ surfaceSupport.rhiSupport.reset();
+ surfaceSupport.rhiSupport.create();
+ if (!surfaceSupport.rhiSupport.rhi())
qWarning("Rhi backingstore: failed to reinitialize after losing the device");
}
diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h
index 1d55e9ab6a..2f27a2aa2c 100644
--- a/src/gui/painting/qplatformbackingstore.h
+++ b/src/gui/painting/qplatformbackingstore.h
@@ -39,6 +39,8 @@ class QRhiResourceUpdateBatch;
struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
{
+ Q_GADGET
+public:
enum Api {
OpenGL,
Metal,
@@ -47,6 +49,7 @@ struct Q_GUI_EXPORT QPlatformBackingStoreRhiConfig
D3D12,
Null
};
+ Q_ENUM(Api)
QPlatformBackingStoreRhiConfig()
: m_enable(false)
@@ -171,10 +174,10 @@ public:
virtual void beginPaint(const QRegion &);
virtual void endPaint();
- void setRhiConfig(const QPlatformBackingStoreRhiConfig &config);
- QRhi *rhi() const;
+ void createRhi(QWindow *window, QPlatformBackingStoreRhiConfig config);
+ QRhi *rhi(QWindow *window) const;
void surfaceAboutToBeDestroyed();
- void graphicsDeviceReportedLost();
+ void graphicsDeviceReportedLost(QWindow *window);
private:
QPlatformBackingStorePrivate *d_ptr;
diff --git a/src/gui/painting/qregion.cpp b/src/gui/painting/qregion.cpp
index 8b712ee46d..f9089d7bba 100644
--- a/src/gui/painting/qregion.cpp
+++ b/src/gui/painting/qregion.cpp
@@ -876,9 +876,15 @@ QRegion QRegion::intersect(const QRect &r) const
/*!
\fn void QRegion::setRects(const QRect *rects, int number)
+ \overload
+ \obsolete Use the QSpan overload instead.
+*/
+
+/*!
+ \fn void QRegion::setRects(QSpan<const QRect> rects)
+ \since 6.8
- Sets the region using the array of rectangles specified by \a rects and
- \a number.
+ Sets the region using the array of rectangles specified by \a rects.
The rectangles \e must be optimally Y-X sorted and follow these restrictions:
\list
@@ -892,6 +898,11 @@ QRegion QRegion::intersect(const QRect &r) const
\omit
Only some platforms have these restrictions (Qt for Embedded Linux, X11 and \macos).
\endomit
+
+ \note For historical reasons, \c{rects.size()} must be less than \c{INT_MAX}
+ (see rectCount()).
+
+ \sa rects()
*/
namespace {
@@ -4214,18 +4225,39 @@ QRegion::const_iterator QRegion::end() const noexcept
return d->qt_rgn ? d->qt_rgn->end() : nullptr;
}
-void QRegion::setRects(const QRect *rects, int num)
+static Q_DECL_COLD_FUNCTION
+void set_rects_warn(const char *what)
+{
+ qWarning("QRegion::setRects(): %s", what);
+}
+
+void QRegion::setRects(const QRect *r, int n)
{
+ if (!r && n) { // old setRects() allowed this, but QSpan doesn't
+ set_rects_warn("passing num != 0 when rects == nullptr is deprecated.");
+ n = 0;
+ }
+ setRects(QSpan<const QRect>(r, n));
+}
+
+void QRegion::setRects(QSpan<const QRect> rects)
+{
+ const auto num = int(rects.size());
+ if (num != rects.size()) {
+ set_rects_warn("span size exceeds INT_MAX, ignoring");
+ return;
+ }
+
*this = QRegion();
- if (!rects || num == 0 || (num == 1 && rects->isEmpty()))
+ if (!rects.data() || num == 0 || (num == 1 && rects.front().isEmpty()))
return;
detach();
d->qt_rgn->numRects = num;
if (num == 1) {
- d->qt_rgn->extents = *rects;
- d->qt_rgn->innerRect = *rects;
+ d->qt_rgn->extents = rects.front();
+ d->qt_rgn->innerRect = rects.front();
} else {
d->qt_rgn->rects.resize(num);
@@ -4246,12 +4278,30 @@ void QRegion::setRects(const QRect *rects, int num)
}
}
+/*!
+ \since 6.8
+
+ Returns a span of non-overlapping rectangles that make up the region. The
+ span remains valid until the next call of a mutating (non-const) method on
+ this region.
+
+ The union of all the rectangles is equal to the original region.
+
+ \note This functions existed in Qt 5, too, but returned QVector<QRect>
+ instead.
+
+ \sa setRects()
+*/
+QSpan<const QRect> QRegion::rects() const noexcept
+{
+ return {begin(), end()};
+};
+
int QRegion::rectCount() const noexcept
{
return (d->qt_rgn ? d->qt_rgn->numRects : 0);
}
-
bool QRegion::operator==(const QRegion &r) const
{
if (!d->qt_rgn)
diff --git a/src/gui/painting/qregion.h b/src/gui/painting/qregion.h
index b0051b6067..4b852815f3 100644
--- a/src/gui/painting/qregion.h
+++ b/src/gui/painting/qregion.h
@@ -8,11 +8,11 @@
#include <QtCore/qatomic.h>
#include <QtCore/qrect.h>
#include <QtGui/qwindowdefs.h>
-#include <QtCore/qcontainerfwd.h>
#ifndef QT_NO_DATASTREAM
#include <QtCore/qdatastream.h>
#endif
+#include <QtCore/qspan.h>
QT_BEGIN_NAMESPACE
@@ -75,6 +75,8 @@ public:
QRect boundingRect() const noexcept;
void setRects(const QRect *rect, int num);
+ void setRects(QSpan<const QRect> r);
+ QSpan<const QRect> rects() const noexcept;
int rectCount() const noexcept;
QRegion operator|(const QRegion &r) const;
diff --git a/src/gui/painting/qrhibackingstore.cpp b/src/gui/painting/qrhibackingstore.cpp
index 586dfb44a4..d59cc2d83c 100644
--- a/src/gui/painting/qrhibackingstore.cpp
+++ b/src/gui/painting/qrhibackingstore.cpp
@@ -20,30 +20,27 @@ void QRhiBackingStore::flush(QWindow *flushedWindow, const QRegion &region, cons
Q_UNUSED(region);
Q_UNUSED(offset);
- if (flushedWindow->surfaceType() != window()->surfaceType()) {
- qWarning() << "Cannot flush child window" << flushedWindow
- << "with surface type" << flushedWindow->surfaceType() << ";"
- << "Must match" << window()->surfaceType() << "of" << window();
-
- // FIXME: Support different surface types by not tying the
- // RHI config to the backing store itself (per window config).
- return;
- }
-
- if (!rhi()) {
+ if (!rhi(flushedWindow)) {
QPlatformBackingStoreRhiConfig rhiConfig;
- switch (window()->surfaceType()) {
+ switch (flushedWindow->surfaceType()) {
case QSurface::OpenGLSurface:
rhiConfig.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
break;
case QSurface::MetalSurface:
rhiConfig.setApi(QPlatformBackingStoreRhiConfig::Metal);
break;
+ case QSurface::Direct3DSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+ break;
+ case QSurface::VulkanSurface:
+ rhiConfig.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+ break;
default:
Q_UNREACHABLE();
}
+
rhiConfig.setEnabled(true);
- setRhiConfig(rhiConfig);
+ createRhi(flushedWindow, rhiConfig);
}
static QPlatformTextureList emptyTextureList;
diff --git a/src/gui/platform/darwin/qmetallayer.mm b/src/gui/platform/darwin/qmetallayer.mm
new file mode 100644
index 0000000000..e8a27a7b06
--- /dev/null
+++ b/src/gui/platform/darwin/qmetallayer.mm
@@ -0,0 +1,73 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qmetallayer_p.h"
+
+#include <QtCore/qreadwritelock.h>
+#include <QtCore/qrect.h>
+
+using namespace std::chrono_literals;
+
+QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcMetalLayer, "qt.gui.metal")
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@implementation QMetalLayer
+{
+ std::unique_ptr<QReadWriteLock> m_displayLock;
+}
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ m_displayLock.reset(new QReadWriteLock(QReadWriteLock::Recursive));
+ self.mainThreadPresentation = nil;
+ }
+
+ return self;
+}
+
+- (QReadWriteLock &)displayLock
+{
+ return *m_displayLock.get();
+}
+
+- (void)setNeedsDisplay
+{
+ [self setNeedsDisplayInRect:CGRectInfinite];
+}
+
+- (void)setNeedsDisplayInRect:(CGRect)rect
+{
+ if (!self.needsDisplay) {
+ // We lock for writing here, blocking in case a secondary thread is in
+ // the middle of presenting to the layer, as we want the main thread's
+ // display to happen after the secondary thread finishes presenting.
+ qCDebug(lcMetalLayer) << "Locking" << self << "for writing"
+ << "due to needing display in rect" << QRectF::fromCGRect(rect);
+
+ // For added safety, we use a 5 second timeout, and try to fail
+ // gracefully by not marking the layer as needing display, as
+ // doing so would lead us to unlock and unheld lock in displayLayer.
+ if (!self.displayLock.tryLockForWrite(5s)) {
+ qCWarning(lcMetalLayer) << "Timed out waiting for display lock";
+ return;
+ }
+ }
+
+ [super setNeedsDisplayInRect:rect];
+}
+
+- (id<CAMetalDrawable>)nextDrawable
+{
+ // Drop the presentation block early, so that if the main thread for
+ // some reason doesn't handle the presentation, the block won't hold on
+ // to a drawable unnecessarily.
+ self.mainThreadPresentation = nil;
+ return [super nextDrawable];
+}
+
+
+@end
diff --git a/src/gui/platform/darwin/qmetallayer_p.h b/src/gui/platform/darwin/qmetallayer_p.h
new file mode 100644
index 0000000000..1c19f21866
--- /dev/null
+++ b/src/gui/platform/darwin/qmetallayer_p.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QMETALLAYER_P_H
+#define QMETALLAYER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtGui/qtguiglobal.h>
+
+#include <QtCore/qreadwritelock.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#include <QuartzCore/CAMetalLayer.h>
+
+QT_BEGIN_NAMESPACE
+class QReadWriteLock;
+
+Q_DECLARE_EXPORTED_LOGGING_CATEGORY(lcMetalLayer, Q_GUI_EXPORT)
+
+QT_END_NAMESPACE
+
+#if defined(__OBJC__)
+Q_GUI_EXPORT
+#endif
+QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMetalLayer, CAMetalLayer
+@property (nonatomic, readonly) QT_PREPEND_NAMESPACE(QReadWriteLock) &displayLock;
+@property (atomic, copy) void (^mainThreadPresentation)();
+)
+
+#endif // QMETALLAYER_P_H
diff --git a/src/gui/platform/ios/qiosnativeinterface.cpp b/src/gui/platform/ios/qiosnativeinterface.cpp
new file mode 100644
index 0000000000..c942709e33
--- /dev/null
+++ b/src/gui/platform/ios/qiosnativeinterface.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtGui/private/qguiapplication_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace QNativeInterface::Private;
+
+#if defined(Q_OS_VISIONOS)
+
+/*!
+ \class QNativeInterface::QVisionOSApplication
+ \since 6.8
+ \internal
+ \preliminary
+ \brief Native interface to QGuiApplication, to be retrieved from QPlatformIntegration.
+ \inmodule QtGui
+ \ingroup native-interfaces
+*/
+
+QT_DEFINE_NATIVE_INTERFACE(QVisionOSApplication);
+
+#endif // Q_OS_VISIONOS
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp
index fc4b2296d2..8a7f7cd6f7 100644
--- a/src/gui/platform/unix/qgenericunixthemes.cpp
+++ b/src/gui/platform/unix/qgenericunixthemes.cpp
@@ -78,17 +78,14 @@ static const char defaultFixedFontNameC[] = "monospace";
enum { defaultSystemFontSize = 9 };
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
-static bool isDBusTrayAvailable() {
- static bool dbusTrayAvailable = false;
- static bool dbusTrayAvailableKnown = false;
- if (!dbusTrayAvailableKnown) {
- QDBusMenuConnection conn;
- if (conn.isWatcherRegistered())
- dbusTrayAvailable = true;
- dbusTrayAvailableKnown = true;
- qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable;
- }
- return dbusTrayAvailable;
+static bool shouldUseDBusTray() {
+ // There's no other tray implementation to fallback to on non-X11
+ // and QDBusTrayIcon can register the icon on the fly after creation
+ if (QGuiApplication::platformName() != "xcb"_L1)
+ return true;
+ const bool result = QDBusMenuConnection().isWatcherRegistered();
+ qCDebug(qLcTray) << "D-Bus tray available:" << result;
+ return result;
}
#endif
@@ -476,7 +473,7 @@ QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
{
- if (isDBusTrayAvailable())
+ if (shouldUseDBusTray())
return new QDBusTrayIcon();
return nullptr;
}
@@ -541,6 +538,48 @@ class QKdeThemePrivate : public QPlatformThemePrivate
{
public:
+ enum class KdeSettingType {
+ Root,
+ KDE,
+ Icons,
+ ToolBarIcons,
+ ToolBarStyle,
+ Fonts,
+ Colors,
+ };
+
+ enum class KdeSetting {
+ WidgetStyle,
+ ColorScheme,
+ SingleClick,
+ ShowIconsOnPushButtons,
+ IconTheme,
+ ToolBarIconSize,
+ ToolButtonStyle,
+ WheelScrollLines,
+ DoubleClickInterval,
+ StartDragDistance,
+ StartDragTime,
+ CursorBlinkRate,
+ Font,
+ Fixed,
+ MenuFont,
+ ToolBarFont,
+ ButtonBackground,
+ WindowBackground,
+ ViewForeground,
+ WindowForeground,
+ ViewBackground,
+ SelectionBackground,
+ SelectionForeground,
+ ViewBackgroundAlternate,
+ ButtonForeground,
+ ViewForegroundLink,
+ ViewForegroundVisited,
+ TooltipBackground,
+ TooltipForeground,
+ };
+
QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
@@ -551,7 +590,9 @@ public:
}
void refresh();
- static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings);
+ static QVariant readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &settings);
+ QVariant readKdeSetting(KdeSetting s) const;
+ void clearKdeSettings() const;
static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal);
static QFont *kdeFont(const QVariant &fontValue);
static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs);
@@ -575,8 +616,9 @@ public:
Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
void updateColorScheme(const QString &themeName);
-#ifndef QT_NO_DBUS
private:
+ mutable QHash<QString, QSettings *> kdeSettings;
+#ifndef QT_NO_DBUS
std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
bool initDbus();
void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
@@ -632,9 +674,136 @@ QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
#endif // QT_NO_DBUS
}
+static constexpr QLatin1StringView settingsPrefix(QKdeThemePrivate::KdeSettingType type)
+{
+ switch (type) {
+ case QKdeThemePrivate::KdeSettingType::Root:
+ return QLatin1StringView();
+ case QKdeThemePrivate::KdeSettingType::KDE:
+ return QLatin1StringView("KDE/");
+ case QKdeThemePrivate::KdeSettingType::Fonts:
+ return QLatin1StringView();
+ case QKdeThemePrivate::KdeSettingType::Colors:
+ return QLatin1StringView("Colors:");
+ case QKdeThemePrivate::KdeSettingType::Icons:
+ return QLatin1StringView("Icons/");
+ case QKdeThemePrivate::KdeSettingType::ToolBarIcons:
+ return QLatin1StringView("ToolbarIcons/");
+ case QKdeThemePrivate::KdeSettingType::ToolBarStyle:
+ return QLatin1StringView("Toolbar style/");
+ }
+ Q_UNREACHABLE_RETURN(QLatin1StringView());
+}
+
+static constexpr QKdeThemePrivate::KdeSettingType settingsType(QKdeThemePrivate::KdeSetting setting)
+{
+#define CASE(s, type) case QKdeThemePrivate::KdeSetting::s:\
+ return QKdeThemePrivate::KdeSettingType::type
+
+ switch (setting) {
+ CASE(WidgetStyle, Root);
+ CASE(ColorScheme, Root);
+ CASE(SingleClick, KDE);
+ CASE(ShowIconsOnPushButtons, KDE);
+ CASE(IconTheme, Icons);
+ CASE(ToolBarIconSize, ToolBarIcons);
+ CASE(ToolButtonStyle, ToolBarStyle);
+ CASE(WheelScrollLines, KDE);
+ CASE(DoubleClickInterval, KDE);
+ CASE(StartDragDistance, KDE);
+ CASE(StartDragTime, KDE);
+ CASE(CursorBlinkRate, KDE);
+ CASE(Font, Root);
+ CASE(Fixed, Root);
+ CASE(MenuFont, Root);
+ CASE(ToolBarFont, Root);
+ CASE(ButtonBackground, Colors);
+ CASE(WindowBackground, Colors);
+ CASE(ViewForeground, Colors);
+ CASE(WindowForeground, Colors);
+ CASE(ViewBackground, Colors);
+ CASE(SelectionBackground, Colors);
+ CASE(SelectionForeground, Colors);
+ CASE(ViewBackgroundAlternate, Colors);
+ CASE(ButtonForeground, Colors);
+ CASE(ViewForegroundLink, Colors);
+ CASE(ViewForegroundVisited, Colors);
+ CASE(TooltipBackground, Colors);
+ CASE(TooltipForeground, Colors);
+ };
+ Q_UNREACHABLE_RETURN(QKdeThemePrivate::KdeSettingType::Root);
+}
+#undef CASE
+
+static constexpr QLatin1StringView settingsKey(QKdeThemePrivate::KdeSetting setting)
+{
+ switch (setting) {
+ case QKdeThemePrivate::KdeSetting::WidgetStyle:
+ return QLatin1StringView("widgetStyle");
+ case QKdeThemePrivate::KdeSetting::ColorScheme:
+ return QLatin1StringView("ColorScheme");
+ case QKdeThemePrivate::KdeSetting::SingleClick:
+ return QLatin1StringView("SingleClick");
+ case QKdeThemePrivate::KdeSetting::ShowIconsOnPushButtons:
+ return QLatin1StringView("ShowIconsOnPushButtons");
+ case QKdeThemePrivate::KdeSetting::IconTheme:
+ return QLatin1StringView("Theme");
+ case QKdeThemePrivate::KdeSetting::ToolBarIconSize:
+ return QLatin1StringView("Size");
+ case QKdeThemePrivate::KdeSetting::ToolButtonStyle:
+ return QLatin1StringView("ToolButtonStyle");
+ case QKdeThemePrivate::KdeSetting::WheelScrollLines:
+ return QLatin1StringView("WheelScrollLines");
+ case QKdeThemePrivate::KdeSetting::DoubleClickInterval:
+ return QLatin1StringView("DoubleClickInterval");
+ case QKdeThemePrivate::KdeSetting::StartDragDistance:
+ return QLatin1StringView("StartDragDist");
+ case QKdeThemePrivate::KdeSetting::StartDragTime:
+ return QLatin1StringView("StartDragTime");
+ case QKdeThemePrivate::KdeSetting::CursorBlinkRate:
+ return QLatin1StringView("CursorBlinkRate");
+ case QKdeThemePrivate::KdeSetting::Font:
+ return QLatin1StringView("font");
+ case QKdeThemePrivate::KdeSetting::Fixed:
+ return QLatin1StringView("fixed");
+ case QKdeThemePrivate::KdeSetting::MenuFont:
+ return QLatin1StringView("menuFont");
+ case QKdeThemePrivate::KdeSetting::ToolBarFont:
+ return QLatin1StringView("toolBarFont");
+ case QKdeThemePrivate::KdeSetting::ButtonBackground:
+ return QLatin1StringView("Button/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::WindowBackground:
+ return QLatin1StringView("Window/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewForeground:
+ return QLatin1StringView("View/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::WindowForeground:
+ return QLatin1StringView("Window/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewBackground:
+ return QLatin1StringView("View/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::SelectionBackground:
+ return QLatin1StringView("Selection/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::SelectionForeground:
+ return QLatin1StringView("Selection/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewBackgroundAlternate:
+ return QLatin1StringView("View/BackgroundAlternate");
+ case QKdeThemePrivate::KdeSetting::ButtonForeground:
+ return QLatin1StringView("Button/ForegroundNormal");
+ case QKdeThemePrivate::KdeSetting::ViewForegroundLink:
+ return QLatin1StringView("View/ForegroundLink");
+ case QKdeThemePrivate::KdeSetting::ViewForegroundVisited:
+ return QLatin1StringView("View/ForegroundVisited");
+ case QKdeThemePrivate::KdeSetting::TooltipBackground:
+ return QLatin1StringView("Tooltip/BackgroundNormal");
+ case QKdeThemePrivate::KdeSetting::TooltipForeground:
+ return QLatin1StringView("Tooltip/ForegroundNormal");
+ };
+ Q_UNREACHABLE_RETURN(QLatin1StringView());
+}
+
void QKdeThemePrivate::refresh()
{
resources.clear();
+ clearKdeSettings();
toolButtonStyle = Qt::ToolButtonTextBesideIcon;
toolBarIconSize = 0;
@@ -647,45 +816,39 @@ void QKdeThemePrivate::refresh()
else
iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen");
- QHash<QString, QSettings*> kdeSettings;
-
QPalette systemPalette = QPalette();
readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, &systemPalette);
resources.palettes[QPlatformTheme::SystemPalette] = new QPalette(systemPalette);
//## TODO tooltip color
- const QVariant styleValue = readKdeSetting(QStringLiteral("widgetStyle"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant styleValue = readKdeSetting(KdeSetting::WidgetStyle);
if (styleValue.isValid()) {
const QString style = styleValue.toString();
if (style != styleNames.front())
styleNames.push_front(style);
}
- const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs,
- kdeVersion, kdeSettings);
+ const QVariant colorScheme = readKdeSetting(KdeSetting::ColorScheme);
- if (colorScheme.isValid())
- updateColorScheme(colorScheme.toString());
- else
- m_colorScheme = Qt::ColorScheme::Unknown;
+ updateColorScheme(colorScheme.toString());
- const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant singleClickValue = readKdeSetting(KdeSetting::SingleClick);
if (singleClickValue.isValid())
singleClick = singleClickValue.toBool();
- const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant showIconsOnPushButtonsValue = readKdeSetting(KdeSetting::ShowIconsOnPushButtons);
if (showIconsOnPushButtonsValue.isValid())
showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool();
- const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant themeValue = readKdeSetting(KdeSetting::IconTheme);
if (themeValue.isValid())
iconThemeName = themeValue.toString();
- const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant toolBarIconSizeValue = readKdeSetting(KdeSetting::ToolBarIconSize);
if (toolBarIconSizeValue.isValid())
toolBarIconSize = toolBarIconSizeValue.toInt();
- const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant toolbarStyleValue = readKdeSetting(KdeSetting::ToolButtonStyle);
if (toolbarStyleValue.isValid()) {
const QString toolBarStyle = toolbarStyleValue.toString();
if (toolBarStyle == "TextBesideIcon"_L1)
@@ -696,35 +859,35 @@ void QKdeThemePrivate::refresh()
toolButtonStyle = Qt::ToolButtonTextUnderIcon;
}
- const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant wheelScrollLinesValue = readKdeSetting(KdeSetting::WheelScrollLines);
if (wheelScrollLinesValue.isValid())
wheelScrollLines = wheelScrollLinesValue.toInt();
- const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant doubleClickIntervalValue = readKdeSetting(KdeSetting::DoubleClickInterval);
if (doubleClickIntervalValue.isValid())
doubleClickInterval = doubleClickIntervalValue.toInt();
- const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant startDragDistValue = readKdeSetting(KdeSetting::StartDragDistance);
if (startDragDistValue.isValid())
startDragDist = startDragDistValue.toInt();
- const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant startDragTimeValue = readKdeSetting(KdeSetting::StartDragTime);
if (startDragTimeValue.isValid())
startDragTime = startDragTimeValue.toInt();
- const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate"), kdeDirs, kdeVersion, kdeSettings);
+ const QVariant cursorBlinkRateValue = readKdeSetting(KdeSetting::CursorBlinkRate);
if (cursorBlinkRateValue.isValid()) {
cursorBlinkRate = cursorBlinkRateValue.toInt();
cursorBlinkRate = cursorBlinkRate > 0 ? qBound(200, cursorBlinkRate, 2000) : 0;
}
// Read system font, ignore 'smallestReadableFont'
- if (QFont *systemFont = kdeFont(readKdeSetting(QStringLiteral("font"), kdeDirs, kdeVersion, kdeSettings)))
+ if (QFont *systemFont = kdeFont(readKdeSetting(KdeSetting::Font)))
resources.fonts[QPlatformTheme::SystemFont] = systemFont;
else
resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize);
- if (QFont *fixedFont = kdeFont(readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (QFont *fixedFont = kdeFont(readKdeSetting(KdeSetting::Fixed))) {
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
} else {
fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize);
@@ -732,12 +895,12 @@ void QKdeThemePrivate::refresh()
resources.fonts[QPlatformTheme::FixedFont] = fixedFont;
}
- if (QFont *menuFont = kdeFont(readKdeSetting(QStringLiteral("menuFont"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (QFont *menuFont = kdeFont(readKdeSetting(KdeSetting::MenuFont))) {
resources.fonts[QPlatformTheme::MenuFont] = menuFont;
resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont);
}
- if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings)))
+ if (QFont *toolBarFont = kdeFont(readKdeSetting(KdeSetting::ToolBarFont)))
resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
QWindowSystemInterface::handleThemeChange();
@@ -747,7 +910,7 @@ void QKdeThemePrivate::refresh()
qDeleteAll(kdeSettings);
}
-QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
+QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings)
{
for (const QString &kdeDir : kdeDirs) {
QSettings *settings = kdeSettings.value(kdeDir);
@@ -759,6 +922,7 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList
}
}
if (settings) {
+ const QString key = settingsPrefix(settingsType(s)) + settingsKey(s);
const QVariant value = settings->value(key);
if (value.isValid())
return value;
@@ -767,6 +931,16 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList
return QVariant();
}
+QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s) const
+{
+ return readKdeSetting(s, kdeDirs, kdeVersion, kdeSettings);
+}
+
+void QKdeThemePrivate::clearKdeSettings() const
+{
+ kdeSettings.clear();
+}
+
// Reads the color from the KDE configuration, and store it in the
// palette with the given color role if found.
static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value)
@@ -782,7 +956,7 @@ static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVari
void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal)
{
- if (!kdeColor(pal, QPalette::Button, readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings))) {
+ if (!kdeColor(pal, QPalette::Button, readKdeSetting(KdeSetting::ButtonBackground, kdeDirs, kdeVersion, kdeSettings))) {
// kcolorscheme.cpp: SetDefaultColors
const QColor defaultWindowBackground(214, 210, 208);
const QColor defaultButtonBackground(223, 220, 217);
@@ -790,18 +964,18 @@ void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeV
return;
}
- kdeColor(pal, QPalette::Window, readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Text, readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::WindowText, readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Base, readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Highlight, readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::HighlightedText, readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::AlternateBase, readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ButtonText, readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::Link, readKdeSetting(QStringLiteral("Colors:View/ForegroundLink"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::LinkVisited, readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
- kdeColor(pal, QPalette::ToolTipText, readKdeSetting(QStringLiteral("Colors:Tooltip/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Window, readKdeSetting(KdeSetting::WindowBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Text, readKdeSetting(KdeSetting::ViewForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::WindowText, readKdeSetting(KdeSetting::WindowForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Base, readKdeSetting(KdeSetting::ViewBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Highlight, readKdeSetting(KdeSetting::SelectionBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::HighlightedText, readKdeSetting(KdeSetting::SelectionForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::AlternateBase, readKdeSetting(KdeSetting::ViewBackgroundAlternate, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ButtonText, readKdeSetting(KdeSetting::ButtonForeground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::Link, readKdeSetting(KdeSetting::ViewForegroundLink, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::LinkVisited, readKdeSetting(KdeSetting::ViewForegroundVisited, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(KdeSetting::TooltipBackground, kdeDirs, kdeVersion, kdeSettings));
+ kdeColor(pal, QPalette::ToolTipText, readKdeSetting(KdeSetting::TooltipForeground, kdeDirs, kdeVersion, kdeSettings));
// The above code sets _all_ color roles to "normal" colors. In KDE, the disabled
// color roles are calculated by applying various effects described in kdeglobals.
@@ -957,13 +1131,13 @@ Qt::ColorScheme QKdeTheme::colorScheme() const
/*!
\internal
- \brief QKdeTheme::updateColorScheme - guess and set appearance for unix themes.
- KDE themes do not have an appearance property.
- The key words "dark" or "light" should be part of the theme name.
+ \brief QKdeTheme::updateColorScheme - guess and set a color scheme for unix themes.
+ KDE themes do not have a color scheme property.
+ The key words "dark" or "light" are usually part of the theme name.
This is, however, not a mandatory convention.
- If \param themeName contains a key word, the respective appearance is set.
- If it doesn't, the appearance is heuristically determined by comparing text and base color
+ If \param themeName contains a valid key word, the respective color scheme is set.
+ If it doesn't, the color scheme is heuristically determined by comparing text and base color
of the system palette.
*/
void QKdeThemePrivate::updateColorScheme(const QString &themeName)
@@ -991,7 +1165,6 @@ void QKdeThemePrivate::updateColorScheme(const QString &themeName)
m_colorScheme = Qt::ColorScheme::Unknown;
}
-
const QPalette *QKdeTheme::palette(Palette type) const
{
Q_D(const QKdeTheme);
@@ -1071,7 +1244,7 @@ QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() const
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const
{
- if (isDBusTrayAvailable())
+ if (shouldUseDBusTray())
return new QDBusTrayIcon();
return nullptr;
}
@@ -1266,7 +1439,7 @@ Qt::ColorScheme QGnomeTheme::colorScheme() const
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
{
- if (isDBusTrayAvailable())
+ if (shouldUseDBusTray())
return new QDBusTrayIcon();
return nullptr;
}
diff --git a/src/gui/qt_cmdline.cmake b/src/gui/qt_cmdline.cmake
index 379cc417e7..446618ebc4 100644
--- a/src/gui/qt_cmdline.cmake
+++ b/src/gui/qt_cmdline.cmake
@@ -27,7 +27,8 @@ qt_commandline_option(opengl TYPE optionalString VALUES no yes desktop es2 dynam
qt_commandline_option(opengl-es-2 TYPE void NAME opengl VALUE es2)
qt_commandline_option(opengles3 TYPE boolean)
qt_commandline_option(openvg TYPE boolean)
-qt_commandline_option(qpa TYPE string NAME qpa_default_platform)
+qt_commandline_option(qpa TYPE string NAME qpa_platforms)
+qt_commandline_option(default-qpa TYPE string NAME qpa_default_platform)
qt_commandline_option(sm TYPE boolean NAME sessionmanager)
qt_commandline_option(tslib TYPE boolean)
qt_commandline_option(vulkan TYPE boolean)
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 62830c291d..7e886a5d00 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -1096,6 +1096,8 @@ bool QRhiGles2::create(QRhi::Flags flags)
caps.glesMultiviewMultisampleRenderToTexture = false;
}
+ caps.unpackRowLength = !caps.gles || caps.ctxMajor >= 3;
+
nativeHandlesStruct.context = ctx;
contextLost = false;
@@ -1426,7 +1428,7 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
case QRhi::PipelineCacheDataLoadSave:
return caps.programBinary;
case QRhi::ImageDataStride:
- return !caps.gles || caps.ctxMajor >= 3;
+ return caps.unpackRowLength;
case QRhi::RenderBufferImport:
return true;
case QRhi::ThreeDimensionalTextures:
@@ -2363,7 +2365,7 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
dataStride = bytesPerLine;
cmd.args.subImage.rowStartAlign = (dataStride & 3) ? 1 : 4;
- cmd.args.subImage.rowLength = bytesPerPixel ? dataStride / bytesPerPixel : 0;
+ cmd.args.subImage.rowLength = caps.unpackRowLength ? (bytesPerPixel ? dataStride / bytesPerPixel : 0) : 0;
cmd.args.subImage.data = data;
};
@@ -2375,7 +2377,15 @@ void QRhiGles2::enqueueSubresUpload(QGles2Texture *texD, QGles2CommandBuffer *cb
const QPoint sp = subresDesc.sourceTopLeft();
if (!subresDesc.sourceSize().isEmpty())
size = subresDesc.sourceSize();
- img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+
+ if (caps.unpackRowLength) {
+ cbD->retainImage(img);
+ // create a non-owning wrapper for the subimage
+ const uchar *data = img.constBits() + sp.y() * img.bytesPerLine() + sp.x() * (qMax(1, img.depth() / 8));
+ img = QImage(data, size.width(), size.height(), img.bytesPerLine(), img.format());
+ } else {
+ img = img.copy(sp.x(), sp.y(), size.width(), size.height());
+ }
}
setCmdByNotCompressedData(cbD->retainImage(img), size, img.bytesPerLine());
@@ -4007,7 +4017,7 @@ void QRhiGles2::bindCombinedSampler(QGles2CommandBuffer *cbD, QGles2Texture *tex
f->glTexParameteri(texD->target, GL_TEXTURE_MAG_FILTER, GLint(samplerD->d.glmagfilter));
f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_S, GLint(samplerD->d.glwraps));
f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_T, GLint(samplerD->d.glwrapt));
- if (caps.texture3D)
+ if (caps.texture3D && texD->target == GL_TEXTURE_3D)
f->glTexParameteri(texD->target, GL_TEXTURE_WRAP_R, GLint(samplerD->d.glwrapr));
if (caps.textureCompareMode) {
if (samplerD->d.gltexcomparefunc != GL_NEVER) {
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
index 4305186c02..4139579864 100644
--- a/src/gui/rhi/qrhigles2_p.h
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -1014,7 +1014,10 @@ public:
halfAttributes(false),
multiView(false),
timestamps(false),
- objectLabel(false)
+ objectLabel(false),
+ glesMultisampleRenderToTexture(false),
+ glesMultiviewMultisampleRenderToTexture(false),
+ unpackRowLength(false)
{ }
int ctxMajor;
int ctxMinor;
@@ -1073,6 +1076,7 @@ public:
uint objectLabel : 1;
uint glesMultisampleRenderToTexture : 1;
uint glesMultiviewMultisampleRenderToTexture : 1;
+ uint unpackRowLength : 1;
} caps;
QGles2SwapChain *currentSwapChain = nullptr;
QSet<GLint> supportedCompressedFormats;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index b99afc596c..887d40c7a5 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -13,6 +13,7 @@
#include <QOperatingSystemVersion>
#include <QtCore/private/qcore_mac_p.h>
+#include <QtGui/private/qmetallayer_p.h>
#ifdef Q_OS_MACOS
#include <AppKit/AppKit.h>
@@ -20,8 +21,9 @@
#include <UIKit/UIKit.h>
#endif
+#include <QuartzCore/CATransaction.h>
+
#include <Metal/Metal.h>
-#include <QuartzCore/CAMetalLayer.h>
QT_BEGIN_NAMESPACE
@@ -461,11 +463,6 @@ struct QMetalSwapChainData
id<MTLTexture> msaaTex[QMTL_FRAMES_IN_FLIGHT];
QRhiTexture::Format rhiColorFormat;
MTLPixelFormat colorFormat;
-#ifdef Q_OS_MACOS
- bool liveResizeObserverSet = false;
- QMacNotificationObserver liveResizeStartObserver;
- QMacNotificationObserver liveResizeEndObserver;
-#endif
};
QRhiMetal::QRhiMetal(QRhiMetalInitParams *params, QRhiMetalNativeHandles *importDevice)
@@ -2396,8 +2393,11 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
QMetalSwapChain *swapChainD = QRHI_RES(QMetalSwapChain, swapChain);
Q_ASSERT(currentSwapChain == swapChainD);
+ // Keep strong reference to command buffer
+ id<MTLCommandBuffer> commandBuffer = swapChainD->cbWrapper.d->cb;
+
__block int thisFrameSlot = currentFrameSlot;
- [swapChainD->cbWrapper.d->cb addCompletedHandler: ^(id<MTLCommandBuffer> cb) {
+ [commandBuffer addCompletedHandler: ^(id<MTLCommandBuffer> cb) {
swapChainD->d->lastGpuTime[thisFrameSlot] += cb.GPUEndTime - cb.GPUStartTime;
dispatch_semaphore_signal(swapChainD->d->sem[thisFrameSlot]);
}];
@@ -2407,30 +2407,75 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
// released before the command buffer is done with it. Manually keep it alive
// to work around this.
id<MTLTexture> drawableTexture = [swapChainD->d->curDrawable.texture retain];
- [swapChainD->cbWrapper.d->cb addCompletedHandler:^(id<MTLCommandBuffer>) {
+ [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer>) {
[drawableTexture release];
}];
#endif
- const bool needsPresent = !flags.testFlag(QRhi::SkipPresent);
- const bool presentsWithTransaction = swapChainD->d->layer.presentsWithTransaction;
- if (!presentsWithTransaction && needsPresent) {
- // beginFrame-endFrame without a render pass inbetween means there is no drawable.
- if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable)
- [swapChainD->cbWrapper.d->cb presentDrawable: drawable];
- }
-
- [swapChainD->cbWrapper.d->cb commit];
-
- if (presentsWithTransaction && needsPresent) {
- // beginFrame-endFrame without a render pass inbetween means there is no drawable.
+ if (flags.testFlag(QRhi::SkipPresent)) {
+ // Just need to commit, that's it
+ [commandBuffer commit];
+ } else {
if (id<CAMetalDrawable> drawable = swapChainD->d->curDrawable) {
- // The layer has presentsWithTransaction set to true to avoid flicker on resizing,
- // so here it is important to follow what the Metal docs say when it comes to the
- // issuing the present.
- [swapChainD->cbWrapper.d->cb waitUntilScheduled];
- [drawable present];
+ // Got something to present
+ if (swapChainD->d->layer.presentsWithTransaction) {
+ [commandBuffer commit];
+ // Keep strong reference to Metal layer
+ auto *metalLayer = swapChainD->d->layer;
+ auto presentWithTransaction = ^{
+ [commandBuffer waitUntilScheduled];
+ // If the layer has been resized while we waited to be scheduled we bail out,
+ // as the drawable is no longer valid for the layer, and we'll get a follow-up
+ // display with the right size. We know we are on the main thread here, which
+ // means we can access the layer directly. We also know that the layer is valid,
+ // since the block keeps a strong reference to it, compared to the QRhiSwapChain
+ // that can go away under our feet by the time we're scheduled.
+ const auto surfaceSize = QSizeF::fromCGSize(metalLayer.bounds.size) * metalLayer.contentsScale;
+ const auto textureSize = QSizeF(drawable.texture.width, drawable.texture.height);
+ if (textureSize == surfaceSize) {
+ [drawable present];
+ } else {
+ qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable << "due to texture size"
+ << textureSize << "not matching surface size" << surfaceSize;
+ }
+ };
+
+ if (NSThread.currentThread == NSThread.mainThread) {
+ presentWithTransaction();
+ } else {
+ auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
+ Q_ASSERT(qtMetalLayer);
+ // Let the main thread present the drawable from displayLayer
+ qtMetalLayer.mainThreadPresentation = presentWithTransaction;
+ }
+ } else {
+ // Keep strong reference to Metal layer so it's valid in the block
+ auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(swapChainD->d->layer);
+ [commandBuffer addScheduledHandler:^(id<MTLCommandBuffer>) {
+ if (qtMetalLayer) {
+ // The schedule handler comes in on the com.Metal.CompletionQueueDispatch
+ // thread, which means we might be racing against a display cycle on the
+ // main thread. If the displayLayer is already in progress, we don't want
+ // to step on its toes.
+ if (qtMetalLayer.displayLock.tryLockForRead()) {
+ [drawable present];
+ qtMetalLayer.displayLock.unlock();
+ } else {
+ qCDebug(QRHI_LOG_INFO) << "Skipping" << drawable
+ << "due to" << qtMetalLayer << "needing display";
+ }
+ } else {
+ [drawable present];
+ }
+ }];
+ [commandBuffer commit];
+ }
+ } else {
+ // Still need to commit, even if we don't have a drawable
+ [commandBuffer commit];
}
+
+ swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
}
// Must not hold on to the drawable, regardless of needsPresent
@@ -2439,9 +2484,6 @@ QRhi::FrameOpResult QRhiMetal::endFrame(QRhiSwapChain *swapChain, QRhi::EndFrame
[d->captureScope endScope];
- if (needsPresent)
- swapChainD->currentFrameSlot = (swapChainD->currentFrameSlot + 1) % QMTL_FRAMES_IN_FLIGHT;
-
swapChainD->frameCount += 1;
currentSwapChain = nullptr;
return QRhi::FrameOpSuccess;
@@ -2589,7 +2631,6 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
int w = img.width();
int h = img.height();
int bpl = img.bytesPerLine();
- int srcOffset = 0;
if (!subresDesc.sourceSize().isEmpty() || !subresDesc.sourceTopLeft().isNull()) {
const int sx = subresDesc.sourceTopLeft().x();
@@ -2598,10 +2639,12 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
w = subresDesc.sourceSize().width();
h = subresDesc.sourceSize().height();
}
- if (img.depth() == 32) {
- memcpy(reinterpret_cast<char *>(mp) + *curOfs, img.constBits(), size_t(fullImageSizeBytes));
- srcOffset = sy * bpl + sx * 4;
- // bpl remains set to the original image's row stride
+ if (w == img.width()) {
+ const int bpc = qMax(1, img.depth() / 8);
+ Q_ASSERT(h * img.bytesPerLine() <= fullImageSizeBytes);
+ memcpy(reinterpret_cast<char *>(mp) + *curOfs,
+ img.constBits() + sy * img.bytesPerLine() + sx * bpc,
+ h * img.bytesPerLine());
} else {
img = img.copy(sx, sy, w, h);
bpl = img.bytesPerLine();
@@ -2613,7 +2656,7 @@ void QRhiMetal::enqueueSubresUpload(QMetalTexture *texD, void *mp, void *blitEnc
}
[blitEnc copyFromBuffer: texD->d->stagingBuf[currentFrameSlot]
- sourceOffset: NSUInteger(*curOfs + srcOffset)
+ sourceOffset: NSUInteger(*curOfs)
sourceBytesPerRow: NSUInteger(bpl)
sourceBytesPerImage: 0
sourceSize: MTLSizeMake(NSUInteger(w), NSUInteger(h), 1)
@@ -2981,9 +3024,11 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
cbD->d->currentPassRpDesc.depthAttachment.loadAction = MTLLoadActionLoad;
cbD->d->currentPassRpDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
}
+ int colorAttCount = 0;
for (auto it = rtTex->m_desc.cbeginColorAttachments(), itEnd = rtTex->m_desc.cendColorAttachments();
it != itEnd; ++it)
{
+ colorAttCount += 1;
if (it->texture()) {
QRHI_RES(QMetalTexture, it->texture())->lastActiveFrameSlot = currentFrameSlot;
if (it->multiViewCount() >= 2)
@@ -2996,8 +3041,12 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
}
if (rtTex->m_desc.depthStencilBuffer())
QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
- if (rtTex->m_desc.depthTexture())
- QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtTex->m_desc.depthTexture()) {
+ QMetalTexture *depthTexture = QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture());
+ depthTexture->lastActiveFrameSlot = currentFrameSlot;
+ if (colorAttCount == 0 && depthTexture->arraySize() >= 2)
+ cbD->d->currentPassRpDesc.renderTargetArrayLength = NSUInteger(depthTexture->arraySize());
+ }
if (rtTex->m_desc.depthResolveTexture())
QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
}
@@ -4850,7 +4899,7 @@ void QMetalGraphicsPipeline::setupAttachmentsInMetalRenderPassDescriptor(void *m
}
QRHI_RES_RHI(QRhiMetal);
- rpDesc.sampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
+ rpDesc.rasterSampleCount = NSUInteger(rhiD->effectiveSampleCount(m_sampleCount));
}
void QMetalGraphicsPipeline::setupMetalDepthStencilDescriptor(void *metalDsDesc)
@@ -6161,12 +6210,6 @@ void QMetalSwapChain::destroy()
d->msaaTex[i] = nil;
}
-#ifdef Q_OS_MACOS
- d->liveResizeStartObserver.remove();
- d->liveResizeEndObserver.remove();
- d->liveResizeObserverSet = false;
-#endif
-
d->layer = nullptr;
m_proxyData = {};
@@ -6373,34 +6416,6 @@ bool QMetalSwapChain::createOrResize()
[d->layer setDevice: rhiD->d->dev];
-#ifdef Q_OS_MACOS
- // Can only use presentsWithTransaction (to get smooth resizing) when
- // presenting from the main (gui) thread. We predict that based on the
- // thread this function is called on since if the QRhiSwapChain is
- // initialied on a given thread then that's almost certainly the thread on
- // which the QRhi renders and presents.
- const bool canUsePresentsWithTransaction = NSThread.isMainThread;
-
- // Have an env.var. just in case it turns out presentsWithTransaction is
- // not desired in some specific case.
- static bool allowPresentsWithTransaction = !qEnvironmentVariableIntValue("QT_MTL_NO_TRANSACTION");
-
- if (allowPresentsWithTransaction && canUsePresentsWithTransaction && !d->liveResizeObserverSet) {
- d->liveResizeObserverSet = true;
- NSView *view = reinterpret_cast<NSView *>(window->winId());
- NSWindow *window = view.window;
- if (window) {
- qCDebug(QRHI_LOG_INFO, "will set presentsWithTransaction during live resize");
- d->liveResizeStartObserver = QMacNotificationObserver(window, NSWindowWillStartLiveResizeNotification, [this] {
- d->layer.presentsWithTransaction = true;
- });
- d->liveResizeEndObserver = QMacNotificationObserver(window, NSWindowDidEndLiveResizeNotification, [this] {
- d->layer.presentsWithTransaction = false;
- });
- }
- }
-#endif
-
[d->curDrawable release];
d->curDrawable = nil;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index f0b51146cc..3dd3c57bd4 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -1598,8 +1598,8 @@ struct RenderPass2SetupHelper
#endif // VK_KHR_create_renderpass2
bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
- const QRhiColorAttachment *firstColorAttachment,
- const QRhiColorAttachment *lastColorAttachment,
+ const QRhiColorAttachment *colorAttachmentsBegin,
+ const QRhiColorAttachment *colorAttachmentsEnd,
bool preserveColor,
bool preserveDs,
bool storeDs,
@@ -1610,7 +1610,7 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
// attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1)
int multiViewCount = 0;
- for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
+ for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
QVkTexture *texD = QRHI_RES(QVkTexture, it->texture());
QVkRenderBuffer *rbD = QRHI_RES(QVkRenderBuffer, it->renderBuffer());
Q_ASSERT(texD || rbD);
@@ -1662,10 +1662,14 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
attDesc.initialLayout = preserveDs ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED;
attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
rpD->attDescs.append(attDesc);
+ if (depthTexture && depthTexture->arraySize() >= 2 && colorAttachmentsBegin == colorAttachmentsEnd) {
+ multiViewCount = depthTexture->arraySize();
+ rpD->multiViewCount = multiViewCount;
+ }
}
rpD->dsRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
- for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
+ for (auto it = colorAttachmentsBegin; it != colorAttachmentsEnd; ++it) {
if (it->resolveTexture()) {
QVkTexture *rtexD = QRHI_RES(QVkTexture, it->resolveTexture());
const VkFormat dstFormat = rtexD->vkformat;
@@ -3497,12 +3501,12 @@ void QRhiVulkan::prepareUploadSubres(QVkTexture *texD, int layer, int level,
const int sy = subresDesc.sourceTopLeft().y();
if (!subresDesc.sourceSize().isEmpty())
size = subresDesc.sourceSize();
- if (image.depth() == 32) {
- // The staging buffer will get the full image
- // regardless, just adjust the vk
- // buffer-to-image copy start offset.
- copyInfo.bufferOffset += VkDeviceSize(sy * image.bytesPerLine() + sx * 4);
- // bufferRowLength remains set to the original image's width
+
+ if (size.width() == image.width()) {
+ // No need to make a QImage copy here, can copy from the source
+ // QImage into staging directly.
+ src = image.constBits() + sy * image.bytesPerLine() + sx * bpc;
+ copySizeBytes = size.height() * image.bytesPerLine();
} else {
image = image.copy(sx, sy, size.width(), size.height());
src = image.constBits();
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index ad8687de5d..f23d8550f0 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -773,8 +773,8 @@ public:
VkSampleCountFlagBits samples,
VkFormat colorFormat);
bool createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
- const QRhiColorAttachment *firstColorAttachment,
- const QRhiColorAttachment *lastColorAttachment,
+ const QRhiColorAttachment *colorAttachmentsBegin,
+ const QRhiColorAttachment *colorAttachmentsEnd,
bool preserveColor,
bool preserveDs,
bool storeDs,
diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm
index 1050c03d75..87ed95f3ac 100644
--- a/src/gui/text/coretext/qfontengine_coretext.mm
+++ b/src/gui/text/coretext/qfontengine_coretext.mm
@@ -336,7 +336,10 @@ void QCoreTextFontEngine::initializeHeightMetrics() const
m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
- m_heightMetricsQueried = true;
+ if (preferTypoLineMetrics())
+ QFontEngine::initializeHeightMetrics();
+ else
+ m_heightMetricsQueried = true;
}
QFixed QCoreTextFontEngine::capHeight() const
diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp
index e5815ffce2..7886d9ba91 100644
--- a/src/gui/text/qcssparser.cpp
+++ b/src/gui/text/qcssparser.cpp
@@ -12,6 +12,7 @@
#include <qfontmetrics.h>
#include <qbrush.h>
#include <qimagereader.h>
+#include <qtextformat.h>
#include <algorithm>
@@ -35,6 +36,7 @@ struct QCssKnownValue
quint64 id;
};
+// This array is sorted alphabetically.
static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-background-role", QtBackgroundRole },
{ "-qt-block-indent", QtBlockIndent },
@@ -46,6 +48,11 @@ static const QCssKnownValue properties[NumProperties - 1] = {
{ "-qt-list-number-suffix", QtListNumberSuffix },
{ "-qt-paragraph-type", QtParagraphType },
{ "-qt-stroke-color", QtStrokeColor },
+ { "-qt-stroke-dasharray", QtStrokeDashArray },
+ { "-qt-stroke-dashoffset", QtStrokeDashOffset },
+ { "-qt-stroke-linecap", QtStrokeLineCap },
+ { "-qt-stroke-linejoin", QtStrokeLineJoin },
+ { "-qt-stroke-miterlimit", QtStrokeMiterLimit },
{ "-qt-stroke-width", QtStrokeWidth },
{ "-qt-style-features", QtStyleFeatures },
{ "-qt-table-type", QtTableType },
@@ -159,6 +166,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "always", Value_Always },
{ "auto", Value_Auto },
{ "base", Value_Base },
+ { "beveljoin", Value_BevelJoin},
{ "bold", Value_Bold },
{ "bottom", Value_Bottom },
{ "bright-text", Value_BrightText },
@@ -175,6 +183,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "dot-dot-dash", Value_DotDotDash },
{ "dotted", Value_Dotted },
{ "double", Value_Double },
+ { "flatcap", Value_FlatCap},
{ "groove", Value_Groove },
{ "highlight", Value_Highlight },
{ "highlighted-text", Value_HighlightedText },
@@ -193,6 +202,7 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "mid", Value_Mid },
{ "middle", Value_Middle },
{ "midlight", Value_Midlight },
+ { "miterjoin", Value_MiterJoin},
{ "native", Value_Native },
{ "none", Value_None },
{ "normal", Value_Normal },
@@ -207,14 +217,18 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
{ "pre-wrap", Value_PreWrap },
{ "ridge", Value_Ridge },
{ "right", Value_Right },
+ { "roundcap", Value_RoundCap},
+ { "roundjoin", Value_RoundJoin},
{ "selected", Value_Selected },
{ "shadow", Value_Shadow },
{ "small" , Value_Small },
{ "small-caps", Value_SmallCaps },
{ "solid", Value_Solid },
{ "square", Value_Square },
+ { "squarecap", Value_SquareCap},
{ "sub", Value_Sub },
{ "super", Value_Super },
+ { "svgmiterjoin", Value_SvgMiterJoin},
{ "text", Value_Text },
{ "top", Value_Top },
{ "transparent", Value_Transparent },
@@ -230,10 +244,10 @@ static const QCssKnownValue values[NumKnownValues - 1] = {
};
//Map id to strings as they appears in the 'values' array above
-static const short indexOfId[NumKnownValues] = { 0, 41, 48, 42, 49, 50, 55, 35, 26, 71, 72, 25, 43, 5, 64, 48,
- 29, 59, 60, 27, 52, 62, 6, 10, 39, 56, 19, 13, 17, 18, 20, 21, 51, 24, 46, 68, 37, 3, 2, 40, 63, 16,
- 11, 58, 14, 32, 65, 33, 66, 56, 67, 34, 70, 8, 28, 38, 12, 36, 61, 7, 9, 4, 69, 54, 22, 23, 30, 31,
- 1, 15, 0, 53, 45, 44 };
+static const short indexOfId[NumKnownValues] = { 0, 44, 51, 45, 52, 53, 60, 37, 28, 78, 79, 27, 46, 6, 71, 50,
+ 31, 65, 66, 29, 55, 69, 7, 11, 42, 62, 20, 14, 18, 19, 21, 23, 54, 26, 49, 75, 39, 3, 2, 43, 70, 17, 12,
+ 63, 15, 34, 72, 35, 73, 61, 74, 36, 64, 22, 56, 41, 5, 57, 67, 77, 9, 30, 40, 13, 38, 68, 8, 10, 4, 76,
+ 59, 24, 25, 32, 33, 1, 16, 0, 58, 48, 47 };
QString Value::toString() const
{
@@ -396,6 +410,8 @@ LengthData ValueExtractor::lengthValue(const Value& v)
if (data.unit != LengthData::None)
s.chop(2);
+ else if (v.type == Value::Percentage)
+ data.unit = LengthData::Percent;
data.number = s.toDouble();
return data;
@@ -409,6 +425,15 @@ static int lengthValueFromData(const LengthData& data, const QFont& f)
return qRound(qBound(double(INT_MIN) + 0.1, scale * data.number, double(INT_MAX)));
}
+QTextLength ValueExtractor::textLength(const Declaration &decl)
+{
+ const LengthData data = lengthValue(decl.d->values.at(0));
+ if (data.unit == LengthData::Percent)
+ return QTextLength(QTextLength::PercentageLength, data.number);
+
+ return QTextLength(QTextLength::FixedLength, lengthValueFromData(data, f));
+}
+
int ValueExtractor::lengthValue(const Declaration &decl)
{
if (decl.d->parsed.isValid())
@@ -1823,6 +1848,35 @@ bool Declaration::borderCollapseValue() const
return d->values.at(0).toString() == "collapse"_L1;
}
+QList<qreal> Declaration::dashArray() const
+{
+ if (d->propertyId != Property::QtStrokeDashArray || d->values.empty())
+ return QList<qreal>();
+
+ bool isValid = true;
+ QList<qreal> dashes;
+ for (int i = 0; i < d->values.size(); i++) {
+ Value v = d->values[i];
+ // Separators must be at odd indices and Numbers at even indices.
+ bool isValidSeparator = (i & 1) && v.type == Value::TermOperatorComma;
+ bool isValidNumber = !(i & 1) && v.type == Value::Number;
+ if (!isValidNumber && !isValidSeparator) {
+ isValid = false;
+ break;
+ } else if (isValidNumber) {
+ bool ok;
+ dashes.append(v.variant.toReal(&ok));
+ if (!ok) {
+ isValid = false;
+ break;
+ }
+ }
+ }
+
+ isValid &= !(dashes.size() & 1);
+ return isValid ? dashes : QList<qreal>();
+}
+
QIcon Declaration::iconValue() const
{
if (d->parsed.isValid())
diff --git a/src/gui/text/qcssparser_p.h b/src/gui/text/qcssparser_p.h
index c1cfb1ac9b..ba4a611df3 100644
--- a/src/gui/text/qcssparser_p.h
+++ b/src/gui/text/qcssparser_p.h
@@ -169,6 +169,11 @@ enum Property {
QtAccent,
QtStrokeWidth,
QtStrokeColor,
+ QtStrokeLineCap,
+ QtStrokeLineJoin,
+ QtStrokeMiterLimit,
+ QtStrokeDashArray,
+ QtStrokeDashOffset,
QtForeground,
NumProperties
};
@@ -226,6 +231,13 @@ enum KnownValue {
Value_SmallCaps,
Value_Uppercase,
Value_Lowercase,
+ Value_SquareCap,
+ Value_FlatCap,
+ Value_RoundCap,
+ Value_MiterJoin,
+ Value_BevelJoin,
+ Value_RoundJoin,
+ Value_SvgMiterJoin,
/* keep these in same order as QPalette::ColorRole */
Value_FirstColorRole,
@@ -392,7 +404,7 @@ QT_CSS_DECLARE_TYPEINFO(BackgroundData, Q_RELOCATABLE_TYPE)
struct LengthData {
qreal number;
- enum { None, Px, Ex, Em } unit;
+ enum { None, Px, Ex, Em, Percent } unit;
};
QT_CSS_DECLARE_TYPEINFO(LengthData, Q_PRIMITIVE_TYPE)
@@ -451,6 +463,8 @@ struct Q_GUI_EXPORT Declaration
void borderImageValue(QString *image, int *cuts, TileMode *h, TileMode *v) const;
bool borderCollapseValue() const;
+
+ QList<qreal> dashArray() const;
};
QT_CSS_DECLARE_TYPEINFO(Declaration, Q_RELOCATABLE_TYPE)
@@ -835,6 +849,7 @@ struct Q_GUI_EXPORT ValueExtractor
bool extractIcon(QIcon *icon, QSize *size);
void lengthValues(const Declaration &decl, int *m);
+ QTextLength textLength(const Declaration &decl);
private:
void extractFont();
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index f3b861957e..c8881a9bf8 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -1470,6 +1470,23 @@ QFont::StyleHint QFont::styleHint() const
font that matches the largest subset of the input string instead. This will be more
expensive for strings where missing glyphs occur, but may give more consistent results.
If \c NoFontMerging is set, then \c ContextFontMerging will have no effect.
+ \value [since 6.8] PreferTypoLineMetrics For compatibility reasons, OpenType fonts contain
+ two competing sets of the vertical line metrics that provide the
+ \l{QFontMetricsF::ascent()}{ascent}, \l{QFontMetricsF::descent()}{descent} and
+ \l{QFontMetricsF::leading()}{leading} of the font. These are often referred to as the
+ \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win}
+ (Windows) metrics and the
+ \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo}
+ (typographical) metrics. While the specification recommends using the \c typo metrics for
+ line spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS}
+ flag is set in the
+ \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection}
+ field of the font. For backwards-compatibility reasons, this is also the case for Qt
+ applications. This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to
+ indicate that the \c{typo} metrics are valid, nor for fonts where the \c{win} metrics
+ and \c{typo} metrics match up. However, for certain fonts the \c{win} metrics may be
+ larger than the preferable line spacing and the \c{USE_TYPO_METRICS} flag may be unset
+ by mistake. For such fonts, setting \c{PreferTypoLineMetrics} may give superior results.
\value NoFontMerging If the font selected for a certain writing system
does not contain a character requested to draw, then Qt automatically chooses a similar
looking font that contains the character. The NoFontMerging flag disables this feature.
@@ -2435,9 +2452,7 @@ std::optional<QFont::Tag> QFont::Tag::fromString(QAnyStringView view) noexcept
By default, no variable axes are set.
- \note In order to use variable axes on Windows, the application has to run with either the
- FreeType or DirectWrite font databases. See the documentation for
- QGuiApplication::QGuiApplication() for more information on how to select these technologies.
+ \note On Windows, variable axes are not supported if the optional GDI font backend is in use.
\sa unsetVariableAxis
*/
diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h
index 66a5f7c155..0b41e31bdb 100644
--- a/src/gui/text/qfont.h
+++ b/src/gui/text/qfont.h
@@ -36,19 +36,20 @@ public:
Q_ENUM(StyleHint)
enum StyleStrategy {
- PreferDefault = 0x0001,
- PreferBitmap = 0x0002,
- PreferDevice = 0x0004,
- PreferOutline = 0x0008,
- ForceOutline = 0x0010,
- PreferMatch = 0x0020,
- PreferQuality = 0x0040,
- PreferAntialias = 0x0080,
- NoAntialias = 0x0100,
- NoSubpixelAntialias = 0x0800,
- PreferNoShaping = 0x1000,
- ContextFontMerging = 0x2000,
- NoFontMerging = 0x8000
+ PreferDefault = 0x0001,
+ PreferBitmap = 0x0002,
+ PreferDevice = 0x0004,
+ PreferOutline = 0x0008,
+ ForceOutline = 0x0010,
+ PreferMatch = 0x0020,
+ PreferQuality = 0x0040,
+ PreferAntialias = 0x0080,
+ NoAntialias = 0x0100,
+ NoSubpixelAntialias = 0x0800,
+ PreferNoShaping = 0x1000,
+ ContextFontMerging = 0x2000,
+ PreferTypoLineMetrics = 0x4000,
+ NoFontMerging = 0x8000
};
Q_ENUM(StyleStrategy)
diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp
index d3a13d801b..3d6d3b7886 100644
--- a/src/gui/text/qfontdatabase.cpp
+++ b/src/gui/text/qfontdatabase.cpp
@@ -670,7 +670,7 @@ static QStringList fallbacksForFamily(const QString &family, QFont::Style style,
return *fallbacks;
// make sure that the db has all fallback families
- QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Common ? QChar::Script_Latin : script);
+ QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Latin ? QChar::Script_Common : script);
QStringList retList = userFallbacks + QGuiApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script);
QStringList::iterator i;
@@ -2187,6 +2187,8 @@ int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString &
// loaded, so it has to be flushed.
QFontCache::instance()->clear();
+ fallbacksCache.clear();
+
emit qApp->fontDatabaseChanged();
return i;
@@ -2386,23 +2388,32 @@ bool QFontDatabase::removeAllApplicationFonts()
be prioritized in reverse order, so that the last family added will be checked first and so
on.
+ \note Qt's font matching algorithm considers \c{QChar::Script_Common} (undetermined script)
+ and \c{QChar::Script_Latin} the same. Adding a fallback for either of these will also apply
+ to the other.
+
\sa setApplicationFallbackFontFamilies(), removeApplicationFallbackFontFamily(), applicationFallbackFontFamilies()
*/
void QFontDatabase::addApplicationFallbackFontFamily(QChar::Script script, const QString &familyName)
{
QMutexLocker locker(fontDatabaseMutex());
- if (script < QChar::Script_Latin) {
+ if (script < QChar::Script_Common) {
qCWarning(lcFontDb) << "Invalid script passed to addApplicationFallbackFontFamily:" << script;
return;
}
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
auto *db = QFontDatabasePrivate::instance();
auto it = db->applicationFallbackFontFamilies.find(script);
if (it == db->applicationFallbackFontFamilies.end())
it = db->applicationFallbackFontFamilies.insert(script, QStringList{});
it->prepend(familyName);
+
+ QFontCache::instance()->clear();
db->fallbacksCache.clear();
}
@@ -2420,6 +2431,14 @@ bool QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script script, co
{
QMutexLocker locker(fontDatabaseMutex());
+ if (script < QChar::Script_Common) {
+ qCWarning(lcFontDb) << "Invalid script passed to removeApplicationFallbackFontFamily:" << script;
+ return false;
+ }
+
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
auto *db = QFontDatabasePrivate::instance();
auto it = db->applicationFallbackFontFamilies.find(script);
if (it != db->applicationFallbackFontFamilies.end()) {
@@ -2452,11 +2471,14 @@ void QFontDatabase::setApplicationFallbackFontFamilies(QChar::Script script, con
{
QMutexLocker locker(fontDatabaseMutex());
- if (script < QChar::Script_Latin) {
+ if (script < QChar::Script_Common) {
qCWarning(lcFontDb) << "Invalid script passed to setApplicationFallbackFontFamilies:" << script;
return;
}
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
auto *db = QFontDatabasePrivate::instance();
db->applicationFallbackFontFamilies[script] = familyNames;
@@ -2476,6 +2498,9 @@ QStringList QFontDatabase::applicationFallbackFontFamilies(QChar::Script script)
{
QMutexLocker locker(fontDatabaseMutex());
+ if (script == QChar::Script_Latin)
+ script = QChar::Script_Common;
+
auto *db = QFontDatabasePrivate::instance();
return db->applicationFallbackFontFamilies.value(script);
}
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
index 4e78aaac2e..ee74c7f8fb 100644
--- a/src/gui/text/qfontengine.cpp
+++ b/src/gui/text/qfontengine.cpp
@@ -430,6 +430,11 @@ void QFontEngine::initializeHeightMetrics() const
m_heightMetricsQueried = true;
}
+bool QFontEngine::preferTypoLineMetrics() const
+{
+ return (fontDef.styleStrategy & QFont::PreferTypoLineMetrics) != 0;
+}
+
bool QFontEngine::processOS2Table() const
{
QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value());
@@ -444,7 +449,7 @@ bool QFontEngine::processOS2Table() const
enum { USE_TYPO_METRICS = 0x80 };
QFixed unitsPerEm = emSquareSize();
- if (fsSelection & USE_TYPO_METRICS) {
+ if (preferTypoLineMetrics() || fsSelection & USE_TYPO_METRICS) {
// Some fonts may have invalid OS/2 data. We detect this and bail out.
if (typoAscent == 0 && typoDescent == 0)
return false;
diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h
index a0e0801354..807f02fdc7 100644
--- a/src/gui/text/qfontengine_p.h
+++ b/src/gui/text/qfontengine_p.h
@@ -148,6 +148,7 @@ public:
return subPixelPositionFor(QFixedPoint(x, 0)).x;
}
+ bool preferTypoLineMetrics() const;
bool isColorFont() const { return glyphFormat == Format_ARGB; }
static bool isIgnorableChar(char32_t ucs4)
{
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp
index 15a313e13d..c39d3514c5 100644
--- a/src/gui/text/qtextdocument.cpp
+++ b/src/gui/text/qtextdocument.cpp
@@ -53,6 +53,9 @@ namespace {
QTextDocument::ResourceProvider qt_defaultResourceProvider;
};
+QAbstractUndoItem::~QAbstractUndoItem()
+ = default;
+
/*!
\fn bool Qt::mightBeRichText(QAnyStringView text)
@@ -2757,6 +2760,51 @@ bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
html += " -qt-stroke-width:"_L1;
html += QString::number(outlinePen.widthF());
html += "px;"_L1;
+
+ html += " -qt-stroke-linecap:"_L1;
+ if (outlinePen.capStyle() == Qt::SquareCap)
+ html += "squarecap;"_L1;
+ else if (outlinePen.capStyle() == Qt::FlatCap)
+ html += "flatcap;"_L1;
+ else if (outlinePen.capStyle() == Qt::RoundCap)
+ html += "roundcap;"_L1;
+
+ html += " -qt-stroke-linejoin:"_L1;
+ if (outlinePen.joinStyle() == Qt::MiterJoin)
+ html += "miterjoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::SvgMiterJoin)
+ html += "svgmiterjoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::BevelJoin)
+ html += "beveljoin;"_L1;
+ else if (outlinePen.joinStyle() == Qt::RoundJoin)
+ html += "roundjoin;"_L1;
+
+ if (outlinePen.joinStyle() == Qt::MiterJoin ||
+ outlinePen.joinStyle() == Qt::SvgMiterJoin) {
+ html += " -qt-stroke-miterlimit:"_L1;
+ html += QString::number(outlinePen.miterLimit());
+ html += u';';
+ }
+
+ if (outlinePen.style() == Qt::CustomDashLine && !outlinePen.dashPattern().empty()) {
+ html += " -qt-stroke-dasharray:"_L1;
+ QString dashArrayString;
+ QList<qreal> dashes = outlinePen.dashPattern();
+
+ for (int i = 0; i < dashes.length() - 1; i++) {
+ qreal dash = dashes[i];
+ dashArrayString += QString::number(dash) + u',';
+ }
+
+ dashArrayString += QString::number(dashes.last());
+ html += dashArrayString;
+ html += u';';
+
+ html += " -qt-stroke-dashoffset:"_L1;
+ html += QString::number(outlinePen.dashOffset());
+ html += u';';
+ }
+
attributesEmitted = true;
}
@@ -2944,6 +2992,17 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
html += "<img"_L1;
+ QString maxWidthCss;
+
+ if (imgFmt.hasProperty(QTextFormat::ImageMaxWidth)) {
+ auto length = imgFmt.lengthProperty(QTextFormat::ImageMaxWidth);
+ maxWidthCss += "max-width:"_L1;
+ if (length.type() == QTextLength::PercentageLength)
+ maxWidthCss += QString::number(length.rawValue()) + "%;"_L1;
+ else if (length.type() == QTextLength::FixedLength)
+ maxWidthCss += QString::number(length.rawValue()) + "px;"_L1;
+ }
+
if (imgFmt.hasProperty(QTextFormat::ImageName))
emitAttribute("src", imgFmt.name());
@@ -2960,9 +3019,11 @@ void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
emitAttribute("height", QString::number(imgFmt.height()));
if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle)
- html += " style=\"vertical-align: middle;\""_L1;
+ html += " style=\"vertical-align: middle;"_L1 + maxWidthCss + u'\"';
else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop)
- html += " style=\"vertical-align: top;\""_L1;
+ html += " style=\"vertical-align: top;"_L1 + maxWidthCss + u'\"';
+ else if (!maxWidthCss.isEmpty())
+ html += " style=\""_L1 + maxWidthCss + u'\"';
if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
emitFloatStyle(imageFrame->frameFormat().position());
diff --git a/src/gui/text/qtextdocument.h b/src/gui/text/qtextdocument.h
index b6253bfa46..11a8abcb50 100644
--- a/src/gui/text/qtextdocument.h
+++ b/src/gui/text/qtextdocument.h
@@ -45,15 +45,11 @@ namespace Qt
class Q_GUI_EXPORT QAbstractUndoItem
{
public:
- virtual ~QAbstractUndoItem() = 0;
+ virtual ~QAbstractUndoItem();
virtual void undo() = 0;
virtual void redo() = 0;
};
-inline QAbstractUndoItem::~QAbstractUndoItem()
-{
-}
-
class QTextDocumentPrivate;
class Q_GUI_EXPORT QTextDocument : public QObject
diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp
index a18157ab9b..08512bead5 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.cpp
@@ -1396,7 +1396,9 @@ void QTextEngine::shapeText(int item) const
QFontEngine *fontEngine = this->fontEngine(si, &si.ascent, &si.descent, &si.leading);
+#if QT_CONFIG(harfbuzz)
bool kerningEnabled;
+#endif
bool letterSpacingIsAbsolute;
bool shapingEnabled = false;
QHash<QFont::Tag, quint32> features;
@@ -1405,8 +1407,8 @@ void QTextEngine::shapeText(int item) const
if (useRawFont) {
QTextCharFormat f = format(&si);
QFont font = f.font();
- kerningEnabled = font.kerning();
# if QT_CONFIG(harfbuzz)
+ kerningEnabled = font.kerning();
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.styleStrategy() & QFont::PreferNoShaping) == 0;
# endif
@@ -1418,8 +1420,8 @@ void QTextEngine::shapeText(int item) const
#endif
{
QFont font = this->font(si);
- kerningEnabled = font.d->kerning;
#if QT_CONFIG(harfbuzz)
+ kerningEnabled = font.d->kerning;
shapingEnabled = QFontEngine::scriptRequiresOpenType(QChar::Script(si.analysis.script))
|| (font.d->request.styleStrategy & QFont::PreferNoShaping) == 0;
#endif
diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp
index 3e4bacf78c..dacef70812 100644
--- a/src/gui/text/qtextformat.cpp
+++ b/src/gui/text/qtextformat.cpp
@@ -745,6 +745,7 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextTableCellFormat &
\value ImageWidth
\value ImageHeight
\value ImageQuality
+ \value ImageMaxWidth This enum value has been added in Qt 6.8.
Selection properties
@@ -3156,7 +3157,8 @@ QTextTableFormat::QTextTableFormat()
: QTextFrameFormat()
{
setObjectType(TableObject);
- setCellSpacing(2);
+ setCellPadding(4);
+ setBorderCollapse(true);
setBorder(1);
}
@@ -3425,7 +3427,7 @@ QTextImageFormat::QTextImageFormat(const QTextFormat &fmt)
Sets the \a width of the rectangle occupied by the image.
- \sa width(), setHeight()
+ \sa width(), setHeight(), maximumWidth()
*/
@@ -3437,6 +3439,24 @@ QTextImageFormat::QTextImageFormat(const QTextFormat &fmt)
\sa height(), setWidth()
*/
+/*!
+ \fn void QTextImageFormat::setMaximumWidth(QTextLength maximumWidth)
+
+ Sets the \a maximumWidth of the rectangle occupied by the image. This
+ can be an absolute number or a percentage of the available document size.
+
+ \sa width(), setHeight()
+*/
+
+
+/*!
+ \fn QTextLength QTextImageFormat::maximumWidth() const
+
+ Returns the maximum width of the rectangle occupied by the image.
+
+ \sa width(), setMaximumWidth()
+*/
+
/*!
\fn void QTextImageFormat::setHeight(qreal height)
diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h
index c009d328cb..2fa86ed0d1 100644
--- a/src/gui/text/qtextformat.h
+++ b/src/gui/text/qtextformat.h
@@ -241,6 +241,7 @@ public:
ImageWidth = 0x5010,
ImageHeight = 0x5011,
ImageQuality = 0x5014,
+ ImageMaxWidth = 0x5015,
// internal
/*
@@ -796,6 +797,10 @@ public:
inline qreal width() const
{ return doubleProperty(ImageWidth); }
+ inline void setMaximumWidth(QTextLength maxWidth);
+ inline QTextLength maximumWidth() const
+ { return lengthProperty(ImageMaxWidth); }
+
inline void setHeight(qreal height);
inline qreal height() const
{ return doubleProperty(ImageHeight); }
@@ -823,6 +828,9 @@ inline void QTextImageFormat::setName(const QString &aname)
inline void QTextImageFormat::setWidth(qreal awidth)
{ setProperty(ImageWidth, awidth); }
+inline void QTextImageFormat::setMaximumWidth(QTextLength maxWidth)
+{ setProperty(ImageMaxWidth, maxWidth); }
+
inline void QTextImageFormat::setHeight(qreal aheight)
{ setProperty(ImageHeight, aheight); }
diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp
index ee92cece78..54c291b82e 100644
--- a/src/gui/text/qtexthtmlparser.cpp
+++ b/src/gui/text/qtexthtmlparser.cpp
@@ -1422,12 +1422,74 @@ void QTextHtmlParserNode::applyCssDeclarations(const QList<QCss::Declaration> &d
}
break;
}
+ case QCss::QtStrokeLineCap:
+ {
+ QPen pen = charFormat.textOutline();
+ switch (identifier) {
+ case QCss::Value_SquareCap: pen.setCapStyle(Qt::SquareCap); break;
+ case QCss::Value_FlatCap: pen.setCapStyle(Qt::FlatCap); break;
+ case QCss::Value_RoundCap: pen.setCapStyle(Qt::RoundCap); break;
+ default: break;
+ }
+ charFormat.setTextOutline(pen);
+ break;
+ }
+ case QCss::QtStrokeLineJoin:
+ {
+ QPen pen = charFormat.textOutline();
+ switch (identifier) {
+ case QCss::Value_MiterJoin: pen.setJoinStyle(Qt::MiterJoin); break;
+ case QCss::Value_BevelJoin: pen.setJoinStyle(Qt::BevelJoin); break;
+ case QCss::Value_RoundJoin: pen.setJoinStyle(Qt::RoundJoin); break;
+ case QCss::Value_SvgMiterJoin: pen.setJoinStyle(Qt::SvgMiterJoin); break;
+ default: break;
+ }
+ charFormat.setTextOutline(pen);
+ break;
+ }
+ case QCss::QtStrokeMiterLimit:
+ {
+ qreal miterLimit;
+ if (decl.realValue(&miterLimit)) {
+ QPen pen = charFormat.textOutline();
+ pen.setMiterLimit(miterLimit);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtStrokeDashArray:
+ {
+ QList<qreal> dashes = decl.dashArray();
+ if (!dashes.empty()) {
+ QPen pen = charFormat.textOutline();
+ pen.setDashPattern(dashes);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
+ case QCss::QtStrokeDashOffset:
+ {
+ qreal dashOffset;
+ if (decl.realValue(&dashOffset)) {
+ QPen pen = charFormat.textOutline();
+ pen.setDashOffset(dashOffset);
+ charFormat.setTextOutline(pen);
+ }
+ break;
+ }
case QCss::QtForeground:
{
QBrush brush = decl.brushValue();
charFormat.setForeground(brush);
break;
}
+ case QCss::MaximumWidth:
+ if (id == Html_img) {
+ auto imageFormat = charFormat.toImageFormat();
+ imageFormat.setMaximumWidth(extractor.textLength(decl));
+ charFormat = imageFormat;
+ }
+ break;
default: break;
}
}
diff --git a/src/gui/text/qtextimagehandler.cpp b/src/gui/text/qtextimagehandler.cpp
index 5c56c30711..920e6c689c 100644
--- a/src/gui/text/qtextimagehandler.cpp
+++ b/src/gui/text/qtextimagehandler.cpp
@@ -12,6 +12,7 @@
#include <private/qtextengine_p.h>
#include <qpalette.h>
#include <qthread.h>
+#include <limits>
QT_BEGIN_NAMESPACE
@@ -72,21 +73,40 @@ template<typename T>
static QSize getSize(QTextDocument *doc, const QTextImageFormat &format)
{
const bool hasWidth = format.hasProperty(QTextFormat::ImageWidth);
- const int width = qRound(format.width());
+ int width = qRound(format.width());
const bool hasHeight = format.hasProperty(QTextFormat::ImageHeight);
const int height = qRound(format.height());
+ const bool hasMaxWidth = format.hasProperty(QTextFormat::ImageMaxWidth);
+ const auto maxWidth = format.maximumWidth();
+
+ int effectiveMaxWidth = std::numeric_limits<int>::max();
+ if (hasMaxWidth) {
+ if (maxWidth.type() == QTextLength::PercentageLength)
+ effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(100) / 100;
+ else
+ effectiveMaxWidth = maxWidth.rawValue();
+
+ width = qMin(effectiveMaxWidth, width);
+ }
+
T source;
QSize size(width, height);
if (!hasWidth || !hasHeight) {
source = getAs<T>(doc, format);
- const QSizeF sourceSize = source.deviceIndependentSize();
+ QSizeF sourceSize = source.deviceIndependentSize();
+
+ if (sourceSize.width() > effectiveMaxWidth) {
+ // image is bigger than effectiveMaxWidth, scale it down
+ sourceSize.setHeight(effectiveMaxWidth * (sourceSize.height() / qreal(sourceSize.width())));
+ sourceSize.setWidth(effectiveMaxWidth);
+ }
if (!hasWidth) {
if (!hasHeight)
size.setWidth(sourceSize.width());
else
- size.setWidth(qRound(height * (sourceSize.width() / qreal(sourceSize.height()))));
+ size.setWidth(qMin(effectiveMaxWidth, qRound(height * (sourceSize.width() / qreal(sourceSize.height())))));
}
if (!hasHeight) {
if (!hasWidth)
diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp
index 4a12f6db6f..7ca138b1b7 100644
--- a/src/gui/util/qdesktopservices.cpp
+++ b/src/gui/util/qdesktopservices.cpp
@@ -18,8 +18,6 @@
#include <qpa/qplatformintegration.h>
#include <qdir.h>
-#include <QtCore/private/qlocking_p.h>
-
QT_BEGIN_NAMESPACE
class QOpenUrlHandlerRegistry
@@ -36,36 +34,10 @@ public:
};
typedef QHash<QString, Handler> HandlerHash;
HandlerHash handlers;
-
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
- QObject context;
-
- void handlerDestroyed(QObject *handler);
-#endif
-
};
Q_GLOBAL_STATIC(QOpenUrlHandlerRegistry, handlerRegistry)
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
-void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler)
-{
- const auto lock = qt_scoped_lock(mutex);
- HandlerHash::Iterator it = handlers.begin();
- while (it != handlers.end()) {
- if (it->receiver == handler) {
- it = handlers.erase(it);
- qWarning("Please call QDesktopServices::unsetUrlHandler() before destroying a "
- "registered URL handler object.\n"
- "Support for destroying a registered URL handler object is deprecated, "
- "and will be removed in Qt 6.6.");
- } else {
- ++it;
- }
- }
-}
-#endif
-
/*!
\class QDesktopServices
\brief The QDesktopServices class provides methods for accessing common desktop services.
@@ -238,10 +210,12 @@ bool QDesktopServices::openUrl(const QUrl &url)
the destruction of the handler object does not overlap with concurrent
invocations of openUrl() using it.
- \section1 iOS
+ \target configuring qdesktopservices url handler on ios and macos
+ \section1 iOS and \macos
- To use this function for receiving data from other apps on iOS you also need to
- add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file:
+ To use this function for receiving data from other apps on iOS/\macos
+ you also need to add the custom scheme to the \c CFBundleURLSchemes
+ list in your Info.plist file:
\snippet code/src_gui_util_qdesktopservices.cpp 4
@@ -256,7 +230,7 @@ bool QDesktopServices::openUrl(const QUrl &url)
\snippet code/src_gui_util_qdesktopservices.cpp 7
- iOS will search for /.well-known/apple-app-site-association on your domain,
+ iOS/\macos will search for /.well-known/apple-app-site-association on your domain,
when the application is installed. If you want to listen to
\c{https://your.domain.com/help?topic=ABCDEF} you need to provide the following
content there:
@@ -266,6 +240,7 @@ bool QDesktopServices::openUrl(const QUrl &url)
For more information, see the Apple Developer Documentation for
\l {iOS: Supporting Associated Domains}{Supporting Associated Domains}.
+ \target configuring qdesktopservices url handler on android
\section1 Android
To use this function for receiving data from other apps on Android, you
@@ -306,11 +281,6 @@ void QDesktopServices::setUrlHandler(const QString &scheme, QObject *receiver, c
h.receiver = receiver;
h.name = method;
registry->handlers.insert(scheme.toLower(), h);
-#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
- QObject::connect(receiver, &QObject::destroyed, &registry->context,
- [registry](QObject *obj) { registry->handlerDestroyed(obj); },
- Qt::DirectConnection);
-#endif
}
/*!