File indexing completed on 2024-05-19 04:45:54
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // Own headers 0005 // First the interface, which forces the header to be self-contained. 0006 #include "rgbcolorspacefactory.h" 0007 0008 #include "rgbcolorspace.h" 0009 #include <qdir.h> 0010 #include <qfileinfo.h> 0011 #include <qglobal.h> 0012 #include <qlist.h> 0013 #include <qstring.h> 0014 #include <qstringbuilder.h> 0015 #include <qstringliteral.h> 0016 #include <type_traits> 0017 #include <utility> 0018 0019 namespace PerceptualColor 0020 { 0021 /** @brief Create an sRGB color space object. 0022 * 0023 * This is build-in, no external ICC file is used. 0024 * 0025 * @pre This function is called from the main thread. 0026 * 0027 * @returns A shared pointer to the newly created color space object. 0028 * 0029 * @internal 0030 * 0031 * @todo This should be implemented as singleton with on-demand 0032 * initialization. This requires however changes to @ref RgbColorSpace 0033 * which should <em>not</em> guarantee that properties like 0034 * @ref RgbColorSpace::profileName() are constant. Instead, 0035 * for the sRGB profiles, the translation should be dynamic. */ 0036 QSharedPointer<PerceptualColor::RgbColorSpace> RgbColorSpaceFactory::createSrgb() 0037 { 0038 return RgbColorSpace::createSrgb(); 0039 } 0040 0041 /** @brief Try to create a color space object for a given ICC file. 0042 * 0043 * @note This function may fail to create the color space object when it 0044 * cannot open the given file, or when the file cannot be interpreted. 0045 * 0046 * @pre This function is called from the main thread. 0047 * 0048 * @param fileName The file name. See <tt>QFile</tt> documentation for what 0049 * are valid file names. The file is only used during the execution of this 0050 * function and it is closed again at the end of this function. The created 0051 * object does not need the file anymore, because all necessary information 0052 * has already been loaded into memory. Accepted are most RGB-based 0053 * ICC profiles up to version 4. 0054 * 0055 * @returns A shared pointer to a newly created color space object on success. 0056 * A shared pointer to <tt>nullptr</tt> on fail. */ 0057 QSharedPointer<PerceptualColor::RgbColorSpace> RgbColorSpaceFactory::createFromFile(const QString &fileName) 0058 { 0059 return RgbColorSpace::createFromFile(fileName); 0060 } 0061 0062 /** @brief List of directories where color profiles are typically 0063 * stored on the current system. 0064 * 0065 * Often, but not always, operating systems have an API to 0066 * get access to this directories with color profiles or 0067 * to get the actual color profile of a specific device 0068 * (screen, printer…). On Linux, this is typically provided by 0069 * <a href="https://www.freedesktop.org/software/colord/index.html">colord</a>. 0070 * Also on Windows, there are specific API calls 0071 * (<a href="https://learn.microsoft.com/en-us/windows/win32/wcs/profile-management-functions">[1]</a> 0072 * <a href="https://learn.microsoft.com/en-us/windows/win32/api/icm/nf-icm-wcsgetdefaultcolorprofile">[2]</a> 0073 * <a href="https://learn.microsoft.com/en-us/windows/win32/api/icm/nf-icm-getcolordirectoryw">[3]</a> 0074 * <a href="https://learn.microsoft.com/en-us/uwp/api/windows.graphics.display.displayinformation.getcolorprofileasync?view=winrt-22621">[4]</a>) 0075 * Some other operating systems have similar APIs. 0076 * 0077 * The best solution is to rely on the operating system’s API. However, 0078 * if you can’t use this API for some reasons, this function provides a 0079 * last-resort alternative. Not all operating systems have standardised 0080 * directories for colour profiles. This function provide a list of typical 0081 * storage locations of ICC profile files and works satisfactorily for at 0082 * least Linux, BSD, MacOS and Windows. 0083 * 0084 * @returns A preference-ordered list of typical storage locations of 0085 * color profiles on the current system. The list might be empty if no 0086 * color profile directories are found on the system. The returned directories 0087 * use '/' as file separator regardless of the operating system, just 0088 * as <tt>QFile</tt> expects. To find color profiles, parse these directories 0089 * recursively, including subdirectories. Note that ICC colour profiles 0090 * traditionally have a file name ending in <tt>.icm</tt> on Windows systems 0091 * and a name ending in <tt>.icc</tt> on all other operating systems, 0092 * but today on every operating system you might find actually both file name 0093 * endings. 0094 * 0095 * @note This function takes into account environment variables, home 0096 * directories and so on. Potential colour profile directories that do not 0097 * actually exist on the current system are not returned. Since these values 0098 * could change, another call of this function could return a different result. 0099 * 0100 * @internal 0101 * 0102 * @note Internal implementation details: User directories appear at the top 0103 * of the list, system-wide directories appear at the bottom. The returned 0104 * directories are absolute paths with all symlinks removed. There are no 0105 * duplicates in the list. All returned directories actually exist. */ 0106 QStringList RgbColorSpaceFactory::colorProfileDirectories() 0107 { 0108 // https://web.archive.org/web/20140625123925/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system 0109 // describes well how to recognize the current system by compiler-defined 0110 // macros. But Qt makes things more comfortable for us… 0111 // 0112 // Q_OS_WIN is defined on all Windows-like systems. 0113 // 0114 // Q_OS_UNIX is defined on all other systems. 0115 // 0116 // Q_OS_DARWIN is defined on MacOS-like systems along with Q_OS_UNIX. 0117 // 0118 // Q_OS_UNIX and Q_OS_WIN are mutually exclusive: They never appear at 0119 // the same time, not even on Cygwin. Reference: Q_OS_UNIX definition at 0120 // https://code.woboq.org/qt6/qtbase/src/corelib/global/qsystemdetection.h.html 0121 0122 QStringList candidates; 0123 0124 #ifdef Q_OS_DARWIN 0125 // MacOS-like systems (including iOS and other derivatives) 0126 // The Qt version we are relying on requires at least MacOS X. Starting 0127 // with MacOS X, those are the relevant dictionaries, as also 0128 // https://stackoverflow.com/a/32729370 describes: 0129 0130 // User-supplied settings: 0131 candidates.append( // 0132 QDir::homePath() + QStringLiteral(u"/Library/ColorSync/Profiles/")); 0133 0134 // Settings supplied by the local machine: 0135 candidates.append(QStringLiteral(u"/Library/ColorSync/Profiles/")); 0136 0137 // Settings supplied by the network administrator: 0138 candidates.append(QStringLiteral(u"/Network/Library/ColorSync/Profiles/")); 0139 0140 // Hard-coded settings of MacOS itself, that cannot be changed: 0141 candidates.append(QStringLiteral(u"/System/Library/ColorSync/Profiles/")); 0142 0143 // Printer drivers also might have color profiles: 0144 candidates.append(QStringLiteral(u"/Library/Printers/")); // TODO Useful? 0145 0146 // Adobe’s applications also might have color profiles: 0147 candidates.append( // TODO Is it useful to support particular programs? 0148 QStringLiteral(u"/Library/Application Support/Adobe/Color/Profiles/")); 0149 0150 #elif defined(Q_OS_UNIX) 0151 // Unix-like systems (including BSD, Linux, Android), excluding those which 0152 // have Q_OS_DARWIN defined (we are after this in the #elif statement). 0153 // The following settings will work probably well on Linux and BSD, 0154 // but not so well on Android which does not seem to have a real standard. 0155 0156 const QString subdirectory1 = QStringLiteral(u"/color/icc/"); 0157 const QString subdirectory2 = QStringLiteral(u"/icc/"); 0158 QString baseDirectory; 0159 baseDirectory = qEnvironmentVariable("XDG_DATA_HOME"); 0160 if (!baseDirectory.isEmpty()) { 0161 candidates.append(baseDirectory + subdirectory1); 0162 candidates.append(baseDirectory + subdirectory2); 0163 } 0164 baseDirectory = QDir::homePath() + QStringLiteral(u"/.local/share/"); 0165 candidates.append(baseDirectory + subdirectory1); 0166 candidates.append(baseDirectory + subdirectory2); 0167 baseDirectory = QDir::homePath(); 0168 candidates.append(baseDirectory + subdirectory1); 0169 candidates.append(baseDirectory + subdirectory2); 0170 candidates.append(baseDirectory + QStringLiteral(u"/.color/icc/")); 0171 0172 QStringList baseDirectoryList = // 0173 qEnvironmentVariable("XDG_DATA_DIRS").split(QStringLiteral(u":")); 0174 // Fallback values for empty XDG_DATA_DIRS, as defined in 0175 // the Free Desktop Specification: 0176 baseDirectoryList.append(QStringLiteral(u"/usr/local/share/")); 0177 baseDirectoryList.append(QStringLiteral(u"/usr/share/")); 0178 // Custom search directory: 0179 baseDirectoryList.append(QStringLiteral(u"/var/lib/")); 0180 for (const QString &path : std::as_const(baseDirectoryList)) { 0181 if (!path.isEmpty()) { 0182 candidates.append(path + QStringLiteral(u"/color/icc/")); 0183 candidates.append(path + QStringLiteral(u"/icc/")); 0184 } 0185 } 0186 0187 #elif defined(Q_OS_WIN) 0188 // Windows-like systems 0189 0190 // NOTE It is possible to get the Windows system directory with 0191 // Windows API calls. However, we want to reduce our dependencies and 0192 // therefore avoid to link against this API. So the following code 0193 // is commented out. 0194 // 0195 // // If an array is initialized and the array length is larger than 0196 // // the number of initialization values, the remaining array values 0197 // // will be initialized to zero. Therefore, the following code 0198 // initializes the entire (!) array to 0. 0199 // wchar_t sysDir[MAX_PATH + 1] = {0}; 0200 // GetSystemDirectoryW(sysDir, MAX_PATH); 0201 // QString winSysDir = QString::fromWCharArray(sysDir); 0202 0203 QString winSysDir = qEnvironmentVariable("windir"); 0204 0205 // Starting with XP, this is the default directory: 0206 candidates.append(winSysDir + QStringLiteral(u"/Spool/Drivers/Color/")); 0207 // In Windows 95, 98, this was the default directory: 0208 candidates.append(winSysDir + QStringLiteral(u"/Color/")); // TODO Useful? 0209 #endif 0210 0211 // Prepare the return value: 0212 QFileInfo info; // QFileInfo isn’t only about files, but also directories! 0213 QStringList result; 0214 // cppcheck-suppress knownEmptyContainer // false positive 0215 for (const QString &path : std::as_const(candidates)) { 0216 // cleanPath() has redundant separators removed. 0217 info = QFileInfo(QDir::cleanPath(path)); 0218 if (info.isDir()) { 0219 // canonicalFilePath() returns an absolute path without redundant 0220 // '.' or '.. ' elements (or an empty string if this is 0221 // not possible): 0222 result.append(info.canonicalFilePath()); 0223 } 0224 } 0225 result.removeDuplicates(); 0226 result.removeAll(QString()); // Remove empty strings 0227 return result; 0228 } 0229 0230 } // namespace PerceptualColor