QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGradientStop.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGradientStop.h"
7#include "QskRgbValue.h"
8
9#include <qhashfunctions.h>
10#include <qvariant.h>
11#include <qbrush.h>
12
13#include <algorithm>
14
15static void qskRegisterGradientStop()
16{
17 qRegisterMetaType< QskGradientStop >();
18
19#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
20 QMetaType::registerEqualsComparator< QskGradientStop >();
21#endif
22}
23
24Q_CONSTRUCTOR_FUNCTION( qskRegisterGradientStop )
25
26void QskGradientStop::setPosition( qreal position ) noexcept
27{
28 m_position = position;
29}
30
31void QskGradientStop::setColor( const QColor& color ) noexcept
32{
33 m_color = color;
34}
35
36void QskGradientStop::setRgb( QRgb rgb ) noexcept
37{
38 m_color = QColor::fromRgba( rgb );
39}
40
41void QskGradientStop::setStop( qreal position, const QColor& color ) noexcept
42{
43 m_position = position;
44 m_color = color;
45}
46
47void QskGradientStop::setStop( qreal position, Qt::GlobalColor color ) noexcept
48{
49 m_position = position;
50 m_color = color;
51}
52
53QskHashValue QskGradientStop::hash( QskHashValue seed ) const noexcept
54{
55 auto hash = qHashBits( &m_position, sizeof( m_position ), seed );
56 return qHashBits( &m_color, sizeof( m_color ), hash );
57}
58
59QColor QskGradientStop::interpolated(
60 const QskGradientStop& s1, const QskGradientStop& s2, qreal position ) noexcept
61{
62 if ( s1.color() == s2.color() )
63 return s1.color();
64
65 auto min = &s1;
66 auto max = &s2;
67
68 if ( min->position() > max->position() )
69 std::swap( min, max );
70
71 if ( position <= min->position() )
72 return min->color();
73
74 if ( position >= max->position() )
75 return max->color();
76
77 const qreal r = ( position - min->position() ) / ( max->position() - min->position() );
78 return QskRgb::interpolated( min->color(), max->color(), r );
79}
80
81#ifndef QT_NO_DEBUG_STREAM
82
83#include <qdebug.h>
84
85QDebug operator<<( QDebug debug, const QskGradientStop& stop )
86{
87 QDebugStateSaver saver( debug );
88 debug.nospace();
89
90 debug << stop.position() << ": ";
91 QskRgb::debugColor( debug, stop.color() );
92
93 return debug;
94}
95
96#endif
97
98#include "moc_QskGradientStop.cpp"
99
100// some helper functions around QskGradientStops
101
102static inline QColor qskInterpolatedColor(
103 const QskGradientStops& stops, int index1, int index2, qreal position )
104{
105 const auto max = static_cast< int >( stops.count() ) - 1;
106
107 index1 = qBound( 0, index1, max );
108 index2 = qBound( 0, index2, max );
109
110 return QskGradientStop::interpolated( stops[ index1 ], stops[ index2 ], position );
111}
112
113bool qskIsVisible( const QskGradientStops& stops ) noexcept
114{
115 for ( const auto& stop : stops )
116 {
117 const auto& c = stop.color();
118 if ( c.isValid() && c.alpha() > 0 )
119 return true;
120 }
121
122 return false;
123}
124
125bool qskIsMonochrome( const QskGradientStops& stops ) noexcept
126{
127 for ( int i = 1; i < stops.size(); i++ )
128 {
129 if ( stops[ i ].color() != stops[ 0 ].color() )
130 return false;
131 }
132
133 return true;
134}
135
136QskGradientStops qskTransparentGradientStops( const QskGradientStops& stops, qreal ratio )
137{
138 auto newStops = stops;
139
140 for ( auto& stop : newStops )
141 {
142 auto c = stop.color();
143 c.setAlpha( qRound( c.alpha() * ratio ) );
144
145 stop.setColor( c );
146 }
147
148 return stops;
149}
150
151QskGradientStops qskInterpolatedGradientStops(
152 const QskGradientStops& stops, const QColor& color, qreal ratio )
153{
154 auto newStops = stops;
155
156 for ( auto& stop : newStops )
157 stop.setColor( QskRgb::interpolated( stop.color(), color, ratio ) );
158
159 return newStops;
160}
161
162QskGradientStops qskInterpolatedGradientStops(
163 const QColor& color, const QskGradientStops& stops, qreal ratio )
164{
165 auto newStops = stops;
166
167 for ( auto& stop : newStops )
168 stop.setColor( QskRgb::interpolated( color, stop.color(), ratio ) );
169
170 return newStops;
171}
172
173static QskGradientStops qskInterpolatedStops(
174 const QskGradientStops& from, const QskGradientStops& to, qreal ratio )
175{
176 /*
177 We have to insert interpolated stops for all positions
178 that can be found in s1 and s2. So in case there is no
179 stop at a specific position of the other stops we
180 have to calculate one temporarily before interpolating.
181 */
182 QVector< QskGradientStop > stops;
183
184 int i = 0, j = 0;
185 while ( ( i < from.count() ) || ( j < to.count() ) )
186 {
187 qreal pos;
188 QColor c1, c2;
189
190 if ( i == from.count() )
191 {
192 c1 = from.last().color();
193 c2 = to[j].color();
194 pos = to[j++].position();
195 }
196 else if ( j == to.count() )
197 {
198 c1 = from[i].color();
199 c2 = to.last().color();
200 pos = from[i++].position();
201 }
202 else
203 {
204 c1 = from[i].color();
205 c2 = to[j].color();
206
207 const qreal dpos = from[i].position() - to[j].position();
208
209 if ( qFuzzyIsNull( dpos ) )
210 {
211 pos = from[i++].position();
212 j++;
213 }
214 else if ( dpos < 0.0 )
215 {
216 pos = from[i++].position();
217
218 if ( j > 0 )
219 {
220 const auto& stop = to[j - 1];
221 c2 = QskRgb::interpolated( stop.color(), c2, pos - stop.position() );
222 }
223 }
224 else
225 {
226 pos = to[j++].position();
227
228 if ( i > 0 )
229 {
230 const auto& stop = from[i - 1];
231 c1 = QskRgb::interpolated( stop.color(), c1, pos - stop.position() );
232 }
233 }
234 }
235
236 stops += QskGradientStop( pos, QskRgb::interpolated( c1, c2, ratio ) );
237 }
238
239 return stops;
240}
241
242QskGradientStops qskInterpolatedGradientStops(
243 const QskGradientStops& from, bool fromIsMonochrome,
244 const QskGradientStops& to, bool toIsMonochrome, qreal ratio )
245{
246 if ( from.isEmpty() && to.isEmpty() )
247 return QskGradientStops();
248
249 if ( from.isEmpty() )
250 return qskTransparentGradientStops( to, ratio );
251
252 if ( to.isEmpty() )
253 return qskTransparentGradientStops( from, 1.0 - ratio );
254
255 if ( fromIsMonochrome && toIsMonochrome )
256 {
257 const auto c = QskRgb::interpolated(
258 from[ 0 ].color(), to[ 0 ].color(), ratio );
259
260 return { { 0.0, c }, { 1.0, c } };
261 }
262
263 if ( fromIsMonochrome )
264 {
265 return qskInterpolatedGradientStops( from[ 0 ].color(), to, ratio );
266 }
267
268 if ( toIsMonochrome )
269 {
270 return qskInterpolatedGradientStops( from, to[ 0 ].color(), ratio );
271 }
272
273 return qskInterpolatedStops( from, to, ratio );
274}
275
276QColor qskInterpolatedColorAt( const QskGradientStops& stops, qreal pos ) noexcept
277{
278 if ( stops.isEmpty() )
279 return QColor();
280
281 pos = qBound( 0.0, pos, 1.0 );
282
283 if ( pos <= stops.first().position() )
284 return stops.first().color();
285
286 for ( int i = 1; i < stops.count(); i++ )
287 {
288 if ( pos <= stops[i].position() )
289 return qskInterpolatedColor( stops, i - 1, i, pos );
290 }
291
292 return stops.last().color();
293}
294
295QskGradientStops qskExtractedGradientStops(
296 const QskGradientStops& gradientStops, qreal from, qreal to )
297{
298 if ( ( from > to ) || ( from > 1.0 ) || gradientStops.isEmpty() )
299 return QskGradientStops();
300
301 if ( ( from <= 0.0 ) && ( to >= 1.0 ) )
302 return gradientStops;
303
304 from = qMax( from, 0.0 );
305 to = qMin( to, 1.0 );
306
307 QVector< QskGradientStop > stops1 = gradientStops;
308
309#if 1
310 // not the most efficient implementation - maybe later TODO ...
311
312 if ( stops1.first().position() > 0.0 )
313 stops1.prepend( QskGradientStop( 0.0, stops1.first().color() ) );
314
315 if ( stops1.last().position() < 1.0 )
316 stops1.append( QskGradientStop( 1.0, stops1.last().color() ) );
317#endif
318
319 QVector< QskGradientStop > stops2;
320 stops2.reserve( stops1.count() );
321
322 if ( stops1.count() == 2 )
323 {
324 const auto rgb1 = stops1.first().rgb();
325 const auto rgb2 = stops1.last().rgb();
326
327 stops2 += QskGradientStop( 0.0, QskRgb::interpolated( rgb1, rgb2, from ) );
328 stops2 += QskGradientStop( 1.0, QskRgb::interpolated( rgb1, rgb2, to ) );
329 }
330 else
331 {
332 int i = 0;
333
334 for ( ; i < stops1.count(); i++ )
335 {
336 if ( stops1[i].position() > from )
337 break;
338 }
339
340 stops2 += QskGradientStop( 0.0,
341 qskInterpolatedColor( stops1, i - 1, i, from ) );
342
343 for ( ; i < stops1.count(); i++ )
344 {
345 if ( stops1[i].position() >= to )
346 break;
347
348 const auto pos = ( stops1[i].position() - from ) / ( to - from );
349 stops2 += QskGradientStop( pos, stops1[i].color() );
350 }
351
352 stops2 += QskGradientStop( 1.0,
353 qskInterpolatedColor( stops1, i, i + 1, to ) );
354 }
355
356 return stops2;
357}
358
359QskGradientStops qskRevertedGradientStops( const QskGradientStops& stops )
360{
361 QVector< QskGradientStop > s;
362 s.reserve( stops.count() );
363
364 for ( auto it = stops.crbegin(); it != stops.crend(); ++it )
365 s += QskGradientStop( 1.0 - it->position(), it->color() );
366
367 return s;
368}
369
370QVector< QskGradientStop > qskBuildGradientStops( const QGradientStops& qtStops )
371{
372 QVector< QskGradientStop > stops;
373 stops.reserve( qtStops.count() );
374
375 for ( const auto& s : qtStops )
376 stops += QskGradientStop( s.first, s.second );
377
378 return stops;
379}
380
381template< typename T >
382static inline QVector< QskGradientStop > qskCreateStops(
383 const QVector< T > colors, bool discrete )
384{
385 QVector< QskGradientStop > stops;
386
387 const auto count = colors.count();
388 if ( count == 0 )
389 return stops;
390
391 if ( count == 1 )
392 {
393 stops.reserve( 2 );
394
395 stops += QskGradientStop( 0.0, colors[0] );
396 stops += QskGradientStop( 1.0, colors[0] );
397
398 return stops;
399 }
400
401 if ( discrete )
402 {
403 const auto step = 1.0 / count;
404 stops.reserve( 2 * count - 2 );
405
406 stops += QskGradientStop( 0.0, colors[0] );
407
408 for ( int i = 1; i < count; i++ )
409 {
410 const qreal pos = i * step;
411 stops += QskGradientStop( pos, colors[i - 1] );
412 stops += QskGradientStop( pos, colors[i] );
413 }
414
415 stops += QskGradientStop( 1.0, colors[count - 1] );
416 }
417 else
418 {
419 const auto step = 1.0 / ( count - 1 );
420 stops.reserve( count );
421
422 stops += QskGradientStop( 0.0, colors[0] );
423
424 for ( int i = 1; i < count - 1; i++ )
425 stops += QskGradientStop( i * step, colors[i] );
426
427 stops += QskGradientStop( 1.0, colors[count - 1] );
428 }
429
430 return stops;
431}
432
433QskGradientStops qskBuildGradientStops( const QVector< QRgb >& colors, bool discrete )
434{
435 return qskCreateStops( colors, discrete );
436}
437
438QskGradientStops qskBuildGradientStops( const QVector< QColor >& colors, bool discrete )
439{
440 return qskCreateStops( colors, discrete );
441}
442
443QGradientStops qskToQGradientStops( const QskGradientStops& stops )
444{
445 QGradientStops qStops;
446 qStops.reserve( stops.count() );
447
448 for ( const auto& stop : stops )
449 {
450 QPair< qreal, QColor > qStop = { stop.position(), stop.color() };
451
452 if ( !qStops.isEmpty() && qStops.last().first == qStop.first )
453 {
454 /*
455 QGradient removes stops at the same position. So we have to insert
456 an invisible dummy offset
457 */
458 qStop.first = qMin( qStop.first + 0.00001, 1.0 );
459 }
460
461 qStops += qStop;
462 }
463
464 return qStops;
465}