Perceptual Color

helper.h
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4#ifndef HELPER_H
5#define HELPER_H
6
7#include "helpermath.h"
8#include "helperqttypes.h"
9#include "lcms2.h"
10#include <QtCore/qsharedpointer.h>
11#include <optional>
12#include <qcolor.h>
13#include <qcontainerfwd.h>
14#include <qcoreapplication.h>
15#include <qglobal.h>
16#include <qicon.h>
17#include <qimage.h>
18#include <qlist.h>
19#include <qmap.h>
20#include <qmetaobject.h>
21#include <qmetatype.h>
22#include <qpair.h>
23#include <qstring.h>
24#include <qstringliteral.h>
25#include <qthread.h>
26
27class QWheelEvent;
28class QWidget;
29
30namespace PerceptualColor
31{
32
33class RgbColorSpace;
34
35/** @brief Represents the appearance of a theme.
36 *
37 * @todo Substitute this by
38 * <a href="https://doc-snapshots.qt.io/qt6-dev/qt.html#ColorScheme-enum"><tt>
39 * enum class Qt::ColorScheme</tt></a> which is available since Qt 6.
40 */
41enum class ColorSchemeType {
42 Light, /**< Light theme. */
43 Dark /**< Dark theme. */
44};
45
46void drawQWidgetStyleSheetAware(QWidget *widget);
47
48QString fromMnemonicToRichText(const QString &mnemonicText);
49
50ColorSchemeType guessColorSchemeTypeFromWidget(QWidget *widget);
51
53
54/** @internal
55 *
56 * @brief Convenience function template that tests if a value is in a list.
57 *
58 * @param first The value
59 * @param t The list
60 *
61 * Usage:
62 * @snippet testhelper.cpp isInUsage
63 *
64 * @returns <tt>true</tt> if “value” is in “list”. <tt>false</tt> otherwise. */
65template<typename First, typename... T>
66bool isIn(First &&first, T &&...t)
67{
68 // Solution as proposed by Nikos C. in https://stackoverflow.com/a/15181949
69 return ((first == t) || ...);
70}
71
72[[nodiscard]] qreal standardWheelStepCount(QWheelEvent *event);
73
74[[nodiscard]] QImage transparencyBackground(qreal devicePixelRatioF);
75
76/** @internal
77 *
78 * @brief Two-dimensional array */
79template<typename T>
80class Array2D
81{
82public:
83 /** @brief Constructor.
84 *
85 * Constructs an array with the size 0 × 0. */
86 Array2D()
87 : m_iCount(0)
88 , m_jCount(0)
89 {
90 }
91
92 /** @brief Constructor.
93 *
94 * @param iCount size (first dimension)
95 * @param jCount size (second dimension)
96 *
97 * The elements are initialized with default-constructed values. */
98 Array2D(QListSizeType iCount, QListSizeType jCount)
99 : m_iCount(iCount)
100 , m_jCount(jCount)
101 {
102 if (m_iCount < 0) {
103 m_iCount = 0;
104 }
105 if (m_jCount < 0) {
106 m_jCount = 0;
107 }
108 const auto elementCount = m_iCount * m_jCount;
109 m_data.reserve(elementCount);
110 for (QListSizeType i = 0; i < elementCount; ++i) {
111 m_data.append(T());
112 }
113 }
114
115 /** @brief Constructor.
116 *
117 * @param iCount size (first dimension)
118 * @param jCount size (second dimension)
119 * @param init Initial values. Excess elements are ignored. Missing
120 * elements are initialized with default-constructed values. */
121 Array2D(QListSizeType iCount, QListSizeType jCount, QList<T> init)
122 : m_iCount(iCount)
123 , m_jCount(jCount)
124 {
125 if (m_iCount < 0) {
126 m_iCount = 0;
127 }
128 if (m_jCount < 0) {
129 m_jCount = 0;
130 }
131 const auto elementCount = m_iCount * m_jCount;
132 m_data.reserve(elementCount);
133 for (QListSizeType i = 0; i < elementCount; ++i) {
134 if (i < init.count()) {
135 m_data.append(init.value(i));
136 } else {
137 m_data.append(T());
138 }
139 }
140 }
141
142 // Define also these functions recommended by “rule of five”:
143 /** @brief Default copy assignment operator
144 * @param other the object to copy
145 * @returns The default implementation’s return value. */
146 Array2D &operator=(const Array2D &other) = default; // clazy:exclude=function-args-by-value
147 /** @brief Default move assignment operator
148 *
149 * @param other the object to move-assign
150 *
151 * @returns The default implementation’s return value. */
152 Array2D &operator=(Array2D &&other) = default;
153 /** @brief Default copy constructor
154 * @param other the object to copy */
155 Array2D(const Array2D &other) = default;
156 /** @brief Default move constructor
157 * @param other the object to move */
158 Array2D(Array2D &&other) = default;
159
160 /** @brief Equal operator
161 *
162 * @param other The object to compare with.
163 *
164 * @returns <tt>true</tt> if equal, <tt>false</tt> otherwise. */
165 bool operator==(const Array2D &other) const
166 {
167 return ( //
168 (m_data == other.m_data) //
169 && (m_iCount == other.m_iCount) //
170 && (m_jCount == other.m_jCount) //
171 );
172 }
173
174 /** @brief Unequal operator
175 *
176 * @param other The object to compare with.
177 *
178 * @returns <tt>true</tt> if unequal, <tt>false</tt> otherwise. */
179 bool operator!=(const Array2D &other) const
180 {
181 return !(*this == other);
182 }
183
184 /** @brief Set value at a given index.
185 *
186 * @param i index (first dimension)
187 * @param j index (second dimension)
188 * @param value value to set */
189 void setValue(QListSizeType i, QListSizeType j, const T &value)
190 {
191 if (isInRange<QListSizeType>(0, i, m_iCount - 1) //
192 && isInRange<QListSizeType>(0, j, m_jCount - 1) //
193 ) {
194 m_data[i + m_iCount * j] = value;
195 }
196 }
197
198 /**
199 * @brief Converts the array to a QList.
200 *
201 * @return Converts the array to a QList. */
202 // Disable cppcheck's returnByReference warning.
203 // Note: cppcheck is giving bad advice here. Returning by (const)
204 // reference can lead to unexpected behavior: the underlying data might
205 // be modified later by the original owner, making it a risky practice.
206 // Anyway, we return a QList. Since QList uses implicit sharing,
207 // creating a copy from it is efficient and inexpensive.
208 // cppcheck-suppress returnByReference
209 QList<T> toQList() const
210 {
211 return m_data;
212 }
213
214 /** @brief Get value at a given index.
215 *
216 * @param i index (first dimension)
217 * @param j index (second dimension)
218 * @returns If the indices are valid, the value at the given indeces.
219 * A default-constructed value otherwise. */
220 T value(QListSizeType i, QListSizeType j) const
221 {
222 if (isInRange<QListSizeType>(0, i, m_iCount - 1) //
223 && isInRange<QListSizeType>(0, j, m_jCount - 1) //
224 ) {
225 return m_data[i + m_iCount * j];
226 }
227 return T(); // Default value if indices are out of bounds
228 }
229
230 /** @brief Size of the first dimension.
231 *
232 * @returns Size of the first dimension. */
233 QListSizeType iCount() const
234 {
235 return m_iCount;
236 }
237
238 /** @brief Size of the second dimension.
239 *
240 * @returns Size of the second dimension. */
241 QListSizeType jCount() const
242 {
243 return m_jCount;
244 }
245
246private:
247 /** @brief Internal storage of the elements. */
248 QList<T> m_data;
249 /** @brief Internal storage of @ref iCount(). */
250 QListSizeType m_iCount;
251 /** @brief Internal storage of @ref jCount(). */
252 QListSizeType m_jCount;
253};
254
255/** @brief Swatches organized in a grid.
256 *
257 * This type is declared as type to Qt’s type system via
258 * <tt>Q_DECLARE_METATYPE</tt>. Depending on your use case (for
259 * example if you want to use for <em>queued</em> signal-slot connections),
260 * you might consider calling <tt>qRegisterMetaType()</tt> for
261 * this type, once you have a QApplication object. */
262using Swatches = Array2D<QColor>;
263
264/** @internal
265 *
266 * @brief Force processing of events in a delayed fashion.
267 *
268 * When there is no running event loop (like in unit tests or in tools
269 * like the screenshot generator), some parts of the asynchronous API
270 * of this library does not work. Calling this function fixes this by
271 * forcing the processing of pending events, but with some delay
272 * in-between, so that maybe existing parallel threads have also
273 * a chance to terminate their work.
274 *
275 * @param msecWaitInitially Delay before starting event processing.
276 * @param msecWaitBetweenEventLoopPasses Delay before each pass through
277 * through the pending events.
278 * @param numberEventLoopPasses Number of passes through the pending events.
279 *
280 * @internal
281 *
282 * @note This is declared as template to prevent that this code is compiled
283 * into the library itself, which does <em>not</em> actually use it itself,
284 * but includes this header file. */
285template<typename T = void>
286void delayedEventProcessing(unsigned long msecWaitInitially = 50, unsigned long msecWaitBetweenEventLoopPasses = 50, int numberEventLoopPasses = 3)
287{
288 // Some OSes might round the sleep time up to 15 ms. We do it ourself
289 // here to make the behaviour a little bit more predictable.
290 msecWaitInitially = qMax<unsigned long>( //
291 msecWaitInitially, //
292 15);
293 msecWaitBetweenEventLoopPasses = //
294 qMax<unsigned long>(msecWaitBetweenEventLoopPasses, 15);
295
296 QThread::msleep(msecWaitInitially);
297 // Hopefully, now the render function has terminated…
298 for (int i = 0; i < numberEventLoopPasses; ++i) {
299 // Wait again (apparently, threaded event processing needs some time…)
300 QThread::msleep(msecWaitBetweenEventLoopPasses);
302 }
303}
304
306
307[[nodiscard]] QIcon qIconFromTheme(const QStringList &names, const QString &fallback, ColorSchemeType type);
308
309[[nodiscard]] QPair<QString, QString> getPrefixSuffix(const QString &formatString);
310
311/** @brief The full-qualified C++ identifier as QString.
312 *
313 * This can be useful for debugging purposes.
314 *
315 * @tparam T The enumeration.
316 *
317 * @pre The enumeration type is declared with
318 * Q_ENUM or Q_ENUM_NS.
319 *
320 * @returns The full-qualified C++ identifier as QString. */
321template<typename T>
323{
324 const auto myMeta = QMetaEnum::fromType<T>();
325 const auto scope = QString::fromUtf8(myMeta.scope());
326 const auto name = QString::fromUtf8(myMeta.name());
327 return QStringLiteral("%1::%2").arg(scope, name);
328}
329
330/** @brief The full-qualified C++ identifier as QString.
331 *
332 * This can be useful for debugging purposes.
333 *
334 * @tparam T The enumeration type. Can usually be omitted.
335 *
336 * @param enumerator An enumerator.
337 *
338 * @pre The enumeration type of the enumerator is declared with
339 * Q_ENUM or Q_ENUM_NS.
340 *
341 * @returns The full-qualified C++ identifier as QString, followed by the
342 * underlying integer value in parenthesis. If the enumerator does not
343 * exist (for example because you have done a static_cast of an invalid
344 * integer to the enum class), an empty String is returned instead. If
345 * the enumerator has synonyms (that means, there exist other enumerators
346 * that share the same integer with the current enumerator), all synonym
347 * enumerators are returned.
348 *
349 * @sa @ref enumeratorToString() */
350template<typename T>
351[[nodiscard]] QString enumeratorToFullString(const T &enumerator)
352{
353 const auto value = static_cast<int>(enumerator);
354 const auto myMeta = QMetaEnum::fromType<T>();
355
356 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing
357 // (synonym) keys for a given value. But it also returns happily
358 // fantasy strings for non-existing values. Therefore, we have check
359 // first with QMetaEnum::valueToKeys (identifier with a final s) which
360 // does only return one single key for each value, but is guaranteed to
361 // return nullptr if the value has no key.
362 if (!myMeta.valueToKey(value)) {
363 return QString();
364 }
365
366 const auto scope = QString::fromUtf8(myMeta.scope());
367 const auto name = QString::fromUtf8(myMeta.name());
368 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value));
369 return QStringLiteral("%1::%2::%3(%4)").arg(scope, name, keys).arg(value);
370}
371
372/** @brief The C++ identifier as QString.
373 *
374 * This can be useful for debugging purposes.
375 *
376 * @tparam T The enumeration type. Can usually be omitted.
377 *
378 * @param enumerator An enumerator.
379 *
380 * @pre The enumeration type of the enumerator is declared with
381 * Q_ENUM or Q_ENUM_NS.
382 *
383 * @returns The C++ identifier as QString, followed by the
384 * underlying integer value in parenthesis. If the enumerator does not
385 * exist (for example because you have done a static_cast of an invalid
386 * integer to the enum class), an empty String is returned instead. If
387 * the enumerator has synonyms (that means, there exist other enumerators
388 * that share the same integer with the current enumerator), all synonym
389 * enumerators are returned.
390 *
391 * @sa @ref enumeratorToFullString() */
392template<typename T>
393[[nodiscard]] QString enumeratorToString(const T &enumerator)
394{
395 const auto value = static_cast<int>(enumerator);
396 const auto myMeta = QMetaEnum::fromType<T>();
397
398 // QMetaEnum::valueToKeys (identifier with a final s) returns all existing
399 // (synonym) keys for a given value. But it also returns happily
400 // fantasy strings for non-existing values. Therefore, we have check
401 // first with QMetaEnum::valueToKeys (identifier with a final s) which
402 // does only return one single key for each value, but is guaranteed to
403 // return nullptr if the value has no key.
404 if (!myMeta.valueToKey(value)) {
405 return QString();
406 }
407
408 const auto keys = QString::fromUtf8(myMeta.valueToKeys(value));
409 return QStringLiteral("%1(%2)").arg(keys).arg(value);
410}
411
412} // namespace PerceptualColor
413
414// Use Q_DECLARE_METATYPE with this data type.
415// Attention: This must be done outside of all name-spaces.
416Q_DECLARE_METATYPE(PerceptualColor::Swatches)
417
418#endif // HELPER_H
The namespace of this library.
QString enumerationToFullString()
The full-qualified C++ identifier as QString.
Definition helper.h:322
Array2D< QColor > Swatches
Swatches organized in a grid.
Definition helper.h:262
QString enumeratorToString(const T &enumerator)
The C++ identifier as QString.
Definition helper.h:393
QString enumeratorToFullString(const T &enumerator)
The full-qualified C++ identifier as QString.
Definition helper.h:351
ColorSchemeType
Represents the appearance of a theme.
Definition helper.h:41
Swatches wcsBasicColors(const QSharedPointer< PerceptualColor::RgbColorSpace > &colorSpace)
Swatch grid derived from the basic colors as by WCS (World color survey).
Definition helper.cpp:465
QMap< cmsUInt32Number, QString > lcmsIntentList()
The rendering intents supported by the LittleCMS library.
Definition helper.cpp:578
QCA_EXPORT void init()
void processEvents(QEventLoop::ProcessEventsFlags flags)
QMetaEnum fromType()
QString fromUtf8(QByteArrayView str)
void msleep(unsigned long msecs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 12:03:13 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.