From 07942adb77f60738a6043665673d51fc7991233b Mon Sep 17 00:00:00 2001 From: Louai Al-Khanji Date: Fri, 6 Jan 2017 15:15:54 -0800 Subject: xcb: Add experimental legacy support for native X11 painting This commit revives the old native QPixmap and QPaintEngine implementations that were present in Qt4. The backing store supports regular raster windows in this commit. Support for render-to-texture widgets and OpenGL compositing will be added in a follow-up commit. Change-Id: I80a9c4f0c42a6f68f571dfee930d95000d5dd950 Reviewed-by: Lars Knoll --- config.tests/x11/xrender/xrender.cpp | 52 + config.tests/x11/xrender/xrender.pro | 3 + src/gui/configure.json | 22 +- src/gui/image/qplatformpixmap.h | 5 +- src/gui/painting/qbrush.cpp | 2 +- src/gui/painting/qpaintengine.cpp | 4 +- .../xcb/nativepainting/nativepainting.pri | 22 + .../xcb/nativepainting/qbackingstore_x11.cpp | 203 ++ .../xcb/nativepainting/qbackingstore_x11_p.h | 70 + .../platforms/xcb/nativepainting/qcolormap_x11.cpp | 644 +++++ .../platforms/xcb/nativepainting/qcolormap_x11_p.h | 75 + .../xcb/nativepainting/qpaintengine_x11.cpp | 2837 ++++++++++++++++++++ .../xcb/nativepainting/qpaintengine_x11_p.h | 118 + .../platforms/xcb/nativepainting/qpixmap_x11.cpp | 2104 +++++++++++++++ .../platforms/xcb/nativepainting/qpixmap_x11_p.h | 160 ++ .../platforms/xcb/nativepainting/qt_x11_p.h | 193 ++ .../platforms/xcb/nativepainting/qtessellator.cpp | 1491 ++++++++++ .../platforms/xcb/nativepainting/qtessellator_p.h | 83 + .../xcb/nativepainting/qxcbnativepainting.cpp | 315 +++ .../xcb/nativepainting/qxcbnativepainting.h | 96 + src/plugins/platforms/xcb/qxcbconnection.cpp | 18 +- src/plugins/platforms/xcb/qxcbconnection.h | 2 + src/plugins/platforms/xcb/qxcbintegration.cpp | 37 + src/plugins/platforms/xcb/qxcbintegration.h | 3 + src/plugins/platforms/xcb/qxcbwindow.cpp | 6 + src/plugins/platforms/xcb/xcb_qpa_lib.pro | 1 + 26 files changed, 8558 insertions(+), 8 deletions(-) create mode 100644 config.tests/x11/xrender/xrender.cpp create mode 100644 config.tests/x11/xrender/xrender.pro create mode 100644 src/plugins/platforms/xcb/nativepainting/nativepainting.pri create mode 100644 src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qt_x11_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qtessellator.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qtessellator_p.h create mode 100644 src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp create mode 100644 src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h diff --git a/config.tests/x11/xrender/xrender.cpp b/config.tests/x11/xrender/xrender.cpp new file mode 100644 index 0000000000..35203cc02c --- /dev/null +++ b/config.tests/x11/xrender/xrender.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the config.tests 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 +#include + +#if RENDER_MAJOR == 0 && RENDER_MINOR < 5 +# error "Required Xrender version 0.6 not found." +#else +int main(int, char **) +{ + XRenderPictFormat *format; + format = 0; + return 0; +} +#endif diff --git a/config.tests/x11/xrender/xrender.pro b/config.tests/x11/xrender/xrender.pro new file mode 100644 index 0000000000..ab5c5efa77 --- /dev/null +++ b/config.tests/x11/xrender/xrender.pro @@ -0,0 +1,3 @@ +SOURCES = xrender.cpp +CONFIG += x11 +CONFIG -= qt diff --git a/src/gui/configure.json b/src/gui/configure.json index 854b187eb6..1e873d3709 100644 --- a/src/gui/configure.json +++ b/src/gui/configure.json @@ -40,6 +40,7 @@ "tslib": "boolean", "vulkan": "boolean", "xcb": { "type": "enum", "values": [ "no", "yes", "qt", "system" ] }, + "xcb-native-painting": "boolean", "xcb-xlib": "boolean", "xinput2": "boolean", "xkb": "boolean", @@ -305,6 +306,13 @@ "sources": [ { "type": "pkgConfig", "args": "xkbcommon xkbcommon-x11 >= 0.4.1" } ] + }, + "xrender": { + "label": "XRender for native painting", + "test": "x11/xrender", + "sources": [ + "-lXrender" + ] } }, @@ -755,6 +763,18 @@ "condition": "libs.xcb_glx", "output": [ "privateFeature" ] }, + "xcb-native-painting": { + "label": "Native painting (experimental)", + "emitIf": "features.xcb", + "condition": "features.xcb-xlib && libs.xrender", + "output": [ "privateFeature" ] + }, + "xrender": { + "label": "XRender for native painting", + "emitIf": "features.xcb && features.xcb-native-painting", + "condition": "features.xcb-native-painting", + "output": [ "privateFeature" ] + }, "xcb-render": { "label": "XCB render", "emitIf": "features.xcb", @@ -1145,7 +1165,7 @@ QMAKE_LIBDIR_OPENGL[_ES2] and QMAKE_LIBS_OPENGL[_ES2] in the mkspec for your pla "section": "X11", "condition": "features.xcb", "entries": [ - "system-xcb", "egl_x11", "xinput2", "xkb", "xlib", "xcb-render", "xcb-glx", "xcb-xlib", "xkbcommon-system" + "system-xcb", "egl_x11", "xinput2", "xkb", "xlib", "xcb-render", "xcb-glx", "xcb-xlib", "xkbcommon-system", "xcb-native-painting" ] }, { diff --git a/src/gui/image/qplatformpixmap.h b/src/gui/image/qplatformpixmap.h index 1857856b07..7635ac2949 100644 --- a/src/gui/image/qplatformpixmap.h +++ b/src/gui/image/qplatformpixmap.h @@ -69,7 +69,7 @@ public: enum ClassId { RasterClass, DirectFBClass, BlitterClass, Direct2DClass, - CustomClass = 1024 }; + X11Class, CustomClass = 1024 }; QPlatformPixmap(PixelType pixelType, int classId); virtual ~QPlatformPixmap(); @@ -147,6 +147,7 @@ protected: private: friend class QPixmap; + friend class QX11PlatformPixmap; friend class QImagePixmapCleanupHooks; // Needs to set is_cached friend class QOpenGLTextureCache; //Needs to check the reference count friend class QExplicitlySharedDataPointer; @@ -162,7 +163,7 @@ private: # define QT_XFORM_TYPE_MSBFIRST 0 # define QT_XFORM_TYPE_LSBFIRST 1 -extern bool qt_xForm_helper(const QTransform&, int, int, int, uchar*, int, int, int, const uchar*, int, int, int); +Q_GUI_EXPORT bool qt_xForm_helper(const QTransform&, int, int, int, uchar*, int, int, int, const uchar*, int, int, int); QT_END_NAMESPACE diff --git a/src/gui/painting/qbrush.cpp b/src/gui/painting/qbrush.cpp index cc3ee76f0d..5c13308d94 100644 --- a/src/gui/painting/qbrush.cpp +++ b/src/gui/painting/qbrush.cpp @@ -100,7 +100,7 @@ const uchar *qt_patternForBrush(int brushStyle, bool invert) return pat_tbl[brushStyle - Qt::Dense1Pattern][invert]; } -QPixmap qt_pixmapForBrush(int brushStyle, bool invert) +Q_GUI_EXPORT QPixmap qt_pixmapForBrush(int brushStyle, bool invert) { QPixmap pm; diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp index 5fc5d5364e..f42fd4ff87 100644 --- a/src/gui/painting/qpaintengine.cpp +++ b/src/gui/painting/qpaintengine.cpp @@ -547,8 +547,8 @@ void qt_fill_tile(QPixmap *tile, const QPixmap &pixmap) } } -void qt_draw_tile(QPaintEngine *gc, qreal x, qreal y, qreal w, qreal h, - const QPixmap &pixmap, qreal xOffset, qreal yOffset) +Q_GUI_EXPORT void qt_draw_tile(QPaintEngine *gc, qreal x, qreal y, qreal w, qreal h, + const QPixmap &pixmap, qreal xOffset, qreal yOffset) { qreal yPos, xPos, drawH, drawW, yOff, xOff; yPos = y; diff --git a/src/plugins/platforms/xcb/nativepainting/nativepainting.pri b/src/plugins/platforms/xcb/nativepainting/nativepainting.pri new file mode 100644 index 0000000000..78ed00843f --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/nativepainting.pri @@ -0,0 +1,22 @@ +qtConfig(xcb-native-painting) { + qtConfig(xrender): QMAKE_USE += xrender + qtConfig(fontconfig): QMAKE_USE_PRIVATE += freetype + + INCLUDEPATH += $$PWD + HEADERS += \ + $$PWD/qtessellator_p.h \ + $$PWD/qpixmap_x11_p.h \ + $$PWD/qpaintengine_x11_p.h \ + $$PWD/qt_x11_p.h \ + $$PWD/qcolormap_x11_p.h \ + $$PWD/qbackingstore_x11_p.h \ + $$PWD/qxcbnativepainting.h + + SOURCES += \ + $$PWD/qtessellator.cpp \ + $$PWD/qpixmap_x11.cpp \ + $$PWD/qpaintengine_x11.cpp \ + $$PWD/qcolormap_x11.cpp \ + $$PWD/qbackingstore_x11.cpp \ + $$PWD/qxcbnativepainting.cpp +} diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp new file mode 100644 index 0000000000..2dd2cdd9e3 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbackingstore_x11_p.h" +#include "qxcbwindow.h" +#include "qpixmap_x11_p.h" + +#include +#include + +#if QT_CONFIG(xrender) +# include +#endif + +#include + +#ifndef None +#define None 0L +#endif + +QT_BEGIN_NAMESPACE + +QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window) + : QPlatformBackingStore(window) + , m_translucentBackground(false) +{ + if (QXcbWindow *w = static_cast(window->handle())) + m_translucentBackground = w->connection()->hasXRender() && QImage::toPixelFormat(w->imageFormat()).alphaSize() > 0; +} + +QXcbNativeBackingStore::~QXcbNativeBackingStore() +{} + +QPaintDevice *QXcbNativeBackingStore::paintDevice() +{ + return &m_pixmap; +} + +void QXcbNativeBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) +{ + if (m_pixmap.isNull()) + return; + + QSize pixmapSize = m_pixmap.size(); + + QRegion clipped = region; + clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window)); + clipped &= QRect(0, 0, pixmapSize.width(), pixmapSize.height()).translated(-offset); + + QRect br = clipped.boundingRect(); + if (br.isNull()) + return; + + QXcbWindow *platformWindow = static_cast(window->handle()); + if (!platformWindow) { + qWarning("QXcbBackingStore::flush: QWindow has no platform window (QTBUG-32681)"); + return; + } + + Window wid = platformWindow->xcb_window(); + Pixmap pid = qt_x11PixmapHandle(m_pixmap); + + QVector clipRects = qt_region_to_xrectangles(clipped); + +#if QT_CONFIG(xrender) + if (m_translucentBackground) + { + XWindowAttributes attrib; + XGetWindowAttributes(display(), wid, &attrib); + XRenderPictFormat *format = XRenderFindVisualFormat(display(), attrib.visual); + + Picture srcPic = qt_x11PictureHandle(m_pixmap); + Picture dstPic = XRenderCreatePicture(display(), wid, format, 0, 0); + + XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size()); + + XRenderComposite(display(), PictOpSrc, srcPic, None, dstPic, + br.x() + offset.x(), br.y() + offset.y(), + 0, 0, + br.x(), br.y(), + br.width(), br.height()); + + XRenderFreePicture(display(), dstPic); + } + else +#endif + { + GC gc = XCreateGC(display(), wid, 0, Q_NULLPTR); + + if (clipRects.size() != 1) + XSetClipRectangles(display(), gc, 0, 0, clipRects.data(), clipRects.size(), YXBanded); + + XCopyArea(display(), pid, wid, gc, br.x() + offset.x(), br.y() + offset.y(), br.width(), br.height(), br.x(), br.y()); + XFreeGC(display(), gc); + } + + + if (platformWindow->needsSync()) { + platformWindow->updateSyncRequestCounter(); + } else { + XFlush(display()); + } +} + +QImage QXcbNativeBackingStore::toImage() const +{ + return m_pixmap.toImage(); +} + +void QXcbNativeBackingStore::resize(const QSize &size, const QRegion &staticContents) +{ + if (size == m_pixmap.size()) + return; + + QPixmap newPixmap(size); + +#if QT_CONFIG(xrender) + if (m_translucentBackground && newPixmap.depth() != 32) + qt_x11Pixmap(newPixmap)->convertToARGB32(); +#endif + + if (!m_pixmap.isNull()) { + Pixmap from = qt_x11PixmapHandle(m_pixmap); + Pixmap to = qt_x11PixmapHandle(newPixmap); + QRect br = staticContents.boundingRect().intersected(QRect(QPoint(0, 0), size)); + + if (!br.isEmpty()) { + GC gc = XCreateGC(display(), to, 0, Q_NULLPTR); + XCopyArea(display(), from, to, gc, br.x(), br.y(), br.width(), br.height(), br.x(), br.y()); + XFreeGC(display(), gc); + } + } + + m_pixmap = newPixmap; +} + +bool QXcbNativeBackingStore::scroll(const QRegion &area, int dx, int dy) +{ + if (m_pixmap.isNull()) + return false; + + QRect rect = area.boundingRect(); + Pixmap pix = qt_x11PixmapHandle(m_pixmap); + + GC gc = XCreateGC(display(), pix, 0, Q_NULLPTR); + XCopyArea(display(), pix, pix, gc, + rect.x(), rect.y(), rect.width(), rect.height(), + rect.x()+dx, rect.y()+dy); + XFreeGC(display(), gc); + return true; +} + +void QXcbNativeBackingStore::beginPaint(const QRegion ®ion) +{ +#if QT_CONFIG(xrender) + if (m_translucentBackground) { + const QVector xrects = qt_region_to_xrectangles(region); + const XRenderColor color = { 0, 0, 0, 0 }; + XRenderFillRectangles(display(), PictOpSrc, + qt_x11PictureHandle(m_pixmap), &color, + xrects.constData(), xrects.size()); + } +#else + Q_UNUSED(region); +#endif +} + +Display *QXcbNativeBackingStore::display() const +{ + return static_cast(static_cast(window()->handle())->connection()->xlib_display()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h new file mode 100644 index 0000000000..5f4c24ec11 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBACKINGSTORE_X11_H +#define QBACKINGSTORE_X11_H + +#include + +typedef struct _XDisplay Display; + +QT_BEGIN_NAMESPACE + +class QXcbWindow; + +class QXcbNativeBackingStore : public QPlatformBackingStore +{ +public: + QXcbNativeBackingStore(QWindow *window); + ~QXcbNativeBackingStore(); + + QPaintDevice *paintDevice() override; + void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; + + QImage toImage() const override; + + void resize(const QSize &size, const QRegion &staticContents) override; + bool scroll(const QRegion &area, int dx, int dy) override; + + void beginPaint(const QRegion ®ion) override; + +private: + Display *display() const; + + QPixmap m_pixmap; + bool m_translucentBackground; +}; + +QT_END_NAMESPACE + +#endif // QBACKINGSTORE_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp new file mode 100644 index 0000000000..8554c5445d --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp @@ -0,0 +1,644 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include + +#include "qcolormap_x11_p.h" +#include "qxcbnativepainting.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +class QXcbColormapPrivate +{ +public: + QXcbColormapPrivate() + : ref(1), mode(QXcbColormap::Direct), depth(0), + colormap(0), defaultColormap(true), + visual(0), defaultVisual(true), + r_max(0), g_max(0), b_max(0), + r_shift(0), g_shift(0), b_shift(0) + {} + + QAtomicInt ref; + + QXcbColormap::Mode mode; + int depth; + + Colormap colormap; + bool defaultColormap; + + Visual *visual; + bool defaultVisual; + + int r_max; + int g_max; + int b_max; + + uint r_shift; + uint g_shift; + uint b_shift; + + QVector colors; + QVector pixels; +}; + +static uint right_align(uint v) +{ + while (!(v & 0x1)) + v >>= 1; + return v; +} + +static int cube_root(int v) +{ + if (v == 1) + return 1; + // brute force algorithm + int i = 1; + for (;;) { + const int b = i * i * i; + if (b <= v) { + ++i; + } else { + --i; + break; + } + } + return i; +} + +static Visual *find_visual(Display *display, + int screen, + int visual_class, + int visual_id, + int *depth, + bool *defaultVisual) +{ + XVisualInfo *vi, rvi; + int count; + + uint mask = VisualScreenMask; + rvi.screen = screen; + + if (visual_class != -1) { + rvi.c_class = visual_class; + mask |= VisualClassMask; + } + if (visual_id != -1) { + rvi.visualid = visual_id; + mask |= VisualIDMask; + } + + Visual *visual = DefaultVisual(display, screen); + *defaultVisual = true; + *depth = DefaultDepth(display, screen); + + vi = XGetVisualInfo(display, mask, &rvi, &count); + if (vi) { + int best = 0; + for (int x = 0; x < count; ++x) { + if (vi[x].depth > vi[best].depth) + best = x; + } + if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) { + visual = vi[best].visual; + *defaultVisual = (visual == DefaultVisual(display, screen)); + *depth = vi[best].depth; + } + } + if (vi) + XFree((char *)vi); + return visual; +} + +static void query_colormap(QXcbColormapPrivate *d, int screen) +{ + Display *display = X11->display; + + // query existing colormap + int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth)); + XColor queried[256]; + memset(queried, 0, sizeof(queried)); + for (int x = 0; x < q_colors; ++x) + queried[x].pixel = x; + XQueryColors(display, d->colormap, queried, q_colors); + + d->colors.resize(q_colors); + for (int x = 0; x < q_colors; ++x) { + if (queried[x].red == 0 + && queried[x].green == 0 + && queried[x].blue == 0 + && queried[x].pixel != BlackPixel(display, screen)) { + // unallocated color cell, skip it + continue; + } + + d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX), + queried[x].green / float(USHRT_MAX), + queried[x].blue / float(USHRT_MAX)); + } + + // for missing colors, find the closest color in the existing colormap + Q_ASSERT(d->pixels.size()); + for (int x = 0; x < d->pixels.size(); ++x) { + if (d->pixels.at(x) != -1) + continue; + + QRgb rgb; + if (d->mode == QXcbColormap::Indexed) { + const int r = (x / (d->g_max * d->b_max)) % d->r_max; + const int g = (x / d->b_max) % d->g_max; + const int b = x % d->b_max; + rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + } else { + rgb = qRgb(x, x, x); + } + + // find closest color + int mindist = INT_MAX, best = -1; + for (int y = 0; y < q_colors; ++y) { + int r = qRed(rgb) - (queried[y].red >> 8); + int g = qGreen(rgb) - (queried[y].green >> 8); + int b = qBlue(rgb) - (queried[y].blue >> 8); + int dist = (r * r) + (g * g) + (b * b); + if (dist < mindist) { + mindist = dist; + best = y; + } + } + + Q_ASSERT(best >= 0 && best < q_colors); + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = queried[best].red; + xcolor.green = queried[best].green; + xcolor.blue = queried[best].blue; + xcolor.pixel = queried[best].pixel; + + if (XAllocColor(display, d->colormap, &xcolor)) { + d->pixels[x] = xcolor.pixel; + } else { + // some weird stuff is going on... + d->pixels[x] = (qGray(rgb) < 127 + ? BlackPixel(display, screen) + : WhitePixel(display, screen)); + } + } else { + d->pixels[x] = best; + } + } +} + +static void init_gray(QXcbColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max); + + for (int g = 0; g < d->g_max; ++g) { + const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1); + const QRgb rgb = qRgb(gray, gray, gray); + + d->pixels[g] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(X11->display, d->colormap, &xcolor)) + d->pixels[g] = xcolor.pixel; + } + } + + query_colormap(d, screen); +} + +static void init_indexed(QXcbColormapPrivate *d, int screen) +{ + d->pixels.resize(d->r_max * d->g_max * d->b_max); + + // create color cube + for (int x = 0, r = 0; r < d->r_max; ++r) { + for (int g = 0; g < d->g_max; ++g) { + for (int b = 0; b < d->b_max; ++b, ++x) { + const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), + (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), + (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); + + d->pixels[x] = -1; + + if (d->visual->c_class & 1) { + XColor xcolor; + xcolor.red = qRed(rgb) * 0x101; + xcolor.green = qGreen(rgb) * 0x101; + xcolor.blue = qBlue(rgb) * 0x101; + xcolor.pixel = 0ul; + + if (XAllocColor(X11->display, d->colormap, &xcolor)) + d->pixels[x] = xcolor.pixel; + } + } + } + } + + query_colormap(d, screen); +} + +static void init_direct(QXcbColormapPrivate *d, bool ownColormap) +{ + if (d->visual->c_class != DirectColor || !ownColormap) + return; + + // preallocate 768 on the stack, so that we don't have to malloc + // for the common case (<= 24 bpp) + QVarLengthArray colorTable(d->r_max + d->g_max + d->b_max); + int i = 0; + + for (int r = 0; r < d->r_max; ++r) { + colorTable[i].red = r << 8 | r; + colorTable[i].pixel = r << d->r_shift; + colorTable[i].flags = DoRed; + ++i; + } + + for (int g = 0; g < d->g_max; ++g) { + colorTable[i].green = g << 8 | g; + colorTable[i].pixel = g << d->g_shift; + colorTable[i].flags = DoGreen; + ++i; + } + + for (int b = 0; b < d->b_max; ++b) { + colorTable[i].blue = (b << 8 | b); + colorTable[i].pixel = b << d->b_shift; + colorTable[i].flags = DoBlue; + ++i; + } + + XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count()); +} + +static QXcbColormap **cmaps = 0; + +void QXcbColormap::initialize() +{ + Display *display = X11->display; + const int screens = ScreenCount(display); + + cmaps = new QXcbColormap*[screens]; + + for (int i = 0; i < screens; ++i) { + cmaps[i] = new QXcbColormap; + QXcbColormapPrivate * const d = cmaps[i]->d; + + bool use_stdcmap = false; + int color_count = X11->color_count; + + // defaults + d->depth = DefaultDepth(display, i); + d->colormap = DefaultColormap(display, i); + d->defaultColormap = true; + d->visual = DefaultVisual(display, i); + d->defaultVisual = true; + + Visual *argbVisual = 0; + + if (X11->visual && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->visual = find_visual(display, i, X11->visual->c_class, + XVisualIDFromVisual(X11->visual), + &d->depth, &d->defaultVisual); + } else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6) + || (X11->visual_id != -1)) { + // look for a specific visual or type of visual + d->visual = find_visual(display, i, X11->visual_class, X11->visual_id, + &d->depth, &d->defaultVisual); + } else if (!X11->custom_cmap) { + XStandardColormap *stdcmap = 0; + int ncmaps = 0; + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + int nvi; + XVisualInfo templ; + templ.screen = i; + templ.depth = 32; + templ.c_class = TrueColor; + XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask | + VisualDepthMask | + VisualClassMask, &templ, &nvi); + for (int idx = 0; idx < nvi; ++idx) { + XRenderPictFormat *format = XRenderFindVisualFormat(X11->display, + xvi[idx].visual); + if (format->type == PictTypeDirect && format->direct.alphaMask) { + argbVisual = xvi[idx].visual; + break; + } + } + XFree(xvi); + } +#endif + if (XGetRGBColormaps(display, RootWindow(display, i), + &stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) { + if (stdcmap) { + for (int c = 0; c < ncmaps; ++c) { + if (!stdcmap[c].red_max || + !stdcmap[c].green_max || + !stdcmap[c].blue_max || + !stdcmap[c].red_mult || + !stdcmap[c].green_mult || + !stdcmap[c].blue_mult) + continue; // invalid stdcmap + + XVisualInfo proto; + proto.visualid = stdcmap[c].visualid; + proto.screen = i; + + int nvisuals = 0; + XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask, + &proto, &nvisuals); + if (vi) { + if (nvisuals > 0) { + use_stdcmap = true; + + d->mode = ((vi[0].visual->c_class < StaticColor) + ? Gray + : ((vi[0].visual->c_class < TrueColor) + ? Indexed + : Direct)); + + d->depth = vi[0].depth; + d->colormap = stdcmap[c].colormap; + d->defaultColormap = true; + d->visual = vi[0].visual; + d->defaultVisual = (d->visual == DefaultVisual(display, i)); + + d->r_max = stdcmap[c].red_max + 1; + d->g_max = stdcmap[c].green_max + 1; + d->b_max = stdcmap[c].blue_max + 1; + + if (d->mode == Direct) { + // calculate offsets + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + } else { + d->r_shift = 0; + d->g_shift = 0; + d->b_shift = 0; + } + } + XFree(vi); + } + break; + } + XFree(stdcmap); + } + } + } + if (!use_stdcmap) { + switch (d->visual->c_class) { + case StaticGray: + d->mode = Gray; + + d->r_max = d->g_max = d->b_max = d->visual->map_entries; + break; + + case XGrayScale: + d->mode = Gray; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = color_count; + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 4096; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 512; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = 12; + else + d->r_max = d->g_max = d->b_max = 4; + break; + + case StaticColor: + d->mode = Indexed; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + break; + + case PseudoColor: + d->mode = Indexed; + + // follow precedent set in libXmu... + if (color_count != 0) + d->r_max = d->g_max = d->b_max = cube_root(color_count); + else if (d->visual->map_entries > 65000) + d->r_max = d->g_max = d->b_max = 27; + else if (d->visual->map_entries > 4000) + d->r_max = d->g_max = d->b_max = 12; + else if (d->visual->map_entries > 250) + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125); + else + d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries); + break; + + case TrueColor: + case DirectColor: + d->mode = Direct; + + d->r_max = right_align(d->visual->red_mask) + 1; + d->g_max = right_align(d->visual->green_mask) + 1; + d->b_max = right_align(d->visual->blue_mask) + 1; + + d->r_shift = lowest_bit(d->visual->red_mask); + d->g_shift = lowest_bit(d->visual->green_mask); + d->b_shift = lowest_bit(d->visual->blue_mask); + break; + } + } + + bool ownColormap = false; + if (X11->colormap && i == DefaultScreen(display)) { + // only use the outside colormap on the default screen + d->colormap = X11->colormap; + d->defaultColormap = (d->colormap == DefaultColormap(display, i)); + } else if ((!use_stdcmap + && (((d->visual->c_class & 1) && X11->custom_cmap) + || d->visual != DefaultVisual(display, i))) + || d->visual->c_class == DirectColor) { + // allocate custom colormap (we always do this when using DirectColor visuals) + d->colormap = + XCreateColormap(display, RootWindow(display, i), d->visual, + d->visual->c_class == DirectColor ? AllocAll : AllocNone); + d->defaultColormap = false; + ownColormap = true; + } + + switch (d->mode) { + case Gray: + init_gray(d, i); + break; + case Indexed: + init_indexed(d, i); + break; + case Direct: + init_direct(d, ownColormap); + break; + } + + QX11InfoData *screen = X11->screens + i; + screen->depth = d->depth; + screen->visual = d->visual; + screen->defaultVisual = d->defaultVisual; + screen->colormap = d->colormap; + screen->defaultColormap = d->defaultColormap; + screen->cells = screen->visual->map_entries; + + if (argbVisual) { + X11->argbVisuals[i] = argbVisual; + X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone); + } + + // ### + // We assume that 8bpp == pseudocolor, but this is not + // always the case (according to the X server), so we need + // to make sure that our internal data is setup in a way + // that is compatible with our assumptions + if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8) + screen->cells = 256; + } +} + +void QXcbColormap::cleanup() +{ + Display *display = X11->display; + const int screens = ScreenCount(display); + + for (int i = 0; i < screens; ++i) + delete cmaps[i]; + + delete [] cmaps; + cmaps = 0; +} + + +QXcbColormap QXcbColormap::instance(int screen) +{ + if (screen == -1) + screen = QXcbX11Info::appScreen(); + return *cmaps[screen]; +} + +/*! \internal + Constructs a new colormap. +*/ +QXcbColormap::QXcbColormap() + : d(new QXcbColormapPrivate) +{} + +QXcbColormap::QXcbColormap(const QXcbColormap &colormap) + :d (colormap.d) +{ d->ref.ref(); } + +QXcbColormap::~QXcbColormap() +{ + if (!d->ref.deref()) { + if (!d->defaultColormap) + XFreeColormap(X11->display, d->colormap); + delete d; + } +} + +QXcbColormap::Mode QXcbColormap::mode() const +{ return d->mode; } + +int QXcbColormap::depth() const +{ return d->depth; } + +int QXcbColormap::size() const +{ + return (d->mode == Gray + ? d->r_max + : (d->mode == Indexed + ? d->r_max * d->g_max * d->b_max + : -1)); +} + +uint QXcbColormap::pixel(const QColor &color) const +{ + const QRgba64 rgba64 = color.rgba64(); + // XXX We emulate the raster engine here by getting the + // 8-bit values, but we could instead use the 16-bit + // values for slightly better color accuracy + const uint r = (rgba64.red8() * d->r_max) >> 8; + const uint g = (rgba64.green8() * d->g_max) >> 8; + const uint b = (rgba64.blue8() * d->b_max) >> 8; + if (d->mode != Direct) { + if (d->mode == Gray) + return d->pixels.at((r * 30 + g * 59 + b * 11) / 100); + return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b); + } + return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift); +} + +const QColor QXcbColormap::colorAt(uint pixel) const +{ + if (d->mode != Direct) { + Q_ASSERT(pixel <= (uint)d->colors.size()); + return d->colors.at(pixel); + } + + const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max; + const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max; + const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max; + return QColor(r, g, b); +} + +const QVector QXcbColormap::colormap() const +{ return d->colors; } + +QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap) +{ + qAtomicAssign(d, colormap.d); + return *this; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h new file mode 100644 index 0000000000..530e3113e4 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOLORMAP_X11_H +#define QCOLORMAP_X11_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QXcbColormapPrivate; +class QXcbColormap +{ +public: + enum Mode { Direct, Indexed, Gray }; + + static void initialize(); + static void cleanup(); + + static QXcbColormap instance(int screen = -1); + + QXcbColormap(const QXcbColormap &colormap); + ~QXcbColormap(); + + QXcbColormap &operator=(const QXcbColormap &colormap); + + Mode mode() const; + + int depth() const; + int size() const; + + uint pixel(const QColor &color) const; + const QColor colorAt(uint pixel) const; + + const QVector colormap() const; + +private: + QXcbColormap(); + QXcbColormapPrivate *d; +}; + +QT_END_NAMESPACE + +#endif // QCOLORMAP_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp new file mode 100644 index 0000000000..3364b07c08 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp @@ -0,0 +1,2837 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#if QT_CONFIG(fontconfig) +#include +#endif + +#include "qpaintengine_x11_p.h" +#include "qtessellator_p.h" +#include "qpixmap_x11_p.h" +#include "qcolormap_x11_p.h" +#include "qt_x11_p.h" +#include "qxcbexport.h" +#include "qxcbnativepainting.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(xrender) + +class QXRenderTessellator : public QTessellator +{ +public: + QXRenderTessellator() : traps(0), allocated(0), size(0) {} + ~QXRenderTessellator() { free(traps); } + XTrapezoid *traps; + int allocated; + int size; + void addTrap(const Trapezoid &trap); + QRect tessellate(const QPointF *points, int nPoints, bool winding) { + size = 0; + setWinding(winding); + return QTessellator::tessellate(points, nPoints).toRect(); + } + void done() { + if (allocated > 64) { + free(traps); + traps = 0; + allocated = 0; + } + } +}; + +void QXRenderTessellator::addTrap(const Trapezoid &trap) +{ + if (size == allocated) { + allocated = qMax(2*allocated, 64); + traps = q_check_ptr((XTrapezoid *)realloc(traps, allocated * sizeof(XTrapezoid))); + } + traps[size].top = Q27Dot5ToXFixed(trap.top); + traps[size].bottom = Q27Dot5ToXFixed(trap.bottom); + traps[size].left.p1.x = Q27Dot5ToXFixed(trap.topLeft->x); + traps[size].left.p1.y = Q27Dot5ToXFixed(trap.topLeft->y); + traps[size].left.p2.x = Q27Dot5ToXFixed(trap.bottomLeft->x); + traps[size].left.p2.y = Q27Dot5ToXFixed(trap.bottomLeft->y); + traps[size].right.p1.x = Q27Dot5ToXFixed(trap.topRight->x); + traps[size].right.p1.y = Q27Dot5ToXFixed(trap.topRight->y); + traps[size].right.p2.x = Q27Dot5ToXFixed(trap.bottomRight->x); + traps[size].right.p2.y = Q27Dot5ToXFixed(trap.bottomRight->y); + ++size; +} + +#endif // QT_CONFIG(xrender) + +class QX11PaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QX11PaintEngine) +public: + QX11PaintEnginePrivate() + { + opacity = 1.0; + scrn = -1; + hd = 0; + picture = 0; + gc = gc_brush = 0; + dpy = 0; + xinfo = 0; + txop = QTransform::TxNone; + has_clipping = false; + render_hints = 0; + xform_scale = 1; +#if QT_CONFIG(xrender) + tessellator = 0; +#endif + } + enum GCMode { + PenGC, + BrushGC + }; + + void init(); + void fillPolygon_translated(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPolygon_dev(const QPointF *points, int pointCount, GCMode gcMode, + QPaintEngine::PolygonDrawMode mode); + void fillPath(const QPainterPath &path, GCMode gcmode, bool transform); + void strokePolygon_dev(const QPointF *points, int pointCount, bool close); + void strokePolygon_translated(const QPointF *points, int pointCount, bool close); + void setupAdaptedOrigin(const QPoint &p); + void resetAdaptedOrigin(); + void decidePathFallback() { + use_path_fallback = has_alpha_brush + || has_alpha_pen + || has_custom_pen + || has_complex_xform + || (render_hints & QPainter::Antialiasing); + } + void decideCoordAdjust() { + adjust_coords = !(render_hints & QPainter::Antialiasing) + && (render_hints & QPainter::Qt4CompatiblePainting) + && (has_alpha_pen + || (has_alpha_brush && has_pen && !has_alpha_pen) + || (cpen.style() > Qt::SolidLine)); + } + void clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly); + void systemStateChanged() override; + inline bool isCosmeticPen() const { + if ((render_hints & QPainter::Qt4CompatiblePainting) && cpen == QPen()) + return true; + return cpen.isCosmetic(); + } + + Display *dpy; + int scrn; + int pdev_depth; + unsigned long hd; + QPixmap brush_pm; +#if QT_CONFIG(xrender) + unsigned long picture; + unsigned long current_brush; + QPixmap bitmap_texture; + int composition_mode; +#else + unsigned long picture; +#endif + GC gc; + GC gc_brush; + + QPen cpen; + QBrush cbrush; + QRegion crgn; + QTransform matrix; + qreal opacity; + + uint has_complex_xform : 1; + uint has_scaling_xform : 1; + uint has_non_scaling_xform : 1; + uint has_custom_pen : 1; + uint use_path_fallback : 1; + uint adjust_coords : 1; + uint has_clipping : 1; + uint adapted_brush_origin : 1; + uint adapted_pen_origin : 1; + uint has_pen : 1; + uint has_brush : 1; + uint has_texture : 1; + uint has_alpha_texture : 1; + uint has_pattern : 1; + uint has_alpha_pen : 1; + uint has_alpha_brush : 1; + uint render_hints; + + const QXcbX11Info *xinfo; + QPointF bg_origin; + QTransform::TransformationType txop; + qreal xform_scale; + + struct qt_float_point + { + qreal x, y; + }; + QPolygonClipper polygonClipper; + + int xlibMaxLinePoints; +#if QT_CONFIG(xrender) + QXRenderTessellator *tessellator; +#endif +}; + +#if QT_CONFIG(xrender) +class QXRenderGlyphCache : public QFontEngineGlyphCache +{ +public: + QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix); + ~QXRenderGlyphCache(); + + bool addGlyphs(const QTextItemInt &ti, QVarLengthArray glyphs, QVarLengthArray positions); + bool draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti); + + inline GlyphSet glyphSet(); + inline int glyphBufferSize(const QFontEngineFT::Glyph &glyph) const; + + inline QImage::Format imageFormat() const; + inline const XRenderPictFormat *renderPictFormat() const; + + static inline QFontEngine::GlyphFormat glyphFormatForDepth(QFontEngine *fontEngine, int depth); + static inline Glyph glyphId(glyph_t glyph, QFixed subPixelPosition); + + static inline bool isValidCoordinate(const QFixedPoint &fp); + +private: + QXcbX11Info xinfo; + GlyphSet gset; + QSet cachedGlyphs; +}; +#endif // QT_CONFIG(xrender) + +extern QPixmap qt_pixmapForBrush(int brushStyle, bool invert); //in qbrush.cpp +extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); + +extern "C" { +Q_XCB_EXPORT Drawable qt_x11Handle(const QPaintDevice *pd) +{ + if (!pd) + return 0; + +// if (pd->devType() == QInternal::Widget) +// return static_cast(pd)->handle(); + + if (pd->devType() == QInternal::Pixmap) + return qt_x11PixmapHandle(*static_cast(pd)); + + return 0; +} +} + +static const QXcbX11Info *qt_x11Info(const QPaintDevice *pd) +{ + if (!pd) + return 0; + +// if (pd->devType() == QInternal::Widget) +// return &static_cast(pd)->x11Info(); + + if (pd->devType() == QInternal::Pixmap) + return &qt_x11Info(*static_cast(pd)); + + return 0; +} + +// use the same rounding as in qrasterizer.cpp (6 bit fixed point) +static const qreal aliasedCoordinateDelta = 0.5 - 0.015625; + +#undef X11 // defined in qt_x11_p.h +extern "C" { +/*! + Returns the X11 specific pen GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +GC Q_XCB_EXPORT qt_x11_get_pen_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast(p->paintEngine())->d_func()->gc; + } + return 0; +} + +/*! + Returns the X11 specific brush GC for the painter \a p. Note that + QPainter::begin() must be called before this function returns a + valid GC. +*/ +GC Q_XCB_EXPORT qt_x11_get_brush_gc(QPainter *p) +{ + if (p && p->paintEngine() + && p->paintEngine()->isActive() + && p->paintEngine()->type() == QPaintEngine::X11) { + return static_cast(p->paintEngine())->d_func()->gc_brush; + } + return 0; +} +} +#define X11 qt_x11Data + +// internal helper. Converts an integer value to an unique string token +template + struct HexString +{ + inline HexString(const T t) + : val(t) + {} + + inline void write(QChar *&dest) const + { + const ushort hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + const char *c = reinterpret_cast(&val); + for (uint i = 0; i < sizeof(T); ++i) { + *dest++ = hexChars[*c & 0xf]; + *dest++ = hexChars[(*c & 0xf0) >> 4]; + ++c; + } + } + const T val; +}; + +// specialization to enable fast concatenating of our string tokens to a string +template + struct QConcatenable > +{ + typedef HexString type; + enum { ExactSize = true }; + static int size(const HexString &) { return sizeof(T) * 2; } + static inline void appendTo(const HexString &str, QChar *&out) { str.write(out); } + typedef QString ConvertTo; +}; + +#if QT_CONFIG(xrender) +static const int compositionModeToRenderOp[QPainter::CompositionMode_Xor + 1] = { + PictOpOver, //CompositionMode_SourceOver, + PictOpOverReverse, //CompositionMode_DestinationOver, + PictOpClear, //CompositionMode_Clear, + PictOpSrc, //CompositionMode_Source, + PictOpDst, //CompositionMode_Destination, + PictOpIn, //CompositionMode_SourceIn, + PictOpInReverse, //CompositionMode_DestinationIn, + PictOpOut, //CompositionMode_SourceOut, + PictOpOutReverse, //CompositionMode_DestinationOut, + PictOpAtop, //CompositionMode_SourceAtop, + PictOpAtopReverse, //CompositionMode_DestinationAtop, + PictOpXor //CompositionMode_Xor +}; + +static inline int qpainterOpToXrender(QPainter::CompositionMode mode) +{ + Q_ASSERT(mode <= QPainter::CompositionMode_Xor); + return compositionModeToRenderOp[mode]; +} + +static inline bool complexPictOp(int op) +{ + return op != PictOpOver && op != PictOpSrc; +} +#endif + +static inline void x11SetClipRegion(Display *dpy, GC gc, GC gc2, +#if QT_CONFIG(xrender) + Picture picture, +#else + Qt::HANDLE picture, +#endif + const QRegion &r) +{ +// int num; +// XRectangle *rects = (XRectangle *)qt_getClipRects(r, num); + QVector rects = qt_region_to_xrectangles(r); + int num = rects.size(); + + if (gc) + XSetClipRectangles( dpy, gc, 0, 0, rects.data(), num, Unsorted ); + if (gc2) + XSetClipRectangles( dpy, gc2, 0, 0, rects.data(), num, Unsorted ); + +#if QT_CONFIG(xrender) + if (picture) + XRenderSetPictureClipRectangles(dpy, picture, 0, 0, rects.data(), num); +#else + Q_UNUSED(picture); +#endif // QT_CONFIG(xrender) +} + + +static inline void x11ClearClipRegion(Display *dpy, GC gc, GC gc2, +#if QT_CONFIG(xrender) + Picture picture +#else + Qt::HANDLE picture +#endif + ) +{ + if (gc) + XSetClipMask(dpy, gc, XNone); + if (gc2) + XSetClipMask(dpy, gc2, XNone); + +#if QT_CONFIG(xrender) + if (picture) { + XRenderPictureAttributes attrs; + attrs.clip_mask = XNone; + XRenderChangePicture (dpy, picture, CPClipMask, &attrs); + } +#else + Q_UNUSED(picture); +#endif // QT_CONFIG(xrender) +} + + +#define DITHER_SIZE 16 +static const uchar base_dither_matrix[DITHER_SIZE][DITHER_SIZE] = { + { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, + { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, + { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, + { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, + { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, + { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, + { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, + { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, + { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, + { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, + { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, + { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, + { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, + { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, + { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, + { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } +}; + +static QPixmap qt_patternForAlpha(uchar alpha, int screen) +{ + QPixmap pm; + QString key = QLatin1Literal("$qt-alpha-brush$") + % HexString(alpha) + % HexString(screen); + + if (!QPixmapCache::find(key, pm)) { + // #### why not use a mono image here???? + QImage pattern(DITHER_SIZE, DITHER_SIZE, QImage::Format_ARGB32); + pattern.fill(0xffffffff); + for (int y = 0; y < DITHER_SIZE; ++y) { + for (int x = 0; x < DITHER_SIZE; ++x) { + if (base_dither_matrix[x][y] <= alpha) + pattern.setPixel(x, y, 0x00000000); + } + } + pm = QBitmap::fromImage(pattern); + qt_x11SetScreen(pm, screen); + //pm.x11SetScreen(screen); + QPixmapCache::insert(key, pm); + } + return pm; +} + + +#if QT_CONFIG(xrender) +static Picture getPatternFill(int screen, const QBrush &b) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = X11->preMultiply(b.color()); + XRenderColor bg_color; + + bg_color = X11->preMultiply(QColor(0, 0, 0, 0)); + + for (int i = 0; i < X11->pattern_fill_count; ++i) { + if (X11->pattern_fills[i].screen == screen + && X11->pattern_fills[i].opaque == false + && X11->pattern_fills[i].style == b.style() + && X11->pattern_fills[i].color.alpha == color.alpha + && X11->pattern_fills[i].color.red == color.red + && X11->pattern_fills[i].color.green == color.green + && X11->pattern_fills[i].color.blue == color.blue + && X11->pattern_fills[i].bg_color.alpha == bg_color.alpha + && X11->pattern_fills[i].bg_color.red == bg_color.red + && X11->pattern_fills[i].bg_color.green == bg_color.green + && X11->pattern_fills[i].bg_color.blue == bg_color.blue) + return X11->pattern_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->pattern_fills[i].screen != screen && X11->pattern_fills[i].picture) { + XRenderFreePicture (QXcbX11Info::display(), X11->pattern_fills[i].picture); + X11->pattern_fills[i].picture = 0; + } + + if (!X11->pattern_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (QXcbX11Info::display(), RootWindow (QXcbX11Info::display(), screen), 8, 8, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->pattern_fills[i].picture = XRenderCreatePicture (QXcbX11Info::display(), pixmap, + XRenderFindStandardFormat(QXcbX11Info::display(), PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (QXcbX11Info::display(), pixmap); + } + + X11->pattern_fills[i].screen = screen; + X11->pattern_fills[i].color = color; + X11->pattern_fills[i].bg_color = bg_color; + X11->pattern_fills[i].opaque = false; + X11->pattern_fills[i].style = b.style(); + + XRenderFillRectangle(QXcbX11Info::display(), PictOpSrc, X11->pattern_fills[i].picture, &bg_color, 0, 0, 8, 8); + + QPixmap pattern(qt_pixmapForBrush(b.style(), true)); + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(QXcbX11Info::display(), qt_x11PictureHandle(pattern), CPRepeat, &attrs); + + Picture fill_fg = X11->getSolidFill(screen, b.color()); + XRenderComposite(QXcbX11Info::display(), PictOpOver, fill_fg, qt_x11PictureHandle(pattern), + X11->pattern_fills[i].picture, + 0, 0, 0, 0, 0, 0, 8, 8); + + return X11->pattern_fills[i].picture; +} + +static void qt_render_bitmap(Display *dpy, int scrn, Picture src, Picture dst, + int sx, int sy, int x, int y, int sw, int sh, + const QPen &pen) +{ + Picture fill_fg = X11->getSolidFill(scrn, pen.color()); + XRenderComposite(dpy, PictOpOver, + fill_fg, src, dst, sx, sy, sx, sy, x, y, sw, sh); +} +#endif + +void QX11PaintEnginePrivate::init() +{ + dpy = 0; + scrn = 0; + hd = 0; + picture = 0; + xinfo = 0; +#if QT_CONFIG(xrender) + current_brush = 0; + composition_mode = PictOpOver; + tessellator = new QXRenderTessellator; +#endif +} + +void QX11PaintEnginePrivate::setupAdaptedOrigin(const QPoint &p) +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, p.x(), p.y()); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, p.x(), p.y()); +} + +void QX11PaintEnginePrivate::resetAdaptedOrigin() +{ + if (adapted_pen_origin) + XSetTSOrigin(dpy, gc, 0, 0); + if (adapted_brush_origin) + XSetTSOrigin(dpy, gc_brush, 0, 0); +} + +void QX11PaintEnginePrivate::clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly) +{ + int clipped_count = 0; + qt_float_point *clipped_points = 0; + polygonClipper.clipPolygon((qt_float_point *) poly.data(), poly.size(), + &clipped_points, &clipped_count); + clipped_poly->resize(clipped_count); + for (int i=0; istate ? q->state->painter() : nullptr; + if (painter && painter->hasClipping()) { + if (q->testDirty(QPaintEngine::DirtyTransform)) + q->updateMatrix(q->state->transform()); + QPolygonF clip_poly_dev(matrix.map(painter->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + q->updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + q->updateClipRegion_dev(QRegion(), Qt::NoClip); + } +} + +static QPaintEngine::PaintEngineFeatures qt_decide_features() +{ + QPaintEngine::PaintEngineFeatures features = + QPaintEngine::PrimitiveTransform + | QPaintEngine::PatternBrush + | QPaintEngine::AlphaBlend + | QPaintEngine::PainterPaths + | QPaintEngine::RasterOpModes; + + if (X11->use_xrender) { + features |= QPaintEngine::Antialiasing; + features |= QPaintEngine::PorterDuff; + features |= QPaintEngine::MaskedBrush; +#if 0 + if (X11->xrender_version > 10) { + features |= QPaintEngine::LinearGradientFill; + // ### + } +#endif + } + + return features; +} + +/* + * QX11PaintEngine members + */ + +QX11PaintEngine::QX11PaintEngine() + : QPaintEngine(*(new QX11PaintEnginePrivate), qt_decide_features()) +{ + Q_D(QX11PaintEngine); + d->init(); +} + +QX11PaintEngine::QX11PaintEngine(QX11PaintEnginePrivate &dptr) + : QPaintEngine(dptr, qt_decide_features()) +{ + Q_D(QX11PaintEngine); + d->init(); +} + +QX11PaintEngine::~QX11PaintEngine() +{ +#if QT_CONFIG(xrender) + Q_D(QX11PaintEngine); + delete d->tessellator; +#endif +} + +bool QX11PaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QX11PaintEngine); + d->xinfo = qt_x11Info(pdev); +#if QT_CONFIG(xrender) + if (pdev->devType() == QInternal::Pixmap) { + const QPixmap *pm = static_cast(pdev); + QX11PlatformPixmap *data = static_cast(pm->handle()); + if (X11->use_xrender && data->depth() != 32 && data->x11_mask) + data->convertToARGB32(); + d->picture = qt_x11PictureHandle(*static_cast(pdev)); + } +#else + d->picture = 0; +#endif + d->hd = qt_x11Handle(pdev); + + Q_ASSERT(d->xinfo != 0); + d->dpy = d->xinfo->display(); // get display variable + d->scrn = d->xinfo->screen(); // get screen variable + + d->crgn = QRegion(); + d->gc = XCreateGC(d->dpy, d->hd, 0, 0); + d->gc_brush = XCreateGC(d->dpy, d->hd, 0, 0); + d->has_alpha_brush = false; + d->has_alpha_pen = false; + d->has_clipping = false; + d->has_complex_xform = false; + d->has_scaling_xform = false; + d->has_non_scaling_xform = true; + d->xform_scale = 1; + d->has_custom_pen = false; + d->matrix = QTransform(); + d->pdev_depth = d->pdev->depth(); + d->render_hints = 0; + d->txop = QTransform::TxNone; + d->use_path_fallback = false; +#if QT_CONFIG(xrender) + d->composition_mode = PictOpOver; +#endif + d->xlibMaxLinePoints = 32762; // a safe number used to avoid, call to XMaxRequestSize(d->dpy) - 3; + d->opacity = 1; + + // Set up the polygon clipper. Note: This will only work in + // polyline mode as long as we have a buffer zone, since a + // polyline may be clipped into several non-connected polylines. + const int BUFFERZONE = 1000; + QRect devClipRect(-BUFFERZONE, -BUFFERZONE, + pdev->width() + 2*BUFFERZONE, pdev->height() + 2*BUFFERZONE); + d->polygonClipper.setBoundingRect(devClipRect); + + setSystemClip(systemClip()); + d->systemStateChanged(); + + qt_x11SetDefaultScreen(d->xinfo->screen()); + + updatePen(QPen(Qt::black)); + updateBrush(QBrush(Qt::white), QPoint()); + + setDirty(QPaintEngine::DirtyClipRegion); + setDirty(QPaintEngine::DirtyPen); + setDirty(QPaintEngine::DirtyBrush); + setDirty(QPaintEngine::DirtyBackground); + + setActive(true); + return true; +} + +bool QX11PaintEngine::end() +{ + Q_D(QX11PaintEngine); + +#if QT_CONFIG(xrender) + if (d->picture) { + // reset clipping/subwindow mode on our render picture + XRenderPictureAttributes attrs; + attrs.subwindow_mode = ClipByChildren; + attrs.clip_mask = XNone; + XRenderChangePicture(d->dpy, d->picture, CPClipMask|CPSubwindowMode, &attrs); + } +#endif + + if (d->gc_brush && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc_brush); + d->gc_brush = 0; + } + + if (d->gc && d->pdev->painters < 2) { + XFreeGC(d->dpy, d->gc); + d->gc = 0; + } + + // Restore system clip for alien widgets painting outside the paint event. +// if (d->pdev->devType() == QInternal::Widget && !static_cast(d->pdev)->internalWinId()) + d->currentClipDevice = nullptr; + setSystemClip(QRegion()); + + setActive(false); + return true; +} + +static bool clipLine(QLineF *line, const QRect &rect) +{ + qreal x1 = line->x1(); + qreal x2 = line->x2(); + qreal y1 = line->y1(); + qreal y2 = line->y2(); + + qreal left = rect.x(); + qreal right = rect.x() + rect.width() - 1; + qreal top = rect.y(); + qreal bottom = rect.y() + rect.height() - 1; + + enum { Left, Right, Top, Bottom }; + // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html + int p1 = ((x1 < left) << Left) + | ((x1 > right) << Right) + | ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + int p2 = ((x2 < left) << Left) + | ((x2 > right) << Right) + | ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + + if (p1 & p2) + // completely outside + return false; + + if (p1 | p2) { + qreal dx = x2 - x1; + qreal dy = y2 - y1; + + // clip x coordinates + if (x1 < left) { + y1 += dy/dx * (left - x1); + x1 = left; + } else if (x1 > right) { + y1 -= dy/dx * (x1 - right); + x1 = right; + } + if (x2 < left) { + y2 += dy/dx * (left - x2); + x2 = left; + } else if (x2 > right) { + y2 -= dy/dx * (x2 - right); + x2 = right; + } + p1 = ((y1 < top) << Top) + | ((y1 > bottom) << Bottom); + p2 = ((y2 < top) << Top) + | ((y2 > bottom) << Bottom); + if (p1 & p2) + return false; + // clip y coordinates + if (y1 < top) { + x1 += dx/dy * (top - y1); + y1 = top; + } else if (y1 > bottom) { + x1 -= dx/dy * (y1 - bottom); + y1 = bottom; + } + if (y2 < top) { + x2 += dx/dy * (top - y2); + y2 = top; + } else if (y2 > bottom) { + x2 -= dx/dy * (y2 - bottom); + y2 = bottom; + } + *line = QLineF(QPointF(x1, y1), QPointF(x2, y2)); + } + return true; +} + +void QX11PaintEngine::drawLines(const QLine *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef; + if (d->txop == QTransform::TxNone) { + linef = lines[i]; + } else { + linef = d->matrix.map(QLineF(lines[i])); + } + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +void QX11PaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_ASSERT(lines); + Q_ASSERT(lineCount); + Q_D(QX11PaintEngine); + + if (d->has_alpha_brush + || d->has_alpha_pen + || d->has_custom_pen + || (d->cpen.widthF() > 0 && d->has_complex_xform + && !d->has_non_scaling_xform) + || (d->render_hints & QPainter::Antialiasing)) { + for (int i = 0; i < lineCount; ++i) { + QPainterPath path(lines[i].p1()); + path.lineTo(lines[i].p2()); + drawPath(path); + } + return; + } + + if (d->has_pen) { + for (int i = 0; i < lineCount; ++i) { + QLineF linef = d->matrix.map(lines[i]); + if (clipLine(&linef, d->polygonClipper.boundingRect())) { + int x1 = qRound(linef.x1() + aliasedCoordinateDelta); + int y1 = qRound(linef.y1() + aliasedCoordinateDelta); + int x2 = qRound(linef.x2() + aliasedCoordinateDelta); + int y2 = qRound(linef.y2() + aliasedCoordinateDelta); + + XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); + } + } + } +} + +static inline QLine clipStraightLine(const QRect &clip, const QLine &l) +{ + if (l.p1().x() == l.p2().x()) { + int x = qBound(clip.left(), l.p1().x(), clip.right()); + int y1 = qBound(clip.top(), l.p1().y(), clip.bottom()); + int y2 = qBound(clip.top(), l.p2().y(), clip.bottom()); + + return QLine(x, y1, x, y2); + } else { + Q_ASSERT(l.p1().y() == l.p2().y()); + + int x1 = qBound(clip.left(), l.p1().x(), clip.right()); + int x2 = qBound(clip.left(), l.p2().x(), clip.right()); + int y = qBound(clip.top(), l.p1().y(), clip.bottom()); + + return QLine(x1, y, x2, y); + } +} + +void QX11PaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (rectCount != 1 + || d->has_pen + || d->has_alpha_brush + || d->has_complex_xform + || d->has_custom_pen + || d->cbrush.style() != Qt::SolidPattern +#if QT_CONFIG(xrender) + || complexPictOp(d->composition_mode) +#endif + ) + { + QPaintEngine::drawRects(rects, rectCount); + return; + } + + QPoint alignedOffset; + if (d->txop == QTransform::TxTranslate) { + QPointF offset(d->matrix.dx(), d->matrix.dy()); + alignedOffset = offset.toPoint(); + if (offset != QPointF(alignedOffset)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + } + + const QRectF& r = rects[0]; + QRect alignedRect = r.toAlignedRect(); + if (r != QRectF(alignedRect)) { + QPaintEngine::drawRects(rects, rectCount); + return; + } + alignedRect.translate(alignedOffset); + + QRect clip(d->polygonClipper.boundingRect()); + alignedRect = alignedRect.intersected(clip); + if (alignedRect.isEmpty()) + return; + + // simple-case: + // the rectangle is pixel-aligned + // the fill brush is just a solid non-alpha color + // the painter transform is only integer translation + // ignore: antialiasing and just XFillRectangles directly + XRectangle xrect; + xrect.x = short(alignedRect.x()); + xrect.y = short(alignedRect.y()); + xrect.width = ushort(alignedRect.width()); + xrect.height = ushort(alignedRect.height()); + XFillRectangles(d->dpy, d->hd, d->gc_brush, &xrect, 1); +} + +void QX11PaintEngine::drawRects(const QRect *rects, int rectCount) +{ + Q_D(QX11PaintEngine); + Q_ASSERT(rects); + Q_ASSERT(rectCount); + + if (d->has_alpha_pen + || d->has_complex_xform + || d->has_custom_pen + || (d->render_hints & QPainter::Antialiasing)) + { + for (int i = 0; i < rectCount; ++i) { + QPainterPath path; + path.addRect(rects[i]); + drawPath(path); + } + return; + } + + QRect clip(d->polygonClipper.boundingRect()); + QPoint offset(qRound(d->matrix.dx()), qRound(d->matrix.dy())); +#if QT_CONFIG(xrender) + ::Picture pict = d->picture; + + if (X11->use_xrender && pict && d->has_brush && d->pdev_depth != 1 + && (d->has_texture || d->has_alpha_brush || complexPictOp(d->composition_mode))) + { + XRenderColor xc; + if (!d->has_texture && !d->has_pattern) + xc = X11->preMultiply(d->cbrush.color()); + + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + if (d->has_texture || d->has_pattern) { + XRenderComposite(d->dpy, d->composition_mode, d->current_brush, 0, pict, + qRound(r.x() - d->bg_origin.x()), qRound(r.y() - d->bg_origin.y()), + 0, 0, r.x(), r.y(), r.width(), r.height()); + } else { + XRenderFillRectangle(d->dpy, d->composition_mode, pict, &xc, r.x(), r.y(), r.width(), r.height()); + } + if (d->has_pen) + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + } else +#endif // QT_CONFIG(xrender) + { + if (d->has_brush && d->has_pen) { + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) + continue; + d->setupAdaptedOrigin(r.topLeft()); + XFillRectangle(d->dpy, d->hd, d->gc_brush, r.x(), r.y(), r.width(), r.height()); + XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); + } + d->resetAdaptedOrigin(); + } else { + QVarLengthArray xrects(rectCount); + int numClipped = rectCount; + for (int i = 0; i < rectCount; ++i) { + QRect r(rects[i]); + if (d->txop == QTransform::TxTranslate) + r.translate(offset); + + if (r.width() == 0 || r.height() == 0) { + --numClipped; + if (d->has_pen) { + const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); + XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); + } + continue; + } + + r = r.intersected(clip); + if (r.isEmpty()) { + --numClipped; + continue; + } + xrects[i].x = short(r.x()); + xrects[i].y = short(r.y()); + xrects[i].width = ushort(r.width()); + xrects[i].height = ushort(r.height()); + } + if (numClipped) { + d->setupAdaptedOrigin(rects[0].topLeft()); + if (d->has_brush) + XFillRectangles(d->dpy, d->hd, d->gc_brush, xrects.data(), numClipped); + else if (d->has_pen) + XDrawRectangles(d->dpy, d->hd, d->gc, xrects.data(), numClipped); + d->resetAdaptedOrigin(); + } + } + } +} + +static inline void setCapStyle(int cap_style, GC gc) +{ + ulong mask = GCCapStyle; + XGCValues vals; + vals.cap_style = cap_style; + XChangeGC(QXcbX11Info::display(), gc, mask, &vals); +} + +void QX11PaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + const QPoint *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x()+.005, points->y()); + drawPath(path); + ++points; + } + + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPoint &xformed = d->matrix.map(points[i]); + int x = xformed.x(); + int y = xformed.y(); + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +void QX11PaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_ASSERT(points); + Q_ASSERT(pointCount); + Q_D(QX11PaintEngine); + + if (!d->has_pen) + return; + + // use the same test here as in drawPath to ensure that we don't use the path fallback + // and end up in XDrawLines for pens with width <= 1 + if (d->cpen.widthF() > 1.0f + || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) + { + Qt::PenCapStyle capStyle = d->cpen.capStyle(); + if (capStyle == Qt::FlatCap) { + setCapStyle(CapProjecting, d->gc); + d->cpen.setCapStyle(Qt::SquareCap); + } + + const QPointF *end = points + pointCount; + while (points < end) { + QPainterPath path; + path.moveTo(*points); + path.lineTo(points->x() + 0.005, points->y()); + drawPath(path); + ++points; + } + if (capStyle == Qt::FlatCap) { + setCapStyle(CapButt, d->gc); + d->cpen.setCapStyle(capStyle); + } + return; + } + + static const int BUF_SIZE = 1024; + XPoint xPoints[BUF_SIZE]; + int i = 0, j = 0; + while (i < pointCount) { + while (i < pointCount && j < BUF_SIZE) { + const QPointF &xformed = d->matrix.map(points[i]); + int x = qFloor(xformed.x()); + int y = qFloor(xformed.y()); + + if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { + xPoints[j].x = x; + xPoints[j].y = y; + ++j; + } + ++i; + } + if (j) + XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); + + j = 0; + } +} + +QPainter::RenderHints QX11PaintEngine::supportedRenderHints() const +{ +#if QT_CONFIG(xrender) + if (X11->use_xrender) + return QPainter::Antialiasing; +#endif + return QFlag(0); +} + +void QX11PaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QX11PaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + + if (flags & DirtyOpacity) { + d->opacity = state.opacity(); + // Force update pen/brush as to get proper alpha colors propagated + flags |= DirtyPen; + flags |= DirtyBrush; + } + + if (flags & DirtyTransform) updateMatrix(state.transform()); + if (flags & DirtyPen) updatePen(state.pen()); + if (flags & (DirtyBrush | DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin()); + if (flags & DirtyFont) updateFont(state.font()); + + if (state.state() & DirtyClipEnabled) { + if (state.isClipEnabled()) { + QPolygonF clip_poly_dev(d->matrix.map(painter()->clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); + } else { + updateClipRegion_dev(QRegion(), Qt::NoClip); + } + } + + if (flags & DirtyClipPath) { + QPolygonF clip_poly_dev(d->matrix.map(state.clipPath().toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon(), state.clipPath().fillRule()), + state.clipOperation()); + } else if (flags & DirtyClipRegion) { + extern QPainterPath qt_regionToPath(const QRegion ®ion); + QPainterPath clip_path = qt_regionToPath(state.clipRegion()); + QPolygonF clip_poly_dev(d->matrix.map(clip_path.toFillPolygon())); + QPolygonF clipped_poly_dev; + d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); + updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), state.clipOperation()); + } + if (flags & DirtyHints) updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) { + int function = GXcopy; + if (state.compositionMode() >= QPainter::RasterOp_SourceOrDestination) { + switch (state.compositionMode()) { + case QPainter::RasterOp_SourceOrDestination: + function = GXor; + break; + case QPainter::RasterOp_SourceAndDestination: + function = GXand; + break; + case QPainter::RasterOp_SourceXorDestination: + function = GXxor; + break; + case QPainter::RasterOp_NotSourceAndNotDestination: + function = GXnor; + break; + case QPainter::RasterOp_NotSourceOrNotDestination: + function = GXnand; + break; + case QPainter::RasterOp_NotSourceXorDestination: + function = GXequiv; + break; + case QPainter::RasterOp_NotSource: + function = GXcopyInverted; + break; + case QPainter::RasterOp_SourceAndNotDestination: + function = GXandReverse; + break; + case QPainter::RasterOp_NotSourceAndDestination: + function = GXandInverted; + break; + default: + function = GXcopy; + } + } +#if QT_CONFIG(xrender) + else { + d->composition_mode = + qpainterOpToXrender(state.compositionMode()); + } +#endif + XSetFunction(X11->display, d->gc, function); + XSetFunction(X11->display, d->gc_brush, function); + } + d->decidePathFallback(); + d->decideCoordAdjust(); +} + +void QX11PaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QX11PaintEngine); + d->render_hints = hints; + +#if QT_CONFIG(xrender) + if (X11->use_xrender && d->picture) { + XRenderPictureAttributes attrs; + attrs.poly_edge = (hints & QPainter::Antialiasing) ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(d->dpy, d->picture, CPPolyEdge, &attrs); + } +#endif +} + +void QX11PaintEngine::updatePen(const QPen &pen) +{ + Q_D(QX11PaintEngine); + d->cpen = pen; + int cp = CapButt; + int jn = JoinMiter; + int ps = pen.style(); + + if (d->opacity < 1.0) { + QColor c = d->cpen.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cpen.setColor(c); + } + + d->has_pen = (ps != Qt::NoPen); + d->has_alpha_pen = (pen.color().alpha() != 255); + + switch (pen.capStyle()) { + case Qt::SquareCap: + cp = CapProjecting; + break; + case Qt::RoundCap: + cp = CapRound; + break; + case Qt::FlatCap: + default: + cp = CapButt; + break; + } + switch (pen.joinStyle()) { + case Qt::BevelJoin: + jn = JoinBevel; + break; + case Qt::RoundJoin: + jn = JoinRound; + break; + case Qt::MiterJoin: + default: + jn = JoinMiter; + break; + } + + d->adapted_pen_origin = false; + + char dashes[10]; // custom pen dashes + int dash_len = 0; // length of dash list + int xStyle = LineSolid; + + /* + We are emulating Windows here. Windows treats cpen.width() == 1 + (or 0) as a very special case. The fudge variable unifies this + case with the general case. + */ + qreal pen_width = pen.widthF(); + int scale = qRound(pen_width < 1 ? 1 : pen_width); + int space = (pen_width < 1 && pen_width > 0 ? 1 : (2 * scale)); + int dot = 1 * scale; + int dash = 4 * scale; + + d->has_custom_pen = false; + + switch (ps) { + case Qt::NoPen: + case Qt::SolidLine: + xStyle = LineSolid; + break; + case Qt::DashLine: + dashes[0] = dash; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DotLine: + dashes[0] = dot; + dashes[1] = space; + dash_len = 2; + xStyle = LineOnOffDash; + break; + case Qt::DashDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dash_len = 4; + xStyle = LineOnOffDash; + break; + case Qt::DashDotDotLine: + dashes[0] = dash; + dashes[1] = space; + dashes[2] = dot; + dashes[3] = space; + dashes[4] = dot; + dashes[5] = space; + dash_len = 6; + xStyle = LineOnOffDash; + break; + case Qt::CustomDashLine: + d->has_custom_pen = true; + break; + } + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures | GCLineWidth + | GCCapStyle | GCJoinStyle | GCLineStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(pen.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (d->pdev->devType() == QInternal::Pixmap && d->pdev_depth == 32 + && X11->use_xrender) { + vals.foreground = pen.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + vals.foreground = cmap.pixel(pen.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + } + + + vals.line_width = qRound(pen.widthF()); + vals.cap_style = cp; + vals.join_style = jn; + vals.line_style = xStyle; + + XChangeGC(d->dpy, d->gc, mask, &vals); + + if (dash_len) { // make dash list + XSetDashes(d->dpy, d->gc, 0, dashes, dash_len); + } + + if (!d->has_clipping) { // if clipping is set the paintevent clip region is merged with the clip region + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc, 0, d->picture); + } +} + +void QX11PaintEngine::updateBrush(const QBrush &brush, const QPointF &origin) +{ + Q_D(QX11PaintEngine); + d->cbrush = brush; + d->bg_origin = origin; + d->adapted_brush_origin = false; +#if QT_CONFIG(xrender) + d->current_brush = 0; +#endif + if (d->opacity < 1.0) { + QColor c = d->cbrush.color(); + c.setAlpha(qRound(c.alpha()*d->opacity)); + d->cbrush.setColor(c); + } + + int s = FillSolid; + int bs = d->cbrush.style(); + d->has_brush = (bs != Qt::NoBrush); + d->has_pattern = bs >= Qt::Dense1Pattern && bs <= Qt::DiagCrossPattern; + d->has_texture = bs == Qt::TexturePattern; + d->has_alpha_brush = brush.color().alpha() != 255; + d->has_alpha_texture = d->has_texture && d->cbrush.texture().hasAlphaChannel(); + + ulong mask = GCForeground | GCBackground | GCGraphicsExposures + | GCLineStyle | GCCapStyle | GCJoinStyle | GCFillStyle; + XGCValues vals; + vals.graphics_exposures = false; + if (d->pdev_depth == 1) { + vals.foreground = qGray(d->cbrush.color().rgb()) > 127 ? 0 : 1; + vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; + } else if (X11->use_xrender && d->pdev->devType() == QInternal::Pixmap + && d->pdev_depth == 32) { + vals.foreground = d->cbrush.color().rgba(); + vals.background = QColor(Qt::transparent).rgba(); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + vals.foreground = cmap.pixel(d->cbrush.color()); + vals.background = cmap.pixel(QColor(Qt::transparent)); + + if (!X11->use_xrender && d->has_brush && !d->has_pattern && !brush.isOpaque()) { + QPixmap pattern = qt_patternForAlpha(brush.color().alpha(), d->scrn); + mask |= GCStipple; + vals.stipple = qt_x11PixmapHandle(pattern); + s = FillStippled; + d->adapted_brush_origin = true; + } + } + vals.cap_style = CapButt; + vals.join_style = JoinMiter; + vals.line_style = LineSolid; + + if (d->has_pattern || d->has_texture) { + if (bs == Qt::TexturePattern) { + d->brush_pm = qt_toX11Pixmap(d->cbrush.texture()); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->brush_pm), CPRepeat, &attrs); + QX11PlatformPixmap *data = static_cast(d->brush_pm.handle()); + if (data->mask_picture) + XRenderChangePicture(d->dpy, data->mask_picture, CPRepeat, &attrs); + } +#endif + } else { + d->brush_pm = qt_toX11Pixmap(qt_pixmapForBrush(bs, true)); + } + qt_x11SetScreen(d->brush_pm, d->scrn); + if (d->brush_pm.depth() == 1) { + mask |= GCStipple; + vals.stipple = qt_x11PixmapHandle(d->brush_pm); + s = FillStippled; +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + d->bitmap_texture = QPixmap(d->brush_pm.size()); + d->bitmap_texture.fill(Qt::transparent); + d->bitmap_texture = qt_toX11Pixmap(d->bitmap_texture); + qt_x11SetScreen(d->bitmap_texture, d->scrn); + + ::Picture src = X11->getSolidFill(d->scrn, d->cbrush.color()); + XRenderComposite(d->dpy, PictOpSrc, src, qt_x11PictureHandle(d->brush_pm), + qt_x11PictureHandle(d->bitmap_texture), + 0, 0, d->brush_pm.width(), d->brush_pm.height(), + 0, 0, d->brush_pm.width(), d->brush_pm.height()); + + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->bitmap_texture), CPRepeat, &attrs); + + d->current_brush = qt_x11PictureHandle(d->bitmap_texture); + } +#endif + } else { + mask |= GCTile; +#if QT_CONFIG(xrender) + if (d->pdev_depth == 32 && d->brush_pm.depth() != 32) { + d->brush_pm.detach(); + QX11PlatformPixmap *brushData = static_cast(d->brush_pm.handle()); + brushData->convertToARGB32(); + } +#endif + vals.tile = (d->brush_pm.depth() == d->pdev_depth + ? qt_x11PixmapHandle(d->brush_pm) + : static_cast(d->brush_pm.handle())->x11ConvertToDefaultDepth()); + s = FillTiled; +#if QT_CONFIG(xrender) + d->current_brush = qt_x11PictureHandle(d->cbrush.texture()); +#endif + } + + mask |= GCTileStipXOrigin | GCTileStipYOrigin; + vals.ts_x_origin = qRound(origin.x()); + vals.ts_y_origin = qRound(origin.y()); + } +#if QT_CONFIG(xrender) + else if (d->has_alpha_brush) { + d->current_brush = X11->getSolidFill(d->scrn, d->cbrush.color()); + } +#endif + + vals.fill_style = s; + XChangeGC(d->dpy, d->gc_brush, mask, &vals); + if (!d->has_clipping) { + QRegion sysClip = systemClip(); + if (!sysClip.isEmpty()) + x11SetClipRegion(d->dpy, d->gc_brush, 0, d->picture, sysClip); + else + x11ClearClipRegion(d->dpy, d->gc_brush, 0, d->picture); + } +} + +void QX11PaintEngine::drawEllipse(const QRectF &rect) +{ + QRect aligned = rect.toAlignedRect(); + if (aligned == rect) + drawEllipse(aligned); + else + QPaintEngine::drawEllipse(rect); +} + +void QX11PaintEngine::drawEllipse(const QRect &rect) +{ + if (rect.isEmpty()) { + drawRects(&rect, 1); + return; + } + + Q_D(QX11PaintEngine); + QRect devclip(SHRT_MIN, SHRT_MIN, SHRT_MAX*2 - 1, SHRT_MAX*2 - 1); + QRect r(rect); + if (d->txop < QTransform::TxRotate) { + r = d->matrix.mapRect(rect); + } else if (d->txop == QTransform::TxRotate && rect.width() == rect.height()) { + QPainterPath path; + path.addEllipse(rect); + r = d->matrix.map(path).boundingRect().toRect(); + } + + if (d->has_alpha_brush || d->has_alpha_pen || d->has_custom_pen || (d->render_hints & QPainter::Antialiasing) + || d->has_alpha_texture || devclip.intersected(r) != r + || (d->has_complex_xform + && !(d->has_non_scaling_xform && rect.width() == rect.height()))) + { + QPainterPath path; + path.addEllipse(rect); + drawPath(path); + return; + } + + int x = r.x(); + int y = r.y(); + int w = r.width(); + int h = r.height(); + if (w < 1 || h < 1) + return; + if (w == 1 && h == 1) { + XDrawPoint(d->dpy, d->hd, d->has_pen ? d->gc : d->gc_brush, x, y); + return; + } + d->setupAdaptedOrigin(rect.topLeft()); + if (d->has_brush) { // draw filled ellipse + XFillArc(d->dpy, d->hd, d->gc_brush, x, y, w, h, 0, 360*64); + if (!d->has_pen) // make smoother outline + XDrawArc(d->dpy, d->hd, d->gc_brush, x, y, w-1, h-1, 0, 360*64); + } + if (d->has_pen) // draw outline + XDrawArc(d->dpy, d->hd, d->gc, x, y, w, h, 0, 360*64); + d->resetAdaptedOrigin(); +} + + + +void QX11PaintEnginePrivate::fillPolygon_translated(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + + QVarLengthArray translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) + offset += QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta); + + for (int i = 0; i < pointCount; ++i) { + translated_points[i] = polygonPoints[i] + offset; + + translated_points[i].rx() = qRound(translated_points[i].x()) + offs; + translated_points[i].ry() = qRound(translated_points[i].y()) + offs; + } + + fillPolygon_dev(translated_points.data(), pointCount, gcMode, mode); +} + +#if QT_CONFIG(xrender) +static void qt_XRenderCompositeTrapezoids(Display *dpy, + int op, + Picture src, + Picture dst, + _Xconst XRenderPictFormat *maskFormat, + int xSrc, + int ySrc, + const XTrapezoid *traps, int size) +{ + const int MAX_TRAPS = 50000; + while (size) { + int to_draw = size; + if (to_draw > MAX_TRAPS) + to_draw = MAX_TRAPS; + XRenderCompositeTrapezoids(dpy, op, src, dst, + maskFormat, + xSrc, ySrc, + traps, to_draw); + size -= to_draw; + traps += to_draw; + } +} +#endif + +void QX11PaintEnginePrivate::fillPolygon_dev(const QPointF *polygonPoints, int pointCount, + QX11PaintEnginePrivate::GCMode gcMode, + QPaintEngine::PolygonDrawMode mode) +{ + Q_Q(QX11PaintEngine); + + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + +#if QT_CONFIG(xrender) + //can change if we switch to pen if gcMode != BrushGC + bool has_fill_texture = has_texture; + bool has_fill_pattern = has_pattern; + ::Picture src; +#endif + QBrush fill; + GC fill_gc; + if (gcMode == BrushGC) { + fill = cbrush; + fill_gc = gc_brush; +#if QT_CONFIG(xrender) + if (current_brush) + src = current_brush; + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } else { + fill = QBrush(cpen.brush()); + fill_gc = gc; +#if QT_CONFIG(xrender) + //we use the pens brush + has_fill_texture = (fill.style() == Qt::TexturePattern); + has_fill_pattern = (fill.style() >= Qt::Dense1Pattern && fill.style() <= Qt::DiagCrossPattern); + if (has_fill_texture) + src = qt_x11PictureHandle(fill.texture()); + else if (has_fill_pattern) + src = getPatternFill(scrn, fill); + else + src = X11->getSolidFill(scrn, fill.color()); +#endif + } + + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount); + +#if QT_CONFIG(xrender) + bool solid_fill = fill.color().alpha() == 255; + if (has_fill_texture && fill.texture().depth() == 1 && solid_fill) { + has_fill_texture = false; + has_fill_pattern = true; + } + + bool antialias = render_hints & QPainter::Antialiasing; + + if (X11->use_xrender + && picture + && !has_fill_pattern + && (clippedCount > 0) + && (fill.style() != Qt::NoBrush) + && ((has_fill_texture && fill.texture().hasAlpha()) || antialias || !solid_fill || has_alpha_pen != has_alpha_brush)) + { + tessellator->tessellate((QPointF *)clippedPoints, clippedCount, + mode == QPaintEngine::WindingMode); + if (tessellator->size > 0) { + XRenderPictureAttributes attrs; + attrs.poly_edge = antialias ? PolyEdgeSmooth : PolyEdgeSharp; + XRenderChangePicture(dpy, picture, CPPolyEdge, &attrs); + int x_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.x) - bg_origin.x()); + int y_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.y) - bg_origin.y()); + qt_XRenderCompositeTrapezoids(dpy, composition_mode, src, picture, + antialias + ? XRenderFindStandardFormat(dpy, PictStandardA8) + : XRenderFindStandardFormat(dpy, PictStandardA1), + x_offset, y_offset, + tessellator->traps, tessellator->size); + tessellator->done(); + } + } else +#endif + if (fill.style() != Qt::NoBrush) { + if (clippedCount > 200000) { + QPolygon poly; + for (int i = 0; i < clippedCount; ++i) + poly << QPoint(qFloor(clippedPoints[i].x), qFloor(clippedPoints[i].y)); + + const QRect bounds = poly.boundingRect(); + const QRect aligned = bounds + & QRect(QPoint(), QSize(pdev->width(), pdev->height())); + + QImage img(aligned.size(), QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QPainter painter(&img); + painter.translate(-aligned.x(), -aligned.y()); + painter.setPen(Qt::NoPen); + painter.setBrush(fill); + if (gcMode == BrushGC) + painter.setBrushOrigin(q->painter()->brushOrigin()); + painter.drawPolygon(poly); + painter.end(); + + q->drawImage(aligned, img, img.rect(), Qt::AutoColor); + } else if (clippedCount > 0) { + QVarLengthArray xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qFloor(clippedPoints[i].x); + xpoints[i].y = qFloor(clippedPoints[i].y); + } + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, WindingRule); + setupAdaptedOrigin(QPoint(xpoints[0].x, xpoints[0].y)); + XFillPolygon(dpy, hd, fill_gc, + xpoints.data(), clippedCount, + mode == QPaintEngine::ConvexMode ? Convex : Complex, CoordModeOrigin); + resetAdaptedOrigin(); + if (mode == QPaintEngine::WindingMode) + XSetFillRule(dpy, fill_gc, EvenOddRule); + } + } +} + +void QX11PaintEnginePrivate::strokePolygon_translated(const QPointF *polygonPoints, int pointCount, bool close) +{ + QVarLengthArray translated_points(pointCount); + QPointF offset(matrix.dx(), matrix.dy()); + for (int i = 0; i < pointCount; ++i) + translated_points[i] = polygonPoints[i] + offset; + strokePolygon_dev(translated_points.data(), pointCount, close); +} + +void QX11PaintEnginePrivate::strokePolygon_dev(const QPointF *polygonPoints, int pointCount, bool close) +{ + int clippedCount = 0; + qt_float_point *clippedPoints = 0; + polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, + &clippedPoints, &clippedCount, close); + + if (clippedCount > 0) { + QVarLengthArray xpoints(clippedCount); + for (int i = 0; i < clippedCount; ++i) { + xpoints[i].x = qRound(clippedPoints[i].x + aliasedCoordinateDelta); + xpoints[i].y = qRound(clippedPoints[i].y + aliasedCoordinateDelta); + } + uint numberPoints = qMin(clippedCount, xlibMaxLinePoints); + XPoint *pts = xpoints.data(); + XDrawLines(dpy, hd, gc, pts, numberPoints, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + while (clippedCount) { + XDrawLines(dpy, hd, gc, pts-1, numberPoints+1, CoordModeOrigin); + pts += numberPoints; + clippedCount -= numberPoints; + numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); + } + } +} + +void QX11PaintEngine::drawPolygon(const QPointF *polygonPoints, int pointCount, PolygonDrawMode mode) +{ + Q_D(QX11PaintEngine); + + if (d->use_path_fallback) { + QPainterPath path(polygonPoints[0]); + for (int i = 1; i < pointCount; ++i) + path.lineTo(polygonPoints[i]); + if (mode == PolylineMode) { + QBrush oldBrush = d->cbrush; + d->cbrush = QBrush(Qt::NoBrush); + path.setFillRule(Qt::WindingFill); + drawPath(path); + d->cbrush = oldBrush; + } else { + path.setFillRule(mode == OddEvenMode ? Qt::OddEvenFill : Qt::WindingFill); + path.closeSubpath(); + drawPath(path); + } + return; + } + if (mode != PolylineMode && d->has_brush) + d->fillPolygon_translated(polygonPoints, pointCount, QX11PaintEnginePrivate::BrushGC, mode); + + if (d->has_pen) + d->strokePolygon_translated(polygonPoints, pointCount, mode != PolylineMode); +} + + +void QX11PaintEnginePrivate::fillPath(const QPainterPath &path, QX11PaintEnginePrivate::GCMode gc_mode, bool transform) +{ + qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; + + QPainterPath clippedPath; + QPainterPath clipPath; + clipPath.addRect(polygonClipper.boundingRect()); + + if (transform) + clippedPath = (path*matrix).intersected(clipPath); + else + clippedPath = path.intersected(clipPath); + + QList polys = clippedPath.toFillPolygons(); + for (int i = 0; i < polys.size(); ++i) { + QVarLengthArray translated_points(polys.at(i).size()); + + for (int j = 0; j < polys.at(i).size(); ++j) { + translated_points[j] = polys.at(i).at(j); + if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) { + translated_points[j].rx() = qRound(translated_points[j].rx() + aliasedCoordinateDelta) + offs; + translated_points[j].ry() = qRound(translated_points[j].ry() + aliasedCoordinateDelta) + offs; + } + } + + fillPolygon_dev(translated_points.data(), polys.at(i).size(), gc_mode, + path.fillRule() == Qt::OddEvenFill ? QPaintEngine::OddEvenMode : QPaintEngine::WindingMode); + } +} + +void QX11PaintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QX11PaintEngine); + if (path.isEmpty()) + return; + + if (d->has_brush) + d->fillPath(path, QX11PaintEnginePrivate::BrushGC, true); + if (d->has_pen + && ((X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) + || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate + && !d->has_non_scaling_xform) + || (d->cpen.style() == Qt::CustomDashLine))) { + QPainterPathStroker stroker; + if (d->cpen.style() == Qt::CustomDashLine) { + stroker.setDashPattern(d->cpen.dashPattern()); + stroker.setDashOffset(d->cpen.dashOffset()); + } else { + stroker.setDashPattern(d->cpen.style()); + } + stroker.setCapStyle(d->cpen.capStyle()); + stroker.setJoinStyle(d->cpen.joinStyle()); + QPainterPath stroke; + qreal width = d->cpen.widthF(); + QPolygonF poly; + QRectF deviceRect(0, 0, d->pdev->width(), d->pdev->height()); + // necessary to get aliased alphablended primitives to be drawn correctly + if (d->isCosmeticPen() || d->has_scaling_xform) { + if (d->isCosmeticPen()) + stroker.setWidth(width == 0 ? 1 : width); + else + stroker.setWidth(width * d->xform_scale); + stroker.d_ptr->stroker.setClipRect(deviceRect); + stroke = stroker.createStroke(path * d->matrix); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, false); + } else { + stroker.setWidth(width); + stroker.d_ptr->stroker.setClipRect(d->matrix.inverted().mapRect(deviceRect)); + stroke = stroker.createStroke(path); + if (stroke.isEmpty()) + return; + stroke.setFillRule(Qt::WindingFill); + d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, true); + } + } else if (d->has_pen) { + // if we have a cosmetic pen - use XDrawLine() for speed + QList polys = path.toSubpathPolygons(d->matrix); + for (int i = 0; i < polys.size(); ++i) + d->strokePolygon_dev(polys.at(i).data(), polys.at(i).size(), false); + } +} + +Q_GUI_EXPORT void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image, + Drawable hd, GC gc, Display *dpy, Visual *visual, int depth) +{ + Q_ASSERT(image.format() == QImage::Format_RGB32); + Q_ASSERT(image.depth() == 32); + + XImage *xi; + // Note: this code assumes either RGB or BGR, 8 bpc server layouts + const uint red_mask = (uint) visual->red_mask; + bool bgr_layout = (red_mask == 0xff); + + const int w = rect.width(); + const int h = rect.height(); + + QImage im; + int image_byte_order = ImageByteOrder(QXcbX11Info::display()); + if ((QSysInfo::ByteOrder == QSysInfo::BigEndian && ((image_byte_order == LSBFirst) || bgr_layout)) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + im = image.copy(rect); + const int iw = im.bytesPerLine() / 4; + uint *data = (uint *)im.bits(); + for (int i=0; i < h; i++) { + uint *p = data; + uint *end = p + w; + if (bgr_layout && image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) { + while (p < end) { + *p = ((*p << 8) & 0xffffff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == LSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) { + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } else if ((image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) + || (image_byte_order == LSBFirst && bgr_layout)) + { + while (p < end) { + *p = ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff) + | ((*p ) & 0xff00ff00); + p++; + } + } + data += iw; + } + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) im.bits(), w, h, 32, im.bytesPerLine()); + } else { + xi = XCreateImage(dpy, visual, depth, ZPixmap, + 0, (char *) image.scanLine(rect.y())+rect.x()*sizeof(uint), w, h, 32, image.bytesPerLine()); + } + XPutImage(dpy, hd, gc, xi, 0, 0, pos.x(), pos.y(), w, h); + xi->data = 0; // QImage owns these bits + XDestroyImage(xi); +} + +void QX11PaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + Q_D(QX11PaintEngine); + + if (image.format() == QImage::Format_RGB32 + && d->pdev_depth >= 24 && image.depth() == 32 + && r.size() == sr.size()) + { + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + + qt_x11_drawImage(QRect(sx, sy, w, h), QPoint(x, y), image, d->hd, d->gc, d->dpy, + (Visual *)d->xinfo->visual(), d->pdev_depth); + } else { + QPaintEngine::drawImage(r, image, sr, flags); + } +} + +void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRectF &_sr) +{ + Q_D(QX11PaintEngine); + QRectF sr = _sr; + int x = qRound(r.x()); + int y = qRound(r.y()); + int sx = qRound(sr.x()); + int sy = qRound(sr.y()); + int sw = qRound(sr.width()); + int sh = qRound(sr.height()); + + QPixmap pixmap = qt_toX11Pixmap(px); + if (pixmap.isNull()) + return; + + if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) + || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { + qt_x11SetScreen(pixmap, d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display)); + } + + qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); + +#if QT_CONFIG(xrender) + ::Picture src_pict = qt_x11PictureHandle(pixmap); + if (src_pict && d->picture) { + const int pDepth = pixmap.depth(); + if (pDepth == 1 && (d->has_alpha_pen)) { + qt_render_bitmap(d->dpy, d->scrn, src_pict, d->picture, + sx, sy, x, y, sw, sh, d->cpen); + return; + } else if (pDepth != 1 && (pDepth == 32 || pDepth != d->pdev_depth)) { + XRenderComposite(d->dpy, d->composition_mode, + src_pict, 0, d->picture, sx, sy, 0, 0, x, y, sw, sh); + return; + } + } +#endif + + bool mono_src = pixmap.depth() == 1; + bool mono_dst = d->pdev_depth == 1; + bool restore_clip = false; + + if (static_cast(pixmap.handle())->x11_mask) { // pixmap has a mask + QBitmap comb(sw, sh); + GC cgc = XCreateGC(d->dpy, qt_x11PixmapHandle(comb), 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + if (!d->crgn.isEmpty()) { + QVector rects = qt_region_to_xrectangles(d->crgn); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); + } else if (d->has_clipping) { + XSetClipRectangles(d->dpy, cgc, 0, 0, 0, 0, Unsorted); + } + XSetFillStyle(d->dpy, cgc, FillOpaqueStippled); + XSetTSOrigin(d->dpy, cgc, -sx, -sy); + XSetStipple(d->dpy, cgc, + static_cast(pixmap.handle())->x11_mask); + XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); + XFreeGC(d->dpy, cgc); + + XSetClipOrigin(d->dpy, d->gc, x, y); + XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(comb)); + restore_clip = true; + } + + if (mono_src) { + if (!d->crgn.isEmpty()) { + Pixmap comb = XCreatePixmap(d->dpy, d->hd, sw, sh, 1); + GC cgc = XCreateGC(d->dpy, comb, 0, 0); + XSetForeground(d->dpy, cgc, 0); + XFillRectangle(d->dpy, comb, cgc, 0, 0, sw, sh); + QVector rects = qt_region_to_xrectangles(d->crgn); + XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); + XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), comb, cgc, sx, sy, sw, sh, 0, 0); + XFreeGC(d->dpy, cgc); + + XSetClipMask(d->dpy, d->gc, comb); + XSetClipOrigin(d->dpy, d->gc, x, y); + XFreePixmap(d->dpy, comb); + } else { + XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); + XSetClipOrigin(d->dpy, d->gc, x - sx, y - sy); + } + + if (mono_dst) { + XSetForeground(d->dpy, d->gc, qGray(d->cpen.color().rgb()) > 127 ? 0 : 1); + } else { + QXcbColormap cmap = QXcbColormap::instance(d->scrn); + XSetForeground(d->dpy, d->gc, cmap.pixel(d->cpen.color())); + } + XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh); + restore_clip = true; + } else if (mono_dst && !mono_src) { + QBitmap bitmap(pixmap); + XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y); + } else { + XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y); + } + + if (d->pdev->devType() == QInternal::Pixmap) { + const QPixmap *px = static_cast(d->pdev); + Pixmap src_mask = static_cast(pixmap.handle())->x11_mask; + Pixmap dst_mask = static_cast(px->handle())->x11_mask; + if (dst_mask) { + GC cgc = XCreateGC(d->dpy, dst_mask, 0, 0); + XSetClipOrigin(d->dpy, cgc, x, y); + XSetClipMask(d->dpy, cgc, src_mask); + if (src_mask) { // copy src mask into dst mask + XCopyArea(d->dpy, src_mask, dst_mask, cgc, sx, sy, sw, sh, x, y); + } else { // no src mask, but make sure the area copied is opaque in dest + XSetBackground(d->dpy, cgc, 0); + XSetForeground(d->dpy, cgc, 1); + XFillRectangle(d->dpy, dst_mask, cgc, x, y, sw, sh); + } + XFreeGC(d->dpy, cgc); + } + } + + if (restore_clip) { + XSetClipOrigin(d->dpy, d->gc, 0, 0); + QVector rects = qt_region_to_xrectangles(d->crgn); + if (rects.isEmpty()) + XSetClipMask(d->dpy, d->gc, XNone); + else + XSetClipRectangles(d->dpy, d->gc, 0, 0, rects.data(), rects.size(), Unsorted); + } +} + +void QX11PaintEngine::updateMatrix(const QTransform &mtx) +{ + Q_D(QX11PaintEngine); + d->txop = mtx.type(); + d->matrix = mtx; + + d->has_complex_xform = (d->txop > QTransform::TxTranslate); + + extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); + bool scaling = qt_scaleForTransform(d->matrix, &d->xform_scale); + d->has_scaling_xform = scaling && d->xform_scale != 1.0; + d->has_non_scaling_xform = scaling && d->xform_scale == 1.0; +} + +/* + NB! the clip region is expected to be in dev coordinates +*/ +void QX11PaintEngine::updateClipRegion_dev(const QRegion &clipRegion, Qt::ClipOperation op) +{ + Q_D(QX11PaintEngine); + QRegion sysClip = systemClip(); + if (op == Qt::NoClip) { + d->has_clipping = false; + d->crgn = sysClip; + if (!sysClip.isEmpty()) { + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, sysClip); + } else { + x11ClearClipRegion(d->dpy, d->gc, d->gc_brush, d->picture); + } + return; + } + + switch (op) { + case Qt::IntersectClip: + if (d->has_clipping) { + d->crgn &= clipRegion; + break; + } + // fall through + case Qt::ReplaceClip: + if (!sysClip.isEmpty()) + d->crgn = clipRegion.intersected(sysClip); + else + d->crgn = clipRegion; + break; +// case Qt::UniteClip: +// d->crgn |= clipRegion; +// if (!sysClip.isEmpty()) +// d->crgn = d->crgn.intersected(sysClip); +// break; + default: + break; + } + d->has_clipping = true; + x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, d->crgn); +} + +void QX11PaintEngine::updateFont(const QFont &) +{ +} + +Drawable QX11PaintEngine::handle() const +{ + Q_D(const QX11PaintEngine); + Q_ASSERT(isActive()); + Q_ASSERT(d->hd); + return d->hd; +} + +extern void qt_draw_tile(QPaintEngine *, qreal, qreal, qreal, qreal, const QPixmap &, + qreal, qreal); + +void QX11PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) +{ + int x = qRound(r.x()); + int y = qRound(r.y()); + int w = qRound(r.width()); + int h = qRound(r.height()); + int sx = qRound(p.x()); + int sy = qRound(p.y()); + + bool mono_src = pixmap.depth() == 1; + Q_D(QX11PaintEngine); + + if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) + || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { + QPixmap* p = const_cast(&pixmap); + qt_x11SetScreen(*p, d->xinfo ? d->xinfo->screen() : DefaultScreen(QXcbX11Info::display())); + } + + qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); + +#if QT_CONFIG(xrender) + if (X11->use_xrender && d->picture && qt_x11PictureHandle(pixmap)) { +#if 0 + // ### Qt 5: enable this + XRenderPictureAttributes attrs; + attrs.repeat = true; + XRenderChangePicture(d->dpy, pixmap.x11PictureHandle(), CPRepeat, &attrs); + + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, pixmap.x11PictureHandle(), d->picture, + sx, sy, x, y, w, h, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + pixmap.x11PictureHandle(), XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#else + const int numTiles = (w / pixmap.width()) * (h / pixmap.height()); + if (numTiles < 100) { + // this is essentially qt_draw_tile(), inlined for + // the XRenderComposite call + int yPos, xPos, drawH, drawW, yOff, xOff; + yPos = y; + yOff = sy; + while (yPos < y + h) { + drawH = pixmap.height() - yOff; // Cropping first row + if (yPos + drawH > y + h) // Cropping last row + drawH = y + h - yPos; + xPos = x; + xOff = sx; + while (xPos < x + w) { + drawW = pixmap.width() - xOff; // Cropping first column + if (xPos + drawW > x + w) // Cropping last column + drawW = x + w - xPos; + if (mono_src) { + qt_render_bitmap(d->dpy, d->scrn, qt_x11PictureHandle(pixmap), d->picture, + xOff, yOff, xPos, yPos, drawW, drawH, d->cpen); + } else { + XRenderComposite(d->dpy, d->composition_mode, + qt_x11PictureHandle(pixmap), XNone, d->picture, + xOff, yOff, 0, 0, xPos, yPos, drawW, drawH); + } + xPos += drawW; + xOff = 0; + } + yPos += drawH; + yOff = 0; + } + } else { + w = qMin(w, d->pdev->width() - x); + h = qMin(h, d->pdev->height() - y); + if (w <= 0 || h <= 0) + return; + + const int pw = w + sx; + const int ph = h + sy; + QPixmap pm(pw, ph); + if (pixmap.hasAlpha() || mono_src) + pm.fill(Qt::transparent); + + const int mode = pixmap.hasAlpha() ? PictOpOver : PictOpSrc; + const ::Picture pmPicture = qt_x11PictureHandle(pm); + + // first tile + XRenderComposite(d->dpy, mode, + qt_x11PictureHandle(pixmap), XNone, pmPicture, + 0, 0, 0, 0, 0, 0, qMin(pw, pixmap.width()), qMin(ph, pixmap.height())); + + // first row of tiles + int xPos = pixmap.width(); + const int sh = qMin(ph, pixmap.height()); + while (xPos < pw) { + const int sw = qMin(xPos, pw - xPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, xPos, 0, sw, sh); + xPos *= 2; + } + + // remaining rows + int yPos = pixmap.height(); + const int sw = pw; + while (yPos < ph) { + const int sh = qMin(yPos, ph - yPos); + XRenderComposite(d->dpy, mode, + pmPicture, XNone, pmPicture, + 0, 0, 0, 0, 0, yPos, sw, sh); + yPos *= 2; + } + + // composite + if (mono_src) + qt_render_bitmap(d->dpy, d->scrn, pmPicture, d->picture, + sx, sy, x, y, w, h, d->cpen); + else + XRenderComposite(d->dpy, d->composition_mode, + pmPicture, XNone, d->picture, + sx, sy, 0, 0, x, y, w, h); + } +#endif + } else +#endif // QT_CONFIG(xrender) + if (pixmap.depth() > 1 && !static_cast(pixmap.handle())->x11_mask) { + XSetTile(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); + XSetFillStyle(d->dpy, d->gc, FillTiled); + XSetTSOrigin(d->dpy, d->gc, x-sx, y-sy); + XFillRectangle(d->dpy, d->hd, d->gc, x, y, w, h); + XSetTSOrigin(d->dpy, d->gc, 0, 0); + XSetFillStyle(d->dpy, d->gc, FillSolid); + } else { + qt_draw_tile(this, x, y, w, h, pixmap, sx, sy); + } +} + +bool QX11PaintEngine::drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti) +{ +#if QT_CONFIG(xrender) + Q_D(QX11PaintEngine); + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + if (!X11->use_xrender) + return false; + + QFontEngineFT *ft = static_cast(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); + + if (!set || set->outline_drawing) + return false; + + QFontEngine::GlyphFormat glyphFormat = QXRenderGlyphCache::glyphFormatForDepth(ft, d->pdev_depth); + + QXRenderGlyphCache *cache = static_cast(ft->glyphCache(set, glyphFormat, transform)); + if (!cache) { + cache = new QXRenderGlyphCache(QXcbX11Info(), glyphFormat, transform); + ft->setGlyphCache(set, cache); + } + + return cache->draw(X11->getSolidFill(d->scrn, d->cpen.color()), d->picture, transform, ti); +#else // !QT_CONFIG(xrender) + return false; +#endif // QT_CONFIG(xrender) +} + +void QX11PaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + Q_D(QX11PaintEngine); + const QTextItemInt &ti = static_cast(textItem); + + switch (ti.fontEngine->type()) { + case QFontEngine::TestFontEngine: + case QFontEngine::Box: + d->drawBoxTextItem(p, ti); + break; +#if QT_CONFIG(fontconfig) + case QFontEngine::Freetype: + drawFreetype(p, ti); + break; +#endif + default: + Q_ASSERT(false); + } +} + +#if QT_CONFIG(fontconfig) +static bool path_for_glyphs(QPainterPath *path, + const QVarLengthArray &glyphs, + const QVarLengthArray &positions, + const QFontEngineFT *ft) +{ + bool result = true; + *path = QPainterPath(); + path->setFillRule(Qt::WindingFill); + ft->lockFace(); + int i = 0; + while (i < glyphs.size()) { + QFontEngineFT::Glyph *glyph = ft->loadGlyph(glyphs[i], 0, QFontEngineFT::Format_Mono); + // #### fix case where we don't get a glyph + if (!glyph || glyph->format != QFontEngineFT::Format_Mono) { + result = false; + break; + } + + int n = 0; + int h = glyph->height; + int xp = qRound(positions[i].x); + int yp = qRound(positions[i].y); + + xp += glyph->x; + yp += -glyph->y + glyph->height; + int pitch = ((glyph->width + 31) & ~31) >> 3; + + uchar *src = glyph->data; + while (h--) { + for (int x = 0; x < glyph->width; ++x) { + bool set = src[x >> 3] & (0x80 >> (x & 7)); + if (set) { + QRect r(xp + x, yp - h, 1, 1); + while (x+1 < glyph->width && src[(x+1) >> 3] & (0x80 >> ((x+1) & 7))) { + ++x; + r.setRight(r.right()+1); + } + + path->addRect(r); + ++n; + } + } + src += pitch; + } + ++i; + } + ft->unlockFace(); + return result; +} + +void QX11PaintEngine::drawFreetype(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QX11PaintEngine); + + if (!ti.glyphs.numGlyphs) + return; + + if (!d->cpen.isSolid()) { + QPaintEngine::drawTextItem(p, ti); + return; + } + + const bool xrenderPath = (X11->use_xrender + && !(d->pdev->devType() == QInternal::Pixmap + && static_cast(d->pdev)->handle()->pixelType() == QPlatformPixmap::BitmapType)); + + if (xrenderPath) { + QTransform transform = d->matrix; + transform.translate(p.x(), p.y()); + + if (drawCachedGlyphs(transform, ti)) + return; + } + + QTransform transform; + transform.translate(p.x(), p.y()); + + QVarLengthArray positions; + QVarLengthArray glyphs; + ti.fontEngine->getGlyphPositions(ti.glyphs, transform, ti.flags, glyphs, positions); + + if (glyphs.count() == 0) + return; + + QFontEngineFT *ft = static_cast(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); + QPainterPath path; + + if (!set || set->outline_drawing || !path_for_glyphs(&path, glyphs, positions, ft)) { + QPaintEngine::drawTextItem(p, ti); + return; + } + + if (path.elementCount() <= 1) + return; + + Q_ASSERT((path.elementCount() % 5) == 0); + if (d->txop >= QTransform::TxScale) { + painter()->save(); + painter()->setBrush(d->cpen.brush()); + painter()->setPen(Qt::NoPen); + painter()->drawPath(path); + painter()->restore(); + return; + } + + const int rectcount = 256; + XRectangle rects[rectcount]; + int num_rects = 0; + + QPoint delta(qRound(d->matrix.dx()), qRound(d->matrix.dy())); + QRect clip(d->polygonClipper.boundingRect()); + for (int i=0; i < path.elementCount(); i+=5) { + int x = qRound(path.elementAt(i).x); + int y = qRound(path.elementAt(i).y); + int w = qRound(path.elementAt(i+1).x) - x; + int h = qRound(path.elementAt(i+2).y) - y; + + QRect rect = QRect(x + delta.x(), y + delta.y(), w, h); + rect = rect.intersected(clip); + if (rect.isEmpty()) + continue; + + rects[num_rects].x = short(rect.x()); + rects[num_rects].y = short(rect.y()); + rects[num_rects].width = ushort(rect.width()); + rects[num_rects].height = ushort(rect.height()); + ++num_rects; + if (num_rects == rectcount) { + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); + num_rects = 0; + } + } + if (num_rects > 0) + XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); +} +#endif // QT_CONFIG(fontconfig) + +#if QT_CONFIG(xrender) +QXRenderGlyphCache::QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix) + : QFontEngineGlyphCache(format, matrix) + , xinfo(x) + , gset(XNone) +{} + +QXRenderGlyphCache::~QXRenderGlyphCache() +{ + if (gset != XNone) + XRenderFreeGlyphSet(xinfo.display(), gset); +} + +bool QXRenderGlyphCache::addGlyphs(const QTextItemInt &ti, QVarLengthArray glyphs, QVarLengthArray positions) +{ + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + QFontEngineFT *ft = static_cast(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform()); + + XGlyphInfo xglyphinfo; + + for (int i = 0; i < glyphs.size(); ++i) { + const QFixed spp = ft->subPixelPositionForX(positions[i].x); + QFontEngineFT::Glyph *glyph = set->getGlyph(glyphs[i], spp); + Glyph xglyphid = qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyphs[i], spp)); + + if (glyph && glyph->format == glyphFormat()) { + if (cachedGlyphs.contains(xglyphid)) { + continue; + } else { + set->setGlyph(glyphs[i], spp, nullptr); + delete glyph; + glyph = 0; + } + } + + glyph = ft->loadGlyphFor(glyphs[i], spp, glyphFormat(), transform()); + + if (glyph == 0 || glyph->format != glyphFormat()) + return false; + + set->setGlyph(glyphs[i], spp, glyph); + Q_ASSERT(glyph->data || glyph->width == 0 || glyph->height == 0); + + xglyphinfo.width = glyph->width; + xglyphinfo.height = glyph->height; + xglyphinfo.x = -glyph->x; + xglyphinfo.y = glyph->y; + xglyphinfo.xOff = glyph->advance; + xglyphinfo.yOff = 0; + + XRenderAddGlyphs(xinfo.display(), glyphSet(), &xglyphid, &xglyphinfo, 1, (const char *) glyph->data, glyphBufferSize(*glyph)); + cachedGlyphs.insert(xglyphid); + } + + return true; +} + +bool QXRenderGlyphCache::draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti) +{ + Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); + + if (ti.glyphs.numGlyphs == 0) + return true; + + QFontEngineFT *ft = static_cast(ti.fontEngine); + QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(matrix); + + QVarLengthArray glyphs; + QVarLengthArray positions; + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + if (glyphs.isEmpty()) + return true; + + if (!addGlyphs(ti, glyphs, positions)) + return false; + + QVarLengthArray chars(glyphs.size()); + + for (int i = 0; i < glyphs.size(); ++i) + chars[i] = glyphId(glyphs[i], ft->subPixelPositionForX(positions[i].x)); + + int i = 0; + while (i < glyphs.size() && !isValidCoordinate(positions[i])) + ++i; + + if (i >= glyphs.size()) + return true; + + QFixed xp = positions[i].x; + QFixed yp = positions[i].y; + QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + + XGlyphElt32 elt; + elt.glyphset = gset; + elt.chars = &chars[i]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + + ++i; + + for (; i < glyphs.size(); ++i) { + if (!isValidCoordinate(positions[i])) + break; + + const QFixed spp = ft->subPixelPositionForX(positions[i].x); + QFontEngineFT::Glyph *g = set->getGlyph(glyphs[i], spp); + + if (g + && positions[i].x == xp + g->advance + && positions[i].y == yp + && elt.nchars < 253 // don't draw more than 253 characters as some X servers + // hang with it + ) { + elt.nchars++; + xp += g->advance; + } else { + xp = positions[i].x; + yp = positions[i].y; + + XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, + renderPictFormat(), 0, 0, 0, 0, + &elt, 1); + elt.chars = &chars[i]; + elt.nchars = 1; + elt.xOff = qRound(xp + offs); + elt.yOff = qRound(yp + offs); + } + } + + XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, + renderPictFormat(), 0, 0, 0, 0, &elt, 1); + + return true; +} + +GlyphSet QXRenderGlyphCache::glyphSet() +{ + if (gset == XNone) + gset = XRenderCreateGlyphSet(xinfo.display(), renderPictFormat()); + + Q_ASSERT(gset != XNone); + return gset; +} + +int QXRenderGlyphCache::glyphBufferSize(const QFontEngineFT::Glyph &glyph) const +{ + int pitch = 0; + + switch (glyphFormat()) { + case QFontEngine::Format_Mono: + pitch = ((glyph.width + 31) & ~31) >> 3; + break; + case QFontEngine::Format_A8: + pitch = (glyph.width + 3) & ~3; + break; + default: + pitch = glyph.width * 4; + break; + } + + return pitch * glyph.height; +} + +QImage::Format QXRenderGlyphCache::imageFormat() const +{ + switch (glyphFormat()) { + case QFontEngine::Format_None: + Q_UNREACHABLE(); + break; + case QFontEngine::Format_Mono: + return QImage::Format_Mono; + break; + case QFontEngine::Format_A8: + return QImage::Format_Alpha8; + break; + case QFontEngine::Format_A32: + case QFontEngine::Format_ARGB: + return QImage::Format_ARGB32_Premultiplied; + break; + } + + Q_UNREACHABLE(); +} + +const XRenderPictFormat *QXRenderGlyphCache::renderPictFormat() const +{ + switch (glyphFormat()) { + case QFontEngine::Format_None: + Q_UNREACHABLE(); + break; + case QFontEngine::Format_Mono: + return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); + break; + case QFontEngine::Format_A8: + return XRenderFindStandardFormat(xinfo.display(), PictStandardA8); + break; + case QFontEngine::Format_A32: + case QFontEngine::Format_ARGB: + return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); + break; + } + + Q_UNREACHABLE(); +} + +QFontEngine::GlyphFormat QXRenderGlyphCache::glyphFormatForDepth(QFontEngine *fontEngine, int depth) +{ + QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat; + + if (glyphFormat == QFontEngine::Format_None) { + switch (depth) { + case 32: + glyphFormat = QFontEngine::Format_ARGB; + break; + case 24: + glyphFormat = QFontEngine::Format_A32; + break; + case 1: + glyphFormat = QFontEngine::Format_Mono; + break; + default: + glyphFormat = QFontEngine::Format_A8; + break; + } + } + + return glyphFormat; +} + +Glyph QXRenderGlyphCache::glyphId(glyph_t glyph, QFixed subPixelPosition) +{ + return qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyph, subPixelPosition)); +} + +bool QXRenderGlyphCache::isValidCoordinate(const QFixedPoint &fp) +{ + enum { t_min = SHRT_MIN, t_max = SHRT_MAX }; + return (fp.x < t_min || fp.x > t_max || fp.y < t_min || fp.y > t_max) ? false : true; +} +#endif // QT_CONFIG(xrender) + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h new file mode 100644 index 0000000000..34b5d929d5 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QPAINTENGINE_X11_H +#define QPAINTENGINE_X11_H + +#include + +typedef unsigned long XID; +typedef XID Drawable; +typedef struct _XGC *GC; + +QT_BEGIN_NAMESPACE + +extern "C" { +Drawable qt_x11Handle(const QPaintDevice *pd); +GC qt_x11_get_pen_gc(QPainter *); +GC qt_x11_get_brush_gc(QPainter *); +} + +class QX11PaintEnginePrivate; +class QX11PaintEngine : public QPaintEngine +{ + Q_DECLARE_PRIVATE(QX11PaintEngine) +public: + QX11PaintEngine(); + ~QX11PaintEngine(); + + bool begin(QPaintDevice *pdev) Q_DECL_OVERRIDE; + bool end() Q_DECL_OVERRIDE; + + void updateState(const QPaintEngineState &state) Q_DECL_OVERRIDE; + + void updatePen(const QPen &pen); + void updateBrush(const QBrush &brush, const QPointF &pt); + void updateRenderHints(QPainter::RenderHints hints); + void updateFont(const QFont &font); + void updateMatrix(const QTransform &matrix); + void updateClipRegion_dev(const QRegion ®ion, Qt::ClipOperation op); + + void drawLines(const QLine *lines, int lineCount) Q_DECL_OVERRIDE; + void drawLines(const QLineF *lines, int lineCount) Q_DECL_OVERRIDE; + + void drawRects(const QRect *rects, int rectCount) Q_DECL_OVERRIDE; + void drawRects(const QRectF *rects, int rectCount) Q_DECL_OVERRIDE; + + void drawPoints(const QPoint *points, int pointCount) Q_DECL_OVERRIDE; + void drawPoints(const QPointF *points, int pointCount) Q_DECL_OVERRIDE; + + void drawEllipse(const QRect &r) Q_DECL_OVERRIDE; + void drawEllipse(const QRectF &r) Q_DECL_OVERRIDE; + + virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) Q_DECL_OVERRIDE; + inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) Q_DECL_OVERRIDE + { QPaintEngine::drawPolygon(points, pointCount, mode); } + + void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) Q_DECL_OVERRIDE; + void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) Q_DECL_OVERRIDE; + void drawPath(const QPainterPath &path) Q_DECL_OVERRIDE; + void drawTextItem(const QPointF &p, const QTextItem &textItem) Q_DECL_OVERRIDE; + void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags = Qt::AutoColor) Q_DECL_OVERRIDE; + + virtual Drawable handle() const; + inline Type type() const Q_DECL_OVERRIDE { return QPaintEngine::X11; } + + QPainter::RenderHints supportedRenderHints() const; + +protected: + QX11PaintEngine(QX11PaintEnginePrivate &dptr); + +#if QT_CONFIG(fontconfig) + void drawFreetype(const QPointF &p, const QTextItemInt &ti); + bool drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti); +#endif // QT_CONFIG(fontconfig) + + friend class QPixmap; + friend class QFontEngineBox; + friend GC qt_x11_get_pen_gc(QPainter *); + friend GC qt_x11_get_brush_gc(QPainter *); + +private: + Q_DISABLE_COPY(QX11PaintEngine) +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_X11_H diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp new file mode 100644 index 0000000000..72fe1cd05d --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp @@ -0,0 +1,2104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include +#include +#include + +#include "qxcbnativepainting.h" +#include "qpixmap_x11_p.h" +#include "qcolormap_x11_p.h" +#include "qpaintengine_x11_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_POINTER_SIZE == 8 // 64-bit versions + +Q_ALWAYS_INLINE uint PREMUL(uint x) { + uint a = x >> 24; + quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8; + t &= 0x000000ff00ff00ff; + return (uint(t)) | (uint(t >> 24)) | (a << 24); +} + +#else // 32-bit versions + +Q_ALWAYS_INLINE uint PREMUL(uint x) { + uint a = x >> 24; + uint t = (x & 0xff00ff) * a; + t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; + t &= 0xff00ff; + + x = ((x >> 8) & 0xff) * a; + x = (x + ((x >> 8) & 0xff) + 0x80); + x &= 0xff00; + x |= t | (a << 24); + return x; +} +#endif + + + +struct QXImageWrapper +{ + XImage *xi; +}; + +QPixmap qt_toX11Pixmap(const QImage &image) +{ + QPlatformPixmap *data = + new QX11PlatformPixmap(image.depth() == 1 + ? QPlatformPixmap::BitmapType + : QPlatformPixmap::PixmapType); + + data->fromImage(image, Qt::AutoColor); + + return QPixmap(data); +} + +QPixmap qt_toX11Pixmap(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return QPixmap(); + + if (QPixmap(pixmap).data_ptr()->classId() == QPlatformPixmap::X11Class) + return pixmap; + + return qt_toX11Pixmap(pixmap.toImage()); +} + +// For thread-safety: +// image->data does not belong to X11, so we must free it ourselves. + +inline static void qSafeXDestroyImage(XImage *x) +{ + if (x->data) { + free(x->data); + x->data = 0; + } + XDestroyImage(x); +} + +QBitmap QX11PlatformPixmap::mask_to_bitmap(int screen) const +{ + if (!x11_mask) + return QBitmap(); + qt_x11SetDefaultScreen(screen); + QBitmap bm(w, h); + QX11PlatformPixmap *that = qt_x11Pixmap(bm); + const QXcbX11Info *x = that->x11_info(); + GC gc = XCreateGC(x->display(), that->handle(), 0, 0); + XCopyArea(x->display(), x11_mask, that->handle(), gc, 0, 0, + that->width(), that->height(), 0, 0); + XFreeGC(x->display(), gc); + return bm; +} + +void QX11PlatformPixmap::bitmapFromImage(const QImage &image) +{ + w = image.width(); + h = image.height(); + d = 1; + is_null = (w <= 0 || h <= 0); + hd = createBitmapFromImage(image); +#if QT_CONFIG(xrender) + if (X11->use_xrender) + picture = XRenderCreatePicture(xinfo.display(), hd, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); +#endif // QT_CONFIG(xrender) +} + +bool QX11PlatformPixmap::canTakeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + if (xi->format != ZPixmap) + return false; + + // ARGB32_Premultiplied + if (picture && depth() == 32) + return true; + + // RGB32 + if (depth() == 24 && xi->bits_per_pixel == 32 && xi->red_mask == 0xff0000 + && xi->green_mask == 0xff00 && xi->blue_mask == 0xff) + return true; + + // RGB16 + if (depth() == 16 && xi->bits_per_pixel == 16 && xi->red_mask == 0xf800 + && xi->green_mask == 0x7e0 && xi->blue_mask == 0x1f) + return true; + + return false; +} + +QImage QX11PlatformPixmap::takeQImageFromXImage(const QXImageWrapper &xiWrapper) const +{ + XImage *xi = xiWrapper.xi; + + QImage::Format format = QImage::Format_ARGB32_Premultiplied; + if (depth() == 24) + format = QImage::Format_RGB32; + else if (depth() == 16) + format = QImage::Format_RGB16; + + QImage image((uchar *)xi->data, xi->width, xi->height, xi->bytes_per_line, format); + image.setDevicePixelRatio(devicePixelRatio()); + // take ownership + image.data_ptr()->own_data = true; + xi->data = 0; + + // we may have to swap the byte order + if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst) + || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) + { + for (int i=0; i < image.height(); i++) { + if (depth() == 16) { + ushort *p = (ushort*)image.scanLine(i); + ushort *end = p + image.width(); + while (p < end) { + *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff); + p++; + } + } else { + uint *p = (uint*)image.scanLine(i); + uint *end = p + image.width(); + while (p < end) { + *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) + | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); + p++; + } + } + } + } + + // fix-up alpha channel + if (format == QImage::Format_RGB32) { + QRgb *p = (QRgb *)image.bits(); + for (int y = 0; y < xi->height; ++y) { + for (int x = 0; x < xi->width; ++x) + p[x] |= 0xff000000; + p += xi->bytes_per_line / 4; + } + } + + XDestroyImage(xi); + return image; +} + +XID QX11PlatformPixmap::bitmap_to_mask(const QBitmap &bitmap, int screen) +{ + if (bitmap.isNull()) + return 0; + QBitmap bm = bitmap; + qt_x11SetScreen(bm, screen); + + QX11PlatformPixmap *that = qt_x11Pixmap(bm); + const QXcbX11Info *x = that->x11_info(); + Pixmap mask = XCreatePixmap(x->display(), RootWindow(x->display(), screen), + that->width(), that->height(), 1); + GC gc = XCreateGC(x->display(), mask, 0, 0); + XCopyArea(x->display(), that->handle(), mask, gc, 0, 0, + that->width(), that->height(), 0, 0); + XFreeGC(x->display(), gc); + return mask; +} + +Drawable qt_x11Handle(const QPixmap &pixmap) +{ + if (pixmap.isNull()) + return XNone; + + if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) + return XNone; + + return static_cast(pixmap.handle())->handle(); +} + + +/***************************************************************************** + Internal functions + *****************************************************************************/ + +//extern const uchar *qt_get_bitflip_array(); // defined in qimage.cpp + +// Returns position of highest bit set or -1 if none +static int highest_bit(uint v) +{ + int i; + uint b = (uint)1 << 31; + for (i=31; ((b & v) == 0) && i>=0; i--) + b >>= 1; + return i; +} + +// Counts the number of bits set in 'v' +static uint n_bits(uint v) +{ + int i = 0; + while (v) { + v = v & (v - 1); + i++; + } + return i; +} + +static uint *red_scale_table = 0; +static uint *green_scale_table = 0; +static uint *blue_scale_table = 0; + +static void cleanup_scale_tables() +{ + delete[] red_scale_table; + delete[] green_scale_table; + delete[] blue_scale_table; +} + +/* + Could do smart bitshifting, but the "obvious" algorithm only works for + nBits >= 4. This is more robust. +*/ +static void build_scale_table(uint **table, uint nBits) +{ + if (nBits > 7) { + qWarning("build_scale_table: internal error, nBits = %i", nBits); + return; + } + if (!*table) { + static bool firstTable = true; + if (firstTable) { + qAddPostRoutine(cleanup_scale_tables); + firstTable = false; + } + *table = new uint[256]; + } + int maxVal = (1 << nBits) - 1; + int valShift = 8 - nBits; + int i; + for (i = 0 ; i < maxVal + 1 ; i++) + (*table)[i << valShift] = i*255/maxVal; +} + +static int defaultScreen = -1; + +int qt_x11SetDefaultScreen(int screen) +{ + int old = defaultScreen; + defaultScreen = screen; + return old; +} + +void qt_x11SetScreen(QPixmap &pixmap, int screen) +{ + if (pixmap.paintingActive()) { + qWarning("qt_x11SetScreen(): Cannot change screens during painting"); + return; + } + + if (pixmap.isNull()) + return; + + if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) + return; + + if (screen < 0) + screen = QXcbX11Info::appScreen(); + + QX11PlatformPixmap *pm = static_cast(pixmap.handle()); + if (screen == pm->xinfo.screen()) + return; // nothing to do + + if (pixmap.isNull()) { + pm->xinfo = QXcbX11Info::fromScreen(screen); + return; + } + +#if 0 + qDebug("qt_x11SetScreen for %p from %d to %d. Size is %d/%d", pm, pm->xinfo.screen(), screen, pm->width(), pm->height()); +#endif + + qt_x11SetDefaultScreen(screen); + pixmap = qt_toX11Pixmap(pixmap.toImage()); +} + +/***************************************************************************** + QPixmap member functions + *****************************************************************************/ + +QBasicAtomicInt qt_pixmap_serial = Q_BASIC_ATOMIC_INITIALIZER(0); +int Q_GUI_EXPORT qt_x11_preferred_pixmap_depth = 0; + +QX11PlatformPixmap::QX11PlatformPixmap(PixelType pixelType) + : QPlatformPixmap(pixelType, X11Class), hd(0), + flags(Uninitialized), x11_mask(0), picture(0), mask_picture(0), hd2(0), + dpr(1.0), pengine(0) +{} + +QX11PlatformPixmap::~QX11PlatformPixmap() +{ + // Cleanup hooks have to be called before the handles are freed + if (is_cached) { + QImagePixmapCleanupHooks::executePlatformPixmapDestructionHooks(this); + is_cached = false; + } + + release(); +} + +QPlatformPixmap *QX11PlatformPixmap::createCompatiblePlatformPixmap() const +{ + QX11PlatformPixmap *p = new QX11PlatformPixmap(pixelType()); + p->setDevicePixelRatio(devicePixelRatio()); + return p; +} + +void QX11PlatformPixmap::resize(int width, int height) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = width; + h = height; + is_null = (w <= 0 || h <= 0); + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + xinfo = QXcbX11Info::fromScreen(defaultScreen); + } + + int dd = xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + bool make_null = w <= 0 || h <= 0; // create null pixmap + d = (pixelType() == BitmapType ? 1 : dd); + if (make_null || d == 0) { + w = 0; + h = 0; + is_null = true; + hd = 0; + picture = 0; + d = 0; + if (!make_null) + qWarning("QPixmap: Invalid pixmap parameters"); + return; + } + hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardA1) + : XRenderFindVisualFormat(xinfo.display(), (Visual *) xinfo.visual()); + picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) +} + +struct QX11AlphaDetector +{ + bool hasAlpha() const { + if (checked) + return has; + // Will implicitly also check format and return quickly for opaque types... + checked = true; + has = image->isNull() ? false : const_cast(image)->data_ptr()->checkForAlphaPixels(); + return has; + } + + bool hasXRenderAndAlpha() const { + if (!X11->use_xrender) + return false; + return hasAlpha(); + } + + QX11AlphaDetector(const QImage *i, Qt::ImageConversionFlags flags) + : image(i), checked(false), has(false) + { + if (flags & Qt::NoOpaqueDetection) { + checked = true; + has = image->hasAlphaChannel(); + } + } + + const QImage *image; + mutable bool checked; + mutable bool has; +}; + +void QX11PlatformPixmap::fromImage(const QImage &img, Qt::ImageConversionFlags flags) +{ + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + w = img.width(); + h = img.height(); + d = img.depth(); + is_null = (w <= 0 || h <= 0); + setDevicePixelRatio(img.devicePixelRatio()); + + if (is_null) { + w = h = 0; + return; + } + + if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { + xinfo = QXcbX11Info::fromScreen(defaultScreen); + } + + if (pixelType() == BitmapType) { + bitmapFromImage(img); + return; + } + + if (uint(w) >= 32768 || uint(h) >= 32768) { + w = h = 0; + is_null = true; + return; + } + + QX11AlphaDetector alphaCheck(&img, flags); + int dd = alphaCheck.hasXRenderAndAlpha() ? 32 : xinfo.depth(); + + if (qt_x11_preferred_pixmap_depth) + dd = qt_x11_preferred_pixmap_depth; + + QImage image = img; + + // must be monochrome + if (dd == 1 || (flags & Qt::ColorMode_Mask) == Qt::MonoOnly) { + if (d != 1) { + // dither + image = image.convertToFormat(QImage::Format_MonoLSB, flags); + d = 1; + } + } else { // can be both + bool conv8 = false; + if (d > 8 && dd <= 8) { // convert to 8 bit + if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither) + flags = (flags & ~Qt::DitherMode_Mask) + | Qt::PreferDither; + conv8 = true; + } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) { + conv8 = (d == 1); // native depth wanted + } else if (d == 1) { + if (image.colorCount() == 2) { + QRgb c0 = image.color(0); // Auto: convert to best + QRgb c1 = image.color(1); + conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255); + } else { + // eg. 1-color monochrome images (they do exist). + conv8 = true; + } + } + if (conv8) { + image = image.convertToFormat(QImage::Format_Indexed8, flags); + d = 8; + } + } + + if (d == 1 || d == 16 || d == 24 || image.format() == QImage::Format_Grayscale8) { + image = image.convertToFormat(QImage::Format_RGB32, flags); + fromImage(image, Qt::AutoColor); + return; + } + + Display *dpy = xinfo.display(); + Visual *visual = (Visual *)xinfo.visual(); + XImage *xi = 0; + bool trucol = (visual->c_class >= TrueColor); + int nbytes = image.byteCount(); + uchar *newbits= 0; + +#if QT_CONFIG(xrender) + if (alphaCheck.hasXRenderAndAlpha()) { + const QImage &cimage = image; + + d = 32; + + if (QXcbX11Info::appDepth() != d) { + xinfo.setDepth(d); + } + + hd = XCreatePixmap(dpy, RootWindow(dpy, xinfo.screen()), w, h, d); + picture = XRenderCreatePicture(dpy, hd, + XRenderFindStandardFormat(dpy, PictStandardARGB32), 0, 0); + + xi = XCreateImage(dpy, visual, d, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + xi->data = (char *)newbits; + + switch (cimage.format()) { + case QImage::Format_Indexed8: { + QVector colorTable = cimage.colorTable(); + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const uchar *p = cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = colorTable[p[x]]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + } + break; + case QImage::Format_RGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) + *xidata++ = p[x] | 0xff000000; + } + } + break; + case QImage::Format_ARGB32: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + for (int x = 0; x < w; ++x) { + const QRgb rgb = p[x]; + const int a = qAlpha(rgb); + if (a == 0xff) + *xidata = rgb; + else + // RENDER expects premultiplied alpha + *xidata = qRgba(qt_div_255(qRed(rgb) * a), + qt_div_255(qGreen(rgb) * a), + qt_div_255(qBlue(rgb) * a), + a); + ++xidata; + } + } + + } + break; + case QImage::Format_ARGB32_Premultiplied: { + uint *xidata = (uint *)xi->data; + for (int y = 0; y < h; ++y) { + const QRgb *p = (const QRgb *) cimage.scanLine(y); + memcpy(xidata, p, w*sizeof(QRgb)); + xidata += w; + } + } + break; + default: + Q_ASSERT(false); + } + + if ((xi->byte_order == MSBFirst) != (QSysInfo::ByteOrder == QSysInfo::BigEndian)) { + uint *xidata = (uint *)xi->data; + uint *xiend = xidata + w*h; + while (xidata < xiend) { + *xidata = (*xidata >> 24) + | ((*xidata >> 8) & 0xff00) + | ((*xidata << 8) & 0xff0000) + | (*xidata << 24); + ++xidata; + } + } + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + + return; + } +#endif // QT_CONFIG(xrender) + + if (trucol) { // truecolor display + if (image.format() == QImage::Format_ARGB32_Premultiplied) + image = image.convertToFormat(QImage::Format_ARGB32); + + const QImage &cimage = image; + QRgb pix[256]; // pixel translation table + const bool d8 = (d == 8); + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + const uint rbits = highest_bit(red_mask) - lowest_bit(red_mask) + 1; + const uint gbits = highest_bit(green_mask) - lowest_bit(green_mask) + 1; + const uint bbits = highest_bit(blue_mask) - lowest_bit(blue_mask) + 1; + + if (d8) { // setup pixel translation + QVector ctable = cimage.colorTable(); + for (int i=0; i < cimage.colorCount(); i++) { + int r = qRed (ctable[i]); + int g = qGreen(ctable[i]); + int b = qBlue (ctable[i]); + r = red_shift > 0 ? r << red_shift : r >> -red_shift; + g = green_shift > 0 ? g << green_shift : g >> -green_shift; + b = blue_shift > 0 ? b << blue_shift : b >> -blue_shift; + pix[i] = (b & blue_mask) | (g & green_mask) | (r & red_mask) + | ~(blue_mask | green_mask | red_mask); + } + } + + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + Q_CHECK_PTR(xi); + newbits = (uchar *)malloc(xi->bytes_per_line*h); + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + int bppc = xi->bits_per_pixel; + + bool contig_bits = n_bits(red_mask) == rbits && + n_bits(green_mask) == gbits && + n_bits(blue_mask) == bbits; + bool dither_tc = + // Want it? + (flags & Qt::Dither_Mask) != Qt::ThresholdDither && + (flags & Qt::DitherMode_Mask) != Qt::AvoidDither && + // Need it? + bppc < 24 && !d8 && + // Can do it? (Contiguous bits?) + contig_bits; + + static bool init=false; + static int D[16][16]; + if (dither_tc && !init) { + // I also contributed this code to XV - WWA. + /* + The dither matrix, D, is obtained with this formula: + + D2 = [0 2] + [3 1] + + + D2*n = [4*Dn 4*Dn+2*Un] + [4*Dn+3*Un 4*Dn+1*Un] + */ + int n,i,j; + init=1; + + /* Set D2 */ + D[0][0]=0; + D[1][0]=2; + D[0][1]=3; + D[1][1]=1; + + /* Expand using recursive definition given above */ + for (n=2; n<16; n*=2) { + for (i=0; ibyte_order == MSBFirst) == (QSysInfo::ByteOrder == QSysInfo::BigEndian); + + if (bppc == 8) // 8 bit + mode = BPP8; + else if (bppc == 16) { // 16 bit MSB/LSB + if (red_shift == 8 && green_shift == 3 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_565; + else if (red_shift == 7 && green_shift == 2 && blue_shift == -3 && !d8 && same_msb_lsb) + mode = BPP16_555; + else + mode = (xi->byte_order == LSBFirst) ? BPP16_LSB : BPP16_MSB; + } else if (bppc == 24) { // 24 bit MSB/LSB + if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP24_888; + else + mode = (xi->byte_order == LSBFirst) ? BPP24_LSB : BPP24_MSB; + } else if (bppc == 32) { // 32 bit MSB/LSB + if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) + mode = BPP32_8888; + else + mode = (xi->byte_order == LSBFirst) ? BPP32_LSB : BPP32_MSB; + } else + qFatal("Logic error 3"); + +#define GET_PIXEL \ + uint pixel; \ + if (d8) pixel = pix[*src++]; \ + else { \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask) \ + | ~(blue_mask | green_mask | red_mask); \ + } + +#define GET_PIXEL_DITHER_TC \ + int r = qRed (*p); \ + int g = qGreen(*p); \ + int b = qBlue (*p++); \ + const int thres = D[x%16][y%16]; \ + if (r <= (255-(1<<(8-rbits))) && ((r< thres) \ + r += (1<<(8-rbits)); \ + if (g <= (255-(1<<(8-gbits))) && ((g< thres) \ + g += (1<<(8-gbits)); \ + if (b <= (255-(1<<(8-bbits))) && ((b< thres) \ + b += (1<<(8-bbits)); \ + r = red_shift > 0 \ + ? r << red_shift : r >> -red_shift; \ + g = green_shift > 0 \ + ? g << green_shift : g >> -green_shift; \ + b = blue_shift > 0 \ + ? b << blue_shift : b >> -blue_shift; \ + uint pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask); + +// again, optimized case +// can't be optimized that much :( +#define GET_PIXEL_DITHER_TC_OPT(red_shift,green_shift,blue_shift,red_mask,green_mask,blue_mask, \ + rbits,gbits,bbits) \ + const int thres = D[x%16][y%16]; \ + int r = qRed (*p); \ + if (r <= (255-(1<<(8-rbits))) && ((r< thres) \ + r += (1<<(8-rbits)); \ + int g = qGreen(*p); \ + if (g <= (255-(1<<(8-gbits))) && ((g< thres) \ + g += (1<<(8-gbits)); \ + int b = qBlue (*p++); \ + if (b <= (255-(1<<(8-bbits))) && ((b< thres) \ + b += (1<<(8-bbits)); \ + uint pixel = ((r red_shift) & red_mask) \ + | ((g green_shift) & green_mask) \ + | ((b blue_shift) & blue_mask); + +#define CYCLE(body) \ + for (int y=0; ybytes_per_line*y; \ + const QRgb* p = (const QRgb *)src; \ + body \ + } + + if (dither_tc) { + switch (mode) { + case BPP16_565: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x>3,0xf800,0x7e0,0x1f,5,6,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x>3,0x7c00,0x3e0,0x1f,5,5,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x> 8; + } + ) + break; + default: + qFatal("Logic error"); + } + } else { + switch (mode) { + case BPP8: // 8 bit + CYCLE( + Q_UNUSED(p); + for (int x=0; x> 8) & 0xf800) + | ((*p >> 5) & 0x7e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x> 9) & 0x7c00) + | ((*p >> 6) & 0x3e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x> 8; + } + ) + break; + case BPP24_888: + CYCLE( + if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { + for (int x=0; x> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP24_LSB: // 24 bit LSB + CYCLE( + for (int x=0; x> 8; + *dst++ = pixel >> 16; + } + ) + break; + case BPP32_8888: + CYCLE( + memcpy(dst, p, w * 4); + ) + break; + case BPP32_MSB: // 32 bit MSB + CYCLE( + for (int x=0; x> 24; + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP32_LSB: // 32 bit LSB + CYCLE( + for (int x=0; x> 8; + *dst++ = pixel >> 16; + *dst++ = pixel >> 24; + } + ) + break; + default: + qFatal("Logic error 2"); + } + } + xi->data = (char *)newbits; + } + + if (d == 8 && !trucol) { // 8 bit pixmap + int pop[256]; // pixel popularity + + if (image.colorCount() == 0) + image.setColorCount(1); + + const QImage &cimage = image; + memset(pop, 0, sizeof(int)*256); // reset popularity array + for (int i = 0; i < h; i++) { // for each scanline... + const uchar* p = cimage.scanLine(i); + const uchar *end = p + w; + while (p < end) // compute popularity + pop[*p++]++; + } + + newbits = (uchar *)malloc(nbytes); // copy image into newbits + Q_CHECK_PTR(newbits); + if (!newbits) // no memory + return; + uchar* p = newbits; + memcpy(p, cimage.bits(), nbytes); // copy image data into newbits + + /* + * The code below picks the most important colors. It is based on the + * diversity algorithm, implemented in XV 3.10. XV is (C) by John Bradley. + */ + + struct PIX { // pixel sort element + uchar r,g,b,n; // color + pad + int use; // popularity + int index; // index in colormap + int mindist; + }; + int ncols = 0; + for (int i=0; i< cimage.colorCount(); i++) { // compute number of colors + if (pop[i] > 0) + ncols++; + } + for (int i = cimage.colorCount(); i < 256; i++) // ignore out-of-range pixels + pop[i] = 0; + + // works since we make sure above to have at least + // one color in the image + if (ncols == 0) + ncols = 1; + + PIX pixarr[256]; // pixel array + PIX pixarr_sorted[256]; // pixel array (sorted) + memset(pixarr, 0, ncols*sizeof(PIX)); + PIX *px = &pixarr[0]; + int maxpop = 0; + int maxpix = 0; + uint j = 0; + QVector ctable = cimage.colorTable(); + for (int i = 0; i < 256; i++) { // init pixel array + if (pop[i] > 0) { + px->r = qRed (ctable[i]); + px->g = qGreen(ctable[i]); + px->b = qBlue (ctable[i]); + px->n = 0; + px->use = pop[i]; + if (pop[i] > maxpop) { // select most popular entry + maxpop = pop[i]; + maxpix = j; + } + px->index = i; + px->mindist = 1000000; + px++; + j++; + } + } + pixarr_sorted[0] = pixarr[maxpix]; + pixarr[maxpix].use = 0; + + for (int i = 1; i < ncols; i++) { // sort pixels + int minpix = -1, mindist = -1; + px = &pixarr_sorted[i-1]; + int r = px->r; + int g = px->g; + int b = px->b; + int dist; + if ((i & 1) || i<10) { // sort on max distance + for (int j=0; juse) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->mindist > mindist) { + mindist = px->mindist; + minpix = j; + } + } + } + } else { // sort on max popularity + for (int j=0; juse) { + dist = (px->r - r)*(px->r - r) + + (px->g - g)*(px->g - g) + + (px->b - b)*(px->b - b); + if (px->mindist > dist) + px->mindist = dist; + if (px->use > mindist) { + mindist = px->use; + minpix = j; + } + } + } + } + pixarr_sorted[i] = pixarr[minpix]; + pixarr[minpix].use = 0; + } + + QXcbColormap cmap = QXcbColormap::instance(xinfo.screen()); + uint pix[256]; // pixel translation table + px = &pixarr_sorted[0]; + for (int i = 0; i < ncols; i++) { // allocate colors + QColor c(px->r, px->g, px->b); + pix[px->index] = cmap.pixel(c); + px++; + } + + p = newbits; + for (int i = 0; i < nbytes; i++) { // translate pixels + *p = pix[*p]; + p++; + } + } + + if (!xi) { // X image not created + xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); + if (xi->bits_per_pixel == 16) { // convert 8 bpp ==> 16 bpp + ushort *p2; + int p2inc = xi->bytes_per_line/sizeof(ushort); + ushort *newerbits = (ushort *)malloc(xi->bytes_per_line * h); + Q_CHECK_PTR(newerbits); + if (!newerbits) // no memory + return; + uchar* p = newbits; + for (int y = 0; y < h; y++) { // OOPS: Do right byte order!! + p2 = newerbits + p2inc*y; + for (int x = 0; x < w; x++) + *p2++ = *p++; + } + free(newbits); + newbits = (uchar *)newerbits; + } else if (xi->bits_per_pixel != 8) { + qWarning("QPixmap::fromImage: Display not supported " + "(bpp=%d)", xi->bits_per_pixel); + } + xi->data = (char *)newbits; + } + + hd = XCreatePixmap(dpy, + RootWindow(dpy, xinfo.screen()), + w, h, dd); + + GC gc = XCreateGC(dpy, hd, 0, 0); + XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); + XFreeGC(dpy, gc); + + qSafeXDestroyImage(xi); + d = dd; + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 1 + ? XRenderFindStandardFormat(dpy, PictStandardA1) + : XRenderFindVisualFormat(dpy, (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(dpy, hd, format, 0, 0); + } +#endif + + if (alphaCheck.hasAlpha()) { + QBitmap m = QBitmap::fromImage(image.createAlphaMask(flags)); + setMask(m); + } +} + +void QX11PlatformPixmap::copy(const QPlatformPixmap *data, const QRect &rect) +{ + if (data->pixelType() == BitmapType) { + fromImage(data->toImage().copy(rect), Qt::AutoColor); + return; + } + + const QX11PlatformPixmap *x11Data = static_cast(data); + + setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + + flags &= ~Uninitialized; + xinfo = x11Data->xinfo; + d = x11Data->d; + w = rect.width(); + h = rect.height(); + is_null = (w <= 0 || h <= 0); + hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), x11Data->xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = d == 32 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) + : XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); + picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) + if (x11Data->x11_mask) { + x11_mask = XCreatePixmap(xinfo.display(), hd, w, h, 1); +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = x11Data->mask_picture; + XRenderChangePicture(xinfo.display(), x11Data->picture, CPAlphaMap, &attrs); + } +#endif + } + +#if QT_CONFIG(xrender) + if (x11Data->picture && x11Data->d == 32) { + XRenderComposite(xinfo.display(), PictOpSrc, + x11Data->picture, 0, picture, + rect.x(), rect.y(), 0, 0, 0, 0, w, h); + } else +#endif + { + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + XCopyArea(xinfo.display(), x11Data->hd, hd, gc, + rect.x(), rect.y(), w, h, 0, 0); + if (x11Data->x11_mask) { + GC monogc = XCreateGC(xinfo.display(), x11_mask, 0, 0); + XCopyArea(xinfo.display(), x11Data->x11_mask, x11_mask, monogc, + rect.x(), rect.y(), w, h, 0, 0); + XFreeGC(xinfo.display(), monogc); + } + XFreeGC(xinfo.display(), gc); + } +} + +bool QX11PlatformPixmap::scroll(int dx, int dy, const QRect &rect) +{ + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + XCopyArea(xinfo.display(), hd, hd, gc, + rect.left(), rect.top(), rect.width(), rect.height(), + rect.left() + dx, rect.top() + dy); + XFreeGC(xinfo.display(), gc); + return true; +} + +int QX11PlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + switch (metric) { + case QPaintDevice::PdmDevicePixelRatio: + return devicePixelRatio(); + break; + case QPaintDevice::PdmDevicePixelRatioScaled: + return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale(); + break; + case QPaintDevice::PdmWidth: + return w; + case QPaintDevice::PdmHeight: + return h; + case QPaintDevice::PdmNumColors: + return 1 << d; + case QPaintDevice::PdmDepth: + return d; + case QPaintDevice::PdmWidthMM: { + const int screen = xinfo.screen(); + const int mm = DisplayWidthMM(xinfo.display(), screen) * w + / DisplayWidth(xinfo.display(), screen); + return mm; + } + case QPaintDevice::PdmHeightMM: { + const int screen = xinfo.screen(); + const int mm = (DisplayHeightMM(xinfo.display(), screen) * h) + / DisplayHeight(xinfo.display(), screen); + return mm; + } + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmPhysicalDpiX: + return QXcbX11Info::appDpiX(xinfo.screen()); + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiY: + return QXcbX11Info::appDpiY(xinfo.screen()); + default: + qWarning("QX11PlatformPixmap::metric(): Invalid metric"); + return 0; + } +} + +void QX11PlatformPixmap::fill(const QColor &fillColor) +{ + if (fillColor.alpha() != 255) { +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + if (!picture || d != 32) + convertToARGB32(/*preserveContents = */false); + + ::Picture src = X11->getSolidFill(xinfo.screen(), fillColor); + XRenderComposite(xinfo.display(), PictOpSrc, src, 0, picture, + 0, 0, width(), height(), + 0, 0, width(), height()); + } else +#endif + { + QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied); + im.fill(PREMUL(fillColor.rgba())); + release(); + fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither); + } + return; + } + + GC gc = XCreateGC(xinfo.display(), hd, 0, 0); + if (depth() == 1) { + XSetForeground(xinfo.display(), gc, qGray(fillColor.rgb()) > 127 ? 0 : 1); + } else if (X11->use_xrender && d >= 24) { + XSetForeground(xinfo.display(), gc, fillColor.rgba()); + } else { + XSetForeground(xinfo.display(), gc, + QXcbColormap::instance(xinfo.screen()).pixel(fillColor)); + } + XFillRectangle(xinfo.display(), hd, gc, 0, 0, width(), height()); + XFreeGC(xinfo.display(), gc); +} + +QBitmap QX11PlatformPixmap::mask() const +{ + QBitmap mask; +#if QT_CONFIG(xrender) + if (picture && d == 32) { + // #### slow - there must be a better way.. + mask = QBitmap::fromImage(toImage().createAlphaMask()); + } else +#endif + if (d == 1) { + QX11PlatformPixmap *that = const_cast(this); + mask = QPixmap(that); + } else { + mask = mask_to_bitmap(xinfo.screen()); + } + return mask; +} + +void QX11PlatformPixmap::setMask(const QBitmap &newmask) +{ + if (newmask.isNull()) { // clear mask +#if QT_CONFIG(xrender) + if (picture && d == 32) { + QX11PlatformPixmap newData(pixelType()); + newData.resize(w, h); + newData.fill(Qt::black); + XRenderComposite(xinfo.display(), PictOpOver, + picture, 0, newData.picture, + 0, 0, 0, 0, 0, 0, w, h); + release(); + *this = newData; + // the new QX11PlatformPixmap object isn't referenced yet, so + // ref it + ref.ref(); + + // the below is to make sure the QX11PlatformPixmap destructor + // doesn't delete our newly created render picture + newData.hd = 0; + newData.x11_mask = 0; + newData.picture = 0; + newData.mask_picture = 0; + newData.hd2 = 0; + } else +#endif + if (x11_mask) { +#if QT_CONFIG(xrender) + if (picture) { + XRenderPictureAttributes attrs; + attrs.alpha_map = 0; + XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, + &attrs); + } + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + mask_picture = 0; +#endif + XFreePixmap(xinfo.display(), x11_mask); + x11_mask = 0; + } + return; + } + +#if QT_CONFIG(xrender) + if (picture && d == 32) { + XRenderComposite(xinfo.display(), PictOpSrc, + picture, qt_x11Pixmap(newmask)->x11PictureHandle(), + picture, 0, 0, 0, 0, 0, 0, w, h); + } else +#endif + if (depth() == 1) { + XGCValues vals; + vals.function = GXand; + GC gc = XCreateGC(xinfo.display(), hd, GCFunction, &vals); + XCopyArea(xinfo.display(), qt_x11Pixmap(newmask)->handle(), hd, gc, 0, 0, + width(), height(), 0, 0); + XFreeGC(xinfo.display(), gc); + } else { + // ##### should or the masks together + if (x11_mask) { + XFreePixmap(xinfo.display(), x11_mask); +#if QT_CONFIG(xrender) + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); +#endif + } + x11_mask = QX11PlatformPixmap::bitmap_to_mask(newmask, xinfo.screen()); +#if QT_CONFIG(xrender) + if (picture) { + mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, + XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); + XRenderPictureAttributes attrs; + attrs.alpha_map = mask_picture; + XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, &attrs); + } +#endif + } +} + +bool QX11PlatformPixmap::hasAlphaChannel() const +{ + if (picture && d == 32) + return true; + + if (x11_mask && d == 1) + return true; + + return false; +} + +QPixmap QX11PlatformPixmap::transformed(const QTransform &transform, Qt::TransformationMode mode) const +{ + if (mode == Qt::SmoothTransformation || transform.type() >= QTransform::TxProject) { + QImage image = toImage(); + return QPixmap::fromImage(image.transformed(transform, mode)); + } + + uint w = 0; + uint h = 0; // size of target pixmap + uint ws, hs; // size of source pixmap + uchar *dptr; // data in target pixmap + uint dbpl, dbytes; // bytes per line/bytes total + uchar *sptr; // data in original pixmap + int sbpl; // bytes per line in original + int bpp; // bits per pixel + bool depth1 = depth() == 1; + Display *dpy = xinfo.display(); + + ws = width(); + hs = height(); + + QTransform mat(transform.m11(), transform.m12(), transform.m13(), + transform.m21(), transform.m22(), transform.m23(), + 0., 0., 1); + bool complex_xform = false; + qreal scaledWidth; + qreal scaledHeight; + + if (mat.type() <= QTransform::TxScale) { + scaledHeight = qAbs(mat.m22()) * hs + 0.9999; + scaledWidth = qAbs(mat.m11()) * ws + 0.9999; + h = qAbs(int(scaledHeight)); + w = qAbs(int(scaledWidth)); + } else { // rotation or shearing + QPolygonF a(QRectF(0, 0, ws, hs)); + a = mat.map(a); + QRect r = a.boundingRect().toAlignedRect(); + w = r.width(); + h = r.height(); + scaledWidth = w; + scaledHeight = h; + complex_xform = true; + } + mat = QPixmap::trueMatrix(mat, ws, hs); // true matrix + + bool invertible; + mat = mat.inverted(&invertible); // invert matrix + + if (h == 0 || w == 0 || !invertible + || qAbs(scaledWidth) >= 32768 || qAbs(scaledHeight) >= 32768 ) + // error, return null pixmap + return QPixmap(); + + XImage *xi = XGetImage(xinfo.display(), handle(), 0, 0, ws, hs, AllPlanes, + depth1 ? XYPixmap : ZPixmap); + + if (!xi) + return QPixmap(); + + sbpl = xi->bytes_per_line; + sptr = (uchar *)xi->data; + bpp = xi->bits_per_pixel; + + if (depth1) + dbpl = (w+7)/8; + else + dbpl = ((w*bpp+31)/32)*4; + dbytes = dbpl*h; + + dptr = (uchar *)malloc(dbytes); // create buffer for bits + Q_CHECK_PTR(dptr); + if (depth1) // fill with zeros + memset(dptr, 0, dbytes); + else if (bpp == 8) // fill with background color + memset(dptr, WhitePixel(xinfo.display(), xinfo.screen()), dbytes); + else + memset(dptr, 0, dbytes); + + // #define QT_DEBUG_XIMAGE +#if defined(QT_DEBUG_XIMAGE) + qDebug("----IMAGE--INFO--------------"); + qDebug("width............. %d", xi->width); + qDebug("height............ %d", xi->height); + qDebug("xoffset........... %d", xi->xoffset); + qDebug("format............ %d", xi->format); + qDebug("byte order........ %d", xi->byte_order); + qDebug("bitmap unit....... %d", xi->bitmap_unit); + qDebug("bitmap bit order.. %d", xi->bitmap_bit_order); + qDebug("depth............. %d", xi->depth); + qDebug("bytes per line.... %d", xi->bytes_per_line); + qDebug("bits per pixel.... %d", xi->bits_per_pixel); +#endif + + int type; + if (xi->bitmap_bit_order == MSBFirst) + type = QT_XFORM_TYPE_MSBFIRST; + else + type = QT_XFORM_TYPE_LSBFIRST; + int xbpl, p_inc; + if (depth1) { + xbpl = (w+7)/8; + p_inc = dbpl - xbpl; + } else { + xbpl = (w*bpp)/8; + p_inc = dbpl - xbpl; + } + + if (!qt_xForm_helper(mat, xi->xoffset, type, bpp, dptr, xbpl, p_inc, h, sptr, sbpl, ws, hs)){ + qWarning("QPixmap::transform: display not supported (bpp=%d)",bpp); + QPixmap pm; + return pm; + } + + qSafeXDestroyImage(xi); + + if (depth1) { // mono bitmap + QBitmap bm = QBitmap::fromData(QSize(w, h), dptr, + BitmapBitOrder(xinfo.display()) == MSBFirst + ? QImage::Format_Mono + : QImage::Format_MonoLSB); + free(dptr); + return bm; + } else { // color pixmap + QX11PlatformPixmap *x11Data = new QX11PlatformPixmap(QPlatformPixmap::PixmapType); + QPixmap pm(x11Data); + x11Data->flags &= ~QX11PlatformPixmap::Uninitialized; + x11Data->xinfo = xinfo; + x11Data->d = d; + x11Data->w = w; + x11Data->h = h; + x11Data->is_null = (w <= 0 || h <= 0); + x11Data->hd = XCreatePixmap(xinfo.display(), + RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); + x11Data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + XRenderPictFormat *format = x11Data->d == 32 + ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) + : XRenderFindVisualFormat(xinfo.display(), (Visual *) x11Data->xinfo.visual()); + x11Data->picture = XRenderCreatePicture(xinfo.display(), x11Data->hd, format, 0, 0); + } +#endif // QT_CONFIG(xrender) + + GC gc = XCreateGC(xinfo.display(), x11Data->hd, 0, 0); + xi = XCreateImage(dpy, (Visual*)x11Data->xinfo.visual(), + x11Data->d, + ZPixmap, 0, (char *)dptr, w, h, 32, 0); + XPutImage(dpy, qt_x11Pixmap(pm)->handle(), gc, xi, 0, 0, 0, 0, w, h); + qSafeXDestroyImage(xi); + XFreeGC(xinfo.display(), gc); + + if (x11_mask) { // xform mask, too + pm.setMask(mask_to_bitmap(xinfo.screen()).transformed(transform)); + } else if (d != 32 && complex_xform) { // need a mask! + QBitmap mask(ws, hs); + mask.fill(Qt::color1); + pm.setMask(mask.transformed(transform)); + } + return pm; + } +} + +QImage QX11PlatformPixmap::toImage() const +{ + return toImage(QRect(0, 0, w, h)); +} + +QImage QX11PlatformPixmap::toImage(const QRect &rect) const +{ + Window root_return; + int x_return; + int y_return; + unsigned int width_return; + unsigned int height_return; + unsigned int border_width_return; + unsigned int depth_return; + + XGetGeometry(xinfo.display(), hd, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return); + + QXImageWrapper xiWrapper; + xiWrapper.xi = XGetImage(xinfo.display(), hd, rect.x(), rect.y(), rect.width(), rect.height(), + AllPlanes, (depth() == 1) ? XYPixmap : ZPixmap); + + Q_CHECK_PTR(xiWrapper.xi); + if (!xiWrapper.xi) + return QImage(); + + if (!x11_mask && canTakeQImageFromXImage(xiWrapper)) + return takeQImageFromXImage(xiWrapper); + + QImage image = toImage(xiWrapper, rect); + qSafeXDestroyImage(xiWrapper.xi); + return image; +} + +#if QT_CONFIG(xrender) +static XRenderPictFormat *qt_renderformat_for_depth(const QXcbX11Info &xinfo, int depth) +{ + if (depth == 1) + return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); + else if (depth == 32) + return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); + else + return XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); +} +#endif + +Q_GLOBAL_STATIC(QX11PaintEngine, qt_x11_paintengine) + +QPaintEngine *QX11PlatformPixmap::paintEngine() const +{ + QX11PlatformPixmap *that = const_cast(this); + + if ((flags & Readonly)/* && share_mode == QPixmap::ImplicitlyShared*/) { + // if someone wants to draw onto us, copy the shared contents + // and turn it into a fully fledged QPixmap + ::Pixmap hd_copy = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), + w, h, d); +#if QT_CONFIG(xrender) + if (picture && d == 32) { + XRenderPictFormat *format = qt_renderformat_for_depth(xinfo, d); + ::Picture picture_copy = XRenderCreatePicture(xinfo.display(), + hd_copy, format, + 0, 0); + + XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, picture_copy, + 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(xinfo.display(), picture); + that->picture = picture_copy; + } else +#endif + { + GC gc = XCreateGC(xinfo.display(), hd_copy, 0, 0); + XCopyArea(xinfo.display(), hd, hd_copy, gc, 0, 0, w, h, 0, 0); + XFreeGC(xinfo.display(), gc); + } + that->hd = hd_copy; + that->flags &= ~QX11PlatformPixmap::Readonly; + } + + if (qt_x11_paintengine->isActive()) { + if (!that->pengine) + that->pengine = new QX11PaintEngine; + + return that->pengine; + } + + return qt_x11_paintengine(); +} + +qreal QX11PlatformPixmap::devicePixelRatio() const +{ + return dpr; +} + +void QX11PlatformPixmap::setDevicePixelRatio(qreal scaleFactor) +{ + dpr = scaleFactor; +} + +Pixmap QX11PlatformPixmap::x11ConvertToDefaultDepth() +{ +#if QT_CONFIG(xrender) + if (d == xinfo.appDepth() || !X11->use_xrender) + return hd; + if (!hd2) { + hd2 = XCreatePixmap(xinfo.display(), hd, w, h, xinfo.appDepth()); + XRenderPictFormat *format = XRenderFindVisualFormat(xinfo.display(), + (Visual*) xinfo.visual()); + Picture pic = XRenderCreatePicture(xinfo.display(), hd2, format, 0, 0); + XRenderComposite(xinfo.display(), PictOpSrc, picture, + XNone, pic, 0, 0, 0, 0, 0, 0, w, h); + XRenderFreePicture(xinfo.display(), pic); + } + return hd2; +#else + return hd; +#endif +} + +XID QX11PlatformPixmap::createBitmapFromImage(const QImage &image) +{ + QImage img = image.convertToFormat(QImage::Format_MonoLSB); + const QRgb c0 = QColor(Qt::black).rgb(); + const QRgb c1 = QColor(Qt::white).rgb(); + if (img.color(0) == c0 && img.color(1) == c1) { + img.invertPixels(); + img.setColor(0, c1); + img.setColor(1, c0); + } + + char *bits; + uchar *tmp_bits; + int w = img.width(); + int h = img.height(); + int bpl = (w + 7) / 8; + int ibpl = img.bytesPerLine(); + if (bpl != ibpl) { + tmp_bits = new uchar[bpl*h]; + bits = (char *)tmp_bits; + uchar *p, *b; + int y; + b = tmp_bits; + p = img.scanLine(0); + for (y = 0; y < h; y++) { + memcpy(b, p, bpl); + b += bpl; + p += ibpl; + } + } else { + bits = (char *)img.bits(); + tmp_bits = 0; + } + XID hd = XCreateBitmapFromData(QXcbX11Info::display(), + QXcbX11Info::appRootWindow(), + bits, w, h); + if (tmp_bits) // Avoid purify complaint + delete [] tmp_bits; + return hd; +} + +#if QT_CONFIG(xrender) +void QX11PlatformPixmap::convertToARGB32(bool preserveContents) +{ + if (!X11->use_xrender) + return; + + // Q_ASSERT(count == 1); + if ((flags & Readonly)/* && share_mode == QPixmap::ExplicitlyShared*/) + return; + + Pixmap pm = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), + w, h, 32); + Picture p = XRenderCreatePicture(xinfo.display(), pm, + XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32), 0, 0); + if (picture) { + if (preserveContents) + XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, p, 0, 0, 0, 0, 0, 0, w, h); + if (!(flags & Readonly)) + XRenderFreePicture(xinfo.display(), picture); + } + if (hd && !(flags & Readonly)) + XFreePixmap(xinfo.display(), hd); + if (x11_mask) { + XFreePixmap(xinfo.display(), x11_mask); + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + x11_mask = 0; + mask_picture = 0; + } + hd = pm; + picture = p; + + d = 32; + xinfo.setDepth(32); + + XVisualInfo visinfo; + if (XMatchVisualInfo(xinfo.display(), xinfo.screen(), 32, TrueColor, &visinfo)) + xinfo.setVisual(visinfo.visual); +} +#endif + +void QX11PlatformPixmap::release() +{ + delete pengine; + pengine = 0; + + if (/*!X11*/ QCoreApplication::closingDown()) { + // At this point, the X server will already have freed our resources, + // so there is nothing to do. + return; + } + + if (x11_mask) { +#if QT_CONFIG(xrender) + if (mask_picture) + XRenderFreePicture(xinfo.display(), mask_picture); + mask_picture = 0; +#endif + XFreePixmap(xinfo.display(), x11_mask); + x11_mask = 0; + } + + if (hd) { +#if QT_CONFIG(xrender) + if (picture) { + XRenderFreePicture(xinfo.display(), picture); + picture = 0; + } +#endif // QT_CONFIG(xrender) + + if (hd2) { + XFreePixmap(xinfo.display(), hd2); + hd2 = 0; + } + if (!(flags & Readonly)) + XFreePixmap(xinfo.display(), hd); + hd = 0; + } +} + +QImage QX11PlatformPixmap::toImage(const QXImageWrapper &xiWrapper, const QRect &rect) const +{ + XImage *xi = xiWrapper.xi; + + int d = depth(); + Visual *visual = (Visual *)xinfo.visual(); + bool trucol = (visual->c_class >= TrueColor) && d > 1; + + QImage::Format format = QImage::Format_Mono; + if (d > 1 && d <= 8) { + d = 8; + format = QImage::Format_Indexed8; + } + // we could run into the situation where d == 8 AND trucol is true, which can + // cause problems when converting to and from images. in this case, always treat + // the depth as 32... + if (d > 8 || trucol) { + d = 32; + format = QImage::Format_RGB32; + } + + if (d == 1 && xi->bitmap_bit_order == LSBFirst) + format = QImage::Format_MonoLSB; + if (x11_mask && format == QImage::Format_RGB32) + format = QImage::Format_ARGB32; + + QImage image(xi->width, xi->height, format); + image.setDevicePixelRatio(devicePixelRatio()); + if (image.isNull()) // could not create image + return image; + + QImage alpha; + if (x11_mask) { + if (rect.contains(QRect(0, 0, w, h))) + alpha = mask().toImage(); + else + alpha = mask().toImage().copy(rect); + } + bool ale = alpha.format() == QImage::Format_MonoLSB; + + if (trucol) { // truecolor + const uint red_mask = (uint)visual->red_mask; + const uint green_mask = (uint)visual->green_mask; + const uint blue_mask = (uint)visual->blue_mask; + const int red_shift = highest_bit(red_mask) - 7; + const int green_shift = highest_bit(green_mask) - 7; + const int blue_shift = highest_bit(blue_mask) - 7; + + const uint red_bits = n_bits(red_mask); + const uint green_bits = n_bits(green_mask); + const uint blue_bits = n_bits(blue_mask); + + static uint red_table_bits = 0; + static uint green_table_bits = 0; + static uint blue_table_bits = 0; + + if (red_bits < 8 && red_table_bits != red_bits) { + build_scale_table(&red_scale_table, red_bits); + red_table_bits = red_bits; + } + if (blue_bits < 8 && blue_table_bits != blue_bits) { + build_scale_table(&blue_scale_table, blue_bits); + blue_table_bits = blue_bits; + } + if (green_bits < 8 && green_table_bits != green_bits) { + build_scale_table(&green_scale_table, green_bits); + green_table_bits = green_bits; + } + + int r, g, b; + + QRgb *dst; + uchar *src; + uint pixel; + int bppc = xi->bits_per_pixel; + + if (bppc > 8 && xi->byte_order == LSBFirst) + bppc++; + + for (int y = 0; y < xi->height; ++y) { + uchar* asrc = x11_mask ? alpha.scanLine(y) : 0; + dst = (QRgb *)image.scanLine(y); + src = (uchar *)xi->data + xi->bytes_per_line*y; + for (int x = 0; x < xi->width; x++) { + switch (bppc) { + case 8: + pixel = *src++; + break; + case 16: // 16 bit MSB + pixel = src[1] | (uint)src[0] << 8; + src += 2; + break; + case 17: // 16 bit LSB + pixel = src[0] | (uint)src[1] << 8; + src += 2; + break; + case 24: // 24 bit MSB + pixel = src[2] | (uint)src[1] << 8 | (uint)src[0] << 16; + src += 3; + break; + case 25: // 24 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16; + src += 3; + break; + case 32: // 32 bit MSB + pixel = src[3] | (uint)src[2] << 8 | (uint)src[1] << 16 | (uint)src[0] << 24; + src += 4; + break; + case 33: // 32 bit LSB + pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16 | (uint)src[3] << 24; + src += 4; + break; + default: // should not really happen + x = xi->width; // leave loop + y = xi->height; + pixel = 0; // eliminate compiler warning + qWarning("QPixmap::convertToImage: Invalid depth %d", bppc); + } + if (red_shift > 0) + r = (pixel & red_mask) >> red_shift; + else + r = (pixel & red_mask) << -red_shift; + if (green_shift > 0) + g = (pixel & green_mask) >> green_shift; + else + g = (pixel & green_mask) << -green_shift; + if (blue_shift > 0) + b = (pixel & blue_mask) >> blue_shift; + else + b = (pixel & blue_mask) << -blue_shift; + + if (red_bits < 8) + r = red_scale_table[r]; + if (green_bits < 8) + g = green_scale_table[g]; + if (blue_bits < 8) + b = blue_scale_table[b]; + + if (x11_mask) { + if (ale) { + *dst++ = (asrc[x >> 3] & (1 << (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } else { + *dst++ = (asrc[x >> 3] & (0x80 >> (x & 7))) ? qRgba(r, g, b, 0xff) : 0; + } + } else { + *dst++ = qRgb(r, g, b); + } + } + } + } else if (xi->bits_per_pixel == d) { // compatible depth + char *xidata = xi->data; // copy each scanline + int bpl = qMin(image.bytesPerLine(),xi->bytes_per_line); + for (int y=0; yheight; y++) { + memcpy(image.scanLine(y), xidata, bpl); + xidata += xi->bytes_per_line; + } + } else { + /* Typically 2 or 4 bits display depth */ + qWarning("QPixmap::convertToImage: Display not supported (bpp=%d)", + xi->bits_per_pixel); + return QImage(); + } + + if (d == 1) { // bitmap + image.setColorCount(2); + image.setColor(0, qRgb(255,255,255)); + image.setColor(1, qRgb(0,0,0)); + } else if (!trucol) { // pixmap with colormap + register uchar *p; + uchar *end; + uchar use[256]; // pixel-in-use table + uchar pix[256]; // pixel translation table + int ncols, bpl; + memset(use, 0, 256); + memset(pix, 0, 256); + bpl = image.bytesPerLine(); + + if (x11_mask) { // which pixels are used? + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (1 << (x & 7))) + use[*p] = 1; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (asrc[x >> 3] & (0x80 >> (x & 7))) + use[*p] = 1; + ++p; + } + } + } + } else { + for (int i = 0; i < xi->height; i++) { + p = image.scanLine(i); + end = p + bpl; + while (p < end) + use[*p++] = 1; + } + } + ncols = 0; + for (int i = 0; i < 256; i++) { // build translation table + if (use[i]) + pix[i] = ncols++; + } + for (int i = 0; i < xi->height; i++) { // translate pixels + p = image.scanLine(i); + end = p + bpl; + while (p < end) { + *p = pix[*p]; + p++; + } + } + if (x11_mask) { + int trans; + if (ncols < 256) { + trans = ncols++; + image.setColorCount(ncols); // create color table + image.setColor(trans, 0x00000000); + } else { + image.setColorCount(ncols); // create color table + // oh dear... no spare "transparent" pixel. + // use first pixel in image (as good as any). + trans = image.scanLine(0)[0]; + } + for (int i = 0; i < xi->height; i++) { + uchar* asrc = alpha.scanLine(i); + p = image.scanLine(i); + if (ale) { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (x & 7)))) + *p = trans; + ++p; + } + } else { + for (int x = 0; x < xi->width; x++) { + if (!(asrc[x >> 3] & (1 << (7 -(x & 7))))) + *p = trans; + ++p; + } + } + } + } else { + image.setColorCount(ncols); // create color table + } + QVector colors = QXcbColormap::instance(xinfo.screen()).colormap(); + int j = 0; + for (int i=0; i +#include + +#include +#include "qxcbnativepainting.h" + +typedef unsigned long XID; +typedef XID Drawable; +typedef XID Picture; +typedef XID Pixmap; + +QT_BEGIN_NAMESPACE + +class QX11PaintEngine; +struct QXImageWrapper; + +class QX11PlatformPixmap : public QPlatformPixmap +{ +public: + QX11PlatformPixmap(PixelType pixelType); + ~QX11PlatformPixmap(); + + QPlatformPixmap *createCompatiblePlatformPixmap() const Q_DECL_OVERRIDE; + void resize(int width, int height) Q_DECL_OVERRIDE; + void fromImage(const QImage &img, Qt::ImageConversionFlags flags) Q_DECL_OVERRIDE; + void copy(const QPlatformPixmap *data, const QRect &rect) Q_DECL_OVERRIDE; + bool scroll(int dx, int dy, const QRect &rect) Q_DECL_OVERRIDE; + int metric(QPaintDevice::PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + void fill(const QColor &fillColor) Q_DECL_OVERRIDE; + QBitmap mask() const Q_DECL_OVERRIDE; + void setMask(const QBitmap &mask) Q_DECL_OVERRIDE; + bool hasAlphaChannel() const Q_DECL_OVERRIDE; + QPixmap transformed(const QTransform &matrix, Qt::TransformationMode mode) const Q_DECL_OVERRIDE; + QImage toImage() const Q_DECL_OVERRIDE; + QImage toImage(const QRect &rect) const Q_DECL_OVERRIDE; + QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; + qreal devicePixelRatio() const Q_DECL_OVERRIDE; + void setDevicePixelRatio(qreal scaleFactor) Q_DECL_OVERRIDE; + + inline Drawable handle() const { return hd; } + inline Picture x11PictureHandle() const { return picture; } + inline const QXcbX11Info *x11_info() const { return &xinfo; } + + Pixmap x11ConvertToDefaultDepth(); + static XID createBitmapFromImage(const QImage &image); + +#if QT_CONFIG(xrender) + void convertToARGB32(bool preserveContents = true); +#endif + +private: + friend class QX11PaintEngine; + friend const QXcbX11Info &qt_x11Info(const QPixmap &pixmap); + friend void qt_x11SetScreen(QPixmap &pixmap, int screen); + + void release(); + QImage toImage(const QXImageWrapper &xi, const QRect &rect) const; + QBitmap mask_to_bitmap(int screen) const; + static Pixmap bitmap_to_mask(const QBitmap &, int screen); + void bitmapFromImage(const QImage &image); + bool canTakeQImageFromXImage(const QXImageWrapper &xi) const; + QImage takeQImageFromXImage(const QXImageWrapper &xi) const; + + Pixmap hd = 0; + + enum Flag { + NoFlags = 0x0, + Uninitialized = 0x1, + Readonly = 0x2, + InvertedWhenBoundToTexture = 0x4, + GlSurfaceCreatedWithAlpha = 0x8 + }; + uint flags; + + QXcbX11Info xinfo; + Pixmap x11_mask; + Picture picture; + Picture mask_picture; + Pixmap hd2; // sorted in the default display depth + //QPixmap::ShareMode share_mode; + qreal dpr; + + QX11PaintEngine *pengine; +}; + +inline QX11PlatformPixmap *qt_x11Pixmap(const QPixmap &pixmap) +{ + return (pixmap.handle() && pixmap.handle()->classId() == QPlatformPixmap::X11Class) + ? static_cast(pixmap.handle()) + : Q_NULLPTR; +} + +inline Picture qt_x11PictureHandle(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) + return pm->x11PictureHandle(); + + return 0; +} + +inline Pixmap qt_x11PixmapHandle(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) + return pm->handle(); + + return 0; +} + +inline const QXcbX11Info &qt_x11Info(const QPixmap &pixmap) +{ + if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) { + return pm->xinfo; + } else { + static QXcbX11Info nullX11Info; + return nullX11Info; + } +} + +int qt_x11SetDefaultScreen(int screen); +void qt_x11SetScreen(QPixmap &pixmap, int screen); + +QT_END_NAMESPACE + +#endif // QX11PLATFORMPIXMAP_H diff --git a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h new file mode 100644 index 0000000000..a0e5131ea7 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_X11_P_H +#define QT_X11_P_H + +#include +#include + +#if QT_CONFIG(xrender) +# include "qtessellator_p.h" +# include +#endif + +#if QT_CONFIG(fontconfig) +#include +#endif + +#if defined(FT_LCD_FILTER_H) +#include FT_LCD_FILTER_H +#endif + +#if defined(FC_LCD_FILTER) + +#ifndef FC_LCD_FILTER_NONE +#define FC_LCD_FILTER_NONE FC_LCD_NONE +#endif + +#ifndef FC_LCD_FILTER_DEFAULT +#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT +#endif + +#ifndef FC_LCD_FILTER_LIGHT +#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT +#endif + +#ifndef FC_LCD_FILTER_LEGACY +#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY +#endif + +#endif + +QT_BEGIN_NAMESPACE + +// rename a couple of X defines to get rid of name clashes +// resolve the conflict between X11's FocusIn and QEvent::FocusIn +enum { + XFocusOut = FocusOut, + XFocusIn = FocusIn, + XKeyPress = KeyPress, + XKeyRelease = KeyRelease, + XNone = None, + XRevertToParent = RevertToParent, + XGrayScale = GrayScale, + XCursorShape = CursorShape, +}; +#undef FocusOut +#undef FocusIn +#undef KeyPress +#undef KeyRelease +#undef None +#undef RevertToParent +#undef GrayScale +#undef CursorShape + +#ifdef FontChange +#undef FontChange +#endif + +Q_DECLARE_TYPEINFO(XPoint, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XRectangle, Q_PRIMITIVE_TYPE); +Q_DECLARE_TYPEINFO(XChar2b, Q_PRIMITIVE_TYPE); +#if QT_CONFIG(xrender) +Q_DECLARE_TYPEINFO(XGlyphElt32, Q_PRIMITIVE_TYPE); +#endif + +struct QX11InfoData; + +enum DesktopEnvironment { + DE_UNKNOWN, + DE_KDE, + DE_GNOME, + DE_CDE, + DE_MEEGO_COMPOSITOR, + DE_4DWM +}; + +struct QXcbX11Data { + Display *display = nullptr; + + // true if Qt is compiled w/ RENDER support and RENDER is supported on the connected Display + bool use_xrender = false; + int xrender_major = 0; + int xrender_version = 0; + + QX11InfoData *screens = nullptr; + Visual **argbVisuals = nullptr; + Colormap *argbColormaps = nullptr; + int screenCount = 0; + int defaultScreen = 0; + + // options + int visual_class = 0; + int visual_id = 0; + int color_count = 0; + bool custom_cmap = false; + + // outside visual/colormap + Visual *visual = nullptr; + Colormap colormap = 0; + +#if QT_CONFIG(xrender) + enum { solid_fill_count = 16 }; + struct SolidFills { + XRenderColor color; + int screen; + Picture picture; + } solid_fills[solid_fill_count]; + enum { pattern_fill_count = 16 }; + struct PatternFills { + XRenderColor color; + XRenderColor bg_color; + int screen; + int style; + bool opaque; + Picture picture; + } pattern_fills[pattern_fill_count]; + Picture getSolidFill(int screen, const QColor &c); + XRenderColor preMultiply(const QColor &c); +#endif + + bool fc_antialias = true; + int fc_hint_style = 0; + + DesktopEnvironment desktopEnvironment = DE_GNOME; +}; + +extern QXcbX11Data *qt_x11Data; +#define X11 qt_x11Data + +struct QX11InfoData { + int screen; + int dpiX; + int dpiY; + int depth; + int cells; + Colormap colormap; + Visual *visual; + bool defaultColormap; + bool defaultVisual; + int subpixel = 0; +}; + +template +Q_DECL_RELAXED_CONSTEXPR inline int lowest_bit(T v) Q_DECL_NOTHROW +{ + int result = qCountTrailingZeroBits(v); + return ((result >> 3) == sizeof(T)) ? -1 : result; +} + +QT_END_NAMESPACE + +#endif // QT_X11_P_H diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp new file mode 100644 index 0000000000..7ffdef54f0 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp @@ -0,0 +1,1491 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtessellator_p.h" + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +//#define DEBUG +#ifdef DEBUG +#define QDEBUG qDebug +#else +#define QDEBUG if (1){} else qDebug +#endif + +static const bool emit_clever = true; +static const bool mark_clever = false; + +enum VertexFlags { + LineBeforeStarts = 0x1, + LineBeforeEnds = 0x2, + LineBeforeHorizontal = 0x4, + LineAfterStarts = 0x8, + LineAfterEnds = 0x10, + LineAfterHorizontal = 0x20 +}; + + + +class QTessellatorPrivate { +public: + struct Vertices; + + QTessellatorPrivate() {} + + QRectF collectAndSortVertices(const QPointF *points, int *maxActiveEdges); + void cancelCoincidingEdges(); + + void emitEdges(QTessellator *tessellator); + void processIntersections(); + void removeEdges(); + void addEdges(); + void addIntersections(); + + struct Vertex : public QTessellator::Vertex + { + int flags; + }; + + struct Intersection + { + Q27Dot5 y; + int edge; + bool operator <(const Intersection &other) const { + if (y != other.y) + return y < other.y; + return edge < other.edge; + } + }; + struct IntersectionLink + { + int next; + int prev; + }; + typedef QMap Intersections; + + struct Edge { + Edge(const Vertices &v, int _edge); + int edge; + const Vertex *v0; + const Vertex *v1; + Q27Dot5 y_left; + Q27Dot5 y_right; + signed int winding : 8; + bool mark; + bool free; + bool intersect_left; + bool intersect_right; + bool isLeftOf(const Edge &other, Q27Dot5 y) const; + Q27Dot5 positionAt(Q27Dot5 y) const; + bool intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const; + + }; + + class EdgeSorter + { + public: + EdgeSorter(int _y) : y(_y) {} + bool operator() (const Edge *e1, const Edge *e2); + int y; + }; + + class Scanline { + public: + Scanline(); + ~Scanline(); + + void init(int maxActiveEdges); + void done(); + + int findEdgePosition(Q27Dot5 x, Q27Dot5 y) const; + int findEdgePosition(const Edge &e) const; + int findEdge(int edge) const; + void clearMarks(); + + void swap(int p1, int p2) { + Edge *tmp = edges[p1]; + edges[p1] = edges[p2]; + edges[p2] = tmp; + } + void insert(int pos, const Edge &e); + void removeAt(int pos); + void markEdges(int pos1, int pos2); + + void prepareLine(); + void lineDone(); + + Edge **old; + int old_size; + + Edge **edges; + int size; + + private: + Edge *edge_table; + int first_unused; + int max_edges; + enum { default_alloc = 32 }; + }; + + struct Vertices { + enum { default_alloc = 128 }; + Vertices(); + ~Vertices(); + void init(int maxVertices); + void done(); + Vertex *storage; + Vertex **sorted; + + Vertex *operator[] (int i) { return storage + i; } + const Vertex *operator[] (int i) const { return storage + i; } + int position(const Vertex *v) const { + return v - storage; + } + Vertex *next(Vertex *v) { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + const Vertex *next(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + v = storage; + return v; + } + int nextPos(const Vertex *v) const { + ++v; + if (v == storage + nPoints) + return 0; + return v - storage; + } + Vertex *prev(Vertex *v) { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + const Vertex *prev(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v; + } + int prevPos(const Vertex *v) const { + if (v == storage) + v = storage + nPoints; + --v; + return v - storage; + } + int nPoints; + int allocated; + }; + Vertices vertices; + Intersections intersections; + Scanline scanline; + bool winding; + Q27Dot5 y; + int currentVertex; + +private: + void addIntersection(const Edge *e1, const Edge *e2); + bool edgeInChain(Intersection i, int edge); +}; + + +QTessellatorPrivate::Edge::Edge(const QTessellatorPrivate::Vertices &vertices, int edge) +{ + this->edge = edge; + intersect_left = intersect_right = true; + mark = false; + free = false; + + v0 = vertices[edge]; + v1 = vertices.next(v0); + + Q_ASSERT(v0->y != v1->y); + + if (v0->y > v1->y) { + qSwap(v0, v1); + winding = -1; + } else { + winding = 1; + } + y_left = y_right = v0->y; +} + +// This is basically the algorithm from graphics gems. The algorithm +// is cubic in the coordinates at one place. Since we use 64bit +// integers, this implies, that the allowed range for our coordinates +// is limited to 21 bits. With 5 bits behind the decimal, this +// implies that differences in coordaintes can range from 2*SHORT_MIN +// to 2*SHORT_MAX, giving us efficiently a coordinate system from +// SHORT_MIN to SHORT_MAX. +// + +// WARNING: It's absolutely critical that the intersect() and isLeftOf() methods use +// exactly the same algorithm to calulate yi. It's also important to be sure the algorithms +// are transitive (ie. the conditions below are true for all input data): +// +// a.intersect(b) == b.intersect(a) +// a.isLeftOf(b) != b.isLeftOf(a) +// +// This is tricky to get right, so be very careful when changing anything in here! + +static inline bool sameSign(qint64 a, qint64 b) { + return (((qint64) ((quint64) a ^ (quint64) b)) >= 0 ); +} + +bool QTessellatorPrivate::Edge::intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const +{ + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) + return false; + + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 r3 = a1 * other.v0->x + b1 * other.v0->y + c1; + qint64 r4 = a1 * other.v1->x + b1 * other.v1->y + c1; + + // Check signs of r3 and r4. If both point 3 and point 4 lie on + // same side of line 1, the line segments do not intersect. + QDEBUG() << " " << r3 << r4; + if (r3 != 0 && r4 != 0 && sameSign( r3, r4 )) + return false; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; + qint64 r2 = a2 * v1->x + b2 * v1->y + c2; + + // Check signs of r1 and r2. If both point 1 and point 2 lie + // on same side of second line segment, the line segments do not intersect. + QDEBUG() << " " << r1 << r2; + if (r1 != 0 && r2 != 0 && sameSign( r1, r2 )) + return false; + + // The det/2 is to get rounding instead of truncating. It + // is added or subtracted to the numerator, depending upon the + // sign of the numerator. + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + *y = ( num < 0 ? num - offset : num + offset ) / det; + + *det_positive = (det > 0); + + return true; +} + +#undef SAME_SIGNS + +bool QTessellatorPrivate::Edge::isLeftOf(const Edge &other, Q27Dot5 y) const +{ +// QDEBUG() << "isLeftOf" << edge << other.edge << y; + qint64 a1 = v1->y - v0->y; + qint64 b1 = v0->x - v1->x; + qint64 a2 = other.v1->y - other.v0->y; + qint64 b2 = other.v0->x - other.v1->x; + + qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; + + qint64 det = a1 * b2 - a2 * b1; + if (det == 0) { + // lines are parallel. Only need to check side of one point + // fixed ordering for coincident edges + qint64 r1 = a2 * v0->x + b2 * v0->y + c2; +// QDEBUG() << "det = 0" << r1; + if (r1 == 0) + return edge < other.edge; + return (r1 < 0); + } + + // not parallel, need to find the y coordinate of the intersection point + qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; + + qint64 offset = det < 0 ? -det : det; + offset >>= 1; + + qint64 num = a2 * c1 - a1 * c2; + qint64 yi = ( num < 0 ? num - offset : num + offset ) / det; +// QDEBUG() << " num=" << num << "offset=" << offset << "det=" << det; + + return ((yi > y) ^ (det < 0)); +} + +static inline bool compareVertex(const QTessellatorPrivate::Vertex *p1, + const QTessellatorPrivate::Vertex *p2) +{ + if (p1->y == p2->y) { + if (p1->x == p2->x) + return p1 < p2; + return p1->x < p2->x; + } + return p1->y < p2->y; +} + +Q27Dot5 QTessellatorPrivate::Edge::positionAt(Q27Dot5 y) const +{ + if (y == v0->y) + return v0->x; + else if (y == v1->y) + return v1->x; + + qint64 d = v1->x - v0->x; + return (v0->x + d*(y - v0->y)/(v1->y-v0->y)); +} + +bool QTessellatorPrivate::EdgeSorter::operator() (const Edge *e1, const Edge *e2) +{ + return e1->isLeftOf(*e2, y); +} + + +QTessellatorPrivate::Scanline::Scanline() +{ + edges = 0; + edge_table = 0; + old = 0; +} + +void QTessellatorPrivate::Scanline::init(int maxActiveEdges) +{ + maxActiveEdges *= 2; + if (!edges || maxActiveEdges > default_alloc) { + max_edges = maxActiveEdges; + int s = qMax(maxActiveEdges + 1, default_alloc + 1); + edges = q_check_ptr((Edge **)realloc(edges, s*sizeof(Edge *))); + edge_table = q_check_ptr((Edge *)realloc(edge_table, s*sizeof(Edge))); + old = q_check_ptr((Edge **)realloc(old, s*sizeof(Edge *))); + } + size = 0; + old_size = 0; + first_unused = 0; + for (int i = 0; i < maxActiveEdges; ++i) + edge_table[i].edge = i+1; + edge_table[maxActiveEdges].edge = -1; +} + +void QTessellatorPrivate::Scanline::done() +{ + if (max_edges > default_alloc) { + free(edges); + free(old); + free(edge_table); + edges = 0; + old = 0; + edge_table = 0; + } +} + +QTessellatorPrivate::Scanline::~Scanline() +{ + free(edges); + free(old); + free(edge_table); +} + +int QTessellatorPrivate::Scanline::findEdgePosition(Q27Dot5 x, Q27Dot5 y) const +{ + int min = 0; + int max = size - 1; + while (min < max) { + int pos = min + ((max - min + 1) >> 1); + Q27Dot5 ax = edges[pos]->positionAt(y); + if (ax > x) { + max = pos - 1; + } else { + min = pos; + } + } + return min; +} + +int QTessellatorPrivate::Scanline::findEdgePosition(const Edge &e) const +{ +// qDebug() << ">> findEdgePosition"; + int min = 0; + int max = size; + while (min < max) { + int pos = min + ((max - min) >> 1); +// qDebug() << " " << min << max << pos << edges[pos]->isLeftOf(e, e.y0); + if (edges[pos]->isLeftOf(e, e.v0->y)) { + min = pos + 1; + } else { + max = pos; + } + } +// qDebug() << "<< findEdgePosition got" << min; + return min; +} + +int QTessellatorPrivate::Scanline::findEdge(int edge) const +{ + for (int i = 0; i < size; ++i) { + int item_edge = edges[i]->edge; + if (item_edge == edge) + return i; + } + //Q_ASSERT(false); + return -1; +} + +void QTessellatorPrivate::Scanline::clearMarks() +{ + for (int i = 0; i < size; ++i) { + edges[i]->mark = false; + edges[i]->intersect_left = false; + edges[i]->intersect_right = false; + } +} + +void QTessellatorPrivate::Scanline::prepareLine() +{ + Edge **end = edges + size; + Edge **e = edges; + Edge **o = old; + while (e < end) { + *o = *e; + ++o; + ++e; + } + old_size = size; +} + +void QTessellatorPrivate::Scanline::lineDone() +{ + Edge **end = old + old_size; + Edge **e = old; + while (e < end) { + if ((*e)->free) { + (*e)->edge = first_unused; + first_unused = (*e - edge_table); + } + ++e; + } +} + +void QTessellatorPrivate::Scanline::insert(int pos, const Edge &e) +{ + Edge *edge = edge_table + first_unused; + first_unused = edge->edge; + Q_ASSERT(first_unused != -1); + *edge = e; + memmove(edges + pos + 1, edges + pos, (size - pos)*sizeof(Edge *)); + edges[pos] = edge; + ++size; +} + +void QTessellatorPrivate::Scanline::removeAt(int pos) +{ + Edge *e = edges[pos]; + e->free = true; + --size; + memmove(edges + pos, edges + pos + 1, (size - pos)*sizeof(Edge *)); +} + +void QTessellatorPrivate::Scanline::markEdges(int pos1, int pos2) +{ + if (pos2 < pos1) + return; + + for (int i = pos1; i <= pos2; ++i) + edges[i]->mark = true; +} + + +QTessellatorPrivate::Vertices::Vertices() +{ + storage = 0; + sorted = 0; + allocated = 0; + nPoints = 0; +} + +QTessellatorPrivate::Vertices::~Vertices() +{ + if (storage) { + free(storage); + free(sorted); + } +} + +void QTessellatorPrivate::Vertices::init(int maxVertices) +{ + if (!storage || maxVertices > allocated) { + int size = qMax((int)default_alloc, maxVertices); + storage = q_check_ptr((Vertex *)realloc(storage, size*sizeof(Vertex))); + sorted = q_check_ptr((Vertex **)realloc(sorted, size*sizeof(Vertex *))); + allocated = maxVertices; + } +} + +void QTessellatorPrivate::Vertices::done() +{ + if (allocated > default_alloc) { + free(storage); + free(sorted); + storage = 0; + sorted = 0; + allocated = 0; + } +} + + + +static inline void fillTrapezoid(Q27Dot5 y1, Q27Dot5 y2, int left, int right, + const QTessellatorPrivate::Vertices &vertices, + QTessellator::Trapezoid *trap) +{ + trap->top = y1; + trap->bottom = y2; + const QTessellatorPrivate::Vertex *v = vertices[left]; + trap->topLeft = v; + trap->bottomLeft = vertices.next(v); + if (trap->topLeft->y > trap->bottomLeft->y) + qSwap(trap->topLeft,trap->bottomLeft); + v = vertices[right]; + trap->topRight = v; + trap->bottomRight = vertices.next(v); + if (trap->topRight->y > trap->bottomRight->y) + qSwap(trap->topRight, trap->bottomRight); +} + +QRectF QTessellatorPrivate::collectAndSortVertices(const QPointF *points, int *maxActiveEdges) +{ + *maxActiveEdges = 0; + Vertex *v = vertices.storage; + Vertex **vv = vertices.sorted; + + qreal xmin(points[0].x()); + qreal xmax(points[0].x()); + qreal ymin(points[0].y()); + qreal ymax(points[0].y()); + + // collect vertex data + Q27Dot5 y_prev = FloatToQ27Dot5(points[vertices.nPoints-1].y()); + Q27Dot5 x_next = FloatToQ27Dot5(points[0].x()); + Q27Dot5 y_next = FloatToQ27Dot5(points[0].y()); + int j = 0; + int i = 0; + while (i < vertices.nPoints) { + Q27Dot5 y_curr = y_next; + + *vv = v; + + v->x = x_next; + v->y = y_next; + v->flags = 0; + + next_point: + + xmin = qMin(xmin, points[i+1].x()); + xmax = qMax(xmax, points[i+1].x()); + ymin = qMin(ymin, points[i+1].y()); + ymax = qMax(ymax, points[i+1].y()); + + y_next = FloatToQ27Dot5(points[i+1].y()); + x_next = FloatToQ27Dot5(points[i+1].x()); + + // skip vertices on top of each other + if (v->x == x_next && v->y == y_next) { + ++i; + if (i < vertices.nPoints) + goto next_point; + Vertex *v0 = vertices.storage; + v0->flags &= ~(LineBeforeStarts|LineBeforeEnds|LineBeforeHorizontal); + if (y_prev < y_curr) + v0->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v0->flags |= LineBeforeStarts; + else + v0->flags |= LineBeforeHorizontal; + if ((v0->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v0->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + break; + } + + if (y_prev < y_curr) + v->flags |= LineBeforeEnds; + else if (y_prev > y_curr) + v->flags |= LineBeforeStarts; + else + v->flags |= LineBeforeHorizontal; + + + if (y_curr < y_next) + v->flags |= LineAfterStarts; + else if (y_curr > y_next) + v->flags |= LineAfterEnds; + else + v->flags |= LineAfterHorizontal; + // ### could probably get better limit by looping over sorted list and counting down on ending edges + if ((v->flags & (LineBeforeStarts|LineAfterStarts)) + && !(v->flags & (LineAfterEnds|LineBeforeEnds))) + *maxActiveEdges += 2; + y_prev = y_curr; + ++v; + ++vv; + ++j; + ++i; + } + vertices.nPoints = j; + + QDEBUG() << "maxActiveEdges=" << *maxActiveEdges; + vv = vertices.sorted; + std::sort(vv, vv + vertices.nPoints, compareVertex); + + return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); +} + +struct QCoincidingEdge { + QTessellatorPrivate::Vertex *start; + QTessellatorPrivate::Vertex *end; + bool used; + bool before; + + inline bool operator<(const QCoincidingEdge &e2) const + { + return end->y == e2.end->y ? end->x < e2.end->x : end->y < e2.end->y; + } +}; + +static void cancelEdges(QCoincidingEdge &e1, QCoincidingEdge &e2) +{ + if (e1.before) { + e1.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e1.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e1.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e1.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + if (e2.before) { + e2.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); + e2.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); + } else { + e2.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); + e2.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); + } + e1.used = e2.used = true; +} + +void QTessellatorPrivate::cancelCoincidingEdges() +{ + Vertex **vv = vertices.sorted; + + QCoincidingEdge *tl = 0; + int tlSize = 0; + + for (int i = 0; i < vertices.nPoints - 1; ++i) { + Vertex *v = vv[i]; + int testListSize = 0; + while (i < vertices.nPoints - 1) { + Vertex *n = vv[i]; + if (v->x != n->x || v->y != n->y) + break; + + if (testListSize > tlSize - 2) { + tlSize = qMax(tlSize*2, 16); + tl = q_check_ptr((QCoincidingEdge *)realloc(tl, tlSize*sizeof(QCoincidingEdge))); + } + if (n->flags & (LineBeforeStarts|LineBeforeHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.prev(n); + tl[testListSize].used = false; + tl[testListSize].before = true; + ++testListSize; + } + if (n->flags & (LineAfterStarts|LineAfterHorizontal)) { + tl[testListSize].start = n; + tl[testListSize].end = vertices.next(n); + tl[testListSize].used = false; + tl[testListSize].before = false; + ++testListSize; + } + ++i; + } + if (!testListSize) + continue; + + std::sort(tl, tl + testListSize); + + for (int j = 0; j < testListSize; ++j) { + if (tl[j].used) + continue; + + for (int k = j + 1; k < testListSize; ++k) { + if (tl[j].end->x != tl[k].end->x + || tl[j].end->y != tl[k].end->y + || tl[k].used) + break; + + if (!winding || tl[j].before != tl[k].before) { + cancelEdges(tl[j], tl[k]); + break; + } + ++k; + } + ++j; + } + } + free(tl); +} + + +void QTessellatorPrivate::emitEdges(QTessellator *tessellator) +{ + //QDEBUG() << "TRAPS:"; + if (!scanline.old_size) + return; + + // emit edges + if (winding) { + // winding fill rule + int w = 0; + + scanline.old[0]->y_left = y; + + for (int i = 0; i < scanline.old_size - 1; ++i) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + w += left->winding; +// qDebug() << "i=" << i << "edge->winding=" << left->winding << "winding=" << winding; + if (w == 0) { + left->y_right = y; + right->y_left = y; + } else if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + } + right->y_left = y; + left->y_right = y; + } + left->mark = false; + } + if (scanline.old[scanline.old_size - 1]->mark) { + scanline.old[scanline.old_size - 1]->y_right = y; + scanline.old[scanline.old_size - 1]->mark = false; + } + } else { + // odd-even fill rule + for (int i = 0; i < scanline.old_size; i += 2) { + Edge *left = scanline.old[i]; + Edge *right = scanline.old[i+1]; + if (!emit_clever || left->mark || right->mark) { + Q27Dot5 top = qMax(left->y_right, right->y_left); + if (top != y) { + QTessellator::Trapezoid trap; + fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); + tessellator->addTrap(trap); + } +// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; + left->y_left = y; + left->y_right = y; + right->y_left = y; + right->y_right = y; + left->mark = right->mark = false; + } + } + } +} + + +void QTessellatorPrivate::processIntersections() +{ + QDEBUG() << "PROCESS INTERSECTIONS"; + // process intersections + while (!intersections.isEmpty()) { + Intersections::iterator it = intersections.begin(); + if (it.key().y != y) + break; + + // swap edges + QDEBUG() << " swapping intersecting edges "; + int min = scanline.size; + int max = 0; + Q27Dot5 xmin = INT_MAX; + Q27Dot5 xmax = INT_MIN; + int num = 0; + while (1) { + const Intersection i = it.key(); + int next = it->next; + + int edgePos = scanline.findEdge(i.edge); + if (edgePos >= 0) { + ++num; + min = qMin(edgePos, min); + max = qMax(edgePos, max); + Edge *edge = scanline.edges[edgePos]; + xmin = qMin(xmin, edge->positionAt(y)); + xmax = qMax(xmax, edge->positionAt(y)); + } + Intersection key; + key.y = y; + key.edge = next; + it = intersections.find(key); + intersections.remove(i); + if (it == intersections.end()) + break; + } + if (num < 2) + continue; + + Q_ASSERT(min != max); + QDEBUG() << "sorting between" << min << "and" << max << "xpos=" << xmin << xmax; + while (min > 0 && scanline.edges[min - 1]->positionAt(y) >= xmin) { + QDEBUG() << " adding edge on left"; + --min; + } + while (max < scanline.size - 1 && scanline.edges[max + 1]->positionAt(y) <= xmax) { + QDEBUG() << " adding edge on right"; + ++max; + } + + std::sort(scanline.edges + min, scanline.edges + max + 1, EdgeSorter(y)); +#ifdef DEBUG + for (int i = min; i <= max; ++i) + QDEBUG() << " " << scanline.edges[i]->edge << "at pos" << i; +#endif + for (int i = min; i <= max; ++i) { + Edge *edge = scanline.edges[i]; + edge->intersect_left = true; + edge->intersect_right = true; + edge->mark = true; + } + } +} + +void QTessellatorPrivate::removeEdges() +{ + int cv = currentVertex; + while (cv < vertices.nPoints) { + const Vertex *v = vertices.sorted[cv]; + if (v->y > y) + break; + if (v->flags & LineBeforeEnds) { + QDEBUG() << " removing edge" << vertices.prevPos(v); + int pos = scanline.findEdge(vertices.prevPos(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + if (v->flags & LineAfterEnds) { + QDEBUG() << " removing edge" << vertices.position(v); + int pos = scanline.findEdge(vertices.position(v)); + if (pos == -1) + continue; + scanline.edges[pos]->mark = true; + if (pos > 0) + scanline.edges[pos - 1]->intersect_right = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->intersect_left = true; + scanline.removeAt(pos); + } + ++cv; + } +} + +void QTessellatorPrivate::addEdges() +{ + while (currentVertex < vertices.nPoints) { + const Vertex *v = vertices.sorted[currentVertex]; + if (v->y > y) + break; + if (v->flags & LineBeforeStarts) { + // add new edge + int start = vertices.prevPos(v); + Edge e(vertices, start); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << start << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineAfterEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterStarts) { + Edge e(vertices, vertices.position(v)); + int pos = scanline.findEdgePosition(e); + QDEBUG() << " adding edge" << vertices.position(v) << "at position" << pos; + scanline.insert(pos, e); + if (!mark_clever || !(v->flags & LineBeforeEnds)) { + if (pos > 0) + scanline.edges[pos - 1]->mark = true; + if (pos < scanline.size - 1) + scanline.edges[pos + 1]->mark = true; + } + } + if (v->flags & LineAfterHorizontal) { + int pos1 = scanline.findEdgePosition(v->x, v->y); + const Vertex *next = vertices.next(v); + Q_ASSERT(v->y == next->y); + int pos2 = scanline.findEdgePosition(next->x, next->y); + if (pos2 < pos1) + qSwap(pos1, pos2); + if (pos1 > 0) + --pos1; + if (pos2 == scanline.size) + --pos2; + //QDEBUG() << "marking horizontal edge from " << pos1 << "to" << pos2; + scanline.markEdges(pos1, pos2); + } + ++currentVertex; + } +} + +#ifdef DEBUG +static void checkLinkChain(const QTessellatorPrivate::Intersections &intersections, + QTessellatorPrivate::Intersection i) +{ +// qDebug() << " Link chain: "; + int end = i.edge; + while (1) { + QTessellatorPrivate::IntersectionLink l = intersections.value(i); +// qDebug() << " " << i.edge << "next=" << l.next << "prev=" << l.prev; + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + QTessellatorPrivate::Intersection i2 = i; + i2.edge = l.next; + QTessellatorPrivate::IntersectionLink l2 = intersections.value(i2); + + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); + i = i2; + } +} +#endif + +bool QTessellatorPrivate::edgeInChain(Intersection i, int edge) +{ + int end = i.edge; + while (1) { + if (i.edge == edge) + return true; + IntersectionLink l = intersections.value(i); + if (l.next == end) + break; + Q_ASSERT(l.next != -1); + Q_ASSERT(l.prev != -1); + + Intersection i2 = i; + i2.edge = l.next; + +#ifndef QT_NO_DEBUG + IntersectionLink l2 = intersections.value(i2); + Q_ASSERT(l2.next != -1); + Q_ASSERT(l2.prev != -1); + Q_ASSERT(l.next == i2.edge); + Q_ASSERT(l2.prev == i.edge); +#endif + i = i2; + } + return false; +} + + +void QTessellatorPrivate::addIntersection(const Edge *e1, const Edge *e2) +{ + const IntersectionLink emptyLink = {-1, -1}; + + int next = vertices.nextPos(vertices[e1->edge]); + if (e2->edge == next) + return; + int prev = vertices.prevPos(vertices[e1->edge]); + if (e2->edge == prev) + return; + + Q27Dot5 yi; + bool det_positive; + bool isect = e1->intersect(*e2, &yi, &det_positive); + QDEBUG("checking edges %d and %d", e1->edge, e2->edge); + if (!isect) { + QDEBUG() << " no intersection"; + return; + } + + // don't emit an intersection if it's at the start of a line segment or above us + if (yi <= y) { + if (!det_positive) + return; + QDEBUG() << " ----->>>>>> WRONG ORDER!"; + yi = y; + } + QDEBUG() << " between edges " << e1->edge << "and" << e2->edge << "at point (" + << Q27Dot5ToDouble(yi) << ')'; + + Intersection i1; + i1.y = yi; + i1.edge = e1->edge; + IntersectionLink link1 = intersections.value(i1, emptyLink); + Intersection i2; + i2.y = yi; + i2.edge = e2->edge; + IntersectionLink link2 = intersections.value(i2, emptyLink); + + // new pair of edges + if (link1.next == -1 && link2.next == -1) { + link1.next = link1.prev = i2.edge; + link2.next = link2.prev = i1.edge; + } else if (link1.next == i2.edge || link1.prev == i2.edge + || link2.next == i1.edge || link2.prev == i1.edge) { +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + } else if (link1.next == -1 || link2.next == -1) { + if (link2.next == -1) { + qSwap(i1, i2); + qSwap(link1, link2); + } + Q_ASSERT(link1.next == -1); +#ifdef DEBUG + checkLinkChain(intersections, i2); +#endif + // only i2 in list + link1.next = i2.edge; + link1.prev = link2.prev; + link2.prev = i1.edge; + Intersection other; + other.y = yi; + other.edge = link1.prev; + IntersectionLink link = intersections.value(other, emptyLink); + Q_ASSERT(link.next == i2.edge); + Q_ASSERT(link.prev != -1); + link.next = i1.edge; + intersections.insert(other, link); + } else { + bool connected = edgeInChain(i1, i2.edge); + if (connected) + return; +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); +#endif + // both already in some list. Have to make sure they are connected + // this can be done by cutting open the ring(s) after the two eges and + // connecting them again + Intersection other1; + other1.y = yi; + other1.edge = link1.next; + IntersectionLink linko1 = intersections.value(other1, emptyLink); + Intersection other2; + other2.y = yi; + other2.edge = link2.next; + IntersectionLink linko2 = intersections.value(other2, emptyLink); + + linko1.prev = i2.edge; + link2.next = other1.edge; + + linko2.prev = i1.edge; + link1.next = other2.edge; + intersections.insert(other1, linko1); + intersections.insert(other2, linko2); + } + intersections.insert(i1, link1); + intersections.insert(i2, link2); +#ifdef DEBUG + checkLinkChain(intersections, i1); + checkLinkChain(intersections, i2); + Q_ASSERT(edgeInChain(i1, i2.edge)); +#endif + return; + +} + + +void QTessellatorPrivate::addIntersections() +{ + if (scanline.size) { + QDEBUG() << "INTERSECTIONS"; + // check marked edges for intersections +#ifdef DEBUG + for (int i = 0; i < scanline.size; ++i) { + Edge *e = scanline.edges[i]; + QDEBUG() << " " << i << e->edge << "isect=(" << e->intersect_left << e->intersect_right + << ')'; + } +#endif + + for (int i = 0; i < scanline.size - 1; ++i) { + Edge *e1 = scanline.edges[i]; + Edge *e2 = scanline.edges[i + 1]; + // check for intersection + if (e1->intersect_right || e2->intersect_left) + addIntersection(e1, e2); + } + } +#if 0 + if (intersections.constBegin().key().y == y) { + QDEBUG() << "----------------> intersection on same line"; + scanline.clearMarks(); + scanline.processIntersections(y, &intersections); + goto redo; + } +#endif +} + + +QTessellator::QTessellator() +{ + d = new QTessellatorPrivate; +} + +QTessellator::~QTessellator() +{ + delete d; +} + +void QTessellator::setWinding(bool w) +{ + d->winding = w; +} + + +QRectF QTessellator::tessellate(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + +#ifdef DEBUG + QDEBUG()<< "POINTS:"; + for (int i = 0; i < nPoints; ++i) { + QDEBUG() << points[i]; + } +#endif + + // collect edges and calculate bounds + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + int maxActiveEdges = 0; + QRectF br = d->collectAndSortVertices(points, &maxActiveEdges); + d->cancelCoincidingEdges(); + +#ifdef DEBUG + QDEBUG() << "nPoints = " << nPoints << "using " << d->vertices.nPoints; + QDEBUG()<< "VERTICES:"; + for (int i = 0; i < d->vertices.nPoints; ++i) { + QDEBUG() << " " << i << ": " + << "point=" << d->vertices.position(d->vertices.sorted[i]) + << "flags=" << d->vertices.sorted[i]->flags + << "pos=(" << Q27Dot5ToDouble(d->vertices.sorted[i]->x) << '/' + << Q27Dot5ToDouble(d->vertices.sorted[i]->y) << ')'; + } +#endif + + d->scanline.init(maxActiveEdges); + d->y = INT_MIN/256; + d->currentVertex = 0; + + while (d->currentVertex < d->vertices.nPoints) { + d->scanline.clearMarks(); + + d->y = d->vertices.sorted[d->currentVertex]->y; + if (!d->intersections.isEmpty()) + d->y = qMin(d->y, d->intersections.constBegin().key().y); + + QDEBUG()<< "===== SCANLINE: y =" << Q27Dot5ToDouble(d->y) << " ====="; + + d->scanline.prepareLine(); + d->processIntersections(); + d->removeEdges(); + d->addEdges(); + d->addIntersections(); + d->emitEdges(this); + d->scanline.lineDone(); + +#ifdef DEBUG + QDEBUG()<< "===== edges:"; + for (int i = 0; i < d->scanline.size; ++i) { + QDEBUG() << " " << d->scanline.edges[i]->edge + << "p0= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v0->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v0->y) + << ") p1= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v1->x) + << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v1->y) << ')' + << "x=" << Q27Dot5ToDouble(d->scanline.edges[i]->positionAt(d->y)) + << "isLeftOfNext=" + << ((i < d->scanline.size - 1) + ? d->scanline.edges[i]->isLeftOf(*d->scanline.edges[i+1], d->y) + : true); + } +#endif +} + + d->scanline.done(); + d->intersections.clear(); + return br; +} + +// tessellates the given convex polygon +void QTessellator::tessellateConvex(const QPointF *points, int nPoints) +{ + Q_ASSERT(points[0] == points[nPoints-1]); + --nPoints; + + d->vertices.nPoints = nPoints; + d->vertices.init(nPoints); + + for (int i = 0; i < nPoints; ++i) { + d->vertices[i]->x = FloatToQ27Dot5(points[i].x()); + d->vertices[i]->y = FloatToQ27Dot5(points[i].y()); + } + + int left = 0, right = 0; + + int top = 0; + for (int i = 1; i < nPoints; ++i) { + if (d->vertices[i]->y < d->vertices[top]->y) + top = i; + } + + left = (top + nPoints - 1) % nPoints; + right = (top + 1) % nPoints; + + while (d->vertices[left]->x == d->vertices[top]->x && d->vertices[left]->y == d->vertices[top]->y && left != right) + left = (left + nPoints - 1) % nPoints; + + while (d->vertices[right]->x == d->vertices[top]->x && d->vertices[right]->y == d->vertices[top]->y && left != right) + right = (right + 1) % nPoints; + + if (left == right) + return; + + int dir = 1; + + Vertex dLeft = { d->vertices[top]->x - d->vertices[left]->x, + d->vertices[top]->y - d->vertices[left]->y }; + + Vertex dRight = { d->vertices[right]->x - d->vertices[top]->x, + d->vertices[right]->y - d->vertices[top]->y }; + + Q27Dot5 cross = dLeft.x * dRight.y - dLeft.y * dRight.x; + + // flip direction if polygon is clockwise + if (cross < 0 || (cross == 0 && dLeft.x > 0)) { + qSwap(left, right); + dir = -1; + } + + Vertex *lastLeft = d->vertices[top]; + Vertex *lastRight = d->vertices[top]; + + QTessellator::Trapezoid trap; + + while (lastLeft->y == d->vertices[left]->y && left != right) { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + + while (lastRight->y == d->vertices[right]->y && left != right) { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + + while (true) { + trap.top = qMax(lastRight->y, lastLeft->y); + trap.bottom = qMin(d->vertices[left]->y, d->vertices[right]->y); + trap.topLeft = lastLeft; + trap.topRight = lastRight; + trap.bottomLeft = d->vertices[left]; + trap.bottomRight = d->vertices[right]; + + if (trap.bottom > trap.top) + addTrap(trap); + + if (left == right) + break; + + if (d->vertices[right]->y < d->vertices[left]->y) { + do { + lastRight = d->vertices[right]; + right = (right + nPoints + dir) % nPoints; + } + while (lastRight->y == d->vertices[right]->y && left != right); + } else { + do { + lastLeft = d->vertices[left]; + left = (left + nPoints - dir) % nPoints; + } + while (lastLeft->y == d->vertices[left]->y && left != right); + } + } +} + +// tessellates the stroke of the line from a_ to b_ with the given width and a flat cap +void QTessellator::tessellateRect(const QPointF &a_, const QPointF &b_, qreal width) +{ + Vertex a = { FloatToQ27Dot5(a_.x()), FloatToQ27Dot5(a_.y()) }; + Vertex b = { FloatToQ27Dot5(b_.x()), FloatToQ27Dot5(b_.y()) }; + + QPointF pa = a_, pb = b_; + + if (a.y > b.y) { + qSwap(a, b); + qSwap(pa, pb); + } + + Vertex delta = { b.x - a.x, b.y - a.y }; + + if (delta.x == 0 && delta.y == 0) + return; + + qreal hw = 0.5 * width; + + if (delta.x == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + Vertex topLeft = { a.x - halfWidth, a.y }; + Vertex topRight = { a.x + halfWidth, a.y }; + Vertex bottomLeft = { a.x - halfWidth, b.y }; + Vertex bottomRight = { a.x + halfWidth, b.y }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else if (delta.y == 0) { + Q27Dot5 halfWidth = FloatToQ27Dot5(hw); + + if (halfWidth == 0) + return; + + if (a.x > b.x) + qSwap(a.x, b.x); + + Vertex topLeft = { a.x, a.y - halfWidth }; + Vertex topRight = { b.x, a.y - halfWidth }; + Vertex bottomLeft = { a.x, a.y + halfWidth }; + Vertex bottomRight = { b.x, a.y + halfWidth }; + + QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; + addTrap(trap); + } else { + QPointF perp(pb.y() - pa.y(), pa.x() - pb.x()); + qreal length = qSqrt(perp.x() * perp.x() + perp.y() * perp.y()); + + if (qFuzzyIsNull(length)) + return; + + // need the half of the width + perp *= hw / length; + + QPointF pta = pa + perp; + QPointF ptb = pa - perp; + QPointF ptc = pb - perp; + QPointF ptd = pb + perp; + + Vertex ta = { FloatToQ27Dot5(pta.x()), FloatToQ27Dot5(pta.y()) }; + Vertex tb = { FloatToQ27Dot5(ptb.x()), FloatToQ27Dot5(ptb.y()) }; + Vertex tc = { FloatToQ27Dot5(ptc.x()), FloatToQ27Dot5(ptc.y()) }; + Vertex td = { FloatToQ27Dot5(ptd.x()), FloatToQ27Dot5(ptd.y()) }; + + if (ta.y < tb.y) { + if (tb.y < td.y) { + QTessellator::Trapezoid top = { ta.y, tb.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { td.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { tb.y, td.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { ta.y, td.y, &ta, &tb, &ta, &td }; + QTessellator::Trapezoid bottom = { tb.y, tc.y, &tb, &tc, &td, &tc }; + addTrap(top); + addTrap(bottom); + + if (tb.y != td.y) { + QTessellator::Trapezoid middle = { td.y, tb.y, &ta, &tb, &td, &tc }; + addTrap(middle); + } + } + } else { + if (ta.y < tc.y) { + QTessellator::Trapezoid top = { tb.y, ta.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { tc.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + QTessellator::Trapezoid middle = { ta.y, tc.y, &tb, &tc, &ta, &td }; + addTrap(middle); + } else { + QTessellator::Trapezoid top = { tb.y, tc.y, &tb, &tc, &tb, &ta }; + QTessellator::Trapezoid bottom = { ta.y, td.y, &tc, &td, &ta, &td }; + addTrap(top); + addTrap(bottom); + + if (ta.y != tc.y) { + QTessellator::Trapezoid middle = { tc.y, ta.y, &tc, &td, &tb, &ta }; + addTrap(middle); + } + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h new file mode 100644 index 0000000000..65ae6bdc41 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QTESSELATOR_P_H +#define QTESSELATOR_P_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QTessellatorPrivate; + +typedef int Q27Dot5; +#define Q27Dot5ToDouble(i) ((i)/32.) +#define FloatToQ27Dot5(i) (int)((i) * 32) +#define IntToQ27Dot5(i) ((i) << 5) +#define Q27Dot5ToXFixed(i) ((i) << 11) +#define Q27Dot5Factor 32 + +class QTessellator { +public: + QTessellator(); + virtual ~QTessellator(); + + QRectF tessellate(const QPointF *points, int nPoints); + void tessellateConvex(const QPointF *points, int nPoints); + void tessellateRect(const QPointF &a, const QPointF &b, qreal width); + + void setWinding(bool w); + + struct Vertex { + Q27Dot5 x; + Q27Dot5 y; + }; + struct Trapezoid { + Q27Dot5 top; + Q27Dot5 bottom; + const Vertex *topLeft; + const Vertex *bottomLeft; + const Vertex *topRight; + const Vertex *bottomRight; + }; + virtual void addTrap(const Trapezoid &trap) = 0; + +private: + friend class QTessellatorPrivate; + QTessellatorPrivate *d; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp new file mode 100644 index 0000000000..23500eb4ab --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qxcbconnection.h" +#include "qcolormap_x11_p.h" +#include "qxcbnativepainting.h" +#include "qt_x11_p.h" + +QT_BEGIN_NAMESPACE + +QXcbX11Data *qt_x11Data = Q_NULLPTR; + +void qt_xcb_native_x11_info_init(QXcbConnection *conn) +{ + qt_x11Data = new QXcbX11Data; + X11->display = (Display *) conn->xlib_display(); + X11->defaultScreen = DefaultScreen(X11->display); + X11->screenCount = ScreenCount(X11->display); + + X11->screens = new QX11InfoData[X11->screenCount]; + X11->argbVisuals = new Visual *[X11->screenCount]; + X11->argbColormaps = new Colormap[X11->screenCount]; + + for (int s = 0; s < X11->screenCount; s++) { + QX11InfoData *screen = X11->screens + s; + //screen->ref = 1; // ensures it doesn't get deleted + screen->screen = s; + + int widthMM = DisplayWidthMM(X11->display, s); + if (widthMM != 0) { + screen->dpiX = (DisplayWidth(X11->display, s) * 254 + widthMM * 5) / (widthMM * 10); + } else { + screen->dpiX = 72; + } + + int heightMM = DisplayHeightMM(X11->display, s); + if (heightMM != 0) { + screen->dpiY = (DisplayHeight(X11->display, s) * 254 + heightMM * 5) / (heightMM * 10); + } else { + screen->dpiY = 72; + } + + X11->argbVisuals[s] = 0; + X11->argbColormaps[s] = 0; + } + + X11->use_xrender = conn->hasXRender() && !qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING_NO_XRENDER"); + +#if QT_CONFIG(xrender) + memset(X11->solid_fills, 0, sizeof(X11->solid_fills)); + for (int i = 0; i < X11->solid_fill_count; ++i) + X11->solid_fills[i].screen = -1; + memset(X11->pattern_fills, 0, sizeof(X11->pattern_fills)); + for (int i = 0; i < X11->pattern_fill_count; ++i) + X11->pattern_fills[i].screen = -1; +#endif + + QXcbColormap::initialize(); + +#if QT_CONFIG(xrender) + if (X11->use_xrender) { + // XRender is supported, let's see if we have a PictFormat for the + // default visual + XRenderPictFormat *format = + XRenderFindVisualFormat(X11->display, + (Visual *) QXcbX11Info::appVisual(X11->defaultScreen)); + + if (!format) { + X11->use_xrender = false; + } + } +#endif // QT_CONFIG(xrender) +} + +QVector qt_region_to_xrectangles(const QRegion &r) +{ + const int numRects = r.rectCount(); + const QVector input = r.rects(); + QVector output(numRects); + for (int i = 0; i < numRects; ++i) { + const QRect &in = input[i]; + XRectangle &out = output[i]; + out.x = qMax(SHRT_MIN, in.x()); + out.y = qMax(SHRT_MIN, in.y()); + out.width = qMin((int)USHRT_MAX, in.width()); + out.height = qMin((int)USHRT_MAX, in.height()); + } + return output; +} + +class QXcbX11InfoData : public QSharedData, public QX11InfoData +{}; + +QXcbX11Info::QXcbX11Info() + : d(Q_NULLPTR) +{} + +QXcbX11Info::~QXcbX11Info() +{} + +QXcbX11Info::QXcbX11Info(const QXcbX11Info &other) + : d(other.d) +{} + +QXcbX11Info &QXcbX11Info::operator=(const QXcbX11Info &other) +{ + d = other.d; + return *this; +} + +QXcbX11Info QXcbX11Info::fromScreen(int screen) +{ + QXcbX11InfoData *xd = new QXcbX11InfoData; + xd->screen = screen; + xd->depth = QXcbX11Info::appDepth(screen); + xd->cells = QXcbX11Info::appCells(screen); + xd->colormap = QXcbX11Info::appColormap(screen); + xd->defaultColormap = QXcbX11Info::appDefaultColormap(screen); + xd->visual = (Visual *)QXcbX11Info::appVisual(screen); + xd->defaultVisual = QXcbX11Info::appDefaultVisual(screen); + + QXcbX11Info info; + info.d = xd; + return info; +} + +void QXcbX11Info::setDepth(int depth) +{ + if (!d) + *this = fromScreen(appScreen()); + + d->depth = depth; +} + +Display *QXcbX11Info::display() +{ + return X11 ? X11->display : 0; +} + +int QXcbX11Info::screen() const +{ + return d ? d->screen : QXcbX11Info::appScreen(); +} + +int QXcbX11Info::depth() const +{ + return d ? d->depth : QXcbX11Info::appDepth(); +} + +Colormap QXcbX11Info::colormap() const +{ + return d ? d->colormap : QXcbX11Info::appColormap(); +} + +void *QXcbX11Info::visual() const +{ + return d ? d->visual : QXcbX11Info::appVisual(); +} + +void QXcbX11Info::setVisual(void *visual) +{ + if (!d) + *this = fromScreen(appScreen()); + + d->visual = (Visual *) visual; +} + +int QXcbX11Info::appScreen() +{ + return X11 ? X11->defaultScreen : 0; +} + +int QXcbX11Info::appDepth(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].depth : 32; +} + +int QXcbX11Info::appCells(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].cells : 0; +} + +Colormap QXcbX11Info::appColormap(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].colormap : 0; +} + +void *QXcbX11Info::appVisual(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].visual : 0; +} + +Window QXcbX11Info::appRootWindow(int screen) +{ + return X11 ? RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen) : 0; +} + +bool QXcbX11Info::appDefaultColormap(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultColormap : true; +} + +bool QXcbX11Info::appDefaultVisual(int screen) +{ + return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultVisual : true; +} + +int QXcbX11Info::appDpiX(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiX; +} + +int QXcbX11Info::appDpiY(int screen) +{ + if (!X11) + return 75; + if (screen < 0) + screen = X11->defaultScreen; + if (screen > X11->screenCount) + return 0; + return X11->screens[screen].dpiY; +} + +#if QT_CONFIG(xrender) +Picture QXcbX11Data::getSolidFill(int screen, const QColor &c) +{ + if (!X11->use_xrender) + return XNone; + + XRenderColor color = preMultiply(c); + for (int i = 0; i < X11->solid_fill_count; ++i) { + if (X11->solid_fills[i].screen == screen + && X11->solid_fills[i].color.alpha == color.alpha + && X11->solid_fills[i].color.red == color.red + && X11->solid_fills[i].color.green == color.green + && X11->solid_fills[i].color.blue == color.blue) + return X11->solid_fills[i].picture; + } + // none found, replace one + int i = qrand() % 16; + + if (X11->solid_fills[i].screen != screen && X11->solid_fills[i].picture) { + XRenderFreePicture (X11->display, X11->solid_fills[i].picture); + X11->solid_fills[i].picture = 0; + } + + if (!X11->solid_fills[i].picture) { + Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 1, 1, 32); + XRenderPictureAttributes attrs; + attrs.repeat = True; + X11->solid_fills[i].picture = XRenderCreatePicture (X11->display, pixmap, + XRenderFindStandardFormat(X11->display, PictStandardARGB32), + CPRepeat, &attrs); + XFreePixmap (X11->display, pixmap); + } + + X11->solid_fills[i].color = color; + X11->solid_fills[i].screen = screen; + XRenderFillRectangle (X11->display, PictOpSrc, X11->solid_fills[i].picture, &color, 0, 0, 1, 1); + return X11->solid_fills[i].picture; +} + +XRenderColor QXcbX11Data::preMultiply(const QColor &c) +{ + XRenderColor color; + const uint A = c.alpha(), + R = c.red(), + G = c.green(), + B = c.blue(); + color.alpha = (A | A << 8); + color.red = (R | R << 8) * color.alpha / 0x10000; + color.green = (G | G << 8) * color.alpha / 0x10000; + color.blue = (B | B << 8) * color.alpha / 0x10000; + return color; +} +#endif // QT_CONFIG(xrender) + + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h new file mode 100644 index 0000000000..f3011286c9 --- /dev/null +++ b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QXCBNATIVEPAINTING_H +#define QXCBNATIVEPAINTING_H + +#include +#include "qt_x11_p.h" + +typedef struct _FcPattern FcPattern; +typedef unsigned long XID; +typedef XID Colormap; +typedef XID Window; +typedef struct _XDisplay Display; + +QT_BEGIN_NAMESPACE + +class QXcbConnection; +class QPixmap; + +void qt_xcb_native_x11_info_init(QXcbConnection *conn); +QVector qt_region_to_xrectangles(const QRegion &r); + +class QXcbX11InfoData; +class QXcbX11Info +{ +public: + QXcbX11Info(); + ~QXcbX11Info(); + QXcbX11Info(const QXcbX11Info &other); + QXcbX11Info &operator=(const QXcbX11Info &other); + + static QXcbX11Info fromScreen(int screen); + static Display *display(); + + int depth() const; + void setDepth(int depth); + + int screen() const; + Colormap colormap() const; + + void *visual() const; + void setVisual(void *visual); + + static int appScreen(); + static int appDepth(int screen = -1); + static int appCells(int screen = -1); + static Colormap appColormap(int screen = -1); + static void *appVisual(int screen = -1); + static Window appRootWindow(int screen = -1); + static bool appDefaultColormap(int screen = -1); + static bool appDefaultVisual(int screen = -1); + static int appDpiX(int screen = -1); + static int appDpiY(int screen = -1); + +private: + QSharedDataPointer d; + + friend class QX11PaintEngine; + friend class QX11PlatformPixmap; + friend void qt_x11SetScreen(QPixmap &pixmap, int screen); +}; + +QT_END_NAMESPACE + +#endif // QXCBNATIVEPAINTING_H diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp index c2e4edf2d5..58799793ae 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.cpp +++ b/src/plugins/platforms/xcb/qxcbconnection.cpp @@ -148,8 +148,18 @@ static const char * const xcbConnectionErrors[] = { "Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */ }; -static int nullErrorHandler(Display *, XErrorEvent *) +static int nullErrorHandler(Display *dpy, XErrorEvent *err) { +#ifndef Q_XCB_DEBUG + Q_UNUSED(dpy); + Q_UNUSED(err); +#else + const int buflen = 1024; + char buf[buflen]; + + XGetErrorText(dpy, err->error_code, buf, buflen); + fprintf(stderr, "X Error: serial %lu error %d %s\n", err->serial, (int) err->error_code, buf); +#endif return 0; } @@ -528,6 +538,7 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra , m_defaultVisualId(defaultVisualId) , m_displayName(displayName ? QByteArray(displayName) : qgetenv("DISPLAY")) , m_nativeInterface(nativeInterface) + , has_render_extension(false) { #ifdef XCB_USE_XLIB Display *dpy = XOpenDisplay(m_displayName.constData()); @@ -1980,11 +1991,14 @@ void QXcbConnection::initializeXRender() if (!reply || !reply->present) return; + has_render_extension = true; auto xrender_query = Q_XCB_REPLY(xcb_render_query_version, m_connection, XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION); - if (!xrender_query || (xrender_query->major_version == 0 && xrender_query->minor_version < 5)) + if (!xrender_query || (xrender_query->major_version == 0 && xrender_query->minor_version < 5)) { qWarning("QXcbConnection: Failed to initialize XRender"); + has_render_extension = false; + } #endif } diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h index 512a1d09b9..1cd7824441 100644 --- a/src/plugins/platforms/xcb/qxcbconnection.h +++ b/src/plugins/platforms/xcb/qxcbconnection.h @@ -467,6 +467,7 @@ public: bool hasXRandr() const { return has_randr_extension; } bool hasInputShape() const { return has_input_shape; } bool hasXKB() const { return has_xkb; } + bool hasXRender() const { return has_render_extension; } bool supportsThreadedRendering() const { return m_reader->isRunning(); } bool threadedEventHandling() const { return m_reader->isRunning(); } @@ -660,6 +661,7 @@ private: bool has_randr_extension = false; bool has_input_shape; bool has_xkb = false; + bool has_render_extension; Qt::MouseButtons m_buttons = 0; diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index 396d8837e9..da7ce9bb17 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -65,6 +65,11 @@ #ifdef XCB_USE_XLIB #include +#if QT_CONFIG(xcb_native_painting) +#include "qxcbnativepainting.h" +#include "qpixmap_x11_p.h" +#include "qbackingstore_x11_p.h" +#endif #endif #include @@ -192,6 +197,13 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char } m_fontDatabase.reset(new QGenericUnixFontDatabase()); + +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) { + qDebug("QXCB USING NATIVE PAINTING"); + qt_xcb_native_x11_info_init(defaultConnection()); + } +#endif } QXcbIntegration::~QXcbIntegration() @@ -200,6 +212,16 @@ QXcbIntegration::~QXcbIntegration() m_instance = Q_NULLPTR; } +QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const +{ +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) + return new QX11PlatformPixmap(type); +#endif + + return QPlatformIntegration::createPlatformPixmap(type); +} + QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const { QXcbScreen *screen = static_cast(window->screen()->handle()); @@ -263,6 +285,11 @@ QPlatformOpenGLContext *QXcbIntegration::createPlatformOpenGLContext(QOpenGLCont QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *window) const { +#if QT_CONFIG(xcb_native_painting) + if (nativePaintingEnabled()) + return new QXcbNativeBackingStore(window); +#endif + return new QXcbBackingStore(window); } @@ -511,6 +538,16 @@ void QXcbIntegration::beep() const xcb_bell(connection, 0); } +bool QXcbIntegration::nativePaintingEnabled() const +{ +#if QT_CONFIG(xcb_native_painting) + static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING"); + return enabled; +#else + return false; +#endif +} + #if QT_CONFIG(vulkan) QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const { diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index 6e12739138..561aa9dce6 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -60,6 +60,7 @@ public: QXcbIntegration(const QStringList ¶meters, int &argc, char **argv); ~QXcbIntegration(); + QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const override; QPlatformWindow *createPlatformWindow(QWindow *window) const override; QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const override; #ifndef QT_NO_OPENGL @@ -113,6 +114,8 @@ public: void beep() const override; + bool nativePaintingEnabled() const; + #if QT_CONFIG(vulkan) QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; #endif diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp index ff4408e3f1..b90ea50a7f 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.cpp +++ b/src/plugins/platforms/xcb/qxcbwindow.cpp @@ -219,6 +219,12 @@ static inline QImage::Format imageFormatForVisual(int depth, quint32 red_mask, q return QImage::Format_RGB555; } break; +#if QT_CONFIG(xcb_native_painting) + case 8: + if (QXcbIntegration::instance() && QXcbIntegration::instance()->nativePaintingEnabled()) + return QImage::Format_Indexed8; + break; +#endif default: break; } diff --git a/src/plugins/platforms/xcb/xcb_qpa_lib.pro b/src/plugins/platforms/xcb/xcb_qpa_lib.pro index c7f2993220..76bb692590 100644 --- a/src/plugins/platforms/xcb/xcb_qpa_lib.pro +++ b/src/plugins/platforms/xcb/xcb_qpa_lib.pro @@ -71,6 +71,7 @@ qtConfig(xcb-sm) { } include(gl_integrations/gl_integrations.pri) +include(nativepainting/nativepainting.pri) qtConfig(vulkan) { SOURCES += \ -- cgit v1.2.3