blob: 03be44e095812096e72c3f9cd7594c78578c4a2c [file] [log] [blame]
/****************************************************************************
**
** 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