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