QGIS API Documentation 4.1.0-Master (60fea48833c)
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 ), qgsDoubleToString( green, 7 ), qgsDoubleToString( blue, 7 ), 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 ), qgsDoubleToString( s ), qgsDoubleToString( v ), qgsDoubleToString( alpha ) );
211 }
212
213 case QColor::Hsl:
214 {
215 float h = 1;
216 float s = 1;
217 float l = 1;
218 float alpha = 1;
219
220 color.getHslF( &h, &s, &l, &alpha );
221 return compatString + u"hsl:%1,%2,%3,%4"_s.arg( qgsDoubleToString( h ), qgsDoubleToString( s ), qgsDoubleToString( l ), qgsDoubleToString( alpha ) );
222 }
223
224 case QColor::Cmyk:
225 {
226 float c = 1;
227 float m = 1;
228 float y = 1;
229 float k = 1;
230 float alpha = 1;
231
232 color.getCmykF( &c, &m, &y, &k, &alpha );
233 return compatString + u"cmyk:%1,%2,%3,%4,%5"_s.arg( qgsDoubleToString( c ), qgsDoubleToString( m ), qgsDoubleToString( y ), qgsDoubleToString( k ), qgsDoubleToString( alpha ) );
234 }
235 }
236 return QString();
237}
238
239QColor QgsColorUtils::colorFromString( const QString &string )
240{
241 if ( string.isEmpty() )
242 return QColor();
243
244 const thread_local QRegularExpression rx( u"^(.*),([a-z]+):([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),([\\d\\.\\-]+),?([\\d\\.\\-]*)$"_s );
245 const QRegularExpressionMatch match = rx.match( string );
246 if ( !match.hasMatch() )
247 {
248 // try reading older color format and hex strings
249 const QStringList lst = string.split( ',' );
250 if ( lst.count() < 3 )
251 {
252 return QColor( string );
253 }
254 int red, green, blue, alpha;
255 red = lst[0].toInt();
256 green = lst[1].toInt();
257 blue = lst[2].toInt();
258 alpha = lst.count() > 3 ? lst[3].toInt() : 255;
259 return QColor( red, green, blue, alpha );
260 }
261
262 const QString spec = match.captured( 2 );
263
264 if ( spec == "rgb"_L1 )
265 {
266 // QColor will automatically adapt between extended rgb/rgb based on value of red/green/blue components
267 const double red = match.captured( 3 ).toDouble();
268 const double green = match.captured( 4 ).toDouble();
269 const double blue = match.captured( 5 ).toDouble();
270 const double alpha = match.captured( 6 ).toDouble();
271 return QColor::fromRgbF( red, green, blue, alpha );
272 }
273 else if ( spec == "hsv"_L1 )
274 {
275 const double hue = match.captured( 3 ).toDouble();
276 const double saturation = match.captured( 4 ).toDouble();
277 const double value = match.captured( 5 ).toDouble();
278 const double alpha = match.captured( 6 ).toDouble();
279 return QColor::fromHsvF( hue, saturation, value, alpha );
280 }
281 else if ( spec == "hsl"_L1 )
282 {
283 const double hue = match.captured( 3 ).toDouble();
284 const double saturation = match.captured( 4 ).toDouble();
285 const double lightness = match.captured( 5 ).toDouble();
286 const double alpha = match.captured( 6 ).toDouble();
287 return QColor::fromHslF( hue, saturation, lightness, alpha );
288 }
289 else if ( spec == "cmyk"_L1 )
290 {
291 const double cyan = match.captured( 3 ).toDouble();
292 const double magenta = match.captured( 4 ).toDouble();
293 const double yellow = match.captured( 5 ).toDouble();
294 const double black = match.captured( 6 ).toDouble();
295 const double alpha = match.captured( 7 ).toDouble();
296 return QColor::fromCmykF( cyan, magenta, yellow, black, alpha );
297 }
298 return QColor();
299}
300
301QColorSpace QgsColorUtils::iccProfile( const QString &iccProfileFilePath, QString &errorMsg )
302{
303 if ( iccProfileFilePath.isEmpty() )
304 return QColorSpace();
305
306 QFile file( iccProfileFilePath );
307 if ( !file.open( QIODevice::ReadOnly ) )
308 {
309 errorMsg = QObject::tr( "Failed to open ICC Profile: %1" ).arg( iccProfileFilePath );
310 return QColorSpace();
311 }
312
313 QColorSpace colorSpace = QColorSpace::fromIccProfile( file.readAll() );
314 if ( !colorSpace.isValid() )
315 {
316 errorMsg = QObject::tr( "Invalid ICC Profile: %1" ).arg( iccProfileFilePath );
317 return colorSpace;
318 }
319
320 return colorSpace;
321}
322
323
324QString QgsColorUtils::saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath )
325{
326 if ( !colorSpace.isValid() )
327 return QObject::tr( "Invalid ICC profile" );
328
329 QFile iccProfile( iccProfileFilePath );
330 if ( !iccProfile.open( QIODevice::WriteOnly ) )
331 return QObject::tr( "File access error '%1'" ).arg( iccProfileFilePath );
332
333 if ( iccProfile.write( colorSpace.iccProfile() ) < 0 )
334 return QObject::tr( "Error while writing to file '%1'" ).arg( iccProfileFilePath );
335
336 return QString();
337}
338
339#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
340
341Qgis::ColorModel QgsColorUtils::toColorModel( QColorSpace::ColorModel colorModel, bool *ok )
342{
343 bool lok = false;
345 switch ( colorModel )
346 {
347 case QColorSpace::ColorModel::Cmyk:
348 lok = true;
350 break;
351
352 case QColorSpace::ColorModel::Rgb:
353 lok = true;
355 break;
356
357 case QColorSpace::ColorModel::Undefined:
358 case QColorSpace::ColorModel::Gray: // not supported
359 lok = false;
360 }
361
362 if ( ok )
363 *ok = lok;
364
365 return res;
366}
367
368#endif
ColorModel
Color model types.
Definition qgis.h:6344
@ Cmyk
CMYK color model.
Definition qgis.h:6346
@ Rgb
RGB color model.
Definition qgis.h:6345
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:6893