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