Perceptual Color

rgbcolorspacefactory.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "rgbcolorspacefactory.h"
7
8#include "rgbcolorspace.h"
9#include <qdir.h>
10#include <qfileinfo.h>
11#include <qglobal.h>
12#include <qlist.h>
13#include <qstring.h>
14#include <qstringbuilder.h>
15#include <qstringliteral.h>
16#include <type_traits>
17#include <utility>
18
19namespace PerceptualColor
20{
21/** @brief Create an sRGB color space object.
22 *
23 * This is build-in, no external ICC file is used.
24 *
25 * @pre This function is called from the main thread.
26 *
27 * @returns A shared pointer to the newly created color space object.
28 *
29 * @internal
30 *
31 * @todo This should be implemented as singleton with on-demand
32 * initialization. This requires however changes to @ref RgbColorSpace
33 * which should <em>not</em> guarantee that properties like
34 * @ref RgbColorSpace::profileName() are constant. Instead,
35 * for the sRGB profiles, the translation should be dynamic. */
40
41/** @brief Try to create a color space object for a given ICC file.
42 *
43 * @note This function may fail to create the color space object when it
44 * cannot open the given file, or when the file cannot be interpreted.
45 *
46 * @pre This function is called from the main thread.
47 *
48 * @param fileName The file name. See <tt>QFile</tt> documentation for what
49 * are valid file names. The file is only used during the execution of this
50 * function and it is closed again at the end of this function. The created
51 * object does not need the file anymore, because all necessary information
52 * has already been loaded into memory. Accepted are most RGB-based
53 * ICC profiles up to version 4.
54 *
55 * @returns A shared pointer to a newly created color space object on success.
56 * A shared pointer to <tt>nullptr</tt> on fail. */
58{
59 return RgbColorSpace::createFromFile(fileName);
60}
61
62/** @brief List of directories where color profiles are typically
63 * stored on the current system.
64 *
65 * Often, but not always, operating systems have an API to
66 * get access to this directories with color profiles or
67 * to get the actual color profile of a specific device
68 * (screen, printer…). On Linux, this is typically provided by
69 * <a href="https://www.freedesktop.org/software/colord/index.html">colord</a>.
70 * Also on Windows, there are specific API calls
71 * (<a href="https://learn.microsoft.com/en-us/windows/win32/wcs/profile-management-functions">[1]</a>
72 * <a href="https://learn.microsoft.com/en-us/windows/win32/api/icm/nf-icm-wcsgetdefaultcolorprofile">[2]</a>
73 * <a href="https://learn.microsoft.com/en-us/windows/win32/api/icm/nf-icm-getcolordirectoryw">[3]</a>
74 * <a href="https://learn.microsoft.com/en-us/uwp/api/windows.graphics.display.displayinformation.getcolorprofileasync?view=winrt-22621">[4]</a>)
75 * Some other operating systems have similar APIs.
76 *
77 * The best solution is to rely on the operating system’s API. However,
78 * if you can’t use this API for some reasons, this function provides a
79 * last-resort alternative. Not all operating systems have standardised
80 * directories for colour profiles. This function provide a list of typical
81 * storage locations of ICC profile files and works satisfactorily for at
82 * least Linux, BSD, MacOS and Windows.
83 *
84 * @returns A preference-ordered list of typical storage locations of
85 * color profiles on the current system. The list might be empty if no
86 * color profile directories are found on the system. The returned directories
87 * use '/' as file separator regardless of the operating system, just
88 * as <tt>QFile</tt> expects. To find color profiles, parse these directories
89 * recursively, including subdirectories. Note that ICC colour profiles
90 * traditionally have a file name ending in <tt>.icm</tt> on Windows systems
91 * and a name ending in <tt>.icc</tt> on all other operating systems,
92 * but today on every operating system you might find actually both file name
93 * endings.
94 *
95 * @note This function takes into account environment variables, home
96 * directories and so on. Potential colour profile directories that do not
97 * actually exist on the current system are not returned. Since these values
98 * could change, another call of this function could return a different result.
99 *
100 * @internal
101 *
102 * @note Internal implementation details: User directories appear at the top
103 * of the list, system-wide directories appear at the bottom. The returned
104 * directories are absolute paths with all symlinks removed. There are no
105 * duplicates in the list. All returned directories actually exist. */
107{
108 // https://web.archive.org/web/20140625123925/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system
109 // describes well how to recognize the current system by compiler-defined
110 // macros. But Qt makes things more comfortable for us…
111 //
112 // Q_OS_WIN is defined on all Windows-like systems.
113 //
114 // Q_OS_UNIX is defined on all other systems.
115 //
116 // Q_OS_DARWIN is defined on MacOS-like systems along with Q_OS_UNIX.
117 //
118 // Q_OS_UNIX and Q_OS_WIN are mutually exclusive: They never appear at
119 // the same time, not even on Cygwin. Reference: Q_OS_UNIX definition at
120 // https://code.woboq.org/qt6/qtbase/src/corelib/global/qsystemdetection.h.html
121
122 QStringList candidates;
123
124#ifdef Q_OS_DARWIN
125 // MacOS-like systems (including iOS and other derivatives)
126 // The Qt version we are relying on requires at least MacOS X. Starting
127 // with MacOS X, those are the relevant dictionaries, as also
128 // https://stackoverflow.com/a/32729370 describes:
129
130 // User-supplied settings:
131 candidates.append( //
132 QDir::homePath() + QStringLiteral(u"/Library/ColorSync/Profiles/"));
133
134 // Settings supplied by the local machine:
135 candidates.append(QStringLiteral(u"/Library/ColorSync/Profiles/"));
136
137 // Settings supplied by the network administrator:
138 candidates.append(QStringLiteral(u"/Network/Library/ColorSync/Profiles/"));
139
140 // Hard-coded settings of MacOS itself, that cannot be changed:
141 candidates.append(QStringLiteral(u"/System/Library/ColorSync/Profiles/"));
142
143 // Printer drivers also might have color profiles:
144 candidates.append(QStringLiteral(u"/Library/Printers/")); // TODO Useful?
145
146 // Adobe’s applications also might have color profiles:
147 candidates.append( // TODO Is it useful to support particular programs?
148 QStringLiteral(u"/Library/Application Support/Adobe/Color/Profiles/"));
149
150#elif defined(Q_OS_UNIX)
151 // Unix-like systems (including BSD, Linux, Android), excluding those which
152 // have Q_OS_DARWIN defined (we are after this in the #elif statement).
153 // The following settings will work probably well on Linux and BSD,
154 // but not so well on Android which does not seem to have a real standard.
155
156 const QString subdirectory1 = QStringLiteral(u"/color/icc/");
157 const QString subdirectory2 = QStringLiteral(u"/icc/");
158 QString baseDirectory;
159 baseDirectory = qEnvironmentVariable("XDG_DATA_HOME");
160 if (!baseDirectory.isEmpty()) {
161 candidates.append(baseDirectory + subdirectory1);
162 candidates.append(baseDirectory + subdirectory2);
163 }
164 baseDirectory = QDir::homePath() + QStringLiteral(u"/.local/share/");
165 candidates.append(baseDirectory + subdirectory1);
166 candidates.append(baseDirectory + subdirectory2);
167 baseDirectory = QDir::homePath();
168 candidates.append(baseDirectory + subdirectory1);
169 candidates.append(baseDirectory + subdirectory2);
170 candidates.append(baseDirectory + QStringLiteral(u"/.color/icc/"));
171
172 QStringList baseDirectoryList = //
173 qEnvironmentVariable("XDG_DATA_DIRS").split(QStringLiteral(u":"));
174 // Fallback values for empty XDG_DATA_DIRS, as defined in
175 // the Free Desktop Specification:
176 baseDirectoryList.append(QStringLiteral(u"/usr/local/share/"));
177 baseDirectoryList.append(QStringLiteral(u"/usr/share/"));
178 // Custom search directory:
179 baseDirectoryList.append(QStringLiteral(u"/var/lib/"));
180 for (const QString &path : std::as_const(baseDirectoryList)) {
181 if (!path.isEmpty()) {
182 candidates.append(path + QStringLiteral(u"/color/icc/"));
183 candidates.append(path + QStringLiteral(u"/icc/"));
184 }
185 }
186
187#elif defined(Q_OS_WIN)
188 // Windows-like systems
189
190 // NOTE It is possible to get the Windows system directory with
191 // Windows API calls. However, we want to reduce our dependencies and
192 // therefore avoid to link against this API. So the following code
193 // is commented out.
194 //
195 // // If an array is initialized and the array length is larger than
196 // // the number of initialization values, the remaining array values
197 // // will be initialized to zero. Therefore, the following code
198 // initializes the entire (!) array to 0.
199 // wchar_t sysDir[MAX_PATH + 1] = {0};
200 // GetSystemDirectoryW(sysDir, MAX_PATH);
201 // QString winSysDir = QString::fromWCharArray(sysDir);
202
203 QString winSysDir = qEnvironmentVariable("windir");
204
205 // Starting with XP, this is the default directory:
206 candidates.append(winSysDir + QStringLiteral(u"/Spool/Drivers/Color/"));
207 // In Windows 95, 98, this was the default directory:
208 candidates.append(winSysDir + QStringLiteral(u"/Color/")); // TODO Useful?
209#endif
210
211 // Prepare the return value:
212 QFileInfo info; // QFileInfo isn’t only about files, but also directories!
213 QStringList result;
214 // cppcheck-suppress knownEmptyContainer // false positive
215 for (const QString &path : std::as_const(candidates)) {
216 // cleanPath() has redundant separators removed.
217 info = QFileInfo(QDir::cleanPath(path));
218 if (info.isDir()) {
219 // canonicalFilePath() returns an absolute path without redundant
220 // '.' or '.. ' elements (or an empty string if this is
221 // not possible):
222 result.append(info.canonicalFilePath());
223 }
224 }
225 result.removeDuplicates();
226 result.removeAll(QString()); // Remove empty strings
227 return result;
228}
229
230} // namespace PerceptualColor
static QStringList colorProfileDirectories()
List of directories where color profiles are typically stored on the current system.
static QSharedPointer< PerceptualColor::RgbColorSpace > createSrgb()
Create an sRGB color space object.
static QSharedPointer< PerceptualColor::RgbColorSpace > createFromFile(const QString &fileName)
Try to create a color space object for a given ICC file.
The namespace of this library.
QString cleanPath(const QString &path)
QString homePath()
QString canonicalFilePath() const const
bool isDir() const const
void append(QList< T > &&value)
qsizetype removeAll(const AT &t)
bool isEmpty() const const
qsizetype removeDuplicates()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 24 2024 11:48:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.