QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgscolorutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscolorutils.cpp
3 ---------------------------
4 begin : July 2022
5 copyright : (C) 2022 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgscolorutils.h"
19
20#include <QColor>
21#include <QColorSpace>
22#include <QDomDocument>
23#include <QFile>
24#include <QRegularExpression>
25#include <QRegularExpressionMatch>
26#include <QString>
27
28using namespace Qt::StringLiterals;
29
30void QgsColorUtils::writeXml( const QColor &color, const QString &identifier, QDomDocument &document, QDomElement &element, const QgsReadWriteContext & )
31{
32 {
33 const QDomElement oldElement = element.firstChildElement( identifier );
34 if ( !oldElement.isNull() )
35 element.removeChild( oldElement );
36 }
37
38 QDomElement colorElement = document.createElement( identifier );
39 if ( !color.isValid() )
40 {
41 colorElement.setAttribute( u"invalid"_s, u"1"_s );
42 }
43 else
44 {
45 QString spec;
46 switch ( color.spec() )
47 {
48 case QColor::Invalid:
49 break; // not possible
50
51 case QColor::Rgb:
52 case QColor::ExtendedRgb:
53 {
54 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
55 spec = u"rgb"_s;
56 float red = 1;
57 float green = 1;
58 float blue = 1;
59
60 color.getRgbF( &red, &green, &blue );
61 colorElement.setAttribute( u"red"_s, qgsDoubleToString( red ) );
62 colorElement.setAttribute( u"green"_s, qgsDoubleToString( green ) );
63 colorElement.setAttribute( u"blue"_s, qgsDoubleToString( blue ) );
64 break;
65 }
66
67 case QColor::Hsv:
68 {
69 spec = u"hsv"_s;
70
71 float h = 1;
72 float s = 1;
73 float v = 1;
74
75 color.getHsvF( &h, &s, &v );
76 colorElement.setAttribute( u"hue"_s, qgsDoubleToString( h ) );
77 colorElement.setAttribute( u"saturation"_s, qgsDoubleToString( s ) );
78 colorElement.setAttribute( u"value"_s, qgsDoubleToString( v ) );
79 break;
80 }
81
82 case QColor::Hsl:
83 {
84 spec = u"hsl"_s;
85
86 float h = 1;
87 float s = 1;
88 float l = 1;
89
90 color.getHslF( &h, &s, &l );
91 colorElement.setAttribute( u"hue"_s, qgsDoubleToString( h ) );
92 colorElement.setAttribute( u"saturation"_s, qgsDoubleToString( s ) );
93 colorElement.setAttribute( u"lightness"_s, qgsDoubleToString( l ) );
94 break;
95 }
96
97 case QColor::Cmyk:
98 {
99 spec = u"cmyk"_s;
100
101 float c = 1;
102 float m = 1;
103 float y = 1;
104 float k = 1;
105
106 color.getCmykF( &c, &y, &m, &k );
107 colorElement.setAttribute( u"c"_s, qgsDoubleToString( c ) );
108 colorElement.setAttribute( u"m"_s, qgsDoubleToString( m ) );
109 colorElement.setAttribute( u"y"_s, qgsDoubleToString( y ) );
110 colorElement.setAttribute( u"k"_s, qgsDoubleToString( k ) );
111 break;
112 }
113 }
114 colorElement.setAttribute( u"spec"_s, spec );
115 if ( color.alphaF() < 1.0 )
116 {
117 colorElement.setAttribute( u"alpha"_s, qgsDoubleToString( color.alphaF() ) );
118 }
119 }
120 element.appendChild( colorElement );
121}
122
123QColor QgsColorUtils::readXml( const QDomElement &element, const QString &identifier, const QgsReadWriteContext & )
124{
125 const QDomElement colorElement = element.firstChildElement( identifier );
126 if ( colorElement.isNull() )
127 return QColor();
128
129 const bool invalid = colorElement.attribute( u"invalid"_s, u"0"_s ).toInt();
130 if ( invalid )
131 return QColor();
132
133 QColor res;
134 const QString spec = colorElement.attribute( u"spec"_s );
135 if ( spec == "rgb"_L1 )
136 {
137 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
138 const double red = colorElement.attribute( u"red"_s ).toDouble();
139 const double green = colorElement.attribute( u"green"_s ).toDouble();
140 const double blue = colorElement.attribute( u"blue"_s ).toDouble();
141 res = QColor::fromRgbF( red, green, blue );
142 }
143 else if ( spec == "hsv"_L1 )
144 {
145 const double hue = colorElement.attribute( u"hue"_s ).toDouble();
146 const double saturation = colorElement.attribute( u"saturation"_s ).toDouble();
147 const double value = colorElement.attribute( u"value"_s ).toDouble();
148 res = QColor::fromHsvF( hue, saturation, value );
149 }
150 else if ( spec == "hsl"_L1 )
151 {
152 const double hue = colorElement.attribute( u"hue"_s ).toDouble();
153 const double saturation = colorElement.attribute( u"saturation"_s ).toDouble();
154 const double value = colorElement.attribute( u"lightness"_s ).toDouble();
155 res = QColor::fromHslF( hue, saturation, value );
156 }
157 else if ( spec == "cmyk"_L1 )
158 {
159 const double cyan = colorElement.attribute( u"c"_s ).toDouble();
160 const double magenta = colorElement.attribute( u"m"_s ).toDouble();
161 const double yellow = colorElement.attribute( u"y"_s ).toDouble();
162 const double black = colorElement.attribute( u"k"_s ).toDouble();
163 res = QColor::fromCmykF( cyan, magenta, yellow, black );
164 }
165
166 {
167 const double alpha = colorElement.attribute( u"alpha"_s, u"1"_s ).toDouble();
168 res.setAlphaF( alpha );
169 }
170
171 return res;
172}
173
174QString QgsColorUtils::colorToString( const QColor &color )
175{
176 if ( !color.isValid() )
177 return QString();
178
179 // this is the pre 3.28 deprecated string format -- we prefix the lossless encoded color with this so that older QGIS versions
180 // can still recover the lossy color via QgsSymbolLayerUtils::decodeColor
181 const QString compatString = u"%1,%2,%3,%4,"_s.arg( color.red() ).arg( color.green() ).arg( color.blue() ).arg( color.alpha() );
182
183 switch ( color.spec() )
184 {
185 case QColor::Invalid:
186 break; // not possible
187
188 case QColor::Rgb:
189 case QColor::ExtendedRgb:
190 {
191 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
192
193 float red = 1;
194 float green = 1;
195 float blue = 1;
196 float alpha = 1;
197
198 color.getRgbF( &red, &green, &blue, &alpha );
199 return compatString + u"rgb:%1,%2,%3,%4"_s.arg( qgsDoubleToString( red, 7 ),
200 qgsDoubleToString( green, 7 ),
201 qgsDoubleToString( blue, 7 ),
202 qgsDoubleToString( alpha, 7 ) );
203 }
204
205 case QColor::Hsv:
206 {
207 float h = 1;
208 float s = 1;
209 float v = 1;
210 float alpha = 1;
211
212 color.getHsvF( &h, &s, &v, &alpha );
213 return compatString + u"hsv:%1,%2,%3,%4"_s.arg( qgsDoubleToString( h ),
216 qgsDoubleToString( alpha ) );
217 }
218
219 case QColor::Hsl:
220 {
221 float h = 1;
222 float s = 1;
223 float l = 1;
224 float alpha = 1;
225
226 color.getHslF( &h, &s, &l, &alpha );
227 return compatString + u"hsl:%1,%2,%3,%4"_s.arg( qgsDoubleToString( h ),
230 qgsDoubleToString( alpha ) );
231 }
232
233 case QColor::Cmyk:
234 {
235 float c = 1;
236 float m = 1;
237 float y = 1;
238 float k = 1;
239 float alpha = 1;
240
241 color.getCmykF( &c, &m, &y, &k, &alpha );
242 return compatString + u"cmyk:%1,%2,%3,%4,%5"_s.arg( qgsDoubleToString( c ),
246 qgsDoubleToString( alpha ) );
247 }
248 }
249 return QString();
250}
251
252QColor QgsColorUtils::colorFromString( const QString &string )
253{
254 if ( string.isEmpty() )
255 return QColor();
256
257 const thread_local QRegularExpression rx( u"^(.*),([a-z]+):([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),?([\\d\\.\\-]*)$"_s );
258 const QRegularExpressionMatch match = rx.match( string );
259 if ( !match.hasMatch() )
260 {
261 // try reading older color format and hex strings
262 const QStringList lst = string.split( ',' );
263 if ( lst.count() < 3 )
264 {
265 return QColor( string );
266 }
267 int red, green, blue, alpha;
268 red = lst[0].toInt();
269 green = lst[1].toInt();
270 blue = lst[2].toInt();
271 alpha = lst.count() > 3 ? lst[3].toInt() : 255;
272 return QColor( red, green, blue, alpha );
273 }
274
275 const QString spec = match.captured( 2 );
276
277 if ( spec == "rgb"_L1 )
278 {
279 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
280 const double red = match.captured( 3 ).toDouble();
281 const double green = match.captured( 4 ).toDouble();
282 const double blue = match.captured( 5 ).toDouble();
283 const double alpha = match.captured( 6 ).toDouble();
284 return QColor::fromRgbF( red, green, blue, alpha );
285 }
286 else if ( spec == "hsv"_L1 )
287 {
288 const double hue = match.captured( 3 ).toDouble();
289 const double saturation = match.captured( 4 ).toDouble();
290 const double value = match.captured( 5 ).toDouble();
291 const double alpha = match.captured( 6 ).toDouble();
292 return QColor::fromHsvF( hue, saturation, value, alpha );
293 }
294 else if ( spec == "hsl"_L1 )
295 {
296 const double hue = match.captured( 3 ).toDouble();
297 const double saturation = match.captured( 4 ).toDouble();
298 const double lightness = match.captured( 5 ).toDouble();
299 const double alpha = match.captured( 6 ).toDouble();
300 return QColor::fromHslF( hue, saturation, lightness, alpha );
301 }
302 else if ( spec == "cmyk"_L1 )
303 {
304 const double cyan = match.captured( 3 ).toDouble();
305 const double magenta = match.captured( 4 ).toDouble();
306 const double yellow = match.captured( 5 ).toDouble();
307 const double black = match.captured( 6 ).toDouble();
308 const double alpha = match.captured( 7 ).toDouble();
309 return QColor::fromCmykF( cyan, magenta, yellow, black, alpha );
310 }
311 return QColor();
312}
313
314QColorSpace QgsColorUtils::iccProfile( const QString &iccProfileFilePath, QString &errorMsg )
315{
316 if ( iccProfileFilePath.isEmpty() )
317 return QColorSpace();
318
319 QFile file( iccProfileFilePath );
320 if ( !file.open( QIODevice::ReadOnly ) )
321 {
322 errorMsg = QObject::tr( "Failed to open ICC Profile: %1" ).arg( iccProfileFilePath );
323 return QColorSpace();
324 }
325
326 QColorSpace colorSpace = QColorSpace::fromIccProfile( file.readAll() );
327 if ( !colorSpace.isValid() )
328 {
329 errorMsg = QObject::tr( "Invalid ICC Profile: %1" ).arg( iccProfileFilePath );
330 return colorSpace;
331 }
332
333 return colorSpace;
334}
335
336
337QString QgsColorUtils::saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath )
338{
339 if ( !colorSpace.isValid() )
340 return QObject::tr( "Invalid ICC profile" );
341
342 QFile iccProfile( iccProfileFilePath );
343 if ( !iccProfile.open( QIODevice::WriteOnly ) )
344 return QObject::tr( "File access error '%1'" ).arg( iccProfileFilePath );
345
346 if ( iccProfile.write( colorSpace.iccProfile() ) < 0 )
347 return QObject::tr( "Error while writing to file '%1'" ).arg( iccProfileFilePath );
348
349 return QString();
350}
351
352#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
353
354Qgis::ColorModel QgsColorUtils::toColorModel( QColorSpace::ColorModel colorModel, bool *ok )
355{
356 bool lok = false;
358 switch ( colorModel )
359 {
360 case QColorSpace::ColorModel::Cmyk:
361 lok = true;
363 break;
364
365 case QColorSpace::ColorModel::Rgb:
366 lok = true;
368 break;
369
370 case QColorSpace::ColorModel::Undefined:
371 case QColorSpace::ColorModel::Gray: // not supported
372 lok = false;
374 }
375
376 if ( ok )
377 *ok = lok;
378
379 return res;
380}
381
382#endif
ColorModel
Color model types.
Definition qgis.h:6252
@ Cmyk
CMYK color model.
Definition qgis.h:6254
@ Rgb
RGB color model.
Definition qgis.h:6253
static QColor readXml(const QDomElement &element, const QString &identifier, const QgsReadWriteContext &context)
Reads a color from an XML element, matching the specified identifier string.
static void writeXml(const QColor &color, const QString &identifier, QDomDocument &document, QDomElement &element, const QgsReadWriteContext &context)
Writes a color to an XML element, storing it under the specified identifier.
static Qgis::ColorModel toColorModel(QColorSpace::ColorModel colorModel, bool *ok=nullptr)
Convert and returns Qt colorModel to Qgis::ColorModel.
static QColor colorFromString(const QString &string)
Decodes a string into a color value.
static QString colorToString(const QColor &color)
Encodes a color into a string value.
static QColorSpace iccProfile(const QString &iccProfileFilePath, QString &errorMsg)
Loads an ICC profile from iccProfileFilePath and returns associated color space.
static QString saveIccProfile(const QColorSpace &colorSpace, const QString &iccProfileFilePath)
Save color space colorSpace to an ICC profile file iccProfileFilePath.
A container for the context for various read/write operations on objects.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6817