| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwindowsdirect2dpaintengine.h" |
| #include "qwindowsdirect2dplatformpixmap.h" |
| #include "qwindowsdirect2dpaintdevice.h" |
| #include "qwindowsdirect2dcontext.h" |
| #include "qwindowsdirect2dhelpers.h" |
| #include "qwindowsdirect2dbitmap.h" |
| #include "qwindowsdirect2ddevicecontext.h" |
| |
| #include <QtFontDatabaseSupport/private/qwindowsfontdatabase_p.h> |
| #include <QtFontDatabaseSupport/private/qwindowsfontengine_p.h> |
| #include "qwindowsintegration.h" |
| |
| #include <QtCore/qmath.h> |
| #include <QtCore/qstack.h> |
| #include <QtCore/qsettings.h> |
| #include <QtGui/private/qpaintengine_p.h> |
| #include <QtGui/private/qtextengine_p.h> |
| #include <QtGui/private/qfontengine_p.h> |
| #include <QtGui/private/qstatictext_p.h> |
| |
| #include <d2d1_1.h> |
| #include <dwrite_1.h> |
| #include <wrl.h> |
| |
| using Microsoft::WRL::ComPtr; |
| |
| QT_BEGIN_NAMESPACE |
| |
| // The enum values below are set as tags on the device context |
| // in the various draw methods. When EndDraw is called the device context |
| // will report the last set tag number in case of errors |
| // along with an error code |
| |
| // Microsoft keeps a list of d2d error codes here: |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/dd370979(v=vs.85).aspx |
| enum { |
| D2DDebugDrawInitialStateTag = -1, |
| D2DDebugFillTag = 1, |
| D2DDebugFillRectTag, |
| D2DDebugDrawRectsTag, |
| D2DDebugDrawRectFsTag, |
| D2DDebugDrawEllipseTag, |
| D2DDebugDrawEllipseFTag, |
| D2DDebugDrawImageTag, |
| D2DDebugDrawPixmapTag, |
| D2DDebugDrawStaticTextItemTag, |
| D2DDebugDrawTextItemTag |
| }; |
| |
| //Clipping flags |
| enum : unsigned { |
| SimpleSystemClip = 0x1 |
| }; |
| |
| enum ClipType { |
| AxisAlignedClip, |
| LayerClip |
| }; |
| |
| // Since d2d is a float-based system we need to be able to snap our drawing to whole pixels. |
| // Applying the magical aliasing offset to coordinates will do so, just make sure that |
| // aliased painting is turned on on the d2d device context. |
| static const qreal MAGICAL_ALIASING_OFFSET = 0.5; |
| |
| #define D2D_TAG(tag) d->dc()->SetTags(tag, tag) |
| |
| Q_GUI_EXPORT QImage qt_imageForBrush(int brushStyle, bool invert); |
| |
| static inline ID2D1Factory1 *factory() |
| { |
| return QWindowsDirect2DContext::instance()->d2dFactory(); |
| } |
| |
| static inline D2D1_MATRIX_3X2_F transformFromLine(const QLineF &line, qreal penWidth, qreal dashOffset) |
| { |
| const qreal halfWidth = penWidth / 2; |
| const qreal angle = -qDegreesToRadians(line.angle()); |
| const qreal sinA = qSin(angle); |
| const qreal cosA = qCos(angle); |
| QTransform transform = QTransform::fromTranslate(line.p1().x() + dashOffset * cosA + sinA * halfWidth, |
| line.p1().y() + dashOffset * sinA - cosA * halfWidth); |
| transform.rotateRadians(angle); |
| return to_d2d_matrix_3x2_f(transform); |
| } |
| |
| static void adjustLine(QPointF *p1, QPointF *p2); |
| static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2); |
| |
| static QVector<D2D1_GRADIENT_STOP> qGradientStopsToD2DStops(const QGradientStops &qstops) |
| { |
| QVector<D2D1_GRADIENT_STOP> stops(qstops.count()); |
| for (int i = 0, count = stops.size(); i < count; ++i) { |
| stops[i].position = FLOAT(qstops.at(i).first); |
| stops[i].color = to_d2d_color_f(qstops.at(i).second); |
| } |
| return stops; |
| } |
| |
| class Direct2DPathGeometryWriter |
| { |
| public: |
| bool begin() |
| { |
| HRESULT hr = factory()->CreatePathGeometry(&m_geometry); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create path geometry: %#lx", __FUNCTION__, hr); |
| return false; |
| } |
| |
| hr = m_geometry->Open(&m_sink); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create geometry sink: %#lx", __FUNCTION__, hr); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void setWindingFillEnabled(bool enable) |
| { |
| if (enable) |
| m_sink->SetFillMode(D2D1_FILL_MODE_WINDING); |
| else |
| m_sink->SetFillMode(D2D1_FILL_MODE_ALTERNATE); |
| } |
| |
| void setAliasingEnabled(bool enable) |
| { |
| m_roundCoordinates = enable; |
| } |
| |
| void setPositiveSlopeAdjustmentEnabled(bool enable) |
| { |
| m_adjustPositivelySlopedLines = enable; |
| } |
| |
| bool isInFigure() const |
| { |
| return m_inFigure; |
| } |
| |
| void moveTo(const QPointF &point) |
| { |
| if (m_inFigure) |
| m_sink->EndFigure(D2D1_FIGURE_END_OPEN); |
| |
| m_sink->BeginFigure(adjusted(point), D2D1_FIGURE_BEGIN_FILLED); |
| m_inFigure = true; |
| m_previousPoint = point; |
| } |
| |
| void lineTo(const QPointF &point) |
| { |
| QPointF pt = point; |
| if (m_adjustPositivelySlopedLines && isLinePositivelySloped(m_previousPoint, point)) { |
| moveTo(m_previousPoint - QPointF(0, 1)); |
| pt -= QPointF(0, 1); |
| } |
| m_sink->AddLine(adjusted(pt)); |
| if (pt != point) |
| moveTo(point); |
| m_previousPoint = point; |
| } |
| |
| void curveTo(const QPointF &p1, const QPointF &p2, const QPointF &p3) |
| { |
| D2D1_BEZIER_SEGMENT segment = { |
| adjusted(p1), |
| adjusted(p2), |
| adjusted(p3) |
| }; |
| |
| m_sink->AddBezier(segment); |
| m_previousPoint = p3; |
| } |
| |
| void close() |
| { |
| if (m_inFigure) |
| m_sink->EndFigure(D2D1_FIGURE_END_OPEN); |
| |
| m_sink->Close(); |
| } |
| |
| ComPtr<ID2D1PathGeometry1> geometry() const |
| { |
| return m_geometry; |
| } |
| |
| private: |
| D2D1_POINT_2F adjusted(const QPointF &point) |
| { |
| static const QPointF adjustment(MAGICAL_ALIASING_OFFSET, |
| MAGICAL_ALIASING_OFFSET); |
| |
| if (m_roundCoordinates) |
| return to_d2d_point_2f(point + adjustment); |
| else |
| return to_d2d_point_2f(point); |
| } |
| |
| ComPtr<ID2D1PathGeometry1> m_geometry; |
| ComPtr<ID2D1GeometrySink> m_sink; |
| |
| bool m_inFigure = false; |
| bool m_roundCoordinates = false; |
| bool m_adjustPositivelySlopedLines = false; |
| QPointF m_previousPoint; |
| }; |
| |
| struct D2DVectorPathCache { |
| ComPtr<ID2D1PathGeometry1> aliased; |
| ComPtr<ID2D1PathGeometry1> antiAliased; |
| |
| static void cleanup_func(QPaintEngineEx *engine, void *data) { |
| Q_UNUSED(engine); |
| auto *e = static_cast<D2DVectorPathCache *>(data); |
| delete e; |
| } |
| }; |
| |
| class QWindowsDirect2DPaintEnginePrivate : public QPaintEngineExPrivate |
| { |
| Q_DECLARE_PUBLIC(QWindowsDirect2DPaintEngine) |
| public: |
| QWindowsDirect2DPaintEnginePrivate(QWindowsDirect2DBitmap *bm, QWindowsDirect2DPaintEngine::Flags flags) |
| : bitmap(bm) |
| , flags(flags) |
| { |
| pen.reset(); |
| brush.reset(); |
| |
| dc()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); |
| } |
| |
| QWindowsDirect2DBitmap *bitmap; |
| QImage fallbackImage; |
| |
| unsigned int clipFlags = 0; |
| QStack<ClipType> pushedClips; |
| QWindowsDirect2DPaintEngine::Flags flags; |
| |
| QPointF currentBrushOrigin; |
| |
| QHash< QFontDef, ComPtr<IDWriteFontFace> > fontCache; |
| |
| struct { |
| bool emulate; |
| QPen qpen; |
| ComPtr<ID2D1Brush> brush; |
| ComPtr<ID2D1StrokeStyle1> strokeStyle; |
| ComPtr<ID2D1BitmapBrush1> dashBrush; |
| int dashLength; |
| |
| inline void reset() { |
| emulate = false; |
| qpen = QPen(); |
| brush.Reset(); |
| strokeStyle.Reset(); |
| dashBrush.Reset(); |
| dashLength = 0; |
| } |
| } pen; |
| |
| struct { |
| bool emulate; |
| QBrush qbrush; |
| ComPtr<ID2D1Brush> brush; |
| |
| inline void reset() { |
| emulate = false; |
| brush.Reset(); |
| qbrush = QBrush(); |
| } |
| } brush; |
| |
| inline ID2D1DeviceContext *dc() const |
| { |
| Q_ASSERT(bitmap); |
| return bitmap->deviceContext()->get(); |
| } |
| |
| inline D2D1_INTERPOLATION_MODE interpolationMode() const |
| { |
| Q_Q(const QWindowsDirect2DPaintEngine); |
| return (q->state()->renderHints & QPainter::SmoothPixmapTransform) ? D2D1_INTERPOLATION_MODE_LINEAR |
| : D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR; |
| } |
| |
| inline D2D1_ANTIALIAS_MODE antialiasMode() const |
| { |
| Q_Q(const QWindowsDirect2DPaintEngine); |
| return (q->state()->renderHints & QPainter::Antialiasing) ? D2D1_ANTIALIAS_MODE_PER_PRIMITIVE |
| : D2D1_ANTIALIAS_MODE_ALIASED; |
| } |
| |
| inline D2D1_LAYER_OPTIONS1 layerOptions() const |
| { |
| if (flags & QWindowsDirect2DPaintEngine::TranslucentTopLevelWindow) |
| return D2D1_LAYER_OPTIONS1_NONE; |
| else |
| return D2D1_LAYER_OPTIONS1_INITIALIZE_FROM_BACKGROUND; |
| } |
| |
| void updateTransform(const QTransform &transform) |
| { |
| dc()->SetTransform(to_d2d_matrix_3x2_f(transform)); |
| } |
| |
| void updateOpacity(qreal opacity) |
| { |
| if (brush.brush) |
| brush.brush->SetOpacity(FLOAT(opacity)); |
| if (pen.brush) |
| pen.brush->SetOpacity(FLOAT(opacity)); |
| } |
| |
| void pushClip(const QVectorPath &path) |
| { |
| Q_Q(QWindowsDirect2DPaintEngine); |
| |
| if (path.isEmpty()) { |
| D2D_RECT_F rect = {0, 0, 0, 0}; |
| dc()->PushAxisAlignedClip(rect, antialiasMode()); |
| pushedClips.push(AxisAlignedClip); |
| } else if (path.isRect() && (q->state()->matrix.type() <= QTransform::TxScale)) { |
| const qreal * const points = path.points(); |
| D2D_RECT_F rect = { |
| FLOAT(points[0]), // left |
| FLOAT(points[1]), // top |
| FLOAT(points[2]), // right, |
| FLOAT(points[5]) // bottom |
| }; |
| |
| dc()->PushAxisAlignedClip(rect, antialiasMode()); |
| pushedClips.push(AxisAlignedClip); |
| } else { |
| ComPtr<ID2D1PathGeometry1> geometry = vectorPathToID2D1PathGeometry(path); |
| if (!geometry) { |
| qWarning("%s: Could not convert vector path to painter path!", __FUNCTION__); |
| return; |
| } |
| |
| dc()->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), |
| geometry.Get(), |
| antialiasMode(), |
| D2D1::IdentityMatrix(), |
| 1.0, |
| nullptr, |
| layerOptions()), |
| nullptr); |
| pushedClips.push(LayerClip); |
| } |
| } |
| |
| void clearClips() |
| { |
| while (!pushedClips.isEmpty()) { |
| switch (pushedClips.pop()) { |
| case AxisAlignedClip: |
| dc()->PopAxisAlignedClip(); |
| break; |
| case LayerClip: |
| dc()->PopLayer(); |
| break; |
| } |
| } |
| } |
| |
| void updateClipEnabled(bool enabled) |
| { |
| if (!enabled) |
| clearClips(); |
| else if (pushedClips.isEmpty()) |
| replayClipOperations(); |
| } |
| |
| void clip(const QVectorPath &path, Qt::ClipOperation operation) |
| { |
| switch (operation) { |
| case Qt::NoClip: |
| clearClips(); |
| break; |
| case Qt::ReplaceClip: |
| clearClips(); |
| pushClip(path); |
| break; |
| case Qt::IntersectClip: |
| pushClip(path); |
| break; |
| } |
| } |
| |
| void updateCompositionMode(QPainter::CompositionMode mode) |
| { |
| switch (mode) { |
| case QPainter::CompositionMode_Source: |
| dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); |
| break; |
| case QPainter::CompositionMode_SourceOver: |
| dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER); |
| break; |
| |
| default: |
| // Activating an unsupported mode at any time will cause the QImage |
| // fallback to be used for the remainder of the active paint session |
| dc()->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY); |
| flags |= QWindowsDirect2DPaintEngine::EmulateComposition; |
| break; |
| } |
| } |
| |
| void updateBrush(const QBrush &newBrush) |
| { |
| Q_Q(const QWindowsDirect2DPaintEngine); |
| |
| if (qbrush_fast_equals(brush.qbrush, newBrush) && (brush.brush || brush.emulate)) |
| return; |
| |
| brush.brush = to_d2d_brush(newBrush, &brush.emulate); |
| brush.qbrush = newBrush; |
| |
| if (brush.brush) { |
| brush.brush->SetOpacity(FLOAT(q->state()->opacity)); |
| applyBrushOrigin(currentBrushOrigin); |
| } |
| } |
| |
| void updateBrushOrigin(const QPointF &brushOrigin) |
| { |
| negateCurrentBrushOrigin(); |
| applyBrushOrigin(brushOrigin); |
| } |
| |
| void negateCurrentBrushOrigin() |
| { |
| if (brush.brush && !currentBrushOrigin.isNull()) { |
| D2D1_MATRIX_3X2_F transform; |
| brush.brush->GetTransform(&transform); |
| |
| brush.brush->SetTransform(*(D2D1::Matrix3x2F::ReinterpretBaseType(&transform)) |
| * D2D1::Matrix3x2F::Translation(FLOAT(-currentBrushOrigin.x()), |
| FLOAT(-currentBrushOrigin.y()))); |
| } |
| } |
| |
| void applyBrushOrigin(const QPointF &origin) |
| { |
| if (brush.brush && !origin.isNull()) { |
| D2D1_MATRIX_3X2_F transform; |
| brush.brush->GetTransform(&transform); |
| |
| brush.brush->SetTransform(*(D2D1::Matrix3x2F::ReinterpretBaseType(&transform)) |
| * D2D1::Matrix3x2F::Translation(FLOAT(origin.x()), FLOAT(origin.y()))); |
| } |
| |
| currentBrushOrigin = origin; |
| } |
| |
| void updatePen(const QPen &newPen) |
| { |
| Q_Q(const QWindowsDirect2DPaintEngine); |
| if (qpen_fast_equals(newPen, pen.qpen) && (pen.brush || pen.emulate)) |
| return; |
| |
| pen.reset(); |
| pen.qpen = newPen; |
| |
| if (newPen.style() == Qt::NoPen) |
| return; |
| |
| pen.brush = to_d2d_brush(newPen.brush(), &pen.emulate); |
| if (!pen.brush) |
| return; |
| |
| pen.brush->SetOpacity(FLOAT(q->state()->opacity)); |
| |
| D2D1_STROKE_STYLE_PROPERTIES1 props = {}; |
| |
| switch (newPen.capStyle()) { |
| case Qt::SquareCap: |
| props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_SQUARE; |
| break; |
| case Qt::RoundCap: |
| props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_ROUND; |
| break; |
| case Qt::FlatCap: |
| default: |
| props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_FLAT; |
| break; |
| } |
| |
| switch (newPen.joinStyle()) { |
| case Qt::BevelJoin: |
| props.lineJoin = D2D1_LINE_JOIN_BEVEL; |
| break; |
| case Qt::RoundJoin: |
| props.lineJoin = D2D1_LINE_JOIN_ROUND; |
| break; |
| case Qt::MiterJoin: |
| default: |
| props.lineJoin = D2D1_LINE_JOIN_MITER; |
| break; |
| } |
| |
| props.miterLimit = FLOAT(newPen.miterLimit() * qreal(2.0)); // D2D and Qt miter specs differ |
| props.dashOffset = FLOAT(newPen.dashOffset()); |
| |
| if (newPen.widthF() == 0) |
| props.transformType = D2D1_STROKE_TRANSFORM_TYPE_HAIRLINE; |
| else if (qt_pen_is_cosmetic(newPen, q->state()->renderHints)) |
| props.transformType = D2D1_STROKE_TRANSFORM_TYPE_FIXED; |
| else |
| props.transformType = D2D1_STROKE_TRANSFORM_TYPE_NORMAL; |
| |
| switch (newPen.style()) { |
| case Qt::SolidLine: |
| props.dashStyle = D2D1_DASH_STYLE_SOLID; |
| break; |
| |
| case Qt::DotLine: |
| case Qt::DashDotLine: |
| case Qt::DashDotDotLine: |
| // Try and match Qt's raster engine in output as closely as possible |
| if (newPen.widthF() <= 1.0) |
| props.startCap = props.endCap = props.dashCap = D2D1_CAP_STYLE_FLAT; |
| |
| Q_FALLTHROUGH(); |
| default: |
| props.dashStyle = D2D1_DASH_STYLE_CUSTOM; |
| break; |
| } |
| |
| HRESULT hr; |
| |
| if (props.dashStyle == D2D1_DASH_STYLE_CUSTOM) { |
| QVector<qreal> dashes = newPen.dashPattern(); |
| QVector<FLOAT> converted(dashes.size()); |
| qreal penWidth = pen.qpen.widthF(); |
| qreal brushWidth = 0; |
| for (int i = 0; i < dashes.size(); i++) { |
| converted[i] = FLOAT(dashes[i]); |
| brushWidth += penWidth * dashes[i]; |
| } |
| |
| hr = factory()->CreateStrokeStyle(props, converted.constData(), UINT32(converted.size()), &pen.strokeStyle); |
| |
| // Create a combined brush/dash pattern for optimized line drawing |
| QWindowsDirect2DBitmap bitmap; |
| bitmap.resize(int(ceil(brushWidth)), int(ceil(penWidth))); |
| bitmap.deviceContext()->begin(); |
| bitmap.deviceContext()->get()->SetAntialiasMode(antialiasMode()); |
| bitmap.deviceContext()->get()->SetTransform(D2D1::IdentityMatrix()); |
| bitmap.deviceContext()->get()->Clear(); |
| const qreal offsetX = (qreal(bitmap.size().width()) - brushWidth) / 2; |
| const qreal offsetY = qreal(bitmap.size().height()) / 2; |
| bitmap.deviceContext()->get()->DrawLine(D2D1::Point2F(FLOAT(offsetX), FLOAT(offsetY)), |
| D2D1::Point2F(FLOAT(brushWidth), FLOAT(offsetY)), |
| pen.brush.Get(), FLOAT(penWidth), pen.strokeStyle.Get()); |
| bitmap.deviceContext()->end(); |
| D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = D2D1::BitmapBrushProperties1( |
| D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_CLAMP, D2D1_INTERPOLATION_MODE_LINEAR); |
| hr = dc()->CreateBitmapBrush(bitmap.bitmap(), bitmapBrushProperties, &pen.dashBrush); |
| pen.dashLength = bitmap.size().width(); |
| } else { |
| hr = factory()->CreateStrokeStyle(props, nullptr, 0, &pen.strokeStyle); |
| } |
| |
| if (FAILED(hr)) |
| qWarning("%s: Could not create stroke style: %#lx", __FUNCTION__, hr); |
| } |
| |
| ComPtr<ID2D1Brush> to_d2d_brush(const QBrush &newBrush, bool *needsEmulation) |
| { |
| HRESULT hr; |
| ComPtr<ID2D1Brush> result; |
| |
| Q_ASSERT(needsEmulation); |
| |
| *needsEmulation = false; |
| |
| switch (newBrush.style()) { |
| case Qt::NoBrush: |
| break; |
| |
| case Qt::SolidPattern: |
| { |
| ComPtr<ID2D1SolidColorBrush> solid; |
| |
| hr = dc()->CreateSolidColorBrush(to_d2d_color_f(newBrush.color()), &solid); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create solid color brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = solid.As(&result); |
| if (FAILED(hr)) |
| qWarning("%s: Could not convert solid color brush: %#lx", __FUNCTION__, hr); |
| } |
| break; |
| |
| case Qt::Dense1Pattern: |
| case Qt::Dense2Pattern: |
| case Qt::Dense3Pattern: |
| case Qt::Dense4Pattern: |
| case Qt::Dense5Pattern: |
| case Qt::Dense6Pattern: |
| case Qt::Dense7Pattern: |
| case Qt::HorPattern: |
| case Qt::VerPattern: |
| case Qt::CrossPattern: |
| case Qt::BDiagPattern: |
| case Qt::FDiagPattern: |
| case Qt::DiagCrossPattern: |
| { |
| ComPtr<ID2D1BitmapBrush1> bitmapBrush; |
| D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = { |
| D2D1_EXTEND_MODE_WRAP, |
| D2D1_EXTEND_MODE_WRAP, |
| interpolationMode() |
| }; |
| |
| QImage brushImg = qt_imageForBrush(newBrush.style(), false); |
| brushImg.setColor(0, newBrush.color().rgba()); |
| brushImg.setColor(1, qRgba(0, 0, 0, 0)); |
| |
| QWindowsDirect2DBitmap bitmap; |
| bool success = bitmap.fromImage(brushImg, Qt::AutoColor); |
| if (!success) { |
| qWarning("%s: Could not create Direct2D bitmap from Qt pattern brush image", __FUNCTION__); |
| break; |
| } |
| |
| hr = dc()->CreateBitmapBrush(bitmap.bitmap(), |
| bitmapBrushProperties, |
| &bitmapBrush); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create Direct2D bitmap brush for Qt pattern brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = bitmapBrush.As(&result); |
| if (FAILED(hr)) |
| qWarning("%s: Could not convert Direct2D bitmap brush for Qt pattern brush: %#lx", __FUNCTION__, hr); |
| } |
| break; |
| |
| case Qt::LinearGradientPattern: |
| if (newBrush.gradient()->spread() != QGradient::PadSpread) { |
| *needsEmulation = true; |
| } else { |
| ComPtr<ID2D1LinearGradientBrush> linear; |
| const auto *qlinear = static_cast<const QLinearGradient *>(newBrush.gradient()); |
| |
| D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES linearGradientBrushProperties; |
| ComPtr<ID2D1GradientStopCollection> gradientStopCollection; |
| |
| linearGradientBrushProperties.startPoint = to_d2d_point_2f(qlinear->start()); |
| linearGradientBrushProperties.endPoint = to_d2d_point_2f(qlinear->finalStop()); |
| |
| const QVector<D2D1_GRADIENT_STOP> stops = qGradientStopsToD2DStops(qlinear->stops()); |
| |
| hr = dc()->CreateGradientStopCollection(stops.constData(), |
| UINT32(stops.size()), |
| &gradientStopCollection); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create gradient stop collection for linear gradient: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = dc()->CreateLinearGradientBrush(linearGradientBrushProperties, gradientStopCollection.Get(), |
| &linear); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create Direct2D linear gradient brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = linear.As(&result); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not convert Direct2D linear gradient brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| } |
| break; |
| |
| case Qt::RadialGradientPattern: |
| if (newBrush.gradient()->spread() != QGradient::PadSpread) { |
| *needsEmulation = true; |
| } else { |
| ComPtr<ID2D1RadialGradientBrush> radial; |
| const auto *qradial = static_cast<const QRadialGradient *>(newBrush.gradient()); |
| |
| D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES radialGradientBrushProperties; |
| ComPtr<ID2D1GradientStopCollection> gradientStopCollection; |
| |
| radialGradientBrushProperties.center = to_d2d_point_2f(qradial->center()); |
| radialGradientBrushProperties.gradientOriginOffset = to_d2d_point_2f(qradial->focalPoint() - qradial->center()); |
| radialGradientBrushProperties.radiusX = FLOAT(qradial->radius()); |
| radialGradientBrushProperties.radiusY = FLOAT(qradial->radius()); |
| |
| const QVector<D2D1_GRADIENT_STOP> stops = qGradientStopsToD2DStops(qradial->stops()); |
| |
| hr = dc()->CreateGradientStopCollection(stops.constData(), stops.size(), &gradientStopCollection); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create gradient stop collection for radial gradient: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = dc()->CreateRadialGradientBrush(radialGradientBrushProperties, gradientStopCollection.Get(), |
| &radial); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create Direct2D radial gradient brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| radial.As(&result); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not convert Direct2D radial gradient brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| } |
| break; |
| |
| case Qt::ConicalGradientPattern: |
| *needsEmulation = true; |
| break; |
| |
| case Qt::TexturePattern: |
| { |
| ComPtr<ID2D1BitmapBrush1> bitmapBrush; |
| D2D1_BITMAP_BRUSH_PROPERTIES1 bitmapBrushProperties = { |
| D2D1_EXTEND_MODE_WRAP, |
| D2D1_EXTEND_MODE_WRAP, |
| interpolationMode() |
| }; |
| |
| QWindowsDirect2DPlatformPixmap *pp = static_cast<QWindowsDirect2DPlatformPixmap *>(newBrush.texture().handle()); |
| QWindowsDirect2DBitmap *bitmap = pp->bitmap(); |
| hr = dc()->CreateBitmapBrush(bitmap->bitmap(), |
| bitmapBrushProperties, |
| &bitmapBrush); |
| |
| if (FAILED(hr)) { |
| qWarning("%s: Could not create texture brush: %#lx", __FUNCTION__, hr); |
| break; |
| } |
| |
| hr = bitmapBrush.As(&result); |
| if (FAILED(hr)) |
| qWarning("%s: Could not convert texture brush: %#lx", __FUNCTION__, hr); |
| } |
| break; |
| } |
| |
| if (result && !newBrush.transform().isIdentity()) |
| result->SetTransform(to_d2d_matrix_3x2_f(newBrush.transform())); |
| |
| return result; |
| } |
| |
| ComPtr<ID2D1PathGeometry1> vectorPathToID2D1PathGeometry(const QVectorPath &path) |
| { |
| Q_Q(QWindowsDirect2DPaintEngine); |
| |
| const bool alias = !q->antiAliasingEnabled(); |
| |
| QVectorPath::CacheEntry *cacheEntry = path.isCacheable() ? path.lookupCacheData(q) |
| : nullptr; |
| |
| if (cacheEntry) { |
| auto *e = static_cast<D2DVectorPathCache *>(cacheEntry->data); |
| if (alias && e->aliased) |
| return e->aliased; |
| else if (!alias && e->antiAliased) |
| return e->antiAliased; |
| } |
| |
| Direct2DPathGeometryWriter writer; |
| if (!writer.begin()) |
| return nullptr; |
| |
| writer.setWindingFillEnabled(path.hasWindingFill()); |
| writer.setAliasingEnabled(alias); |
| writer.setPositiveSlopeAdjustmentEnabled(path.shape() == QVectorPath::LinesHint |
| || path.shape() == QVectorPath::PolygonHint); |
| |
| const QPainterPath::ElementType *types = path.elements(); |
| const int count = path.elementCount(); |
| const qreal *points = path.points(); |
| |
| Q_ASSERT(points); |
| |
| if (types) { |
| qreal x, y; |
| |
| for (int i = 0; i < count; i++) { |
| x = points[i * 2]; |
| y = points[i * 2 + 1]; |
| |
| switch (types[i]) { |
| case QPainterPath::MoveToElement: |
| writer.moveTo(QPointF(x, y)); |
| break; |
| |
| case QPainterPath::LineToElement: |
| writer.lineTo(QPointF(x, y)); |
| break; |
| |
| case QPainterPath::CurveToElement: |
| { |
| Q_ASSERT((i + 2) < count); |
| Q_ASSERT(types[i+1] == QPainterPath::CurveToDataElement); |
| Q_ASSERT(types[i+2] == QPainterPath::CurveToDataElement); |
| |
| i++; |
| const qreal x2 = points[i * 2]; |
| const qreal y2 = points[i * 2 + 1]; |
| |
| i++; |
| const qreal x3 = points[i * 2]; |
| const qreal y3 = points[i * 2 + 1]; |
| |
| writer.curveTo(QPointF(x, y), QPointF(x2, y2), QPointF(x3, y3)); |
| } |
| break; |
| |
| case QPainterPath::CurveToDataElement: |
| qWarning("%s: Unhandled Curve Data Element", __FUNCTION__); |
| break; |
| } |
| } |
| } else { |
| writer.moveTo(QPointF(points[0], points[1])); |
| for (int i = 1; i < count; i++) |
| writer.lineTo(QPointF(points[i * 2], points[i * 2 + 1])); |
| } |
| |
| if (writer.isInFigure()) |
| if (path.hasImplicitClose()) |
| writer.lineTo(QPointF(points[0], points[1])); |
| |
| writer.close(); |
| ComPtr<ID2D1PathGeometry1> geometry = writer.geometry(); |
| |
| if (path.isCacheable()) { |
| if (!cacheEntry) |
| cacheEntry = path.addCacheData(q, new D2DVectorPathCache, D2DVectorPathCache::cleanup_func); |
| |
| auto *e = static_cast<D2DVectorPathCache *>(cacheEntry->data); |
| if (alias) |
| e->aliased = geometry; |
| else |
| e->antiAliased = geometry; |
| } else { |
| path.makeCacheable(); |
| } |
| |
| return geometry; |
| } |
| |
| void updateHints() |
| { |
| dc()->SetAntialiasMode(antialiasMode()); |
| } |
| |
| 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) |
| { |
| Q_Q(QWindowsDirect2DPaintEngine); |
| |
| DWRITE_GLYPH_RUN glyphRun = { |
| fontFace, // IDWriteFontFace *fontFace; |
| FLOAT(fontDef.pixelSize), // FLOAT fontEmSize; |
| UINT32(numGlyphs), // UINT32 glyphCount; |
| glyphIndices, // const UINT16 *glyphIndices; |
| glyphAdvances, // const FLOAT *glyphAdvances; |
| glyphOffsets, // const DWRITE_GLYPH_OFFSET *glyphOffsets; |
| FALSE, // BOOL isSideways; |
| rtl ? 1u : 0u // UINT32 bidiLevel; |
| }; |
| |
| const bool antiAlias = bool((q->state()->renderHints & QPainter::TextAntialiasing) |
| && !(fontDef.styleStrategy & QFont::NoAntialias)); |
| const D2D1_TEXT_ANTIALIAS_MODE antialiasMode = (flags & QWindowsDirect2DPaintEngine::TranslucentTopLevelWindow) |
| ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; |
| dc()->SetTextAntialiasMode(antiAlias ? antialiasMode : D2D1_TEXT_ANTIALIAS_MODE_ALIASED); |
| |
| dc()->DrawGlyphRun(pos, |
| &glyphRun, |
| nullptr, |
| pen.brush.Get(), |
| DWRITE_MEASURING_MODE_GDI_CLASSIC); |
| } |
| |
| void stroke(const QVectorPath &path) |
| { |
| Q_Q(QWindowsDirect2DPaintEngine); |
| |
| QT_WARNING_PUSH |
| QT_WARNING_DISABLE_DEPRECATED |
| // Default path (no optimization) |
| if (!(path.shape() == QVectorPath::LinesHint || path.shape() == QVectorPath::PolygonHint) |
| || !pen.dashBrush |
| #if QT_DEPRECATED_SINCE(5, 14) |
| || q->state()->renderHints.testFlag(QPainter::HighQualityAntialiasing) |
| #endif |
| || q->state()->renderHints.testFlag(QPainter::Antialiasing)) { |
| QT_WARNING_POP |
| ComPtr<ID2D1Geometry> geometry = vectorPathToID2D1PathGeometry(path); |
| if (!geometry) { |
| qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__); |
| return; |
| } |
| dc()->DrawGeometry(geometry.Get(), pen.brush.Get(), |
| FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get()); |
| return; |
| } |
| |
| // Optimized dash line drawing |
| const bool isPolygon = path.shape() == QVectorPath::PolygonHint && path.elementCount() >= 3; |
| const bool implicitClose = isPolygon && (path.hints() & QVectorPath::ImplicitClose); |
| const bool skipJoin = !isPolygon // Non-polygons don't require joins |
| || (pen.qpen.joinStyle() == Qt::MiterJoin && qFuzzyIsNull(pen.qpen.miterLimit())); |
| const qreal *points = path.points(); |
| const int lastElement = path.elementCount() - (implicitClose ? 1 : 2); |
| qreal dashOffset = 0; |
| QPointF jointStart; |
| ID2D1Brush *brush = pen.dashBrush ? pen.dashBrush.Get() : pen.brush.Get(); |
| for (int i = 0; i <= lastElement; ++i) { |
| QPointF p1(points[i * 2], points[i * 2 + 1]); |
| QPointF p2 = implicitClose && i == lastElement ? QPointF(points[0], points[1]) |
| : QPointF(points[i * 2 + 2], points[i * 2 + 3]); |
| if (!isPolygon) // Advance the count for lines |
| ++i; |
| |
| // Match raster engine output |
| if (p1 == p2 && pen.qpen.widthF() <= 1.0) { |
| q->fillRect(QRectF(p1, QSizeF(pen.qpen.widthF(), pen.qpen.widthF())), pen.qpen.brush()); |
| continue; |
| } |
| |
| if (!q->antiAliasingEnabled()) |
| adjustLine(&p1, &p2); |
| |
| q->adjustForAliasing(&p1); |
| q->adjustForAliasing(&p2); |
| |
| const QLineF line(p1, p2); |
| const qreal lineLength = line.length(); |
| if (pen.dashBrush) { |
| pen.dashBrush->SetTransform(transformFromLine(line, pen.qpen.widthF(), dashOffset)); |
| dashOffset = pen.dashLength - fmod(lineLength - dashOffset, pen.dashLength); |
| } |
| dc()->DrawLine(to_d2d_point_2f(p1), to_d2d_point_2f(p2), |
| brush, FLOAT(pen.qpen.widthF()), nullptr); |
| |
| if (skipJoin) |
| continue; |
| |
| // Patch the join with the original brush |
| const qreal patchSegment = pen.dashBrush ? qBound(0.0, (pen.dashLength - dashOffset) / lineLength, 1.0) |
| : pen.qpen.widthF(); |
| if (i > 0) { |
| Direct2DPathGeometryWriter writer; |
| writer.begin(); |
| writer.moveTo(jointStart); |
| writer.lineTo(p1); |
| writer.lineTo(line.pointAt(patchSegment)); |
| writer.close(); |
| dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(), |
| FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get()); |
| } |
| // Record the start position of the next joint |
| jointStart = line.pointAt(1 - patchSegment); |
| |
| if (implicitClose && i == lastElement) { // Close the polygon |
| Direct2DPathGeometryWriter writer; |
| writer.begin(); |
| writer.moveTo(jointStart); |
| writer.lineTo(p2); |
| writer.lineTo(QLineF(p2, QPointF(points[2], points[3])).pointAt(patchSegment)); |
| writer.close(); |
| dc()->DrawGeometry(writer.geometry().Get(), pen.brush.Get(), |
| FLOAT(pen.qpen.widthF()), pen.strokeStyle.Get()); |
| } |
| } |
| } |
| |
| ComPtr<IDWriteFontFace> fontFaceFromFontEngine(QFontEngine *fe) |
| { |
| const QFontDef fontDef = fe->fontDef; |
| ComPtr<IDWriteFontFace> fontFace = fontCache.value(fontDef); |
| if (fontFace) |
| return fontFace; |
| |
| LOGFONT lf = QWindowsFontDatabase::fontDefToLOGFONT(fontDef, QString()); |
| |
| // Get substitute name |
| static const char keyC[] = "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes"; |
| const QString familyName = QString::fromWCharArray(lf.lfFaceName); |
| const QString nameSubstitute = QSettings(QLatin1String(keyC), QSettings::NativeFormat).value(familyName, familyName).toString(); |
| if (nameSubstitute != familyName) { |
| const int nameSubstituteLength = qMin(nameSubstitute.length(), LF_FACESIZE - 1); |
| memcpy(lf.lfFaceName, nameSubstitute.utf16(), size_t(nameSubstituteLength) * sizeof(wchar_t)); |
| lf.lfFaceName[nameSubstituteLength] = 0; |
| } |
| |
| ComPtr<IDWriteFont> dwriteFont; |
| HRESULT hr = QWindowsDirect2DContext::instance()->dwriteGdiInterop()->CreateFontFromLOGFONT(&lf, &dwriteFont); |
| if (FAILED(hr)) { |
| qDebug("%s: CreateFontFromLOGFONT failed: %#lx", __FUNCTION__, hr); |
| return fontFace; |
| } |
| |
| hr = dwriteFont->CreateFontFace(&fontFace); |
| if (FAILED(hr)) { |
| qDebug("%s: CreateFontFace failed: %#lx", __FUNCTION__, hr); |
| return fontFace; |
| } |
| |
| if (fontFace) |
| fontCache.insert(fontDef, fontFace); |
| |
| return fontFace; |
| } |
| }; |
| |
| QWindowsDirect2DPaintEngine::QWindowsDirect2DPaintEngine(QWindowsDirect2DBitmap *bitmap, Flags flags) |
| : QPaintEngineEx(*(new QWindowsDirect2DPaintEnginePrivate(bitmap, flags))) |
| { |
| QPaintEngine::PaintEngineFeatures unsupported = |
| // As of 1.1 Direct2D does not natively support complex composition modes |
| // However, using Direct2D effects that implement them should be possible |
| QPaintEngine::PorterDuff |
| | QPaintEngine::BlendModes |
| | QPaintEngine::RasterOpModes |
| |
| // As of 1.1 Direct2D does not natively support perspective transforms |
| // However, writing a custom effect that implements them should be possible |
| // The built-in 3D transform effect unfortunately changes output image size, making |
| // it unusable for us. |
| | QPaintEngine::PerspectiveTransform; |
| |
| gccaps &= ~unsupported; |
| } |
| |
| bool QWindowsDirect2DPaintEngine::begin(QPaintDevice * pdev) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| |
| d->bitmap->deviceContext()->begin(); |
| d->dc()->SetTransform(D2D1::Matrix3x2F::Identity()); |
| |
| if (systemClip().rectCount() > 1) { |
| QPainterPath p; |
| p.addRegion(systemClip()); |
| |
| ComPtr<ID2D1PathGeometry1> geometry = d->vectorPathToID2D1PathGeometry(qtVectorPathForPath(p)); |
| if (!geometry) |
| return false; |
| |
| d->dc()->PushLayer(D2D1::LayerParameters1(D2D1::InfiniteRect(), |
| geometry.Get(), |
| d->antialiasMode(), |
| D2D1::IdentityMatrix(), |
| 1.0, |
| nullptr, |
| d->layerOptions()), |
| nullptr); |
| } else { |
| QRect clip(0, 0, pdev->width(), pdev->height()); |
| if (!systemClip().isEmpty()) |
| clip &= systemClip().boundingRect(); |
| d->dc()->PushAxisAlignedClip(to_d2d_rect_f(clip), D2D1_ANTIALIAS_MODE_ALIASED); |
| d->clipFlags |= SimpleSystemClip; |
| } |
| |
| D2D_TAG(D2DDebugDrawInitialStateTag); |
| |
| setActive(true); |
| return true; |
| } |
| |
| bool QWindowsDirect2DPaintEngine::end() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| |
| // Always clear all emulation-related things so we are in a clean state for our next painting run |
| const bool emulatingComposition = d->flags.testFlag(EmulateComposition); |
| d->flags &= ~QWindowsDirect2DPaintEngine::EmulateComposition; |
| if (!d->fallbackImage.isNull()) { |
| if (emulatingComposition) |
| drawImage(d->fallbackImage.rect(), d->fallbackImage, d->fallbackImage.rect()); |
| d->fallbackImage = QImage(); |
| } |
| |
| // Pop any user-applied clipping |
| d->clearClips(); |
| // Now the system clip from begin() above |
| if (d->clipFlags & SimpleSystemClip) { |
| d->dc()->PopAxisAlignedClip(); |
| d->clipFlags &= ~SimpleSystemClip; |
| } else { |
| d->dc()->PopLayer(); |
| } |
| |
| return d->bitmap->deviceContext()->end(); |
| } |
| |
| QPaintEngine::Type QWindowsDirect2DPaintEngine::type() const |
| { |
| return QPaintEngine::Direct2D; |
| } |
| |
| void QWindowsDirect2DPaintEngine::setState(QPainterState *s) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| |
| QPaintEngineEx::setState(s); |
| d->clearClips(); |
| |
| clipEnabledChanged(); |
| penChanged(); |
| brushChanged(); |
| brushOriginChanged(); |
| opacityChanged(); |
| compositionModeChanged(); |
| renderHintsChanged(); |
| transformChanged(); |
| } |
| |
| void QWindowsDirect2DPaintEngine::draw(const QVectorPath &path) |
| { |
| const QBrush &brush = state()->brush; |
| if (qbrush_style(brush) != Qt::NoBrush) { |
| if (emulationRequired(BrushEmulation)) |
| rasterFill(path, brush); |
| else |
| fill(path, brush); |
| } |
| |
| const QPen &pen = state()->pen; |
| if (qpen_style(pen) != Qt::NoPen && qbrush_style(qpen_brush(pen)) != Qt::NoBrush) { |
| if (emulationRequired(PenEmulation)) |
| QPaintEngineEx::stroke(path, pen); |
| else |
| stroke(path, pen); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::fill(const QVectorPath &path, const QBrush &brush) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugFillTag); |
| |
| if (path.isEmpty()) |
| return; |
| |
| ensureBrush(brush); |
| if (emulationRequired(BrushEmulation)) { |
| rasterFill(path, brush); |
| return; |
| } |
| |
| if (!d->brush.brush) |
| return; |
| |
| ComPtr<ID2D1Geometry> geometry = d->vectorPathToID2D1PathGeometry(path); |
| if (!geometry) { |
| qWarning("%s: Could not convert path to d2d geometry", __FUNCTION__); |
| return; |
| } |
| |
| d->dc()->FillGeometry(geometry.Get(), d->brush.brush.Get()); |
| } |
| |
| void QWindowsDirect2DPaintEngine::stroke(const QVectorPath &path, const QPen &pen) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugFillTag); |
| |
| if (path.isEmpty()) |
| return; |
| |
| ensurePen(pen); |
| if (emulationRequired(PenEmulation)) { |
| QPaintEngineEx::stroke(path, pen); |
| return; |
| } |
| |
| if (!d->pen.brush) |
| return; |
| |
| d->stroke(path); |
| } |
| |
| void QWindowsDirect2DPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->clip(path, op); |
| } |
| |
| void QWindowsDirect2DPaintEngine::clipEnabledChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateClipEnabled(state()->clipEnabled); |
| } |
| |
| void QWindowsDirect2DPaintEngine::penChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updatePen(state()->pen); |
| } |
| |
| void QWindowsDirect2DPaintEngine::brushChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateBrush(state()->brush); |
| } |
| |
| void QWindowsDirect2DPaintEngine::brushOriginChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateBrushOrigin(state()->brushOrigin); |
| } |
| |
| void QWindowsDirect2DPaintEngine::opacityChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateOpacity(state()->opacity); |
| } |
| |
| void QWindowsDirect2DPaintEngine::compositionModeChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateCompositionMode(state()->compositionMode()); |
| } |
| |
| void QWindowsDirect2DPaintEngine::renderHintsChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateHints(); |
| } |
| |
| void QWindowsDirect2DPaintEngine::transformChanged() |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateTransform(state()->transform()); |
| } |
| |
| void QWindowsDirect2DPaintEngine::fillRect(const QRectF &rect, const QBrush &brush) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugFillRectTag); |
| |
| ensureBrush(brush); |
| |
| if (emulationRequired(BrushEmulation)) { |
| QPaintEngineEx::fillRect(rect, brush); |
| } else { |
| QRectF r = rect.normalized(); |
| adjustForAliasing(&r); |
| |
| if (d->brush.brush) |
| d->dc()->FillRectangle(to_d2d_rect_f(rect), d->brush.brush.Get()); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawRects(const QRect *rects, int rectCount) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawRectsTag); |
| |
| ensureBrush(); |
| ensurePen(); |
| |
| if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) { |
| QPaintEngineEx::drawRects(rects, rectCount); |
| } else { |
| QRectF rect; |
| for (int i = 0; i < rectCount; i++) { |
| rect = rects[i].normalized(); |
| adjustForAliasing(&rect); |
| |
| D2D1_RECT_F d2d_rect = to_d2d_rect_f(rect); |
| |
| if (d->brush.brush) |
| d->dc()->FillRectangle(d2d_rect, d->brush.brush.Get()); |
| |
| if (d->pen.brush) |
| d->dc()->DrawRectangle(d2d_rect, d->pen.brush.Get(), |
| FLOAT(d->pen.qpen.widthF()), d->pen.strokeStyle.Get()); |
| } |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawRects(const QRectF *rects, int rectCount) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawRectFsTag); |
| |
| ensureBrush(); |
| ensurePen(); |
| |
| if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) { |
| QPaintEngineEx::drawRects(rects, rectCount); |
| } else { |
| QRectF rect; |
| for (int i = 0; i < rectCount; i++) { |
| rect = rects[i].normalized(); |
| adjustForAliasing(&rect); |
| |
| D2D1_RECT_F d2d_rect = to_d2d_rect_f(rect); |
| |
| if (d->brush.brush) |
| d->dc()->FillRectangle(d2d_rect, d->brush.brush.Get()); |
| |
| if (d->pen.brush) |
| d->dc()->DrawRectangle(d2d_rect, d->pen.brush.Get(), |
| FLOAT(d->pen.qpen.widthF()), d->pen.strokeStyle.Get()); |
| } |
| } |
| } |
| |
| static bool isLinePositivelySloped(const QPointF &p1, const QPointF &p2) |
| { |
| if (p2.x() > p1.x()) |
| return p2.y() < p1.y(); |
| |
| if (p1.x() > p2.x()) |
| return p1.y() < p2.y(); |
| |
| return false; |
| } |
| |
| static void adjustLine(QPointF *p1, QPointF *p2) |
| { |
| if (isLinePositivelySloped(*p1, *p2)) { |
| p1->ry() -= qreal(1.0); |
| p2->ry() -= qreal(1.0); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawEllipse(const QRectF &r) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawEllipseFTag); |
| |
| ensureBrush(); |
| ensurePen(); |
| |
| if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) { |
| QPaintEngineEx::drawEllipse(r); |
| } else { |
| QPointF p = r.center(); |
| adjustForAliasing(&p); |
| |
| D2D1_ELLIPSE ellipse = { |
| to_d2d_point_2f(p), |
| FLOAT(r.width() / 2.0), |
| FLOAT(r.height() / 2.0) |
| }; |
| |
| if (d->brush.brush) |
| d->dc()->FillEllipse(ellipse, d->brush.brush.Get()); |
| |
| if (d->pen.brush) |
| d->dc()->DrawEllipse(ellipse, d->pen.brush.Get(), |
| FLOAT(d->pen.qpen.widthF()), |
| d->pen.strokeStyle.Get()); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawEllipse(const QRect &r) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawEllipseTag); |
| |
| ensureBrush(); |
| ensurePen(); |
| |
| if (emulationRequired(BrushEmulation) || emulationRequired(PenEmulation)) { |
| QPaintEngineEx::drawEllipse(r); |
| } else { |
| QPointF p = r.center(); |
| adjustForAliasing(&p); |
| |
| D2D1_ELLIPSE ellipse = { |
| to_d2d_point_2f(p), |
| FLOAT(r.width() / 2.0), |
| FLOAT(r.height() / 2.0) |
| }; |
| |
| if (d->brush.brush) |
| d->dc()->FillEllipse(ellipse, d->brush.brush.Get()); |
| |
| if (d->pen.brush) |
| d->dc()->DrawEllipse(ellipse, d->pen.brush.Get(), |
| FLOAT(d->pen.qpen.widthF()), |
| d->pen.strokeStyle.Get()); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawImage(const QRectF &rectangle, const QImage &image, |
| const QRectF &sr, Qt::ImageConversionFlags flags) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawImageTag); |
| |
| QPixmap pixmap = QPixmap::fromImage(image, flags); |
| drawPixmap(rectangle, pixmap, sr); |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawPixmap(const QRectF &r, |
| const QPixmap &pm, |
| const QRectF &sr) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawPixmapTag); |
| |
| if (pm.isNull()) |
| return; |
| |
| if (pm.handle()->pixelType() == QPlatformPixmap::BitmapType) { |
| QImage i = pm.toImage(); |
| i.setColor(0, qRgba(0, 0, 0, 0)); |
| i.setColor(1, d->pen.qpen.color().rgba()); |
| drawImage(r, i, sr); |
| return; |
| } |
| |
| if (d->flags.testFlag(EmulateComposition)) { |
| const qreal points[] = { |
| r.x(), r.y(), |
| r.x() + r.width(), r.y(), |
| r.x() + r.width(), r.y() + r.height(), |
| r.x(), r.y() + r.height() |
| }; |
| const QVectorPath vp(points, 4, nullptr, QVectorPath::RectangleHint); |
| QBrush brush(sr.isValid() ? pm.copy(sr.toRect()) : pm); |
| brush.setTransform(QTransform::fromTranslate(r.x(), r.y())); |
| rasterFill(vp, brush); |
| return; |
| } |
| |
| auto *pp = static_cast<QWindowsDirect2DPlatformPixmap *>(pm.handle()); |
| QWindowsDirect2DBitmap *bitmap = pp->bitmap(); |
| |
| ensurePen(); |
| |
| if (bitmap->bitmap() != d->bitmap->bitmap()) { |
| // Good, src bitmap != dst bitmap |
| if (sr.isValid()) |
| d->dc()->DrawBitmap(bitmap->bitmap(), |
| to_d2d_rect_f(r), FLOAT(state()->opacity), |
| d->interpolationMode(), |
| to_d2d_rect_f(sr)); |
| else |
| d->dc()->DrawBitmap(bitmap->bitmap(), |
| to_d2d_rect_f(r), FLOAT(state()->opacity), |
| d->interpolationMode()); |
| } else { |
| // Ok, so the source pixmap and destination pixmap is the same. |
| // D2D is not fond of this scenario, deal with it through |
| // an intermediate bitmap |
| QWindowsDirect2DBitmap intermediate; |
| |
| if (sr.isValid()) { |
| bool r = intermediate.resize(int(sr.width()), int(sr.height())); |
| if (!r) { |
| qWarning("%s: Could not resize intermediate bitmap to source rect size", __FUNCTION__); |
| return; |
| } |
| |
| D2D1_RECT_U d2d_sr = to_d2d_rect_u(sr.toRect()); |
| HRESULT hr = intermediate.bitmap()->CopyFromBitmap(nullptr, |
| bitmap->bitmap(), |
| &d2d_sr); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not copy source rect area from source bitmap to intermediate bitmap: %#lx", __FUNCTION__, hr); |
| return; |
| } |
| } else { |
| bool r = intermediate.resize(bitmap->size().width(), |
| bitmap->size().height()); |
| if (!r) { |
| qWarning("%s: Could not resize intermediate bitmap to source bitmap size", __FUNCTION__); |
| return; |
| } |
| |
| HRESULT hr = intermediate.bitmap()->CopyFromBitmap(nullptr, |
| bitmap->bitmap(), |
| nullptr); |
| if (FAILED(hr)) { |
| qWarning("%s: Could not copy source bitmap to intermediate bitmap: %#lx", __FUNCTION__, hr); |
| return; |
| } |
| } |
| |
| d->dc()->DrawBitmap(intermediate.bitmap(), |
| to_d2d_rect_f(r), FLOAT(state()->opacity), |
| d->interpolationMode()); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawStaticTextItem(QStaticTextItem *staticTextItem) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawStaticTextItemTag); |
| |
| if (staticTextItem->numGlyphs == 0) |
| return; |
| |
| ensurePen(); |
| |
| // If we can't support the current configuration with Direct2D, fall back to slow path |
| if (emulationRequired(PenEmulation)) { |
| QPaintEngineEx::drawStaticTextItem(staticTextItem); |
| return; |
| } |
| |
| ComPtr<IDWriteFontFace> fontFace = d->fontFaceFromFontEngine(staticTextItem->fontEngine()); |
| if (!fontFace) { |
| qWarning("%s: Could not find font - falling back to slow text rendering path.", __FUNCTION__); |
| QPaintEngineEx::drawStaticTextItem(staticTextItem); |
| return; |
| } |
| |
| QVarLengthArray<UINT16> glyphIndices(staticTextItem->numGlyphs); |
| QVarLengthArray<FLOAT> glyphAdvances(staticTextItem->numGlyphs); |
| QVarLengthArray<DWRITE_GLYPH_OFFSET> glyphOffsets(staticTextItem->numGlyphs); |
| |
| for (int i = 0; i < staticTextItem->numGlyphs; i++) { |
| glyphIndices[i] = UINT16(staticTextItem->glyphs[i]); // Imperfect conversion here |
| |
| // This looks a little funky because the positions are precalculated |
| glyphAdvances[i] = 0; |
| glyphOffsets[i].advanceOffset = FLOAT(staticTextItem->glyphPositions[i].x.toReal()); |
| // Qt and Direct2D seem to disagree on the direction of the ascender offset... |
| glyphOffsets[i].ascenderOffset = FLOAT(staticTextItem->glyphPositions[i].y.toReal() * -1); |
| } |
| |
| d->drawGlyphRun(D2D1::Point2F(0, 0), |
| fontFace.Get(), |
| staticTextItem->fontEngine()->fontDef, |
| staticTextItem->numGlyphs, |
| glyphIndices.constData(), |
| glyphAdvances.constData(), |
| glyphOffsets.constData(), |
| false); |
| } |
| |
| void QWindowsDirect2DPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| D2D_TAG(D2DDebugDrawTextItemTag); |
| |
| const auto &ti = static_cast<const QTextItemInt &>(textItem); |
| if (ti.glyphs.numGlyphs == 0) |
| return; |
| |
| ensurePen(); |
| |
| // If we can't support the current configuration with Direct2D, fall back to slow path |
| if (emulationRequired(PenEmulation)) { |
| QPaintEngine::drawTextItem(p, textItem); |
| return; |
| } |
| |
| ComPtr<IDWriteFontFace> fontFace = d->fontFaceFromFontEngine(ti.fontEngine); |
| if (!fontFace) { |
| qWarning("%s: Could not find font - falling back to slow text rendering path.", __FUNCTION__); |
| QPaintEngine::drawTextItem(p, textItem); |
| return; |
| } |
| |
| QVarLengthArray<UINT16> glyphIndices(ti.glyphs.numGlyphs); |
| QVarLengthArray<FLOAT> glyphAdvances(ti.glyphs.numGlyphs); |
| QVarLengthArray<DWRITE_GLYPH_OFFSET> glyphOffsets(ti.glyphs.numGlyphs); |
| |
| for (int i = 0; i < ti.glyphs.numGlyphs; i++) { |
| glyphIndices[i] = UINT16(ti.glyphs.glyphs[i]); // Imperfect conversion here |
| glyphAdvances[i] = FLOAT(ti.glyphs.effectiveAdvance(i).toReal()); |
| glyphOffsets[i].advanceOffset = FLOAT(ti.glyphs.offsets[i].x.toReal()); |
| |
| // XXX Should we negate the y value like for static text items? |
| glyphOffsets[i].ascenderOffset = FLOAT(ti.glyphs.offsets[i].y.toReal()); |
| } |
| |
| const bool rtl = (ti.flags & QTextItem::RightToLeft); |
| const QPointF offset(rtl ? ti.width.toReal() : 0, 0); |
| |
| d->drawGlyphRun(to_d2d_point_2f(p + offset), |
| fontFace.Get(), |
| ti.fontEngine->fontDef, |
| ti.glyphs.numGlyphs, |
| glyphIndices.constData(), |
| glyphAdvances.constData(), |
| glyphOffsets.constData(), |
| rtl); |
| } |
| |
| void QWindowsDirect2DPaintEngine::ensureBrush() |
| { |
| ensureBrush(state()->brush); |
| } |
| |
| void QWindowsDirect2DPaintEngine::ensureBrush(const QBrush &brush) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updateBrush(brush); |
| } |
| |
| void QWindowsDirect2DPaintEngine::ensurePen() |
| { |
| ensurePen(state()->pen); |
| } |
| |
| void QWindowsDirect2DPaintEngine::ensurePen(const QPen &pen) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| d->updatePen(pen); |
| } |
| |
| void QWindowsDirect2DPaintEngine::rasterFill(const QVectorPath &path, const QBrush &brush) |
| { |
| Q_D(QWindowsDirect2DPaintEngine); |
| |
| if (d->fallbackImage.isNull()) { |
| if (d->flags.testFlag(EmulateComposition)) { |
| QWindowsDirect2DPaintEngineSuspender suspender(this); |
| d->fallbackImage = d->bitmap->toImage(); |
| } else { |
| d->fallbackImage = QImage(d->bitmap->size(), QImage::Format_ARGB32_Premultiplied); |
| d->fallbackImage.fill(Qt::transparent); |
| } |
| } |
| |
| QImage &img = d->fallbackImage; |
| QPainter p; |
| QPaintEngine *engine = img.paintEngine(); |
| |
| if (engine->isExtended() && p.begin(&img)) { |
| p.setRenderHints(state()->renderHints); |
| p.setCompositionMode(state()->compositionMode()); |
| p.setOpacity(state()->opacity); |
| p.setBrushOrigin(state()->brushOrigin); |
| p.setBrush(state()->brush); |
| p.setPen(state()->pen); |
| |
| auto *extended = static_cast<QPaintEngineEx *>(engine); |
| for (const QPainterClipInfo &info : qAsConst(state()->clipInfo)) { |
| extended->state()->matrix = info.matrix; |
| extended->transformChanged(); |
| |
| switch (info.clipType) { |
| case QPainterClipInfo::RegionClip: |
| extended->clip(info.region, info.operation); |
| break; |
| case QPainterClipInfo::PathClip: |
| extended->clip(info.path, info.operation); |
| break; |
| case QPainterClipInfo::RectClip: |
| extended->clip(info.rect, info.operation); |
| break; |
| case QPainterClipInfo::RectFClip: |
| qreal right = info.rectf.x() + info.rectf.width(); |
| qreal bottom = info.rectf.y() + info.rectf.height(); |
| qreal pts[] = { info.rectf.x(), info.rectf.y(), |
| right, info.rectf.y(), |
| right, bottom, |
| info.rectf.x(), bottom }; |
| QVectorPath vp(pts, 4, nullptr, QVectorPath::RectangleHint); |
| extended->clip(vp, info.operation); |
| break; |
| } |
| } |
| |
| extended->state()->matrix = state()->matrix; |
| extended->transformChanged(); |
| |
| extended->fill(path, brush); |
| if (!p.end()) |
| qWarning("%s: Paint Engine end returned false", __FUNCTION__); |
| |
| if (!d->flags.testFlag(EmulateComposition)) { // Emulated fallback will be flattened in end() |
| d->updateClipEnabled(false); |
| d->updateTransform(QTransform()); |
| drawImage(img.rect(), img, img.rect()); |
| d->fallbackImage = QImage(); |
| transformChanged(); |
| clipEnabledChanged(); |
| } |
| } else { |
| qWarning("%s: Could not fall back to QImage", __FUNCTION__); |
| } |
| } |
| |
| bool QWindowsDirect2DPaintEngine::emulationRequired(EmulationType type) const |
| { |
| Q_D(const QWindowsDirect2DPaintEngine); |
| |
| if (d->flags.testFlag(EmulateComposition)) |
| return true; |
| |
| if (!state()->matrix.isAffine()) |
| return true; |
| |
| switch (type) { |
| case PenEmulation: |
| return d->pen.emulate; |
| break; |
| case BrushEmulation: |
| return d->brush.emulate; |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool QWindowsDirect2DPaintEngine::antiAliasingEnabled() const |
| { |
| return state()->renderHints & QPainter::Antialiasing; |
| } |
| |
| void QWindowsDirect2DPaintEngine::adjustForAliasing(QRectF *rect) |
| { |
| if (!antiAliasingEnabled()) { |
| rect->adjust(MAGICAL_ALIASING_OFFSET, |
| MAGICAL_ALIASING_OFFSET, |
| MAGICAL_ALIASING_OFFSET, |
| MAGICAL_ALIASING_OFFSET); |
| } |
| } |
| |
| void QWindowsDirect2DPaintEngine::adjustForAliasing(QPointF *point) |
| { |
| static const QPointF adjustment(MAGICAL_ALIASING_OFFSET, |
| MAGICAL_ALIASING_OFFSET); |
| |
| if (!antiAliasingEnabled()) |
| (*point) += adjustment; |
| } |
| |
| void QWindowsDirect2DPaintEngine::suspend() |
| { |
| end(); |
| } |
| |
| void QWindowsDirect2DPaintEngine::resume() |
| { |
| begin(paintDevice()); |
| clipEnabledChanged(); |
| penChanged(); |
| brushChanged(); |
| brushOriginChanged(); |
| opacityChanged(); |
| compositionModeChanged(); |
| renderHintsChanged(); |
| transformChanged(); |
| } |
| |
| class QWindowsDirect2DPaintEngineSuspenderImpl |
| { |
| Q_DISABLE_COPY_MOVE(QWindowsDirect2DPaintEngineSuspenderImpl) |
| QWindowsDirect2DPaintEngine *m_engine; |
| bool m_active; |
| public: |
| QWindowsDirect2DPaintEngineSuspenderImpl(QWindowsDirect2DPaintEngine *engine) |
| : m_engine(engine) |
| , m_active(engine->isActive()) |
| { |
| if (m_active) |
| m_engine->suspend(); |
| } |
| |
| ~QWindowsDirect2DPaintEngineSuspenderImpl() |
| { |
| if (m_active) |
| m_engine->resume(); |
| } |
| }; |
| |
| class QWindowsDirect2DPaintEngineSuspenderPrivate |
| { |
| public: |
| QWindowsDirect2DPaintEngineSuspenderPrivate(QWindowsDirect2DPaintEngine *engine) |
| : engineSuspender(engine) |
| , dcSuspender(static_cast<QWindowsDirect2DPaintEnginePrivate *>(engine->d_ptr.data())->bitmap->deviceContext()) |
| { |
| } |
| |
| QWindowsDirect2DPaintEngineSuspenderImpl engineSuspender; |
| QWindowsDirect2DDeviceContextSuspender dcSuspender; |
| }; |
| |
| QWindowsDirect2DPaintEngineSuspender::QWindowsDirect2DPaintEngineSuspender(QWindowsDirect2DPaintEngine *engine) |
| : d_ptr(new QWindowsDirect2DPaintEngineSuspenderPrivate(engine)) |
| { |
| |
| } |
| |
| QWindowsDirect2DPaintEngineSuspender::~QWindowsDirect2DPaintEngineSuspender() |
| { |
| } |
| |
| void QWindowsDirect2DPaintEngineSuspender::resume() |
| { |
| d_ptr.reset(); |
| } |
| |
| QT_END_NAMESPACE |