QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGraphic.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGraphic.h"
7#include "QskColorFilter.h"
8#include "QskGraphicPaintEngine.h"
9#include "QskPainterCommand.h"
10
11#include <qguiapplication.h>
12#include <qimage.h>
13#include <qmath.h>
14#include <qpaintengine.h>
15#include <qpainter.h>
16#include <qpainterpath.h>
17#include <qpixmap.h>
18#include <qhashfunctions.h>
19
20QSK_QT_PRIVATE_BEGIN
21#include <private/qpainter_p.h>
22QSK_QT_PRIVATE_END
23
24static inline qreal qskDevicePixelRatio()
25{
26 return qGuiApp ? qGuiApp->devicePixelRatio() : 1.0;
27}
28
29static bool qskHasScalablePen( const QPainter* painter )
30{
31 const QPen pen = painter->pen();
32
33 bool scalablePen = false;
34
35 if ( pen.style() != Qt::NoPen && pen.brush().style() != Qt::NoBrush )
36 {
37 scalablePen = !pen.isCosmetic();
38 }
39
40 return scalablePen;
41}
42
43static QRectF qskStrokedPathRect(
44 const QPainter* painter, const QPainterPath& path )
45{
46 const auto pen = painter->pen();
47
48 QPainterPathStroker stroker;
49 stroker.setWidth( pen.widthF() );
50 stroker.setCapStyle( pen.capStyle() );
51 stroker.setJoinStyle( pen.joinStyle() );
52 stroker.setMiterLimit( pen.miterLimit() );
53
54 QRectF rect;
55 if ( qskHasScalablePen( painter ) )
56 {
57 const QPainterPath stroke = stroker.createStroke( path );
58 rect = painter->transform().map( stroke ).boundingRect();
59 }
60 else
61 {
62 QPainterPath mappedPath = painter->transform().map( path );
63 mappedPath = stroker.createStroke( mappedPath );
64
65 rect = mappedPath.boundingRect();
66 }
67
68 return rect;
69}
70
71static inline void qskExecCommand(
72 QPainter* painter, const QskPainterCommand& cmd,
73 const QskColorFilter& colorFilter,
74 QskGraphic::RenderHints renderHints,
75 const QTransform& transform,
76 const QTransform* initialTransform )
77{
78 switch ( cmd.type() )
79 {
81 {
82 bool doMap = false;
83
84 if ( painter->transform().isScaling() )
85 {
86 if ( painter->pen().isCosmetic() )
87 {
88 // OpenGL2 seems to be buggy for cosmetic pens.
89 // It interpolates curves in too rough steps then
90
91 doMap = painter->paintEngine()->type() == QPaintEngine::OpenGL2;
92 }
93 else
94 {
95 doMap = renderHints.testFlag( QskGraphic::RenderPensUnscaled );
96 }
97 }
98
99 if ( doMap )
100 {
101 const QTransform tr = painter->transform();
102
103 painter->resetTransform();
104
105 QPainterPath path = tr.map( *cmd.path() );
106 if ( initialTransform )
107 {
108 painter->setTransform( *initialTransform );
109 path = initialTransform->inverted().map( path );
110 }
111
112 painter->drawPath( path );
113
114 painter->setTransform( tr );
115 }
116 else
117 {
118 painter->drawPath( *cmd.path() );
119 }
120 break;
121 }
123 {
124 const auto data = cmd.pixmapData();
125 painter->drawPixmap( data->rect, data->pixmap, data->subRect );
126 break;
127 }
129 {
130 const auto data = cmd.imageData();
131 painter->drawImage( data->rect, data->image,
132 data->subRect, data->flags );
133 break;
134 }
136 {
137 const auto data = cmd.stateData();
138
139 if ( data->flags & QPaintEngine::DirtyPen )
140 painter->setPen( colorFilter.substituted( data->pen ) );
141
142 if ( data->flags & QPaintEngine::DirtyBrush )
143 painter->setBrush( colorFilter.substituted( data->brush ) );
144
145 if ( data->flags & QPaintEngine::DirtyBrushOrigin )
146 painter->setBrushOrigin( data->brushOrigin );
147
148 if ( data->flags & QPaintEngine::DirtyFont )
149 painter->setFont( data->font );
150
151 if ( data->flags & QPaintEngine::DirtyBackground )
152 {
153 painter->setBackgroundMode( data->backgroundMode );
154 painter->setBackground( colorFilter.substituted( data->backgroundBrush ) );
155 }
156
157 if ( data->flags & QPaintEngine::DirtyTransform )
158 {
159 painter->setTransform( data->transform * transform );
160 }
161
162 if ( data->flags & QPaintEngine::DirtyClipEnabled )
163 painter->setClipping( data->isClipEnabled );
164
165 if ( data->flags & QPaintEngine::DirtyClipRegion )
166 {
167 painter->setClipRegion( data->clipRegion,
168 data->clipOperation );
169 }
170
171 if ( data->flags & QPaintEngine::DirtyClipPath )
172 {
173 painter->setClipPath( data->clipPath, data->clipOperation );
174 }
175
176 if ( data->flags & QPaintEngine::DirtyHints )
177 {
178#if 1
179 auto& state = QPainterPrivate::get( painter )->state;
180 state->renderHints = data->renderHints;
181
182 // to trigger internal updates we have to set at least one flag
183 const auto hint = QPainter::SmoothPixmapTransform;
184 painter->setRenderHint( hint, data->renderHints.testFlag( hint ) );
185#else
186 for ( int i = 0; i < 8; i++ )
187 {
188 const auto hint = static_cast< QPainter::RenderHint >( 1 << i );
189 painter->setRenderHint( hint, data->renderHints.testFlag( hint ) );
190 }
191#endif
192 }
193
194 if ( data->flags & QPaintEngine::DirtyCompositionMode )
195 painter->setCompositionMode( data->compositionMode );
196
197 if ( data->flags & QPaintEngine::DirtyOpacity )
198 painter->setOpacity( data->opacity );
199
200 break;
201 }
202 default:
203 break;
204 }
205}
206
207/*
208 To avoid subobject-linkage warnings, when including the source code in
209 svg2qvg we don't use an anonymous namespace here
210 */
211namespace QskGraphicPrivate
212{
213 class PathInfo
214 {
215 public:
216 PathInfo()
217 : m_scalablePen( false )
218 {
219 // QVector needs a default constructor
220 }
221
222 PathInfo( const QRectF& pointRect,
223 const QRectF& boundingRect, bool scalablePen )
224 : m_pointRect( pointRect )
225 , m_boundingRect( boundingRect )
226 , m_scalablePen( scalablePen )
227 {
228 }
229
230 inline QRectF scaledBoundingRect( qreal sx, qreal sy, bool scalePens ) const
231 {
232 if ( sx == 1.0 && sy == 1.0 )
233 return m_boundingRect;
234
235 QTransform transform;
236 transform.scale( sx, sy );
237
238 QRectF rect;
239 if ( scalePens && m_scalablePen )
240 {
241 rect = transform.mapRect( m_boundingRect );
242 }
243 else
244 {
245 rect = transform.mapRect( m_pointRect );
246
247 const qreal l = qAbs( m_pointRect.left() - m_boundingRect.left() );
248 const qreal r = qAbs( m_pointRect.right() - m_boundingRect.right() );
249 const qreal t = qAbs( m_pointRect.top() - m_boundingRect.top() );
250 const qreal b = qAbs( m_pointRect.bottom() - m_boundingRect.bottom() );
251
252 rect.adjust( -l, -t, r, b );
253 }
254
255 return rect;
256 }
257
258 inline double scaleFactorX( const QRectF& pathRect,
259 const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const
260 {
261 if ( pathRect.width() <= 0.0 )
262 return 0.0;
263
264 const QPointF p0 = m_pointRect.center();
265
266 const auto p = pathRect.united( m_boundingRect );
267
268 const qreal l = qAbs( p.left() - p0.x() );
269 const qreal r = qAbs( p.right() - p0.x() );
270
271 const double w = 2.0 * qMin( l, r ) *
272 targetRect.width() / graphicBoundingRect.width();
273
274 double sx;
275 if ( scalePens && m_scalablePen )
276 {
277 sx = w / m_boundingRect.width();
278 }
279 else
280 {
281 const qreal pw = qMax(
282 qAbs( m_boundingRect.left() - m_pointRect.left() ),
283 qAbs( m_boundingRect.right() - m_pointRect.right() ) );
284
285 sx = ( w - 2 * pw ) / m_pointRect.width();
286 }
287
288 return sx;
289 }
290
291 inline double scaleFactorY( const QRectF& pathRect,
292 const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const
293 {
294 if ( pathRect.height() <= 0.0 )
295 return 0.0;
296
297 const QPointF p0 = m_pointRect.center();
298
299 const auto p = pathRect.united( m_boundingRect );
300
301 const qreal t = qAbs( p.top() - p0.y() );
302 const qreal b = qAbs( p.bottom() - p0.y() );
303
304 const double h = 2.0 * qMin( t, b ) *
305 targetRect.height() / graphicBoundingRect.height();
306
307 double sy;
308 if ( scalePens && m_scalablePen )
309 {
310 sy = h / m_boundingRect.height();
311 }
312 else
313 {
314 const double pw =
315 qMax( qAbs( m_boundingRect.top() - m_pointRect.top() ),
316 qAbs( m_boundingRect.bottom() - m_pointRect.bottom() ) );
317
318 sy = ( h - 2 * pw ) / m_pointRect.height();
319 }
320
321 return sy;
322 }
323
324 private:
325 QRectF m_pointRect;
326 QRectF m_boundingRect;
327 bool m_scalablePen;
328 };
329}
330
331class QskGraphic::PrivateData : public QSharedData
332{
333 public:
334 PrivateData()
335 : commandTypes( 0 )
336 , renderHints( 0 )
337 {
338 }
339
340 PrivateData( const PrivateData& other )
341 : QSharedData( other )
342 , viewBox( other.viewBox )
343 , commands( other.commands )
344 , pathInfos( other.pathInfos )
345 , boundingRect( other.boundingRect )
346 , pointRect( other.pointRect )
347 , modificationId( other.modificationId )
348 , commandTypes( other.commandTypes )
349 , renderHints( other.renderHints )
350 {
351 }
352
353 inline bool operator==( const PrivateData& other ) const
354 {
355 return ( modificationId == other.modificationId ) &&
356 ( renderHints == other.renderHints ) &&
357 ( viewBox == other.viewBox );
358 }
359
360 void resetCommands()
361 {
362 commands.clear();
363 pathInfos.clear();
364
365 commandTypes = 0;
366 boundingRect = pointRect = { 0.0, 0.0, -1.0, -1.0 };
367
368 modificationId = 0;
369 }
370
371 inline void addCommand( const QskPainterCommand& command )
372 {
373 commands += command;
374
375 static QAtomicInteger< quint64 > nextId( 1 );
376 modificationId = nextId.fetchAndAddRelaxed( 1 );
377 }
378
379 QRectF viewBox = { 0.0, 0.0, -1.0, -1.0 };
380 QVector< QskPainterCommand > commands;
381 QVector< QskGraphicPrivate::PathInfo > pathInfos;
382
383 QRectF boundingRect = { 0.0, 0.0, -1.0, -1.0 };
384 QRectF pointRect = { 0.0, 0.0, -1.0, -1.0 };
385
386 quint64 modificationId = 0;
387
388 uint commandTypes : 4;
389 uint renderHints : 4;
390};
391
392QskGraphic::QskGraphic()
393 : m_data( new PrivateData() )
394 , m_paintEngine( nullptr )
395{
396}
397
398QskGraphic::QskGraphic( const QskGraphic& other )
399 : QPaintDevice()
400 , m_data( other.m_data )
401 , m_paintEngine( nullptr )
402{
403}
404
405QskGraphic::QskGraphic( QskGraphic&& other )
406 : m_data( std::move( other.m_data ) )
407 , m_paintEngine( nullptr )
408{
409}
410
411QskGraphic::~QskGraphic()
412{
413 delete m_paintEngine;
414}
415
416QskGraphic& QskGraphic::operator=( const QskGraphic& other )
417{
418 delete m_paintEngine;
419 m_data = other.m_data;
420 m_paintEngine = nullptr;
421
422 return *this;
423}
424
425QskGraphic& QskGraphic::operator=( QskGraphic&& other )
426{
427 m_data = std::move( other.m_data );
428 m_paintEngine = nullptr;
429 return *this;
430}
431
432bool QskGraphic::operator==( const QskGraphic& other ) const
433{
434 return *m_data == *other.m_data;
435}
436
437QPaintEngine* QskGraphic::paintEngine() const
438{
439 if ( m_paintEngine == nullptr )
440 m_paintEngine = new QskGraphicPaintEngine();
441
442 return m_paintEngine;
443}
444
445int QskGraphic::metric( PaintDeviceMetric deviceMetric ) const
446{
447 int value = 0;
448
449 switch ( deviceMetric )
450 {
451 case PdmWidth:
452 {
453 value = qCeil( defaultSize().width() );
454 break;
455 }
456 case PdmHeight:
457 {
458 value = qCeil( defaultSize().height() );
459 break;
460 }
461 case PdmNumColors:
462 {
463 value = 0xffffffff;
464 break;
465 }
466 case PdmDepth:
467 {
468 value = 32;
469 break;
470 }
471 case PdmPhysicalDpiX:
472 case PdmPhysicalDpiY:
473 case PdmDpiY:
474 case PdmDpiX:
475 {
476 value = 72;
477 break;
478 }
479 case PdmWidthMM:
480 {
481 value = qRound( metric( PdmWidth ) * 25.4 / metric( PdmDpiX ) );
482 break;
483 }
484 case PdmHeightMM:
485 {
486 value = qRound( metric( PdmHeight ) * 25.4 / metric( PdmDpiY ) );
487 break;
488 }
489 case PdmDevicePixelRatio:
490 {
491 value = 1.0;
492 break;
493 }
494 case PdmDevicePixelRatioScaled:
495 {
496 value = metric( PdmDevicePixelRatio ) * devicePixelRatioFScale();
497 break;
498 }
499#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
500 case PdmDevicePixelRatioF_EncodedA:
501 case PdmDevicePixelRatioF_EncodedB:
502 {
503#if 0
504 value = QPaintDevice::encodeMetricF( metric, devicePixelRatio() );
505#endif
506 break;
507 }
508#endif
509 }
510
511 return value;
512}
513
514void QskGraphic::reset()
515{
516 m_data->resetCommands();
517
518 m_data->viewBox = { 0.0, 0.0, -1.0, -1.0 };
519
520 delete m_paintEngine;
521 m_paintEngine = nullptr;
522}
523
524bool QskGraphic::isNull() const
525{
526 return m_data->commands.isEmpty();
527}
528
529bool QskGraphic::isEmpty() const
530{
531 return m_data->boundingRect.isEmpty();
532}
533
534QskGraphic::CommandTypes QskGraphic::commandTypes() const
535{
536 return static_cast< CommandTypes >( m_data->commandTypes );
537}
538
539void QskGraphic::setRenderHint( RenderHint hint, bool on )
540{
541 if ( on )
542 m_data->renderHints |= hint;
543 else
544 m_data->renderHints &= ~hint;
545}
546
547bool QskGraphic::testRenderHint( RenderHint hint ) const
548{
549 return m_data->renderHints & hint;
550}
551
552QskGraphic::RenderHints QskGraphic::renderHints() const
553{
554 return static_cast< QskGraphic::RenderHints >( m_data->renderHints );
555}
556
557QRectF QskGraphic::boundingRect() const
558{
559 if ( m_data->boundingRect.width() < 0 )
560 return QRectF();
561
562 return m_data->boundingRect;
563}
564
565QRectF QskGraphic::controlPointRect() const
566{
567 if ( m_data->pointRect.width() < 0 )
568 return QRectF();
569
570 return m_data->pointRect;
571}
572
573QRectF QskGraphic::scaledBoundingRect( qreal sx, qreal sy ) const
574{
575 if ( sx == 1.0 && sy == 1.0 )
576 return m_data->boundingRect;
577
578 const bool scalePens = !( m_data->renderHints & RenderPensUnscaled );
579
580 QTransform transform;
581 transform.scale( sx, sy );
582
583 QRectF rect = transform.mapRect( m_data->pointRect );
584
585 for ( const auto& info : std::as_const( m_data->pathInfos ) )
586 rect |= info.scaledBoundingRect( sx, sy, scalePens );
587
588 return rect;
589}
590
591QSize QskGraphic::sizeMetrics() const
592{
593 const QSizeF sz = defaultSize();
594 return QSize( qCeil( sz.width() ), qCeil( sz.height() ) );
595}
596
597void QskGraphic::setViewBox( const QRectF& rect )
598{
599 m_data->viewBox = rect;
600}
601
602QRectF QskGraphic::viewBox() const
603{
604 return m_data->viewBox;
605}
606
607QSizeF QskGraphic::defaultSize() const
608{
609 if ( !m_data->viewBox.isEmpty() )
610 return m_data->viewBox.size();
611
612 return boundingRect().size();
613}
614
615qreal QskGraphic::heightForWidth( qreal width ) const
616{
617 const auto sz = defaultSize();
618 if ( sz.isEmpty() )
619 return 0;
620
621 return sz.height() * width / sz.width();
622}
623
624qreal QskGraphic::widthForHeight( qreal height ) const
625{
626 const auto sz = defaultSize();
627 if ( sz.isEmpty() )
628 return 0;
629
630 return sz.width() * height / sz.height();
631}
632
633qreal QskGraphic::aspectRatio() const
634{
635 const auto sz = defaultSize();
636 if ( sz.isEmpty() )
637 return 1.0;
638
639 return sz.width() / sz.height();
640}
641
642void QskGraphic::render( QPainter* painter ) const
643{
644 render( painter, QskColorFilter() );
645}
646
647void QskGraphic::render( QPainter* painter,
648 const QskColorFilter& colorFilter, QTransform* initialTransform ) const
649{
650 if ( isNull() )
651 return;
652
653 const int numCommands = m_data->commands.size();
654 const auto commands = m_data->commands.constData();
655
656 const auto transform = painter->transform();
657 const QskGraphic::RenderHints renderHints( m_data->renderHints );
658
659 painter->save();
660
661 for ( int i = 0; i < numCommands; i++ )
662 {
663 qskExecCommand( painter, commands[ i ], colorFilter,
664 renderHints, transform, initialTransform );
665 }
666
667 painter->restore();
668}
669
670void QskGraphic::render( QPainter* painter, const QSizeF& size,
671 Qt::AspectRatioMode aspectRatioMode ) const
672{
673 const QRectF r( 0.0, 0.0, size.width(), size.height() );
674 render( painter, r, aspectRatioMode );
675}
676
677void QskGraphic::render( QPainter* painter, const QRectF& rect,
678 Qt::AspectRatioMode aspectRatioMode ) const
679{
680 render( painter, rect, QskColorFilter(), aspectRatioMode );
681}
682
683void QskGraphic::render( QPainter* painter, const QRectF& rect,
684 const QskColorFilter& colorFilter, Qt::AspectRatioMode aspectRatioMode ) const
685{
686 if ( isEmpty() || rect.isEmpty() )
687 return;
688
689 const bool scalePens = !( m_data->renderHints & RenderPensUnscaled );
690
691 qreal sx = 1.0;
692 qreal sy = 1.0;
693
694 QRectF boundingBox = m_data->viewBox;
695
696 if ( !boundingBox.isEmpty() )
697 {
698 sx = rect.width() / boundingBox.width();
699 sy = rect.height() / boundingBox.height();
700 }
701 else
702 {
703 boundingBox = m_data->boundingRect;
704
705 if ( m_data->pointRect.width() > 0.0 )
706 sx = rect.width() / m_data->pointRect.width();
707
708 if ( m_data->pointRect.height() > 0.0 )
709 sy = rect.height() / m_data->pointRect.height();
710
711 for ( const auto& info : std::as_const( m_data->pathInfos ) )
712 {
713 const qreal ssx = info.scaleFactorX( m_data->pointRect,
714 rect, m_data->boundingRect, scalePens );
715
716 if ( ssx > 0.0 )
717 sx = qMin( sx, ssx );
718
719 const qreal ssy = info.scaleFactorY( m_data->pointRect,
720 rect, m_data->boundingRect, scalePens );
721
722 if ( ssy > 0.0 )
723 sy = qMin( sy, ssy );
724 }
725 }
726
727 if ( aspectRatioMode == Qt::KeepAspectRatio )
728 {
729 sx = sy = qMin( sx, sy );
730 }
731 else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding )
732 {
733 sx = sy = qMax( sx, sy );
734 }
735
736 QTransform tr;
737
738 {
739 const auto rc = rect.center();
740
741 tr.translate(
742 rc.x() - 0.5 * sx * boundingBox.width(),
743 rc.y() - 0.5 * sy * boundingBox.height() );
744 tr.scale( sx, sy );
745 tr.translate( -boundingBox.x(), -boundingBox.y() );
746 }
747
748 const auto transform = painter->transform();
749
750 painter->setTransform( tr, true );
751
752 if ( !scalePens && transform.isScaling() )
753 {
754 /*
755 We don't want to scale pens according to sx/sy,
756 but we want to apply the initial scaling from the
757 painter transformation.
758 */
759
760 QTransform initialTransform;
761 initialTransform.scale( transform.m11(), transform.m22() );
762
763 render( painter, colorFilter, &initialTransform );
764 }
765 else
766 {
767 render( painter, colorFilter, nullptr );
768 }
769
770 painter->setTransform( transform );
771}
772
773void QskGraphic::render( QPainter* painter,
774 const QPointF& pos, Qt::Alignment alignment ) const
775{
776 QRectF r( pos, defaultSize() );
777
778 if ( alignment & Qt::AlignLeft )
779 {
780 r.moveLeft( pos.x() );
781 }
782 else if ( alignment & Qt::AlignHCenter )
783 {
784 r.moveCenter( QPointF( pos.x(), r.center().y() ) );
785 }
786 else if ( alignment & Qt::AlignRight )
787 {
788 r.moveRight( pos.x() );
789 }
790
791 if ( alignment & Qt::AlignTop )
792 {
793 r.moveTop( pos.y() );
794 }
795 else if ( alignment & Qt::AlignVCenter )
796 {
797 r.moveCenter( QPointF( r.center().x(), pos.y() ) );
798 }
799 else if ( alignment & Qt::AlignBottom )
800 {
801 r.moveBottom( pos.y() );
802 }
803
804 render( painter, r );
805}
806
807QPixmap QskGraphic::toPixmap( qreal devicePixelRatio ) const
808{
809 if ( isNull() )
810 return QPixmap();
811
812 if ( devicePixelRatio <= 0.0 )
813 devicePixelRatio = qskDevicePixelRatio();
814
815 const QSizeF sz = defaultSize();
816
817 const int w = qCeil( sz.width() * devicePixelRatio );
818 const int h = qCeil( sz.height() * devicePixelRatio );
819
820 QPixmap pixmap( w, h );
821 pixmap.setDevicePixelRatio( devicePixelRatio );
822 pixmap.fill( Qt::transparent );
823
824 const QRectF r( 0.0, 0.0, sz.width(), sz.height() );
825
826 QPainter painter( &pixmap );
827 render( &painter, r, Qt::KeepAspectRatio );
828 painter.end();
829
830 return pixmap;
831}
832
833QPixmap QskGraphic::toPixmap( const QSize& size,
834 Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const
835{
836 if ( devicePixelRatio <= 0.0 )
837 devicePixelRatio = qskDevicePixelRatio();
838
839 const int w = qCeil( size.width() * devicePixelRatio );
840 const int h = qCeil( size.height() * devicePixelRatio );
841
842 QPixmap pixmap( w, h );
843 pixmap.setDevicePixelRatio( devicePixelRatio );
844 pixmap.fill( Qt::transparent );
845
846 const QRect r( 0, 0, size.width(), size.height() );
847
848 QPainter painter( &pixmap );
849 render( &painter, r, aspectRatioMode );
850 painter.end();
851
852 return pixmap;
853}
854
855QImage QskGraphic::toImage( const QSize& size,
856 Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const
857{
858 if ( devicePixelRatio <= 0.0 )
859 devicePixelRatio = qskDevicePixelRatio();
860
861 const int w = qCeil( size.width() * devicePixelRatio );
862 const int h = qCeil( size.height() * devicePixelRatio );
863
864 QImage image( w, h, QImage::Format_ARGB32_Premultiplied );
865 image.setDevicePixelRatio( devicePixelRatio );
866 image.fill( 0 );
867
868 const QRect r( 0, 0, size.width(), size.height() );
869
870 QPainter painter( &image );
871 render( &painter, r, aspectRatioMode );
872 painter.end();
873
874 return image;
875}
876
877QImage QskGraphic::toImage( qreal devicePixelRatio ) const
878{
879 if ( isNull() )
880 return QImage();
881
882 if ( devicePixelRatio <= 0.0 )
883 devicePixelRatio = qskDevicePixelRatio();
884
885 const QSizeF sz = defaultSize();
886
887 const int w = qCeil( sz.width() * devicePixelRatio );
888 const int h = qCeil( sz.height() * devicePixelRatio );
889
890 QImage image( w, h, QImage::Format_ARGB32 );
891 image.setDevicePixelRatio( devicePixelRatio );
892 image.fill( 0 );
893
894 const QRect r( 0, 0, sz.width(), sz.height() );
895
896 QPainter painter( &image );
897 render( &painter, r, Qt::KeepAspectRatio );
898 painter.end();
899
900 return image;
901}
902
903void QskGraphic::drawPath( const QPainterPath& path )
904{
905 const auto painter = paintEngine()->painter();
906 if ( painter == nullptr )
907 return;
908
909 m_data->addCommand( QskPainterCommand( path ) );
910 m_data->commandTypes |= QskGraphic::VectorData;
911
912 if ( !path.isEmpty() )
913 {
914 const auto scaledPath = painter->transform().map( path );
915
916 QRectF pointRect = scaledPath.boundingRect();
917 QRectF boundingRect = pointRect;
918
919 if ( painter->pen().style() != Qt::NoPen &&
920 painter->pen().brush().style() != Qt::NoBrush )
921 {
922 boundingRect = qskStrokedPathRect( painter, path );
923 }
924
925 updateControlPointRect( pointRect );
926 updateBoundingRect( boundingRect );
927
928 m_data->pathInfos += QskGraphicPrivate::PathInfo( pointRect,
929 boundingRect, qskHasScalablePen( painter ) );
930 }
931}
932
933void QskGraphic::drawPixmap( const QRectF& rect,
934 const QPixmap& pixmap, const QRectF& subRect )
935{
936 const auto painter = paintEngine()->painter();
937 if ( painter == nullptr )
938 return;
939
940 m_data->addCommand( QskPainterCommand( rect, pixmap, subRect ) );
941 m_data->commandTypes |= QskGraphic::RasterData;
942
943 const QRectF r = painter->transform().mapRect( rect );
944 updateControlPointRect( r );
945 updateBoundingRect( r );
946}
947
948void QskGraphic::drawImage( const QRectF& rect, const QImage& image,
949 const QRectF& subRect, Qt::ImageConversionFlags flags )
950{
951 const auto painter = paintEngine()->painter();
952 if ( painter == nullptr )
953 return;
954
955 m_data->addCommand( QskPainterCommand( rect, image, subRect, flags ) );
956 m_data->commandTypes |= QskGraphic::RasterData;
957
958 const QRectF r = painter->transform().mapRect( rect );
959
960 updateControlPointRect( r );
961 updateBoundingRect( r );
962}
963
964void QskGraphic::updateState( const QPaintEngineState& state )
965{
966 m_data->addCommand( QskPainterCommand( state ) );
967
968 if ( state.state() & QPaintEngine::DirtyTransform )
969 {
970 if ( !( m_data->commandTypes & QskGraphic::Transformation ) )
971 {
972 /*
973 QTransform::isScaling() returns true for all type
974 of transformations beside simple translations
975 even if it is f.e a rotation
976 */
977 if ( state.transform().isScaling() )
978 m_data->commandTypes |= QskGraphic::Transformation;
979 }
980 }
981}
982
983void QskGraphic::updateBoundingRect( const QRectF& rect )
984{
985 QRectF br = rect;
986
987 if ( const auto painter = paintEngine()->painter() )
988 {
989 if ( painter->hasClipping() )
990 {
991 QRectF cr = painter->clipRegion().boundingRect();
992 cr = painter->transform().mapRect( cr );
993
994 br &= cr;
995 }
996 }
997
998 if ( m_data->boundingRect.width() < 0 )
999 m_data->boundingRect = br;
1000 else
1001 m_data->boundingRect |= br;
1002}
1003
1004void QskGraphic::updateControlPointRect( const QRectF& rect )
1005{
1006 if ( m_data->pointRect.width() < 0.0 )
1007 m_data->pointRect = rect;
1008 else
1009 m_data->pointRect |= rect;
1010}
1011
1012const QVector< QskPainterCommand >& QskGraphic::commands() const
1013{
1014 return m_data->commands;
1015}
1016
1017void QskGraphic::setCommands( const QVector< QskPainterCommand >& commands )
1018{
1019 m_data->resetCommands();
1020
1021 const int numCommands = commands.size();
1022 if ( numCommands <= 0 )
1023 return;
1024
1025 // to calculate a proper bounding rectangle we don't simply copy
1026 // the commands.
1027
1028 const auto cmds = commands.constData();
1029
1030 const QskColorFilter noFilter;
1031 const QTransform noTransform;
1032
1033 QPainter painter( this );
1034
1035 for ( int i = 0; i < numCommands; i++ )
1036 {
1037 qskExecCommand( &painter, cmds[ i ],
1038 noFilter, RenderHints(), noTransform, nullptr );
1039 }
1040
1041 painter.end();
1042}
1043
1044quint64 QskGraphic::modificationId() const
1045{
1046 return m_data->modificationId;
1047}
1048
1049QskHashValue QskGraphic::hash( QskHashValue seed ) const
1050{
1051 auto hash = qHash( m_data->renderHints, seed );
1052 hash = qHashBits( &m_data->viewBox, sizeof( QRectF ), hash );
1053
1054 return qHash( m_data->modificationId, hash );
1055}
1056
1057QskGraphic QskGraphic::fromImage( const QImage& image )
1058{
1059 QskGraphic graphic;
1060
1061 if ( !image.isNull() )
1062 {
1063 QPainter painter( &graphic );
1064 painter.drawImage( 0, 0, image );
1065 painter.end();
1066 }
1067
1068 return graphic;
1069}
1070
1071QskGraphic QskGraphic::fromPixmap( const QPixmap& pixmap )
1072{
1073 QskGraphic graphic;
1074
1075 if ( !pixmap.isNull() )
1076 {
1077 QPainter painter( &graphic );
1078 painter.drawPixmap( 0, 0, pixmap );
1079 painter.end();
1080 }
1081
1082 return graphic;
1083}
1084
1085QskGraphic QskGraphic::fromPixmapAsImage( const QPixmap& pixmap )
1086{
1087 /*
1088 Using QPainter::drawPixmap in the scene graph thread leads
1089 to warnings about "not being safe". This is probably not critical
1090 for Qt/Quick as the main thread is waiting, when updating the
1091 scene graph nodes.
1092
1093 It needs to be checked, what is going on, when
1094 using the X11 paint engine, where QPixmap/QImage are more different.
1095
1096 TODO ...
1097 */
1098
1099 return fromImage( pixmap.toImage() );
1100}
1101
1102QskGraphic QskGraphic::fromGraphic(
1103 const QskGraphic& graphic, const QskColorFilter& colorFilter )
1104{
1105 if ( colorFilter.isIdentity() )
1106 return graphic;
1107
1108 QskGraphic recoloredGraphic;
1109
1110 QPainter painter( &recoloredGraphic );
1111 graphic.render( &painter, colorFilter );
1112 painter.end();
1113
1114 return recoloredGraphic;
1115}
1116
1117#ifndef QT_NO_DEBUG_STREAM
1118
1119#include <qdebug.h>
1120
1121QDebug operator<<( QDebug debug, const QskGraphic& graphic )
1122{
1123 QDebugStateSaver saver( debug );
1124
1125 debug << "Graphic" << '(';
1126 debug << "\n boundingRect:" << graphic.boundingRect();
1127 debug << "\n controlPointsRect:" << graphic.boundingRect();
1128 debug << "\n commandTypes:" << graphic.commandTypes();
1129
1130 for ( const auto& command : graphic.commands() )
1131 {
1132 switch ( command.type() )
1133 {
1135 {
1136 const auto& path = *command.path();
1137
1138 debug << "\n Path(" << path.elementCount() << ")";
1139
1140 const char* types[] = { "MoveTo", "LineTo", "CurveTo", "CurveToData" };
1141
1142 for ( int i = 0; i < path.elementCount(); i++ )
1143 {
1144 debug << "\n ";
1145
1146 const auto el = path.elementAt(i);
1147 debug << types[ el.type] << el.x << el.y;
1148 }
1149
1150 break;
1151 }
1153 {
1154 const auto& pixmapData = *command.pixmapData();
1155
1156 debug << "\n Pixmap:";
1157 debug << "\n " << pixmapData.pixmap;
1158 debug << "\n Rect:" << pixmapData.rect;
1159 debug << "\n SubRect:" << pixmapData.subRect;
1160 break;
1161 }
1163 {
1164 const auto& imageData = *command.imageData();
1165
1166 debug << "\n Image:";
1167 debug << "\n " << imageData.image;
1168 debug << "\n ConversionFlags" << imageData.flags;
1169 debug << "\n Rect:" << imageData.rect;
1170 debug << "\n SubRect:" << imageData.subRect;
1171
1172 break;
1173 }
1175 {
1176 const auto& stateData = *command.stateData();
1177 const auto flags = stateData.flags;
1178
1179 debug << "\n State:";
1180
1181 const char indent[] = "\n ";
1182
1183 if ( flags & QPaintEngine::DirtyPen )
1184 {
1185 debug << indent << "Pen:" << stateData.pen;
1186 }
1187
1188 if ( flags & QPaintEngine::DirtyBrush )
1189 {
1190 debug << indent << "Brush:" << stateData.brush;
1191 }
1192
1193 if ( flags & QPaintEngine::DirtyBrushOrigin )
1194 {
1195 debug << indent << "BrushOrigin:" << stateData.brushOrigin;
1196 }
1197
1198 if ( flags & QPaintEngine::DirtyFont )
1199 {
1200 debug << indent << "Font:" << stateData.font;
1201 }
1202
1203 if ( flags & QPaintEngine::DirtyBackground )
1204 {
1205 debug << indent << "Background:"
1206 << stateData.backgroundMode
1207 << stateData.backgroundBrush;
1208 }
1209
1210 if ( flags & QPaintEngine::DirtyTransform )
1211 {
1212 debug << indent << "Transform: " << stateData.transform;
1213 }
1214
1215 if ( flags & QPaintEngine::DirtyClipEnabled )
1216 {
1217 debug << indent << "ClipEnabled: " << stateData.isClipEnabled;
1218 }
1219
1220 if ( flags & QPaintEngine::DirtyClipRegion )
1221 {
1222 debug << indent << "ClipRegion: " << stateData.clipOperation;
1223 }
1224
1225 if ( flags & QPaintEngine::DirtyClipPath )
1226 {
1227 debug << indent << "ClipPath:" << stateData.clipOperation;
1228 }
1229
1230 if ( flags & QPaintEngine::DirtyHints )
1231 {
1232 debug << indent << "RenderHints:" << stateData.renderHints;
1233 }
1234
1235 if ( flags & QPaintEngine::DirtyCompositionMode )
1236 {
1237 debug << indent << "CompositionMode:" << stateData.compositionMode;
1238 }
1239
1240 if ( flags & QPaintEngine::DirtyOpacity )
1241 {
1242 debug << indent << "Opacity:" << stateData.opacity;
1243 }
1244
1245 break;
1246 }
1247 default:
1248 break;
1249 }
1250 }
1251
1252 debug << "\n)";
1253
1254 return debug;
1255}
1256
1257#endif
1258
1259#include "moc_QskGraphic.cpp"
A paint device for scalable graphics.
Definition QskGraphic.h:28
@ Path
Draw a QPainterPath.
@ Image
Draw a QImage.
@ Pixmap
Draw a QPixmap.
@ State
QPainter state change.
Type type() const noexcept