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