QGIS API Documentation 3.99.0-Master (a8882ad4560)
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
27void QgsColorUtils::writeXml( const QColor &color, const QString &identifier, QDomDocument &document, QDomElement &element, const QgsReadWriteContext & )
28{
29 {
30 const QDomElement oldElement = element.firstChildElement( identifier );
31 if ( !oldElement.isNull() )
32 element.removeChild( oldElement );
33 }
34
35 QDomElement colorElement = document.createElement( identifier );
36 if ( !color.isValid() )
37 {
38 colorElement.setAttribute( u"invalid"_s, u"1"_s );
39 }
40 else
41 {
42 QString spec;
43 switch ( color.spec() )
44 {
45 case QColor::Invalid:
46 break; // not possible
47
48 case QColor::Rgb:
49 case QColor::ExtendedRgb:
50 {
51 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
52 spec = u"rgb"_s;
53 float red = 1;
54 float green = 1;
55 float blue = 1;
56
57 color.getRgbF( &red, &green, &blue );
58 colorElement.setAttribute( u"red"_s, qgsDoubleToString( red ) );
59 colorElement.setAttribute( u"green"_s, qgsDoubleToString( green ) );
60 colorElement.setAttribute( u"blue"_s, qgsDoubleToString( blue ) );
61 break;
62 }
63
64 case QColor::Hsv:
65 {
66 spec = u"hsv"_s;
67
68 float h = 1;
69 float s = 1;
70 float v = 1;
71
72 color.getHsvF( &h, &s, &v );
73 colorElement.setAttribute( u"hue"_s, qgsDoubleToString( h ) );
74 colorElement.setAttribute( u"saturation"_s, qgsDoubleToString( s ) );
75 colorElement.setAttribute( u"value"_s, qgsDoubleToString( v ) );
76 break;
77 }
78
79 case QColor::Hsl:
80 {
81 spec = u"hsl"_s;
82
83 float h = 1;
84 float s = 1;
85 float l = 1;
86
87 color.getHslF( &h, &s, &l );
88 colorElement.setAttribute( u"hue"_s, qgsDoubleToString( h ) );
89 colorElement.setAttribute( u"saturation"_s, qgsDoubleToString( s ) );
90 colorElement.setAttribute( u"lightness"_s, qgsDoubleToString( l ) );
91 break;
92 }
93
94 case QColor::Cmyk:
95 {
96 spec = u"cmyk"_s;
97
98 float c = 1;
99 float m = 1;
100 float y = 1;
101 float k = 1;
102
103 color.getCmykF( &c, &y, &m, &k );
104 colorElement.setAttribute( u"c"_s, qgsDoubleToString( c ) );
105 colorElement.setAttribute( u"m"_s, qgsDoubleToString( m ) );
106 colorElement.setAttribute( u"y"_s, qgsDoubleToString( y ) );
107 colorElement.setAttribute( u"k"_s, qgsDoubleToString( k ) );
108 break;
109 }
110 }
111 colorElement.setAttribute( u"spec"_s, spec );
112 if ( color.alphaF() < 1.0 )
113 {
114 colorElement.setAttribute( u"alpha"_s, qgsDoubleToString( color.alphaF() ) );
115 }
116 }
117 element.appendChild( colorElement );
118}
119
120QColor QgsColorUtils::readXml( const QDomElement &element, const QString &identifier, const QgsReadWriteContext & )
121{
122 const QDomElement colorElement = element.firstChildElement( identifier );
123 if ( colorElement.isNull() )
124 return QColor();
125
126 const bool invalid = colorElement.attribute( u"invalid"_s, u"0"_s ).toInt();
127 if ( invalid )
128 return QColor();
129
130 QColor res;
131 const QString spec = colorElement.attribute( u"spec"_s );
132 if ( spec == "rgb"_L1 )
133 {
134 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
135 const double red = colorElement.attribute( u"red"_s ).toDouble();
136 const double green = colorElement.attribute( u"green"_s ).toDouble();
137 const double blue = colorElement.attribute( u"blue"_s ).toDouble();
138 res = QColor::fromRgbF( red, green, blue );
139 }
140 else if ( spec == "hsv"_L1 )
141 {
142 const double hue = colorElement.attribute( u"hue"_s ).toDouble();
143 const double saturation = colorElement.attribute( u"saturation"_s ).toDouble();
144 const double value = colorElement.attribute( u"value"_s ).toDouble();
145 res = QColor::fromHsvF( hue, saturation, value );
146 }
147 else if ( spec == "hsl"_L1 )
148 {
149 const double hue = colorElement.attribute( u"hue"_s ).toDouble();
150 const double saturation = colorElement.attribute( u"saturation"_s ).toDouble();
151 const double value = colorElement.attribute( u"lightness"_s ).toDouble();
152 res = QColor::fromHslF( hue, saturation, value );
153 }
154 else if ( spec == "cmyk"_L1 )
155 {
156 const double cyan = colorElement.attribute( u"c"_s ).toDouble();
157 const double magenta = colorElement.attribute( u"m"_s ).toDouble();
158 const double yellow = colorElement.attribute( u"y"_s ).toDouble();
159 const double black = colorElement.attribute( u"k"_s ).toDouble();
160 res = QColor::fromCmykF( cyan, magenta, yellow, black );
161 }
162
163 {
164 const double alpha = colorElement.attribute( u"alpha"_s, u"1"_s ).toDouble();
165 res.setAlphaF( alpha );
166 }
167
168 return res;
169}
170
171QString QgsColorUtils::colorToString( const QColor &color )
172{
173 if ( !color.isValid() )
174 return QString();
175
176 // this is the pre 3.28 deprecated string format -- we prefix the lossless encoded color with this so that older QGIS versions
177 // can still recover the lossy color via QgsSymbolLayerUtils::decodeColor
178 const QString compatString = u"%1,%2,%3,%4,"_s.arg( color.red() ).arg( color.green() ).arg( color.blue() ).arg( color.alpha() );
179
180 switch ( color.spec() )
181 {
182 case QColor::Invalid:
183 break; // not possible
184
185 case QColor::Rgb:
186 case QColor::ExtendedRgb:
187 {
188 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
189
190 float red = 1;
191 float green = 1;
192 float blue = 1;
193 float alpha = 1;
194
195 color.getRgbF( &red, &green, &blue, &alpha );
196 return compatString + u"rgb:%1,%2,%3,%4"_s.arg( qgsDoubleToString( red, 7 ),
197 qgsDoubleToString( green, 7 ),
198 qgsDoubleToString( blue, 7 ),
199 qgsDoubleToString( alpha, 7 ) );
200 }
201
202 case QColor::Hsv:
203 {
204 float h = 1;
205 float s = 1;
206 float v = 1;
207 float alpha = 1;
208
209 color.getHsvF( &h, &s, &v, &alpha );
210 return compatString + u"hsv:%1,%2,%3,%4"_s.arg( qgsDoubleToString( h ),
213 qgsDoubleToString( alpha ) );
214 }
215
216 case QColor::Hsl:
217 {
218 float h = 1;
219 float s = 1;
220 float l = 1;
221 float alpha = 1;
222
223 color.getHslF( &h, &s, &l, &alpha );
224 return compatString + u"hsl:%1,%2,%3,%4"_s.arg( qgsDoubleToString( h ),
227 qgsDoubleToString( alpha ) );
228 }
229
230 case QColor::Cmyk:
231 {
232 float c = 1;
233 float m = 1;
234 float y = 1;
235 float k = 1;
236 float alpha = 1;
237
238 color.getCmykF( &c, &m, &y, &k, &alpha );
239 return compatString + u"cmyk:%1,%2,%3,%4,%5"_s.arg( qgsDoubleToString( c ),
243 qgsDoubleToString( alpha ) );
244 }
245 }
246 return QString();
247}
248
249QColor QgsColorUtils::colorFromString( const QString &string )
250{
251 if ( string.isEmpty() )
252 return QColor();
253
254 const thread_local QRegularExpression rx( u"^(.*),([a-z]+):([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),?([\\d\\.\\-]*)$"_s );
255 const QRegularExpressionMatch match = rx.match( string );
256 if ( !match.hasMatch() )
257 {
258 // try reading older color format and hex strings
259 const QStringList lst = string.split( ',' );
260 if ( lst.count() < 3 )
261 {
262 return QColor( string );
263 }
264 int red, green, blue, alpha;
265 red = lst[0].toInt();
266 green = lst[1].toInt();
267 blue = lst[2].toInt();
268 alpha = lst.count() > 3 ? lst[3].toInt() : 255;
269 return QColor( red, green, blue, alpha );
270 }
271
272 const QString spec = match.captured( 2 );
273
274 if ( spec == "rgb"_L1 )
275 {
276 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
277 const double red = match.captured( 3 ).toDouble();
278 const double green = match.captured( 4 ).toDouble();
279 const double blue = match.captured( 5 ).toDouble();
280 const double alpha = match.captured( 6 ).toDouble();
281 return QColor::fromRgbF( red, green, blue, alpha );
282 }
283 else if ( spec == "hsv"_L1 )
284 {
285 const double hue = match.captured( 3 ).toDouble();
286 const double saturation = match.captured( 4 ).toDouble();
287 const double value = match.captured( 5 ).toDouble();
288 const double alpha = match.captured( 6 ).toDouble();
289 return QColor::fromHsvF( hue, saturation, value, alpha );
290 }
291 else if ( spec == "hsl"_L1 )
292 {
293 const double hue = match.captured( 3 ).toDouble();
294 const double saturation = match.captured( 4 ).toDouble();
295 const double lightness = match.captured( 5 ).toDouble();
296 const double alpha = match.captured( 6 ).toDouble();
297 return QColor::fromHslF( hue, saturation, lightness, alpha );
298 }
299 else if ( spec == "cmyk"_L1 )
300 {
301 const double cyan = match.captured( 3 ).toDouble();
302 const double magenta = match.captured( 4 ).toDouble();
303 const double yellow = match.captured( 5 ).toDouble();
304 const double black = match.captured( 6 ).toDouble();
305 const double alpha = match.captured( 7 ).toDouble();
306 return QColor::fromCmykF( cyan, magenta, yellow, black, alpha );
307 }
308 return QColor();
309}
310
311QColorSpace QgsColorUtils::iccProfile( const QString &iccProfileFilePath, QString &errorMsg )
312{
313 if ( iccProfileFilePath.isEmpty() )
314 return QColorSpace();
315
316 QFile file( iccProfileFilePath );
317 if ( !file.open( QIODevice::ReadOnly ) )
318 {
319 errorMsg = QObject::tr( "Failed to open ICC Profile: %1" ).arg( iccProfileFilePath );
320 return QColorSpace();
321 }
322
323 QColorSpace colorSpace = QColorSpace::fromIccProfile( file.readAll() );
324 if ( !colorSpace.isValid() )
325 {
326 errorMsg = QObject::tr( "Invalid ICC Profile: %1" ).arg( iccProfileFilePath );
327 return colorSpace;
328 }
329
330 return colorSpace;
331}
332
333
334QString QgsColorUtils::saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath )
335{
336 if ( !colorSpace.isValid() )
337 return QObject::tr( "Invalid ICC profile" );
338
339 QFile iccProfile( iccProfileFilePath );
340 if ( !iccProfile.open( QIODevice::WriteOnly ) )
341 return QObject::tr( "File access error '%1'" ).arg( iccProfileFilePath );
342
343 if ( iccProfile.write( colorSpace.iccProfile() ) < 0 )
344 return QObject::tr( "Error while writing to file '%1'" ).arg( iccProfileFilePath );
345
346 return QString();
347}
348
349#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
350
351Qgis::ColorModel QgsColorUtils::toColorModel( QColorSpace::ColorModel colorModel, bool *ok )
352{
353 bool lok = false;
355 switch ( colorModel )
356 {
357 case QColorSpace::ColorModel::Cmyk:
358 lok = true;
360 break;
361
362 case QColorSpace::ColorModel::Rgb:
363 lok = true;
365 break;
366
367 case QColorSpace::ColorModel::Undefined:
368 case QColorSpace::ColorModel::Gray: // not supported
369 lok = false;
371 }
372
373 if ( ok )
374 *ok = lok;
375
376 return res;
377}
378
379#endif
ColorModel
Color model types.
Definition qgis.h:6240
@ Cmyk
CMYK color model.
Definition qgis.h:6242
@ Rgb
RGB color model.
Definition qgis.h:6241
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:6805