Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwindowsdirect2dpaintengine.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
11
12#include <QtGui/private/qwindowsfontdatabase_p.h>
13#include "qwindowsintegration.h"
14
15#include <QtCore/qmath.h>
16#include <QtCore/qstack.h>
17#include <QtCore/qsettings.h>
18#include <QtGui/private/qpaintengine_p.h>
19#include <QtGui/private/qtextengine_p.h>
20#include <QtGui/private/qfontengine_p.h>
21#include <QtGui/private/qstatictext_p.h>
22
23#include <d2d1_1.h>
24#include <dwrite_1.h>
25#include <wrl.h>
26
28
30
31// The enum values below are set as tags on the device context
32// in the various draw methods. When EndDraw is called the device context
33// will report the last set tag number in case of errors
34// along with an error code
35
36// Microsoft keeps a list of d2d error codes here:
37// http://msdn.microsoft.com/en-us/library/windows/desktop/dd370979(v=vs.85).aspx
38enum {
50};
51
52//Clipping flags
53enum : unsigned {
55};
56
61
62// Since d2d is a float-based system we need to be able to snap our drawing to whole pixels.
63// Applying the magical aliasing offset to coordinates will do so, just make sure that
64// aliased painting is turned on on the d2d device context.
65static const qreal MAGICAL_ALIASING_OFFSET = 0.5;
66
67#define D2D_TAG(tag) d->dc()->SetTags(tag, tag)
68
69Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert);
70
71static inline ID2D1Factory1 *factory()
72{
73 return QWindowsDirect2DContext::instance()->d2dFactory();
74}
75
76static inline D2D1_MATRIX_3X2_F transformFromLine(const QLineF &line, qreal penWidth, qreal dashOffset)
77{
78 const qreal halfWidth = penWidth / 2;
79 const qreal angle = -qDegreesToRadians(line.angle());
80 const qreal sinA = qSin(angle);
81 const qreal cosA = qCos(angle);
82 QTransform transform = QTransform::fromTranslate(line.p1().x() + dashOffset * cosA + sinA * halfWidth,
83 line.p1().y() + dashOffset * sinA - cosA * halfWidth);
84 transform.rotateRadians(angle);
86}
87
88static void adjustLine(QPointF *p1, QPointF *p2);
89static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2);
90
91static QList<D2D1_GRADIENT_STOP> qGradientStopsToD2DStops(const QGradientStops &qstops)
92{
93 QList<D2D1_GRADIENT_STOP> stops(qstops.count());
94 for (int i = 0, count = stops.size(); i < count; ++i) {
95 stops[i].position = FLOAT(qstops.at(i).first);
96 stops[i].color = to_d2d_color_f(qstops.at(i).second);
97 }
98 return stops;
99}
100
102{
103public:
104 bool begin()
105 {
106 HRESULT hr = factory()->CreatePathGeometry(&m_geometry);
107 if (FAILED(hr)) {
108 qWarning("%s: Could not create path geometry: %#lx", __FUNCTION__, hr);
109 return false;
110 }
111
112 hr = m_geometry->Open(&m_sink);
113 if (FAILED(hr)) {
114 qWarning("%s: Could not create geometry sink: %#lx", __FUNCTION__, hr);
115 return false;
116 }
117
118 return true;
119 }
120
122 {
123 if (enable)
124 m_sink->SetFillMode(D2D1_FILL_MODE_WINDING);
125 else
126 m_sink->SetFillMode(D2D1_FILL_MODE_ALTERNATE);
127 }
128
130 {
131 m_roundCoordinates = enable;
132 }
133
135 {
136 m_adjustPositivelySlopedLines = enable;
137 }
138
139 bool isInFigure() const
140 {
141 return m_inFigure;
142 }
143
144 void moveTo(const QPointF &point)
145 {
146 if (m_inFigure)
147 m_sink->EndFigure(D2D1_FIGURE_END_OPEN);
148
149 m_sink->BeginFigure(adjusted(point), D2D1_FIGURE_BEGIN_FILLED);
150 m_inFigure = true;
151 m_previousPoint = point;
152 }
153
154 void lineTo(const QPointF &point)
155 {
156 QPointF pt = point;
157 if (m_adjustPositivelySlopedLines && isLinePositivelySloped(m_previousPoint, point)) {
158 moveTo(m_previousPoint - QPointF(0, 1));
159 pt -= QPointF(0, 1);
160 }
161 m_sink->AddLine(adjusted(pt));
162 if (pt != point)
163 moveTo(point);
164 m_previousPoint = point;
165 }
166
167 void curveTo(const QPointF &p1, const QPointF &p2, const QPointF &p3)
168 {
169 D2D1_BEZIER_SEGMENT segment = {
170 adjusted(p1),
171 adjusted(p2),
172 adjusted(p3)
173 };
174
175 m_sink->AddBezier(segment);
176 m_previousPoint = p3;
177 }
178
179 void close()
180 {
181 if (m_inFigure)
182 m_sink->EndFigure(D2D1_FIGURE_END_OPEN);
183
184 m_sink->Close();
185 }
186
187 ComPtr<ID2D1PathGeometry1> geometry() const
188 {
189 return m_geometry;
190 }
191
192private:
193 D2D1_POINT_2F adjusted(const QPointF &point)
194 {
195 static const QPointF adjustment(MAGICAL_ALIASING_OFFSET,
197
198 if (m_roundCoordinates)
199 return to_d2d_point_2f(point + adjustment);
200 else
201 return to_d2d_point_2f(point);
202 }
203
204 ComPtr<ID2D1PathGeometry1> m_geometry;
205 ComPtr<ID2D1GeometrySink> m_sink;
206
207 bool m_inFigure = false;
208 bool m_roundCoordinates = false;
209 bool m_adjustPositivelySlopedLines = false;
210 QPointF m_previousPoint;
211};
212
214 ComPtr<ID2D1PathGeometry1> aliased;
215 ComPtr<ID2D1PathGeometry1> antiAliased;
216
217 static void cleanup_func(QPaintEngineEx *engine, void *data) {
219 auto *e = static_cast<D2DVectorPathCache *>(data);
220 delete e;
221 }
222};
223
225{
226 Q_DECLARE_PUBLIC(QWindowsDirect2DPaintEngine)
227public:
229 : bitmap(bm)
230 , flags(flags)
231 {
232 pen.reset();
233 brush.reset();
234
235 dc()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
236 }
237
240
241 unsigned int clipFlags = 0;
242 QStack<ClipType> pushedClips;
244
246
247 QHash< QFontDef, ComPtr<IDWriteFontFace> > fontCache;
248
249 struct {
252 ComPtr<ID2D1Brush> brush;
253 ComPtr<ID2D1StrokeStyle1> strokeStyle;
254 ComPtr<ID2D1BitmapBrush1> dashBrush;
256
257 inline void reset() {
258 emulate = false;
259 qpen = QPen();
260 brush.Reset();
261 strokeStyle.Reset();
262 dashBrush.Reset();
263 dashLength = 0;
264 }
266
267 struct {
268 bool emulate;
270 ComPtr<ID2D1Brush> brush;
271
272 inline void reset() {
273 emulate = false;
274 brush.Reset();
275 qbrush = QBrush();
276 }
278
279 inline ID2D1DeviceContext *dc() const
280 {
282 return bitmap->deviceContext()->get();
283 }
284
285 inline D2D1_INTERPOLATION_MODE interpolationMode() const
286 {
288 return (q->state()->renderHints & QPainter::SmoothPixmapTransform) ? D2D1_INTERPOLATION_MODE_LINEAR
289 : D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR;
290 }
291
292 inline D2D1_ANTIALIAS_MODE antialiasMode() const
293 {
295 return (q->state()->renderHints & QPainter::Antialiasing) ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE
296 : D2D1_ANTIALIAS_MODE_ALIASED;
297 }
298
299 inline D2D1_LAYER_OPTIONS1 layerOptions() const
300 {
302 return D2D1_LAYER_OPTIONS1_NONE;
303 else
304 return D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND;
305 }
306
308 {
309 dc()->SetTransform(to_d2d_matrix_3x2_f(transform));
310 }
311
312 void updateOpacity(qreal opacity)
313 {
314 if (brush.brush)
315 brush.brush->SetOpacity(FLOAT(opacity));
316 if (pen.brush)
317 pen.brush->SetOpacity(FLOAT(opacity));
318 }
319
321 {
323
324 if (path.isEmpty()) {
325 D2D_RECT_F rect = {0, 0, 0, 0};
326 dc()->PushAxisAlignedClip(rect, antialiasMode());
328 } else if (path.isRect() && (q->state()->matrix.type() <= QTransform::TxScale)) {
329 const qreal * const points = path.points();
330 D2D_RECT_F rect = {
331 FLOAT(points[0]), // left
332 FLOAT(points[1]), // top
333 FLOAT(points[2]), // right,
334 FLOAT(points[5]) // bottom
335 };
336
337 dc()->PushAxisAlignedClip(rect, antialiasMode());
339 } else {
340 ComPtr<ID2D1PathGeometry1> geometry = vectorPathToID2D1PathGeometry(path);
341 if (!geometry) {
342 qWarning("%s: Could not convert vector path to painter path!", __FUNCTION__);
343 return;
344 }
345
346 dc()->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(),
347 geometry.Get(),
349 D2D1::IdentityMatrix(),
350 1.0,
351 nullptr,
352 layerOptions()),
353 nullptr);
355 }
356 }
357
359 {
360 while (!pushedClips.isEmpty()) {
361 switch (pushedClips.pop()) {
362 case AxisAlignedClip:
363 dc()->PopAxisAlignedClip();
364 break;
365 case LayerClip:
366 dc()->PopLayer();
367 break;
368 }
369 }
370 }
371
373 {
374 if (!enabled)
375 clearClips();
376 else if (pushedClips.isEmpty())
378 }
379
380 void clip(const QVectorPath &path, Qt::ClipOperation operation)
381 {
382 switch (operation) {
383 case Qt::NoClip:
384 clearClips();
385 break;
386 case Qt::ReplaceClip:
387 clearClips();
388 pushClip(path);
389 break;
391 pushClip(path);
392 break;
393 }
394 }
395
397 {
398 switch (mode) {
400 dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
401 break;
403 dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
404 break;
405
406 default:
407 // Activating an unsupported mode at any time will cause the QImage
408 // fallback to be used for the remainder of the active paint session
409 dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
411 break;
412 }
413 }
414
415 void updateBrush(const QBrush &newBrush)
416 {
418
419 if (qbrush_fast_equals(brush.qbrush, newBrush) && (brush.brush || brush.emulate))
420 return;
421
422 brush.brush = to_d2d_brush(newBrush, &brush.emulate);
423 brush.qbrush = newBrush;
424
425 if (brush.brush) {
426 brush.brush->SetOpacity(FLOAT(q->state()->opacity));
428 }
429 }
430
431 void updateBrushOrigin(const QPointF &brushOrigin)
432 {
434 applyBrushOrigin(brushOrigin);
435 }
436
438 {
439 if (brush.brush && !currentBrushOrigin.isNull()) {
440 D2D1_MATRIX_3X2_F transform;
441 brush.brush->GetTransform(&transform);
442
443 brush.brush->SetTransform(*(D2D1::Matrix3x2F::ReinterpretBaseType(&transform))
444 * D2D1::Matrix3x2F::Translation(FLOAT(-currentBrushOrigin.x()),
445 FLOAT(-currentBrushOrigin.y())));
446 }
447 }
448
449 void applyBrushOrigin(const QPointF &origin)
450 {
451 if (brush.brush && !origin.isNull()) {
452 D2D1_MATRIX_3X2_F transform;
453 brush.brush->GetTransform(&transform);
454
455 brush.brush->SetTransform(*(D2D1::Matrix3x2F::ReinterpretBaseType(&transform))
456 * D2D1::Matrix3x2F::Translation(FLOAT(origin.x()), FLOAT(origin.y())));
457 }
458
459 currentBrushOrigin = origin;
460 }
461
462 void updatePen(const QPen &newPen)
463 {
465 if (qpen_fast_equals(newPen, pen.qpen) && (pen.brush || pen.emulate))
466 return;
467
468 pen.reset();
469 pen.qpen = newPen;
470
471 if (newPen.style() == Qt::NoPen)
472 return;
473
474 pen.brush = to_d2d_brush(newPen.brush(), &pen.emulate);
475 if (!pen.brush)
476 return;
477
478 pen.brush->SetOpacity(FLOAT(q->state()->opacity));
479
480 D2D1_STROKE_STYLE_PROPERTIES1 props = {};
481
482 switch (newPen.capStyle()) {
483 case Qt::SquareCap:
484 props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_SQUARE;
485 break;
486 case Qt::RoundCap:
487 props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_ROUND;
488 break;
489 case Qt::FlatCap:
490 default:
491 props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_FLAT;
492 break;
493 }
494
495 switch (newPen.joinStyle()) {
496 case Qt::BevelJoin:
497 props.lineJoin = D2D1_LINE_JOIN_BEVEL;
498 break;
499 case Qt::RoundJoin:
500 props.lineJoin = D2D1_LINE_JOIN_ROUND;
501 break;
502 case Qt::MiterJoin:
503 default:
504 props.lineJoin = D2D1_LINE_JOIN_MITER;
505 break;
506 }
507
508 props.miterLimit = FLOAT(newPen.miterLimit() * qreal(2.0)); // D2D and Qt miter specs differ
509 props.dashOffset = FLOAT(newPen.dashOffset());
510
511 if (newPen.widthF() == 0)
512 props.transformType = D2D1_STROKE_TRANSFORM_TYPE_HAIRLINE;
513 else if (newPen.isCosmetic())
514 props.transformType = D2D1_STROKE_TRANSFORM_TYPE_FIXED;
515 else
516 props.transformType = D2D1_STROKE_TRANSFORM_TYPE_NORMAL;
517
518 switch (newPen.style()) {
519 case Qt::SolidLine:
520 props.dashStyle = D2D1_DASH_STYLE_SOLID;
521 break;
522
523 case Qt::DotLine:
524 case Qt::DashDotLine:
526 // Try and match Qt's raster engine in output as closely as possible
527 if (newPen.widthF() <= 1.0)
528 props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_FLAT;
529
531 default:
532 props.dashStyle = D2D1_DASH_STYLE_CUSTOM;
533 break;
534 }
535
536 HRESULT hr;
537
538 if (props.dashStyle == D2D1_DASH_STYLE_CUSTOM) {
539 QList<qreal> dashes = newPen.dashPattern();
540 QList<FLOAT> converted(dashes.size());
541 qreal penWidth = pen.qpen.widthF();
542 qreal brushWidth = 0;
543 for (int i = 0; i < dashes.size(); i++) {
544 converted[i] = FLOAT(dashes[i]);
545 brushWidth += penWidth * dashes[i];
546 }
547
548 hr = factory()->CreateStrokeStyle(props, converted.constData(), UINT32(converted.size()), &pen.strokeStyle);
549
550 // Create a combined brush/dash pattern for optimized line drawing
552 bitmap.resize(int(ceil(brushWidth)), int(ceil(penWidth)));
553 bitmap.deviceContext()->begin();
554 bitmap.deviceContext()->get()->SetAntialiasMode(antialiasMode());
555 bitmap.deviceContext()->get()->SetTransform(D2D1::IdentityMatrix());
556 bitmap.deviceContext()->get()->Clear();
557 const qreal offsetX = (qreal(bitmap.size().width()) - brushWidth) / 2;
558 const qreal offsetY = qreal(bitmap.size().height()) / 2;
559 bitmap.deviceContext()->get()->DrawLine(D2D1::Point2F(FLOAT(offsetX), FLOAT(offsetY)),
560 D2D1::Point2F(FLOAT(brushWidth), FLOAT(offsetY)),
561 pen.brush.Get(), FLOAT(penWidth), pen.strokeStyle.Get());
562 bitmap.deviceContext()->end();
563 D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = D2D1::BitmapBrushProperties1(
564 D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_CLAMP, D2D1_INTERPOLATION_MODE_LINEAR);
565 hr = dc()->CreateBitmapBrush(bitmap.bitmap(), bitmapBrushProperties, &pen.dashBrush);
566 pen.dashLength = bitmap.size().width();
567 } else {
568 hr = factory()->CreateStrokeStyle(props, nullptr, 0, &pen.strokeStyle);
569 }
570
571 if (FAILED(hr))
572 qWarning("%s: Could not create stroke style: %#lx", __FUNCTION__, hr);
573 }
574
575 ComPtr<ID2D1Brush> to_d2d_brush(const QBrush &newBrush, bool *needsEmulation)
576 {
577 HRESULT hr;
578 ComPtr<ID2D1Brush> result;
579
581
582 *needsEmulation = false;
583
584 switch (newBrush.style()) {
585 case Qt::NoBrush:
586 break;
587
588 case Qt::SolidPattern:
589 {
590 ComPtr<ID2D1SolidColorBrush> solid;
591
592 hr = dc()->CreateSolidColorBrush(to_d2d_color_f(newBrush.color()), &solid);
593 if (FAILED(hr)) {
594 qWarning("%s: Could not create solid color brush: %#lx", __FUNCTION__, hr);
595 break;
596 }
597
598 hr = solid.As(&result);
599 if (FAILED(hr))
600 qWarning("%s: Could not convert solid color brush: %#lx", __FUNCTION__, hr);
601 }
602 break;
603
611 case Qt::HorPattern:
612 case Qt::VerPattern:
613 case Qt::CrossPattern:
614 case Qt::BDiagPattern:
615 case Qt::FDiagPattern:
617 {
618 ComPtr<ID2D1BitmapBrush1> bitmapBrush;
619 D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = {
620 D2D1_EXTEND_MODE_WRAP,
621 D2D1_EXTEND_MODE_WRAP,
623 };
624
625 QImage brushImg = qt_imageForBrush(newBrush.style(), false);
626 brushImg.setColor(0, newBrush.color().rgba());
627 brushImg.setColor(1, qRgba(0, 0, 0, 0));
628
630 bool success = bitmap.fromImage(brushImg, Qt::AutoColor);
631 if (!success) {
632 qWarning("%s: Could not create Direct2D bitmap from Qt pattern brush image", __FUNCTION__);
633 break;
634 }
635
636 hr = dc()->CreateBitmapBrush(bitmap.bitmap(),
637 bitmapBrushProperties,
638 &bitmapBrush);
639 if (FAILED(hr)) {
640 qWarning("%s: Could not create Direct2D bitmap brush for Qt pattern brush: %#lx", __FUNCTION__, hr);
641 break;
642 }
643
644 hr = bitmapBrush.As(&result);
645 if (FAILED(hr))
646 qWarning("%s: Could not convert Direct2D bitmap brush for Qt pattern brush: %#lx", __FUNCTION__, hr);
647 }
648 break;
649
651 if (newBrush.gradient()->spread() != QGradient::PadSpread) {
652 *needsEmulation = true;
653 } else {
654 ComPtr<ID2D1LinearGradientBrush> linear;
655 const auto *qlinear = static_cast<const QLinearGradient *>(newBrush.gradient());
656
657 D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES linearGradientBrushProperties;
658 ComPtr<ID2D1GradientStopCollection> gradientStopCollection;
659
660 linearGradientBrushProperties.startPoint = to_d2d_point_2f(qlinear->start());
661 linearGradientBrushProperties.endPoint = to_d2d_point_2f(qlinear->finalStop());
662
663 const QList<D2D1_GRADIENT_STOP> stops = qGradientStopsToD2DStops(qlinear->stops());
664
665 hr = dc()->CreateGradientStopCollection(stops.constData(),
666 UINT32(stops.size()),
667 &gradientStopCollection);
668 if (FAILED(hr)) {
669 qWarning("%s: Could not create gradient stop collection for linear gradient: %#lx", __FUNCTION__, hr);
670 break;
671 }
672
673 hr = dc()->CreateLinearGradientBrush(linearGradientBrushProperties, gradientStopCollection.Get(),
674 &linear);
675 if (FAILED(hr)) {
676 qWarning("%s: Could not create Direct2D linear gradient brush: %#lx", __FUNCTION__, hr);
677 break;
678 }
679
680 hr = linear.As(&result);
681 if (FAILED(hr)) {
682 qWarning("%s: Could not convert Direct2D linear gradient brush: %#lx", __FUNCTION__, hr);
683 break;
684 }
685 }
686 break;
687
689 if (newBrush.gradient()->spread() != QGradient::PadSpread) {
690 *needsEmulation = true;
691 } else {
692 ComPtr<ID2D1RadialGradientBrush> radial;
693 const auto *qradial = static_cast<const QRadialGradient *>(newBrush.gradient());
694
695 D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES radialGradientBrushProperties;
696 ComPtr<ID2D1GradientStopCollection> gradientStopCollection;
697
698 radialGradientBrushProperties.center = to_d2d_point_2f(qradial->center());
699 radialGradientBrushProperties.gradientOriginOffset = to_d2d_point_2f(qradial->focalPoint() - qradial->center());
700 radialGradientBrushProperties.radiusX = FLOAT(qradial->radius());
701 radialGradientBrushProperties.radiusY = FLOAT(qradial->radius());
702
703 const QList<D2D1_GRADIENT_STOP> stops = qGradientStopsToD2DStops(qradial->stops());
704
705 hr = dc()->CreateGradientStopCollection(stops.constData(), stops.size(), &gradientStopCollection);
706 if (FAILED(hr)) {
707 qWarning("%s: Could not create gradient stop collection for radial gradient: %#lx", __FUNCTION__, hr);
708 break;
709 }
710
711 hr = dc()->CreateRadialGradientBrush(radialGradientBrushProperties, gradientStopCollection.Get(),
712 &radial);
713 if (FAILED(hr)) {
714 qWarning("%s: Could not create Direct2D radial gradient brush: %#lx", __FUNCTION__, hr);
715 break;
716 }
717
718 radial.As(&result);
719 if (FAILED(hr)) {
720 qWarning("%s: Could not convert Direct2D radial gradient brush: %#lx", __FUNCTION__, hr);
721 break;
722 }
723 }
724 break;
725
727 *needsEmulation = true;
728 break;
729
731 {
732 ComPtr<ID2D1BitmapBrush1> bitmapBrush;
733 D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = {
734 D2D1_EXTEND_MODE_WRAP,
735 D2D1_EXTEND_MODE_WRAP,
737 };
738
739 QWindowsDirect2DPlatformPixmap *pp = static_cast<QWindowsDirect2DPlatformPixmap *>(newBrush.texture().handle());
741 hr = dc()->CreateBitmapBrush(bitmap->bitmap(),
742 bitmapBrushProperties,
743 &bitmapBrush);
744
745 if (FAILED(hr)) {
746 qWarning("%s: Could not create texture brush: %#lx", __FUNCTION__, hr);
747 break;
748 }
749
750 hr = bitmapBrush.As(&result);
751 if (FAILED(hr))
752 qWarning("%s: Could not convert texture brush: %#lx", __FUNCTION__, hr);
753 }
754 break;
755 }
756
757 if (result && !newBrush.transform().isIdentity())
758 result->SetTransform(to_d2d_matrix_3x2_f(newBrush.transform()));
759
760 return result;
761 }
762
763 ComPtr<ID2D1PathGeometry1> vectorPathToID2D1PathGeometry(const QVectorPath &path)
764 {
766
767 const bool alias = !q->antiAliasingEnabled();
768
769 QVectorPath::CacheEntry *cacheEntry = path.isCacheable() ? path.lookupCacheData(q)
770 : nullptr;
771
772 if (cacheEntry) {
773 auto *e = static_cast<D2DVectorPathCache *>(cacheEntry->data);
774 if (alias && e->aliased)
775 return e->aliased;
776 else if (!alias && e->antiAliased)
777 return e->antiAliased;
778 }
779
781 if (!writer.begin())
782 return nullptr;
783
784 writer.setWindingFillEnabled(path.hasWindingFill());
785 writer.setAliasingEnabled(alias);
786 writer.setPositiveSlopeAdjustmentEnabled(path.shape() == QVectorPath::LinesHint
787 || path.shape() == QVectorPath::PolygonHint);
788
789 const QPainterPath::ElementType *types = path.elements();
790 const int count = path.elementCount();
791 const qreal *points = path.points();
792
794
795 if (types) {
796 qreal x, y;
797
798 for (int i = 0; i < count; i++) {
799 x = points[i * 2];
800 y = points[i * 2 + 1];
801
802 switch (types[i]) {
804 writer.moveTo(QPointF(x, y));
805 break;
806
808 writer.lineTo(QPointF(x, y));
809 break;
810
812 {
813 Q_ASSERT((i + 2) < count);
816
817 i++;
818 const qreal x2 = points[i * 2];
819 const qreal y2 = points[i * 2 + 1];
820
821 i++;
822 const qreal x3 = points[i * 2];
823 const qreal y3 = points[i * 2 + 1];
824
825 writer.curveTo(QPointF(x, y), QPointF(x2, y2), QPointF(x3, y3));
826 }
827 break;
828
830 qWarning("%s: Unhandled Curve Data Element", __FUNCTION__);
831 break;
832 }
833 }
834 } else {
835 writer.moveTo(QPointF(points[0], points[1]));
836 for (int i = 1; i < count; i++)
837 writer.lineTo(QPointF(points[i * 2], points[i * 2 + 1]));
838 }
839
840 if (writer.isInFigure())
841 if (path.hasImplicitClose())
842 writer.lineTo(QPointF(points[0], points[1]));
843
844 writer.close();
845 ComPtr<ID2D1PathGeometry1> geometry = writer.geometry();
846
847 if (path.isCacheable()) {
848 if (!cacheEntry)
849 cacheEntry = path.addCacheData(q, new D2DVectorPathCache, D2DVectorPathCache::cleanup_func);
850
851 auto *e = static_cast<D2DVectorPathCache *>(cacheEntry->data);
852 if (alias)
853 e->aliased = geometry;
854 else
855 e->antiAliased = geometry;
856 } else {
857 path.makeCacheable();
858 }
859
860 return geometry;
861 }
862
864 {
865 dc()->SetAntialiasMode(antialiasMode());
866 }
867
868 void drawGlyphRun(const D2D1_POINT_2F &pos,
869 IDWriteFontFace *fontFace,
870 const QFontDef &fontDef,
871 int numGlyphs,
872 const UINT16 *glyphIndices,
873 const FLOAT *glyphAdvances,
874 const DWRITE_GLYPH_OFFSET *glyphOffsets,
875 bool rtl)
876 {
878
879 DWRITE_GLYPH_RUN glyphRun = {
880 fontFace, // IDWriteFontFace *fontFace;
881 FLOAT(fontDef.pixelSize), // FLOAT fontEmSize;
882 UINT32(numGlyphs), // UINT32 glyphCount;
883 glyphIndices, // const UINT16 *glyphIndices;
884 glyphAdvances, // const FLOAT *glyphAdvances;
885 glyphOffsets, // const DWRITE_GLYPH_OFFSET *glyphOffsets;
886 FALSE, // BOOL isSideways;
887 rtl ? 1u : 0u // UINT32 bidiLevel;
888 };
889
890 const bool antiAlias = bool((q->state()->renderHints & QPainter::TextAntialiasing)
891 && !(fontDef.styleStrategy & QFont::NoAntialias));
893 ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
894 dc()->SetTextAntialiasMode(antiAlias ? antialiasMode : D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
895
896 dc()->DrawGlyphRun(pos,
897 &glyphRun,
898 nullptr,
899 pen.brush.Get(),
900 DWRITE_MEASURING_MODE_GDI_CLASSIC);
901 }
902
904 {
906
907 // Default path (no optimization)
908 if (!(path.shape() == QVectorPath::LinesHint || path.shape() == QVectorPath::PolygonHint)
909 || !pen.dashBrush
910 || q->state()->renderHints.testFlag(QPainter::Antialiasing)) {
911 ComPtr<ID2D1Geometry> geometry = vectorPathToID2D1PathGeometry(path);
912 if (!geometry) {
913 qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__);
914 return;
915 }
916 dc()->DrawGeometry(geometry.Get(), pen.brush.Get(),
917 FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get());
918 return;
919 }
920
921 // Optimized dash line drawing
922 const bool isPolygon = path.shape() == QVectorPath::PolygonHint && path.elementCount() >= 3;
923 const bool implicitClose = isPolygon && (path.hints() & QVectorPath::ImplicitClose);
924 const bool skipJoin = !isPolygon // Non-polygons don't require joins
925 || (pen.qpen.joinStyle() == Qt::MiterJoin && qFuzzyIsNull(pen.qpen.miterLimit()));
926 const qreal *points = path.points();
927 const int lastElement = path.elementCount() - (implicitClose ? 1 : 2);
928 qreal dashOffset = 0;
929 QPointF jointStart;
930 ID2D1Brush *brush = pen.dashBrush ? pen.dashBrush.Get() : pen.brush.Get();
931 for (int i = 0; i <= lastElement; ++i) {
932 QPointF p1(points[i * 2], points[i * 2 + 1]);
933 QPointF p2 = implicitClose && i == lastElement ? QPointF(points[0], points[1])
934 : QPointF(points[i * 2 + 2], points[i * 2 + 3]);
935 if (!isPolygon) // Advance the count for lines
936 ++i;
937
938 // Match raster engine output
939 if (p1 == p2 && pen.qpen.widthF() <= 1.0) {
940 q->fillRect(QRectF(p1, QSizeF(pen.qpen.widthF(), pen.qpen.widthF())), pen.qpen.brush());
941 continue;
942 }
943
944 if (!q->antiAliasingEnabled())
945 adjustLine(&p1, &p2);
946
947 q->adjustForAliasing(&p1);
948 q->adjustForAliasing(&p2);
949
950 const QLineF line(p1, p2);
951 const qreal lineLength = line.length();
952 if (pen.dashBrush) {
953 pen.dashBrush->SetTransform(transformFromLine(line, pen.qpen.widthF(), dashOffset));
954 dashOffset = pen.dashLength - fmod(lineLength - dashOffset, pen.dashLength);
955 }
956 dc()->DrawLine(to_d2d_point_2f(p1), to_d2d_point_2f(p2),
957 brush, FLOAT(pen.qpen.widthF()), nullptr);
958
959 if (skipJoin)
960 continue;
961
962 // Patch the join with the original brush
963 const qreal patchSegment = pen.dashBrush ? qBound(0.0, (pen.dashLength - dashOffset) / lineLength, 1.0)
964 : pen.qpen.widthF();
965 if (i > 0) {
967 writer.begin();
968 writer.moveTo(jointStart);
969 writer.lineTo(p1);
970 writer.lineTo(line.pointAt(patchSegment));
971 writer.close();
972 dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(),
973 FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get());
974 }
975 // Record the start position of the next joint
976 jointStart = line.pointAt(1 - patchSegment);
977
978 if (implicitClose && i == lastElement) { // Close the polygon
980 writer.begin();
981 writer.moveTo(jointStart);
982 writer.lineTo(p2);
983 writer.lineTo(QLineF(p2, QPointF(points[2], points[3])).pointAt(patchSegment));
984 writer.close();
985 dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(),
986 FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get());
987 }
988 }
989 }
990
991 ComPtr<IDWriteFontFace> fontFaceFromFontEngine(QFontEngine *fe)
992 {
993 const QFontDef fontDef = fe->fontDef;
994 ComPtr<IDWriteFontFace> fontFace = fontCache.value(fontDef);
995 if (fontFace)
996 return fontFace;
997
998 LOGFONT lf = QWindowsFontDatabase::fontDefToLOGFONT(fontDef, QString());
999
1000 // Get substitute name
1001 static const char keyC[] = "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes";
1002 const QString familyName = QString::fromWCharArray(lf.lfFaceName);
1003 const QString nameSubstitute = QSettings(QLatin1StringView(keyC), QSettings::NativeFormat).value(familyName, familyName).toString();
1004 if (nameSubstitute != familyName) {
1005 const int nameSubstituteLength = qMin(nameSubstitute.length(), LF_FACESIZE - 1);
1006 memcpy(lf.lfFaceName, nameSubstitute.data(), size_t(nameSubstituteLength) * sizeof(wchar_t));
1007 lf.lfFaceName[nameSubstituteLength] = 0;
1008 }
1009
1010 ComPtr<IDWriteFont> dwriteFont;
1011 HRESULT hr = QWindowsDirect2DContext::instance()->dwriteGdiInterop()->CreateFontFromLOGFONT(&lf, &dwriteFont);
1012 if (FAILED(hr)) {
1013 qDebug("%s: CreateFontFromLOGFONT failed: %#lx", __FUNCTION__, hr);
1014 return fontFace;
1015 }
1016
1017 hr = dwriteFont->CreateFontFace(&fontFace);
1018 if (FAILED(hr)) {
1019 qDebug("%s: CreateFontFace failed: %#lx", __FUNCTION__, hr);
1020 return fontFace;
1021 }
1022
1023 if (fontFace)
1024 fontCache.insert(fontDef, fontFace);
1025
1026 return fontFace;
1027 }
1028};
1029
1032{
1033 QPaintEngine::PaintEngineFeatures unsupported =
1034 // As of 1.1 Direct2D does not natively support complex composition modes
1035 // However, using Direct2D effects that implement them should be possible
1039
1040 // As of 1.1 Direct2D does not natively support perspective transforms
1041 // However, writing a custom effect that implements them should be possible
1042 // The built-in 3D transform effect unfortunately changes output image size, making
1043 // it unusable for us.
1045
1046 gccaps &= ~unsupported;
1047}
1048
1050{
1052
1053 d->bitmap->deviceContext()->begin();
1054 d->dc()->SetTransform(D2D1::Matrix3x2F::Identity());
1055
1056 if (systemClip().rectCount() > 1) {
1059
1060 ComPtr<ID2D1PathGeometry1> geometry = d->vectorPathToID2D1PathGeometry(qtVectorPathForPath(p));
1061 if (!geometry)
1062 return false;
1063
1064 d->dc()->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(),
1065 geometry.Get(),
1066 d->antialiasMode(),
1067 D2D1::IdentityMatrix(),
1068 1.0,
1069 nullptr,
1070 d->layerOptions()),
1071 nullptr);
1072 } else {
1073 QRect clip(0, 0, pdev->width(), pdev->height());
1074 if (!systemClip().isEmpty())
1076 d->dc()->PushAxisAlignedClip(to_d2d_rect_f(clip), D2D1_ANTIALIAS_MODE_ALIASED);
1077 d->clipFlags |= SimpleSystemClip;
1078 }
1079
1081
1082 setActive(true);
1083 return true;
1084}
1085
1087{
1089
1090 // Always clear all emulation-related things so we are in a clean state for our next painting run
1091 const bool emulatingComposition = d->flags.testFlag(EmulateComposition);
1093 if (!d->fallbackImage.isNull()) {
1094 if (emulatingComposition)
1095 drawImage(d->fallbackImage.rect(), d->fallbackImage, d->fallbackImage.rect());
1096 d->fallbackImage = QImage();
1097 }
1098
1099 // Pop any user-applied clipping
1100 d->clearClips();
1101 // Now the system clip from begin() above
1102 if (d->clipFlags & SimpleSystemClip) {
1103 d->dc()->PopAxisAlignedClip();
1104 d->clipFlags &= ~SimpleSystemClip;
1105 } else {
1106 d->dc()->PopLayer();
1107 }
1108
1109 return d->bitmap->deviceContext()->end();
1110}
1111
1116
1133
1135{
1136 const QBrush &brush = state()->brush;
1137 if (qbrush_style(brush) != Qt::NoBrush) {
1138 if (emulationRequired(BrushEmulation))
1139 rasterFill(path, brush);
1140 else
1141 fill(path, brush);
1142 }
1143
1144 const QPen &pen = state()->pen;
1145 if (qpen_style(pen) != Qt::NoPen && qbrush_style(qpen_brush(pen)) != Qt::NoBrush) {
1146 if (emulationRequired(PenEmulation))
1148 else
1149 stroke(path, pen);
1150 }
1151}
1152
1154{
1157
1158 if (path.isEmpty())
1159 return;
1160
1161 ensureBrush(brush);
1162 if (emulationRequired(BrushEmulation)) {
1163 rasterFill(path, brush);
1164 return;
1165 }
1166
1167 if (!d->brush.brush)
1168 return;
1169
1170 ComPtr<ID2D1Geometry> geometry = d->vectorPathToID2D1PathGeometry(path);
1171 if (!geometry) {
1172 qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__);
1173 return;
1174 }
1175
1176 d->dc()->FillGeometry(geometry.Get(), d->brush.brush.Get());
1177}
1178
1180{
1183
1184 if (path.isEmpty())
1185 return;
1186
1187 ensurePen(pen);
1188 if (emulationRequired(PenEmulation)) {
1190 return;
1191 }
1192
1193 if (!d->pen.brush)
1194 return;
1195
1196 d->stroke(path);
1197}
1198
1204
1206{
1208 d->updateClipEnabled(state()->clipEnabled);
1209}
1210
1212{
1214 d->updatePen(state()->pen);
1215}
1216
1218{
1220 d->updateBrush(state()->brush);
1221}
1222
1224{
1226 d->updateBrushOrigin(state()->brushOrigin);
1227}
1228
1230{
1232 d->updateOpacity(state()->opacity);
1233}
1234
1236{
1238 d->updateCompositionMode(state()->compositionMode());
1239}
1240
1242{
1244 d->updateHints();
1245}
1246
1248{
1250 d->updateTransform(state()->transform());
1251}
1252
1254{
1257
1258 ensureBrush(brush);
1259
1260 if (emulationRequired(BrushEmulation)) {
1262 } else {
1263 QRectF r = rect.normalized();
1264 adjustForAliasing(&r);
1265
1266 if (d->brush.brush)
1267 d->dc()->FillRectangle(to_d2d_rect_f(rect), d->brush.brush.Get());
1268 }
1269}
1270
1271void QWindowsDirect2DPaintEngine::drawRects(const QRect *rects, int rectCount)
1272{
1275
1276 ensureBrush();
1277 ensurePen();
1278
1279 if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) {
1280 QPaintEngineEx::drawRects(rects, rectCount);
1281 } else {
1282 QRectF rect;
1283 for (int i = 0; i < rectCount; i++) {
1284 rect = rects[i].normalized();
1285 adjustForAliasing(&rect);
1286
1287 D2D1_RECT_F d2d_rect = to_d2d_rect_f(rect);
1288
1289 if (d->brush.brush)
1290 d->dc()->FillRectangle(d2d_rect, d->brush.brush.Get());
1291
1292 if (d->pen.brush)
1293 d->dc()->DrawRectangle(d2d_rect, d->pen.brush.Get(),
1294 FLOAT(d->pen.qpen.widthF()), d->pen.strokeStyle.Get());
1295 }
1296 }
1297}
1298
1299void QWindowsDirect2DPaintEngine::drawRects(const QRectF *rects, int rectCount)
1300{
1303
1304 ensureBrush();
1305 ensurePen();
1306
1307 if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) {
1308 QPaintEngineEx::drawRects(rects, rectCount);
1309 } else {
1310 QRectF rect;
1311 for (int i = 0; i < rectCount; i++) {
1312 rect = rects[i].normalized();
1313 adjustForAliasing(&rect);
1314
1315 D2D1_RECT_F d2d_rect = to_d2d_rect_f(rect);
1316
1317 if (d->brush.brush)
1318 d->dc()->FillRectangle(d2d_rect, d->brush.brush.Get());
1319
1320 if (d->pen.brush)
1321 d->dc()->DrawRectangle(d2d_rect, d->pen.brush.Get(),
1322 FLOAT(d->pen.qpen.widthF()), d->pen.strokeStyle.Get());
1323 }
1324 }
1325}
1326
1327static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2)
1328{
1329 if (p2.x() > p1.x())
1330 return p2.y() < p1.y();
1331
1332 if (p1.x() > p2.x())
1333 return p1.y() < p2.y();
1334
1335 return false;
1336}
1337
1339{
1340 if (isLinePositivelySloped(*p1, *p2)) {
1341 p1->ry() -= qreal(1.0);
1342 p2->ry() -= qreal(1.0);
1343 }
1344}
1345
1347{
1350
1351 ensureBrush();
1352 ensurePen();
1353
1354 if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) {
1356 } else {
1357 QPointF p = r.center();
1358 adjustForAliasing(&p);
1359
1360 D2D1_ELLIPSE ellipse = {
1362 FLOAT(r.width() / 2.0),
1363 FLOAT(r.height() / 2.0)
1364 };
1365
1366 if (d->brush.brush)
1367 d->dc()->FillEllipse(ellipse, d->brush.brush.Get());
1368
1369 if (d->pen.brush)
1370 d->dc()->DrawEllipse(ellipse, d->pen.brush.Get(),
1371 FLOAT(d->pen.qpen.widthF()),
1372 d->pen.strokeStyle.Get());
1373 }
1374}
1375
1377{
1380
1381 ensureBrush();
1382 ensurePen();
1383
1384 if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) {
1386 } else {
1387 QPointF p = r.center();
1388 adjustForAliasing(&p);
1389
1390 D2D1_ELLIPSE ellipse = {
1392 FLOAT(r.width() / 2.0),
1393 FLOAT(r.height() / 2.0)
1394 };
1395
1396 if (d->brush.brush)
1397 d->dc()->FillEllipse(ellipse, d->brush.brush.Get());
1398
1399 if (d->pen.brush)
1400 d->dc()->DrawEllipse(ellipse, d->pen.brush.Get(),
1401 FLOAT(d->pen.qpen.widthF()),
1402 d->pen.strokeStyle.Get());
1403 }
1404}
1405
1407 const QRectF &sr, Qt::ImageConversionFlags flags)
1408{
1411
1413 drawPixmap(rectangle, pixmap, sr);
1414}
1415
1417 const QPixmap &pm,
1418 const QRectF &sr)
1419{
1422
1423 if (pm.isNull())
1424 return;
1425
1427 QImage i = pm.toImage();
1428 i.setColor(0, qRgba(0, 0, 0, 0));
1429 i.setColor(1, d->pen.qpen.color().rgba());
1430 drawImage(r, i, sr);
1431 return;
1432 }
1433
1434 if (d->flags.testFlag(EmulateComposition)) {
1435 const qreal points[] = {
1436 r.x(), r.y(),
1437 r.x() + r.width(), r.y(),
1438 r.x() + r.width(), r.y() + r.height(),
1439 r.x(), r.y() + r.height()
1440 };
1441 const QVectorPath vp(points, 4, nullptr, QVectorPath::RectangleHint);
1442 QBrush brush(sr.isValid() ? pm.copy(sr.toRect()) : pm);
1443 brush.setTransform(QTransform::fromTranslate(r.x(), r.y()));
1444 rasterFill(vp, brush);
1445 return;
1446 }
1447
1448 auto *pp = static_cast<QWindowsDirect2DPlatformPixmap *>(pm.handle());
1450
1451 ensurePen();
1452
1453 if (bitmap->bitmap() != d->bitmap->bitmap()) {
1454 // Good, src bitmap != dst bitmap
1455 if (sr.isValid())
1456 d->dc()->DrawBitmap(bitmap->bitmap(),
1457 to_d2d_rect_f(r), FLOAT(state()->opacity),
1458 d->interpolationMode(),
1459 to_d2d_rect_f(sr));
1460 else
1461 d->dc()->DrawBitmap(bitmap->bitmap(),
1462 to_d2d_rect_f(r), FLOAT(state()->opacity),
1463 d->interpolationMode());
1464 } else {
1465 // Ok, so the source pixmap and destination pixmap is the same.
1466 // D2D is not fond of this scenario, deal with it through
1467 // an intermediate bitmap
1468 QWindowsDirect2DBitmap intermediate;
1469
1470 if (sr.isValid()) {
1471 bool r = intermediate.resize(int(sr.width()), int(sr.height()));
1472 if (!r) {
1473 qWarning("%s: Could not resize intermediate bitmap to source rect size", __FUNCTION__);
1474 return;
1475 }
1476
1477 D2D1_RECT_U d2d_sr = to_d2d_rect_u(sr.toRect());
1478 HRESULT hr = intermediate.bitmap()->CopyFromBitmap(nullptr,
1479 bitmap->bitmap(),
1480 &d2d_sr);
1481 if (FAILED(hr)) {
1482 qWarning("%s: Could not copy source rect area from source bitmap to intermediate bitmap: %#lx", __FUNCTION__, hr);
1483 return;
1484 }
1485 } else {
1486 bool r = intermediate.resize(bitmap->size().width(),
1487 bitmap->size().height());
1488 if (!r) {
1489 qWarning("%s: Could not resize intermediate bitmap to source bitmap size", __FUNCTION__);
1490 return;
1491 }
1492
1493 HRESULT hr = intermediate.bitmap()->CopyFromBitmap(nullptr,
1494 bitmap->bitmap(),
1495 nullptr);
1496 if (FAILED(hr)) {
1497 qWarning("%s: Could not copy source bitmap to intermediate bitmap: %#lx", __FUNCTION__, hr);
1498 return;
1499 }
1500 }
1501
1502 d->dc()->DrawBitmap(intermediate.bitmap(),
1503 to_d2d_rect_f(r), FLOAT(state()->opacity),
1504 d->interpolationMode());
1505 }
1506}
1507
1509{
1512
1513 if (staticTextItem->numGlyphs == 0)
1514 return;
1515
1516 ensurePen();
1517
1518 // If we can't support the current configuration with Direct2D, fall back to slow path
1519 if (emulationRequired(PenEmulation)) {
1520 QPaintEngineEx::drawStaticTextItem(staticTextItem);
1521 return;
1522 }
1523
1524 ComPtr<IDWriteFontFace> fontFace = d->fontFaceFromFontEngine(staticTextItem->fontEngine());
1525 if (!fontFace) {
1526 qWarning("%s: Could not find font - falling back to slow text rendering path.", __FUNCTION__);
1527 QPaintEngineEx::drawStaticTextItem(staticTextItem);
1528 return;
1529 }
1530
1531 QVarLengthArray<UINT16> glyphIndices(staticTextItem->numGlyphs);
1532 QVarLengthArray<FLOAT> glyphAdvances(staticTextItem->numGlyphs);
1533 QVarLengthArray<DWRITE_GLYPH_OFFSET> glyphOffsets(staticTextItem->numGlyphs);
1534
1535 for (int i = 0; i < staticTextItem->numGlyphs; i++) {
1536 glyphIndices[i] = UINT16(staticTextItem->glyphs[i]); // Imperfect conversion here
1537
1538 // This looks a little funky because the positions are precalculated
1539 glyphAdvances[i] = 0;
1540 glyphOffsets[i].advanceOffset = FLOAT(staticTextItem->glyphPositions[i].x.toReal());
1541 // Qt and Direct2D seem to disagree on the direction of the ascender offset...
1542 glyphOffsets[i].ascenderOffset = FLOAT(staticTextItem->glyphPositions[i].y.toReal() * -1);
1543 }
1544
1545 d->drawGlyphRun(D2D1::Point2F(0, 0),
1546 fontFace.Get(),
1547 staticTextItem->fontEngine()->fontDef,
1548 staticTextItem->numGlyphs,
1549 glyphIndices.constData(),
1550 glyphAdvances.constData(),
1551 glyphOffsets.constData(),
1552 false);
1553}
1554
1556{
1559
1560 const auto &ti = static_cast<const QTextItemInt &>(textItem);
1561 if (ti.glyphs.numGlyphs == 0)
1562 return;
1563
1564 ensurePen();
1565
1566 // If we can't support the current configuration with Direct2D, fall back to slow path
1567 if (emulationRequired(PenEmulation)) {
1568 QPaintEngine::drawTextItem(p, textItem);
1569 return;
1570 }
1571
1572 ComPtr<IDWriteFontFace> fontFace = d->fontFaceFromFontEngine(ti.fontEngine);
1573 if (!fontFace) {
1574 qWarning("%s: Could not find font - falling back to slow text rendering path.", __FUNCTION__);
1575 QPaintEngine::drawTextItem(p, textItem);
1576 return;
1577 }
1578
1579 QVarLengthArray<UINT16> glyphIndices(ti.glyphs.numGlyphs);
1580 QVarLengthArray<FLOAT> glyphAdvances(ti.glyphs.numGlyphs);
1581 QVarLengthArray<DWRITE_GLYPH_OFFSET> glyphOffsets(ti.glyphs.numGlyphs);
1582
1583 for (int i = 0; i < ti.glyphs.numGlyphs; i++) {
1584 glyphIndices[i] = UINT16(ti.glyphs.glyphs[i]); // Imperfect conversion here
1585 glyphAdvances[i] = FLOAT(ti.glyphs.effectiveAdvance(i).toReal());
1586 glyphOffsets[i].advanceOffset = FLOAT(ti.glyphs.offsets[i].x.toReal());
1587
1588 // XXX Should we negate the y value like for static text items?
1589 glyphOffsets[i].ascenderOffset = FLOAT(ti.glyphs.offsets[i].y.toReal());
1590 }
1591
1592 const bool rtl = (ti.flags & QTextItem::RightToLeft);
1593 const QPointF offset(rtl ? ti.width.toReal() : 0, 0);
1594
1595 d->drawGlyphRun(to_d2d_point_2f(p + offset),
1596 fontFace.Get(),
1597 ti.fontEngine->fontDef,
1598 ti.glyphs.numGlyphs,
1599 glyphIndices.constData(),
1600 glyphAdvances.constData(),
1601 glyphOffsets.constData(),
1602 rtl);
1603}
1604
1605void QWindowsDirect2DPaintEngine::ensureBrush()
1606{
1607 ensureBrush(state()->brush);
1608}
1609
1610void QWindowsDirect2DPaintEngine::ensureBrush(const QBrush &brush)
1611{
1613 d->updateBrush(brush);
1614}
1615
1616void QWindowsDirect2DPaintEngine::ensurePen()
1617{
1618 ensurePen(state()->pen);
1619}
1620
1621void QWindowsDirect2DPaintEngine::ensurePen(const QPen &pen)
1622{
1624 d->updatePen(pen);
1625}
1626
1627void QWindowsDirect2DPaintEngine::rasterFill(const QVectorPath &path, const QBrush &brush)
1628{
1630
1631 if (d->fallbackImage.isNull()) {
1632 if (d->flags.testFlag(EmulateComposition)) {
1634 d->fallbackImage = d->bitmap->toImage();
1635 } else {
1636 d->fallbackImage = QImage(d->bitmap->size(), QImage::Format_ARGB32_Premultiplied);
1637 d->fallbackImage.fill(Qt::transparent);
1638 }
1639 }
1640
1641 QImage &img = d->fallbackImage;
1642 QPainter p;
1643 QPaintEngine *engine = img.paintEngine();
1644
1645 if (engine->isExtended() && p.begin(&img)) {
1646 p.setRenderHints(state()->renderHints);
1647 p.setCompositionMode(state()->compositionMode());
1648 p.setOpacity(state()->opacity);
1649 p.setBrushOrigin(state()->brushOrigin);
1650 p.setBrush(state()->brush);
1651 p.setPen(state()->pen);
1652
1653 auto *extended = static_cast<QPaintEngineEx *>(engine);
1654 for (const QPainterClipInfo &info : std::as_const(state()->clipInfo)) {
1655 extended->state()->matrix = info.matrix;
1656 extended->transformChanged();
1657
1658 switch (info.clipType) {
1660 extended->clip(info.region, info.operation);
1661 break;
1663 extended->clip(info.path, info.operation);
1664 break;
1666 extended->clip(info.rect, info.operation);
1667 break;
1669 qreal right = info.rectf.x() + info.rectf.width();
1670 qreal bottom = info.rectf.y() + info.rectf.height();
1671 qreal pts[] = { info.rectf.x(), info.rectf.y(),
1672 right, info.rectf.y(),
1673 right, bottom,
1674 info.rectf.x(), bottom };
1675 QVectorPath vp(pts, 4, nullptr, QVectorPath::RectangleHint);
1676 extended->clip(vp, info.operation);
1677 break;
1678 }
1679 }
1680
1681 extended->state()->matrix = state()->matrix;
1682 extended->transformChanged();
1683
1684 extended->fill(path, brush);
1685 if (!p.end())
1686 qWarning("%s: Paint Engine end returned false", __FUNCTION__);
1687
1688 if (!d->flags.testFlag(EmulateComposition)) { // Emulated fallback will be flattened in end()
1689 d->updateClipEnabled(false);
1690 d->updateTransform(QTransform());
1691 drawImage(img.rect(), img, img.rect());
1692 d->fallbackImage = QImage();
1695 }
1696 } else {
1697 qWarning("%s: Could not fall back to QImage", __FUNCTION__);
1698 }
1699}
1700
1701bool QWindowsDirect2DPaintEngine::emulationRequired(EmulationType type) const
1702{
1703 Q_D(const QWindowsDirect2DPaintEngine);
1704
1705 if (d->flags.testFlag(EmulateComposition))
1706 return true;
1707
1708 if (!state()->matrix.isAffine())
1709 return true;
1710
1711 switch (type) {
1712 case PenEmulation:
1713 return d->pen.emulate;
1714 break;
1715 case BrushEmulation:
1716 return d->brush.emulate;
1717 break;
1718 }
1719
1720 return false;
1721}
1722
1723bool QWindowsDirect2DPaintEngine::antiAliasingEnabled() const
1724{
1726}
1727
1728void QWindowsDirect2DPaintEngine::adjustForAliasing(QRectF *rect)
1729{
1730 if (!antiAliasingEnabled()) {
1735 }
1736}
1737
1738void QWindowsDirect2DPaintEngine::adjustForAliasing(QPointF *point)
1739{
1740 static const QPointF adjustment(MAGICAL_ALIASING_OFFSET,
1742
1743 if (!antiAliasingEnabled())
1744 (*point) += adjustment;
1745}
1746
1747void QWindowsDirect2DPaintEngine::suspend()
1748{
1749 end();
1750}
1751
1752void QWindowsDirect2DPaintEngine::resume()
1753{
1754 begin(paintDevice());
1756 penChanged();
1757 brushChanged();
1763}
1764
1766{
1767 Q_DISABLE_COPY_MOVE(QWindowsDirect2DPaintEngineSuspenderImpl)
1769 bool m_active;
1770public:
1772 : m_engine(engine)
1774 {
1775 if (m_active)
1776 m_engine->suspend();
1777 }
1778
1780 {
1781 if (m_active)
1782 m_engine->resume();
1783 }
1784};
1785
1798
1804
1808
1813
bool m_active
bool isActive
ComPtr< ID2D1PathGeometry1 > geometry() const
void curveTo(const QPointF &p1, const QPointF &p2, const QPointF &p3)
QPen pen() const
Returns the item's pen.
QBrush brush() const
Returns the item's brush, or an empty brush if no brush has been set.
\inmodule QtGui
Definition qbrush.h:30
@ NoAntialias
Definition qfont.h:47
@ PadSpread
Definition qbrush.h:147
\inmodule QtGui
Definition qimage.h:37
@ Format_ARGB32_Premultiplied
Definition qimage.h:48
void setColor(int i, QRgb c)
Sets the color at the given index in the color table, to the given to colorValue.
Definition qimage.cpp:1595
\inmodule QtCore\compares equality \compareswith equality QLine \endcompareswith
Definition qline.h:192
\inmodule QtGui
Definition qbrush.h:394
bool isEmpty() const noexcept
Definition qlist.h:401
int width() const
int height() const
virtual void drawRects(const QRect *rects, int rectCount) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
virtual void stroke(const QVectorPath &path, const QPen &pen)
virtual void drawEllipse(const QRectF &r) override
Reimplement this function to draw the largest ellipse that can be contained within rectangle rect.
QPainterState * state()
virtual void fillRect(const QRectF &rect, const QBrush &brush)
virtual void setState(QPainterState *s)
virtual void drawStaticTextItem(QStaticTextItem *)
\inmodule QtGui
void setActive(bool newState)
Sets the active state of the paint engine to state.
virtual void drawTextItem(const QPointF &p, const QTextItem &textItem)
This function draws the text item textItem at position p.
PaintEngineFeatures gccaps
QRegion systemClip() const
QPaintDevice * paintDevice() const
Returns the device that this engine is painting on, if painting is active; otherwise returns \nullptr...
Type
\value X11 \value Windows \value MacPrinter \value CoreGraphics \macos's Quartz2D (CoreGraphics) \val...
\inmodule QtGui
ElementType
This enum describes the types of elements used to connect vertices in subpaths.
void addRegion(const QRegion &region)
Adds the given region to the path by adding each rectangle in the region as a separate closed subpath...
QPainter::RenderHints renderHints
Definition qpainter_p.h:127
QTransform matrix
Definition qpainter_p.h:130
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
@ SmoothPixmapTransform
Definition qpainter.h:54
@ Antialiasing
Definition qpainter.h:52
@ TextAntialiasing
Definition qpainter.h:53
CompositionMode
Defines the modes supported for digital image compositing.
Definition qpainter.h:97
@ CompositionMode_SourceOver
Definition qpainter.h:98
@ CompositionMode_Source
Definition qpainter.h:101
\inmodule QtGui
Definition qpen.h:28
QBrush brush() const
Returns the brush used to fill strokes generated with this pen.
Definition qpen.cpp:715
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
QImage toImage() const
Converts the pixmap to a QImage.
Definition qpixmap.cpp:408
bool isNull() const
Returns true if this is a null pixmap; otherwise returns false.
Definition qpixmap.cpp:456
QPlatformPixmap * handle() const
Definition qpixmap.cpp:1506
QPixmap copy(int x, int y, int width, int height) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qpixmap.h:153
static QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags=Qt::AutoColor)
Converts the given image to a pixmap using the specified flags to control the conversion.
Definition qpixmap.cpp:1437
PixelType pixelType() const
\inmodule QtCore\reentrant
Definition qpoint.h:217
constexpr qreal x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:343
constexpr qreal y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:348
bool isNull() const noexcept
Returns true if both the x and y coordinates are set to 0.0 (ignoring the sign); otherwise returns fa...
Definition qpoint.h:338
\inmodule QtGui
Definition qbrush.h:412
QPointF center() const
Returns the center of this radial gradient in logical coordinates.
Definition qbrush.cpp:2157
\inmodule QtCore\reentrant
Definition qrect.h:484
QRectF normalized() const noexcept
Returns a normalized rectangle; i.e., a rectangle that has a non-negative width and height.
Definition qrect.cpp:1522
\inmodule QtCore\reentrant
Definition qrect.h:30
QRect normalized() const noexcept
Returns a normalized rectangle; i.e., a rectangle that has a non-negative width and height.
Definition qrect.cpp:277
QRect boundingRect() const noexcept
Returns the bounding rectangle of this region.
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
\inmodule QtCore
Definition qsettings.h:30
@ NativeFormat
Definition qsettings.h:49
QVariant value(QAnyStringView key, const QVariant &defaultValue) const
Returns the value for setting key.
\inmodule QtCore
Definition qsize.h:208
T pop()
Removes the top item from the stack and returns it.
Definition qstack.h:18
void push(const T &t)
Adds element t to the top of the stack.
Definition qstack.h:17
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromWCharArray(const wchar_t *string, qsizetype size=-1)
Definition qstring.h:1309
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
Internal QTextItem.
\inmodule QtGui
The QTransform class specifies 2D transformations of a coordinate system.
Definition qtransform.h:20
static QTransform fromTranslate(qreal dx, qreal dy)
Creates a matrix which corresponds to a translation of dx along the x axis and dy along the y axis.
QString toString() const
Returns the variant as a QString if the variant has a userType() including, but not limited to:
ID2D1Bitmap1 * bitmap() const
bool resize(int width, int height)
bool fromImage(const QImage &image, Qt::ImageConversionFlags flags)
static QWindowsDirect2DContext * instance()
void clip(const QVectorPath &path, Qt::ClipOperation operation)
void updateBrushOrigin(const QPointF &brushOrigin)
void drawGlyphRun(const D2D1_POINT_2F &pos, IDWriteFontFace *fontFace, const QFontDef &fontDef, int numGlyphs, const UINT16 *glyphIndices, const FLOAT *glyphAdvances, const DWRITE_GLYPH_OFFSET *glyphOffsets, bool rtl)
QWindowsDirect2DPaintEngine::Flags flags
QHash< QFontDef, ComPtr< IDWriteFontFace > > fontCache
QWindowsDirect2DPaintEnginePrivate(QWindowsDirect2DBitmap *bm, QWindowsDirect2DPaintEngine::Flags flags)
void updateTransform(const QTransform &transform)
D2D1_INTERPOLATION_MODE interpolationMode() const
ComPtr< IDWriteFontFace > fontFaceFromFontEngine(QFontEngine *fe)
void updateCompositionMode(QPainter::CompositionMode mode)
struct QWindowsDirect2DPaintEnginePrivate::@430 pen
ComPtr< ID2D1Brush > to_d2d_brush(const QBrush &newBrush, bool *needsEmulation)
ComPtr< ID2D1PathGeometry1 > vectorPathToID2D1PathGeometry(const QVectorPath &path)
QWindowsDirect2DPaintEngineSuspenderImpl(QWindowsDirect2DPaintEngine *engine)
QWindowsDirect2DPaintEngineSuspenderImpl engineSuspender
QWindowsDirect2DPaintEngineSuspenderPrivate(QWindowsDirect2DPaintEngine *engine)
QWindowsDirect2DPaintEngineSuspender(QWindowsDirect2DPaintEngine *engine)
void fillRect(const QRectF &rect, const QBrush &brush) override
Type type() const override
Reimplement this function to return the paint engine \l{Type}.
void drawRects(const QRect *rects, int rectCount) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void draw(const QVectorPath &path) override
void drawEllipse(const QRectF &r) override
Reimplement this function to draw the largest ellipse that can be contained within rectangle rect.
void drawTextItem(const QPointF &p, const QTextItem &textItem) override
This function draws the text item textItem at position p.
void setState(QPainterState *s) override
void stroke(const QVectorPath &path, const QPen &pen) override
void drawStaticTextItem(QStaticTextItem *staticTextItem) override
void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override
Reimplement this function to draw the part of the pm specified by the sr rectangle in the given r.
void fill(const QVectorPath &path, const QBrush &brush) override
void clip(const QVectorPath &path, Qt::ClipOperation op) override
QWindowsDirect2DPaintEngine(QWindowsDirect2DBitmap *bitmap, Flags flags)
bool end() override
Reimplement this function to finish painting on the current paint device.
void drawImage(const QRectF &rectangle, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags=Qt::AutoColor) override
Reimplement this function to draw the part of the image specified by the sr rectangle in the given re...
bool begin(QPaintDevice *pdev) override
Reimplement this function to initialise your paint engine when painting is to start on the paint devi...
static LOGFONT fontDefToLOGFONT(const QFontDef &fontDef, const QString &faceName)
QPixmap p2
QPixmap p1
[0]
rect
[4]
Combined button and popup list for selecting options.
@ AutoColor
Definition qnamespace.h:478
ClipOperation
@ ReplaceClip
@ IntersectClip
@ NoClip
@ transparent
Definition qnamespace.h:47
@ DashDotDotLine
@ DotLine
@ SolidLine
@ DashDotLine
@ NoPen
@ BevelJoin
@ MiterJoin
@ RoundJoin
@ DiagCrossPattern
@ HorPattern
@ BDiagPattern
@ SolidPattern
@ Dense5Pattern
@ RadialGradientPattern
@ Dense1Pattern
@ Dense3Pattern
@ TexturePattern
@ LinearGradientPattern
@ Dense4Pattern
@ NoBrush
@ CrossPattern
@ ConicalGradientPattern
@ FDiagPattern
@ Dense6Pattern
@ Dense7Pattern
@ VerPattern
@ Dense2Pattern
@ SquareCap
@ RoundCap
@ FlatCap
Definition brush.cpp:5
Definition image.cpp:4
#define Q_FALLTHROUGH()
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
#define qDebug
[1]
Definition qlogging.h:164
#define qWarning
Definition qlogging.h:166
auto qCos(T v)
Definition qmath.h:60
auto qSin(T v)
Definition qmath.h:54
constexpr float qDegreesToRadians(float degrees)
Definition qmath.h:260
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLint GLint GLint GLint GLint x
[0]
GLenum mode
GLboolean r
[2]
GLsizei GLenum GLenum * types
GLenum GLenum GLsizei count
GLdouble GLdouble right
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLenum const void GLbitfield GLsizei numGlyphs
GLenum type
GLint GLint bottom
GLfloat angle
GLbitfield flags
GLboolean enable
GLenum GLuint GLsizei const GLenum * props
GLenum GLuint GLintptr offset
GLint y
GLuint GLenum GLenum transform
GLfixed GLfixed GLint GLint GLfixed points
GLdouble s
[6]
Definition qopenglext.h:235
GLboolean reset
GLfixed GLfixed GLfixed y2
GLuint segment
GLint void * img
Definition qopenglext.h:233
GLsizei GLfixed GLfixed GLfixed GLfixed const GLubyte * bitmap
GLuint GLenum matrix
GLfixed GLfixed x2
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
GLboolean invert
Definition qopenglext.h:226
const QVectorPath & qtVectorPathForPath(const QPainterPath &path)
static bool needsEmulation(const QBrush &brush)
Definition qpainter.cpp:154
Qt::BrushStyle qbrush_style(const QBrush &b)
Definition qpainter_p.h:63
bool qpen_fast_equals(const QPen &a, const QPen &b)
Definition qpainter_p.h:53
Qt::PenStyle qpen_style(const QPen &p)
Definition qpainter_p.h:56
QBrush qpen_brush(const QPen &p)
Definition qpainter_p.h:54
bool qbrush_fast_equals(const QBrush &a, const QBrush &b)
Definition qpainter_p.h:62
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
constexpr QRgb qRgba(int r, int g, int b, int a)
Definition qrgb.h:33
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:187
long HRESULT
D2D1::ColorF to_d2d_color_f(const QColor &c)
D2D1_POINT_2F to_d2d_point_2f(const QPointF &qpoint)
D2D1_MATRIX_3X2_F to_d2d_matrix_3x2_f(const QTransform &transform)
QT_BEGIN_NAMESPACE D2D1_RECT_U to_d2d_rect_u(const QRect &qrect)
D2D1_RECT_F to_d2d_rect_f(const QRectF &qrect)
static D2D1_MATRIX_3X2_F transformFromLine(const QLineF &line, qreal penWidth, qreal dashOffset)
#define D2D_TAG(tag)
Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert)
Definition qbrush.cpp:146
static ID2D1Factory1 * factory()
static const qreal MAGICAL_ALIASING_OFFSET
static void adjustLine(QPointF *p1, QPointF *p2)
static QList< D2D1_GRADIENT_STOP > qGradientStopsToD2DStops(const QGradientStops &qstops)
static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2)
@ D2DDebugDrawStaticTextItemTag
QGraphicsEllipseItem * ellipse
widget render & pixmap
QHostInfo info
[0]
QJSEngine engine
[0]
ComPtr< ID2D1PathGeometry1 > antiAliased
static void cleanup_func(QPaintEngineEx *engine, void *data)
ComPtr< ID2D1PathGeometry1 > aliased
uint styleStrategy
Definition qfont_p.h:64
qreal pixelSize
Definition qfont_p.h:61