01b72952c3
Change-Id: I5413cb5e2cbb53998bb40f27b9bbc16342caafe6 Reviewed-on: http://codereview.qt.nokia.com/837 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
4226 lines
136 KiB
C++
4226 lines
136 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
|
** All rights reserved.
|
|
** Contact: Nokia Corporation (qt-info@nokia.com)
|
|
**
|
|
** This file is part of the QtOpenVG module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** GNU Lesser General Public License Usage
|
|
** This file may be used under the terms of the GNU Lesser General Public
|
|
** License version 2.1 as published by the Free Software Foundation and
|
|
** appearing in the file LICENSE.LGPL included in the packaging of this
|
|
** file. Please review the following information to ensure the GNU Lesser
|
|
** General Public License version 2.1 requirements will be met:
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Nokia gives you certain additional
|
|
** rights. These rights are described in the Nokia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU General
|
|
** Public License version 3.0 as published by the Free Software Foundation
|
|
** and appearing in the file LICENSE.GPL included in the packaging of this
|
|
** file. Please review the following information to ensure the GNU General
|
|
** Public License version 3.0 requirements will be met:
|
|
** http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
** Other Usage
|
|
** Alternatively, this file may be used in accordance with the terms and
|
|
** conditions contained in a signed written agreement between you and Nokia.
|
|
**
|
|
**
|
|
**
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qpaintengine_vg_p.h"
|
|
#include "qpixmapdata_vg_p.h"
|
|
#include "qpixmapfilter_vg_p.h"
|
|
#include "qvgcompositionhelper_p.h"
|
|
#include "qvgimagepool_p.h"
|
|
#include "qvgfontglyphcache_p.h"
|
|
#if !defined(QT_NO_EGL)
|
|
#include <QtGui/private/qeglcontext_p.h>
|
|
#include "qwindowsurface_vgegl_p.h"
|
|
#endif
|
|
#include <QtCore/qvarlengtharray.h>
|
|
#include <QtGui/private/qdrawhelper_p.h>
|
|
#include <QtGui/private/qtextengine_p.h>
|
|
#include <QtGui/private/qfontengine_p.h>
|
|
#include <QtGui/private/qpainterpath_p.h>
|
|
#include <QtGui/private/qstatictext_p.h>
|
|
#include <QtGui/QApplication>
|
|
#include <QtGui/QDesktopWidget>
|
|
#include <QtCore/qmath.h>
|
|
#include <QDebug>
|
|
#include <QSet>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
// vgRenderToMask() only exists in OpenVG 1.1 and higher.
|
|
// Also, disable masking completely if we are using the scissor to clip.
|
|
#if !defined(OPENVG_VERSION_1_1) && !defined(QVG_NO_RENDER_TO_MASK)
|
|
#define QVG_NO_RENDER_TO_MASK 1
|
|
#endif
|
|
#if defined(QVG_SCISSOR_CLIP) && !defined(QVG_NO_RENDER_TO_MASK)
|
|
#define QVG_NO_RENDER_TO_MASK 1
|
|
#endif
|
|
|
|
// use the same rounding as in qrasterizer.cpp (6 bit fixed point)
|
|
static const qreal aliasedCoordinateDelta = 0.5 - 0.015625;
|
|
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
|
|
class QVGPaintEnginePrivate;
|
|
|
|
typedef QHash<QFontEngine*, QVGFontGlyphCache*> QVGFontCache;
|
|
|
|
#endif
|
|
|
|
class QVGFontEngineCleaner : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
QVGFontEngineCleaner(QVGPaintEnginePrivate *d);
|
|
~QVGFontEngineCleaner();
|
|
|
|
public slots:
|
|
void fontEngineDestroyed();
|
|
|
|
private:
|
|
QVGPaintEnginePrivate *d_ptr;
|
|
};
|
|
|
|
class QVGPaintEnginePrivate : public QPaintEngineExPrivate
|
|
{
|
|
Q_DECLARE_PUBLIC(QVGPaintEngine)
|
|
public:
|
|
// Extra blending modes from VG_KHR_advanced_blending extension.
|
|
// Use the QT_VG prefix to avoid conflicts with any definitions
|
|
// that may come in via <VG/vgext.h>.
|
|
enum AdvancedBlending {
|
|
QT_VG_BLEND_OVERLAY_KHR = 0x2010,
|
|
QT_VG_BLEND_HARDLIGHT_KHR = 0x2011,
|
|
QT_VG_BLEND_SOFTLIGHT_SVG_KHR = 0x2012,
|
|
QT_VG_BLEND_SOFTLIGHT_KHR = 0x2013,
|
|
QT_VG_BLEND_COLORDODGE_KHR = 0x2014,
|
|
QT_VG_BLEND_COLORBURN_KHR = 0x2015,
|
|
QT_VG_BLEND_DIFFERENCE_KHR = 0x2016,
|
|
QT_VG_BLEND_SUBTRACT_KHR = 0x2017,
|
|
QT_VG_BLEND_INVERT_KHR = 0x2018,
|
|
QT_VG_BLEND_EXCLUSION_KHR = 0x2019,
|
|
QT_VG_BLEND_LINEARDODGE_KHR = 0x201a,
|
|
QT_VG_BLEND_LINEARBURN_KHR = 0x201b,
|
|
QT_VG_BLEND_VIVIDLIGHT_KHR = 0x201c,
|
|
QT_VG_BLEND_LINEARLIGHT_KHR = 0x201d,
|
|
QT_VG_BLEND_PINLIGHT_KHR = 0x201e,
|
|
QT_VG_BLEND_HARDMIX_KHR = 0x201f,
|
|
QT_VG_BLEND_CLEAR_KHR = 0x2020,
|
|
QT_VG_BLEND_DST_KHR = 0x2021,
|
|
QT_VG_BLEND_SRC_OUT_KHR = 0x2022,
|
|
QT_VG_BLEND_DST_OUT_KHR = 0x2023,
|
|
QT_VG_BLEND_SRC_ATOP_KHR = 0x2024,
|
|
QT_VG_BLEND_DST_ATOP_KHR = 0x2025,
|
|
QT_VG_BLEND_XOR_KHR = 0x2026
|
|
};
|
|
|
|
QVGPaintEnginePrivate(QVGPaintEngine *q_ptr);
|
|
~QVGPaintEnginePrivate();
|
|
|
|
void init();
|
|
void initObjects();
|
|
void destroy();
|
|
void setTransform(VGMatrixMode mode, const QTransform& transform);
|
|
void updateTransform(QPaintDevice *pdev);
|
|
void draw(VGPath path, const QPen& pen, const QBrush& brush, VGint rule = VG_EVEN_ODD);
|
|
void stroke(VGPath path, const QPen& pen);
|
|
void fill(VGPath path, const QBrush& brush, VGint rule = VG_EVEN_ODD);
|
|
VGPath vectorPathToVGPath(const QVectorPath& path);
|
|
VGPath painterPathToVGPath(const QPainterPath& path);
|
|
VGPath roundedRectPath(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode);
|
|
VGPaintType setBrush
|
|
(VGPaint paint, const QBrush& brush, VGMatrixMode mode,
|
|
VGPaintType prevPaintType);
|
|
void setPenParams(const QPen& pen);
|
|
void setBrushTransform(const QBrush& brush, VGMatrixMode mode);
|
|
void setupColorRamp(const QGradient *grad, VGPaint paint);
|
|
void setImageOptions();
|
|
void systemStateChanged();
|
|
#if !defined(QVG_SCISSOR_CLIP)
|
|
void ensureMask(QVGPaintEngine *engine, int width, int height);
|
|
void modifyMask
|
|
(QVGPaintEngine *engine, VGMaskOperation op, const QRegion& region);
|
|
void modifyMask
|
|
(QVGPaintEngine *engine, VGMaskOperation op, const QRect& rect);
|
|
#endif
|
|
|
|
VGint maxScissorRects; // Maximum scissor rectangles for clipping.
|
|
|
|
VGPaint penPaint; // Paint for currently active pen.
|
|
VGPaint brushPaint; // Paint for currently active brush.
|
|
VGPaint opacityPaint; // Paint for drawing images with opacity.
|
|
VGPaint fillPaint; // Current fill paint that is active.
|
|
|
|
QPen currentPen; // Current pen set in "penPaint".
|
|
QBrush currentBrush; // Current brush set in "brushPaint".
|
|
|
|
bool forcePenChange; // Force a pen change, even if the same.
|
|
bool forceBrushChange; // Force a brush change, even if the same.
|
|
|
|
bool hasExtendedRadialGradientPen; // Current pen's brush is extended radial gradient.
|
|
bool hasExtendedRadialGradientBrush; // Current brush is extended radial gradient.
|
|
|
|
VGPaintType penType; // Type of the last pen that was set.
|
|
VGPaintType brushType; // Type of the last brush that was set.
|
|
|
|
QPointF brushOrigin; // Current brush origin.
|
|
|
|
VGint fillRule; // Last fill rule that was set.
|
|
|
|
qreal opacity; // Current drawing opacity.
|
|
qreal paintOpacity; // Opacity in opacityPaint.
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
VGPath rectPath; // Cached path for quick drawing of rectangles.
|
|
VGPath linePath; // Cached path for quick drawing of lines.
|
|
VGPath roundRectPath; // Cached path for quick drawing of rounded rects.
|
|
#endif
|
|
|
|
QTransform transform; // Currently active transform.
|
|
bool simpleTransform; // True if the transform is simple (non-projective).
|
|
qreal penScale; // Pen scaling factor from "transform".
|
|
|
|
QTransform pathTransform; // Calculated VG path transformation.
|
|
QTransform imageTransform; // Calculated VG image transformation.
|
|
bool pathTransformSet; // True if path transform set in the VG context.
|
|
|
|
bool maskValid; // True if vgMask() contains valid data.
|
|
bool maskIsSet; // True if mask would be fully set if it was valid.
|
|
bool scissorMask; // True if scissor is used in place of the mask.
|
|
bool rawVG; // True if processing a raw VG escape.
|
|
|
|
QRect maskRect; // Rectangle version of mask if it is simple.
|
|
|
|
QTransform penTransform; // Transform for the pen.
|
|
QTransform brushTransform; // Transform for the brush.
|
|
|
|
VGMatrixMode matrixMode; // Last matrix mode that was set.
|
|
VGImageMode imageMode; // Last image mode that was set.
|
|
|
|
QRegion scissorRegion; // Currently active scissor region.
|
|
bool scissorActive; // True if scissor region is active.
|
|
bool scissorDirty; // True if scissor is dirty after native painting.
|
|
|
|
QPaintEngine::DirtyFlags dirty;
|
|
|
|
QColor clearColor; // Last clear color that was set.
|
|
VGfloat clearOpacity; // Opacity during the last clear.
|
|
|
|
VGBlendMode blendMode; // Active blend mode.
|
|
VGRenderingQuality renderingQuality; // Active rendering quality.
|
|
VGImageQuality imageQuality; // Active image quality.
|
|
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
QVGFontCache fontCache;
|
|
QVGFontEngineCleaner *fontEngineCleaner;
|
|
#endif
|
|
|
|
bool hasAdvancedBlending;
|
|
|
|
QScopedPointer<QPixmapFilter> convolutionFilter;
|
|
QScopedPointer<QPixmapFilter> colorizeFilter;
|
|
QScopedPointer<QPixmapFilter> dropShadowFilter;
|
|
QScopedPointer<QPixmapFilter> blurFilter;
|
|
|
|
// Ensure that the path transform is properly set in the VG context
|
|
// before we perform a vgDrawPath() operation.
|
|
inline void ensurePathTransform()
|
|
{
|
|
if (!pathTransformSet) {
|
|
QTransform aliasedTransform = pathTransform;
|
|
if (renderingQuality == VG_RENDERING_QUALITY_NONANTIALIASED && currentPen != Qt::NoPen)
|
|
aliasedTransform = aliasedTransform
|
|
* QTransform::fromTranslate(aliasedCoordinateDelta, -aliasedCoordinateDelta);
|
|
setTransform(VG_MATRIX_PATH_USER_TO_SURFACE, aliasedTransform);
|
|
pathTransformSet = true;
|
|
}
|
|
}
|
|
|
|
// Ensure that a specific pen has been set into penPaint.
|
|
inline void ensurePen(const QPen& pen) {
|
|
if (forcePenChange || pen != currentPen) {
|
|
currentPen = pen;
|
|
forcePenChange = false;
|
|
penType = setBrush
|
|
(penPaint, pen.brush(),
|
|
VG_MATRIX_STROKE_PAINT_TO_USER, penType);
|
|
setPenParams(pen);
|
|
}
|
|
}
|
|
|
|
// Ensure that a specific brush has been set into brushPaint.
|
|
inline void ensureBrush(const QBrush& brush) {
|
|
if (forceBrushChange || brush != currentBrush) {
|
|
currentBrush = brush;
|
|
forceBrushChange = false;
|
|
brushType = setBrush
|
|
(brushPaint, brush, VG_MATRIX_FILL_PAINT_TO_USER, brushType);
|
|
}
|
|
if (fillPaint != brushPaint) {
|
|
vgSetPaint(brushPaint, VG_FILL_PATH);
|
|
fillPaint = brushPaint;
|
|
}
|
|
}
|
|
|
|
inline bool needsEmulation(const QBrush &brush) const
|
|
{
|
|
extern bool qt_isExtendedRadialGradient(const QBrush &brush);
|
|
return qt_isExtendedRadialGradient(brush);
|
|
}
|
|
|
|
inline bool needsEmulation() const
|
|
{
|
|
return hasExtendedRadialGradientPen || hasExtendedRadialGradientBrush;
|
|
}
|
|
|
|
inline bool needsPenEmulation() const
|
|
{
|
|
return hasExtendedRadialGradientPen;
|
|
}
|
|
|
|
inline bool needsBrushEmulation() const
|
|
{
|
|
return hasExtendedRadialGradientBrush;
|
|
}
|
|
|
|
// Set various modes, but only if different.
|
|
inline void setImageMode(VGImageMode mode);
|
|
inline void setRenderingQuality(VGRenderingQuality mode);
|
|
inline void setImageQuality(VGImageQuality mode);
|
|
inline void setBlendMode(VGBlendMode mode);
|
|
inline void setFillRule(VGint mode);
|
|
|
|
// Clear all lazily-set modes.
|
|
void clearModes();
|
|
|
|
private:
|
|
QVGPaintEngine *q;
|
|
};
|
|
|
|
inline void QVGPaintEnginePrivate::setImageMode(VGImageMode mode)
|
|
{
|
|
if (imageMode != mode) {
|
|
imageMode = mode;
|
|
vgSeti(VG_IMAGE_MODE, mode);
|
|
}
|
|
}
|
|
|
|
inline void QVGPaintEnginePrivate::setRenderingQuality(VGRenderingQuality mode)
|
|
{
|
|
if (renderingQuality != mode) {
|
|
vgSeti(VG_RENDERING_QUALITY, mode);
|
|
renderingQuality = mode;
|
|
pathTransformSet = false; // need to tweak transform for aliased stroking
|
|
}
|
|
}
|
|
|
|
inline void QVGPaintEnginePrivate::setImageQuality(VGImageQuality mode)
|
|
{
|
|
if (imageQuality != mode) {
|
|
vgSeti(VG_IMAGE_QUALITY, mode);
|
|
imageQuality = mode;
|
|
}
|
|
}
|
|
|
|
inline void QVGPaintEnginePrivate::setBlendMode(VGBlendMode mode)
|
|
{
|
|
if (blendMode != mode) {
|
|
vgSeti(VG_BLEND_MODE, mode);
|
|
blendMode = mode;
|
|
}
|
|
}
|
|
|
|
inline void QVGPaintEnginePrivate::setFillRule(VGint mode)
|
|
{
|
|
if (fillRule != mode) {
|
|
fillRule = mode;
|
|
vgSeti(VG_FILL_RULE, mode);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::clearModes()
|
|
{
|
|
matrixMode = (VGMatrixMode)0;
|
|
imageMode = (VGImageMode)0;
|
|
blendMode = (VGBlendMode)0;
|
|
renderingQuality = (VGRenderingQuality)0;
|
|
imageQuality = (VGImageQuality)0;
|
|
}
|
|
|
|
QVGPaintEnginePrivate::QVGPaintEnginePrivate(QVGPaintEngine *q_ptr) : q(q_ptr)
|
|
{
|
|
init();
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::init()
|
|
{
|
|
maxScissorRects = 0;
|
|
|
|
penPaint = 0;
|
|
brushPaint = 0;
|
|
opacityPaint = 0;
|
|
fillPaint = 0;
|
|
|
|
forcePenChange = true;
|
|
forceBrushChange = true;
|
|
|
|
hasExtendedRadialGradientPen = false;
|
|
hasExtendedRadialGradientBrush = false;
|
|
|
|
penType = (VGPaintType)0;
|
|
brushType = (VGPaintType)0;
|
|
|
|
brushOrigin = QPointF(0.0f, 0.0f);
|
|
|
|
fillRule = 0;
|
|
|
|
opacity = 1.0;
|
|
paintOpacity = 1.0f;
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
rectPath = 0;
|
|
linePath = 0;
|
|
roundRectPath = 0;
|
|
#endif
|
|
|
|
simpleTransform = true;
|
|
pathTransformSet = false;
|
|
penScale = 1.0;
|
|
|
|
maskValid = false;
|
|
maskIsSet = false;
|
|
scissorMask = false;
|
|
rawVG = false;
|
|
|
|
scissorActive = false;
|
|
scissorDirty = false;
|
|
|
|
dirty = 0;
|
|
|
|
clearOpacity = 1.0f;
|
|
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
fontEngineCleaner = 0;
|
|
#endif
|
|
|
|
hasAdvancedBlending = false;
|
|
|
|
clearModes();
|
|
}
|
|
|
|
QVGPaintEnginePrivate::~QVGPaintEnginePrivate()
|
|
{
|
|
destroy();
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::initObjects()
|
|
{
|
|
maxScissorRects = vgGeti(VG_MAX_SCISSOR_RECTS);
|
|
|
|
penPaint = vgCreatePaint();
|
|
vgSetParameteri(penPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
|
|
vgSetPaint(penPaint, VG_STROKE_PATH);
|
|
|
|
vgSeti(VG_MATRIX_MODE, VG_MATRIX_STROKE_PAINT_TO_USER);
|
|
vgLoadIdentity();
|
|
|
|
brushPaint = vgCreatePaint();
|
|
vgSetParameteri(brushPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
|
|
vgSetPaint(brushPaint, VG_FILL_PATH);
|
|
fillPaint = brushPaint;
|
|
|
|
vgSeti(VG_MATRIX_MODE, VG_MATRIX_FILL_PAINT_TO_USER);
|
|
vgLoadIdentity();
|
|
matrixMode = VG_MATRIX_FILL_PAINT_TO_USER;
|
|
|
|
opacityPaint = vgCreatePaint();
|
|
vgSetParameteri(opacityPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
|
|
VGfloat values[4];
|
|
values[0] = 1.0f;
|
|
values[1] = 1.0f;
|
|
values[2] = 1.0f;
|
|
values[3] = paintOpacity;
|
|
vgSetParameterfv(opacityPaint, VG_PAINT_COLOR, 4, values);
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
// Create a dummy path for rectangle drawing, which we can
|
|
// modify later with vgModifyPathCoords(). This should be
|
|
// faster than constantly creating and destroying paths.
|
|
rectPath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
5, // segmentCapacityHint
|
|
8, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
static VGubyte const segments[5] = {
|
|
VG_MOVE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CLOSE_PATH
|
|
};
|
|
VGfloat coords[8];
|
|
coords[0] = 0.0f;
|
|
coords[1] = 0.0f;
|
|
coords[2] = 100.0f;
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = 100.0f;
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
vgAppendPathData(rectPath, 5, segments, coords);
|
|
|
|
// Create a dummy line drawing path as well.
|
|
linePath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
2, // segmentCapacityHint
|
|
4, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
vgAppendPathData(linePath, 2, segments, coords);
|
|
#endif
|
|
|
|
const char *extensions = reinterpret_cast<const char *>(vgGetString(VG_EXTENSIONS));
|
|
if (extensions)
|
|
hasAdvancedBlending = strstr(extensions, "VG_KHR_advanced_blending") != 0;
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::destroy()
|
|
{
|
|
if (penPaint)
|
|
vgDestroyPaint(penPaint);
|
|
if (brushPaint)
|
|
vgDestroyPaint(brushPaint);
|
|
if (opacityPaint)
|
|
vgDestroyPaint(opacityPaint);
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
if (rectPath)
|
|
vgDestroyPath(rectPath);
|
|
if (linePath)
|
|
vgDestroyPath(linePath);
|
|
if (roundRectPath)
|
|
vgDestroyPath(roundRectPath);
|
|
#endif
|
|
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
QVGFontCache::Iterator it;
|
|
for (it = fontCache.begin(); it != fontCache.end(); ++it)
|
|
delete it.value();
|
|
fontCache.clear();
|
|
delete fontEngineCleaner;
|
|
#endif
|
|
}
|
|
|
|
// Set a specific VG transformation matrix in the current VG context.
|
|
void QVGPaintEnginePrivate::setTransform
|
|
(VGMatrixMode mode, const QTransform& transform)
|
|
{
|
|
VGfloat mat[9];
|
|
if (mode != matrixMode) {
|
|
vgSeti(VG_MATRIX_MODE, mode);
|
|
matrixMode = mode;
|
|
}
|
|
mat[0] = transform.m11();
|
|
mat[1] = transform.m12();
|
|
mat[2] = transform.m13();
|
|
mat[3] = transform.m21();
|
|
mat[4] = transform.m22();
|
|
mat[5] = transform.m23();
|
|
mat[6] = transform.m31();
|
|
mat[7] = transform.m32();
|
|
mat[8] = transform.m33();
|
|
vgLoadMatrix(mat);
|
|
}
|
|
|
|
Q_GUI_EXPORT bool qt_scaleForTransform(const QTransform &transform, qreal *scale);
|
|
|
|
void QVGPaintEnginePrivate::updateTransform(QPaintDevice *pdev)
|
|
{
|
|
VGfloat devh = pdev->height();
|
|
|
|
// Construct the VG transform by combining the Qt transform with
|
|
// the following viewport transformation:
|
|
// | 1 0 0 |
|
|
// | 0 -1 devh |
|
|
// | 0 0 1 |
|
|
// The full VG transform is effectively:
|
|
// 1. Apply the user's transformation matrix.
|
|
// 2. Flip the co-ordinate system upside down.
|
|
QTransform viewport(1.0f, 0.0f, 0.0f,
|
|
0.0f, -1.0f, 0.0f,
|
|
0.0f, devh, 1.0f);
|
|
|
|
// Compute the path transform and determine if it is projective.
|
|
pathTransform = transform * viewport;
|
|
bool projective = (pathTransform.m13() != 0.0f ||
|
|
pathTransform.m23() != 0.0f ||
|
|
pathTransform.m33() != 1.0f);
|
|
if (projective) {
|
|
// The engine cannot do projective path transforms for us,
|
|
// so we will have to convert the co-ordinates ourselves.
|
|
// Change the matrix to just the viewport transformation.
|
|
pathTransform = viewport;
|
|
simpleTransform = false;
|
|
} else {
|
|
simpleTransform = true;
|
|
}
|
|
pathTransformSet = false;
|
|
|
|
// The image transform is always the full transformation,
|
|
imageTransform = transform * viewport;
|
|
|
|
// Calculate the scaling factor to use for turning cosmetic pens
|
|
// into ordinary non-cosmetic pens.
|
|
qt_scaleForTransform(transform, &penScale);
|
|
}
|
|
|
|
VGPath QVGPaintEnginePrivate::vectorPathToVGPath(const QVectorPath& path)
|
|
{
|
|
int count = path.elementCount();
|
|
const qreal *points = path.points();
|
|
const QPainterPath::ElementType *elements = path.elements();
|
|
|
|
VGPath vgpath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
count + 1, // segmentCapacityHint
|
|
count * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
|
|
// Size is sufficient segments for drawRoundedRect() paths.
|
|
QVarLengthArray<VGubyte, 20> segments;
|
|
|
|
if (sizeof(qreal) == sizeof(VGfloat) && elements && simpleTransform) {
|
|
// If Qt was compiled with qreal the same size as VGfloat,
|
|
// then convert the segment types and use the incoming
|
|
// points array directly.
|
|
for (int i = 0; i < count; ++i) {
|
|
switch (elements[i]) {
|
|
|
|
case QPainterPath::MoveToElement:
|
|
segments.append(VG_MOVE_TO_ABS); break;
|
|
|
|
case QPainterPath::LineToElement:
|
|
segments.append(VG_LINE_TO_ABS); break;
|
|
|
|
case QPainterPath::CurveToElement:
|
|
segments.append(VG_CUBIC_TO_ABS); break;
|
|
|
|
case QPainterPath::CurveToDataElement: break;
|
|
|
|
}
|
|
}
|
|
if (path.hasImplicitClose())
|
|
segments.append(VG_CLOSE_PATH);
|
|
|
|
vgAppendPathData(vgpath, segments.count(), segments.constData(),
|
|
reinterpret_cast<const VGfloat *>(points));
|
|
|
|
return vgpath;
|
|
}
|
|
|
|
// Sizes chosen so that drawRoundedRect() paths fit in these arrays.
|
|
QVarLengthArray<VGfloat, 48> coords;
|
|
|
|
int curvePos = 0;
|
|
QPointF temp;
|
|
|
|
if (elements && simpleTransform) {
|
|
// Convert the members of the element array.
|
|
for (int i = 0; i < count; ++i) {
|
|
switch (elements[i]) {
|
|
|
|
case QPainterPath::MoveToElement:
|
|
{
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::LineToElement:
|
|
{
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToElement:
|
|
{
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
curvePos = 2;
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToDataElement:
|
|
{
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
curvePos += 2;
|
|
if (curvePos == 6) {
|
|
curvePos = 0;
|
|
segments.append(VG_CUBIC_TO_ABS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
points += 2;
|
|
}
|
|
} else if (elements && !simpleTransform) {
|
|
// Convert the members of the element array after applying the
|
|
// current transform to the path locally.
|
|
for (int i = 0; i < count; ++i) {
|
|
switch (elements[i]) {
|
|
|
|
case QPainterPath::MoveToElement:
|
|
{
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::LineToElement:
|
|
{
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToElement:
|
|
{
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
curvePos = 2;
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToDataElement:
|
|
{
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
curvePos += 2;
|
|
if (curvePos == 6) {
|
|
curvePos = 0;
|
|
segments.append(VG_CUBIC_TO_ABS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
points += 2;
|
|
}
|
|
} else if (count > 0 && simpleTransform) {
|
|
// If there is no element array, then the path is assumed
|
|
// to be a MoveTo followed by several LineTo's.
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
while (count > 1) {
|
|
points += 2;
|
|
coords.append(points[0]);
|
|
coords.append(points[1]);
|
|
segments.append(VG_LINE_TO_ABS);
|
|
--count;
|
|
}
|
|
} else if (count > 0 && !simpleTransform) {
|
|
// Convert a simple path, and apply the transform locally.
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
while (count > 1) {
|
|
points += 2;
|
|
temp = transform.map(QPointF(points[0], points[1]));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
segments.append(VG_LINE_TO_ABS);
|
|
--count;
|
|
}
|
|
}
|
|
|
|
// Close the path if specified.
|
|
if (path.hasImplicitClose())
|
|
segments.append(VG_CLOSE_PATH);
|
|
|
|
vgAppendPathData(vgpath, segments.count(),
|
|
segments.constData(), coords.constData());
|
|
|
|
return vgpath;
|
|
}
|
|
|
|
VGPath QVGPaintEnginePrivate::painterPathToVGPath(const QPainterPath& path)
|
|
{
|
|
int count = path.elementCount();
|
|
|
|
VGPath vgpath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
count + 1, // segmentCapacityHint
|
|
count * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
|
|
if (count == 0)
|
|
return vgpath;
|
|
|
|
const QPainterPath::Element *elements = &(path.elementAt(0));
|
|
|
|
// Sizes chosen so that drawRoundedRect() paths fit in these arrays.
|
|
QVarLengthArray<VGfloat, 48> coords;
|
|
QVarLengthArray<VGubyte, 20> segments;
|
|
|
|
int curvePos = 0;
|
|
QPointF temp;
|
|
|
|
// Keep track of the start and end of each sub-path. QPainterPath
|
|
// does not have an "implicit close" flag like QVectorPath does.
|
|
// We therefore have to detect closed paths by looking for a LineTo
|
|
// element that connects back to the initial MoveTo element.
|
|
qreal startx = 0.0;
|
|
qreal starty = 0.0;
|
|
qreal endx = 0.0;
|
|
qreal endy = 0.0;
|
|
bool haveStart = false;
|
|
bool haveEnd = false;
|
|
|
|
if (simpleTransform) {
|
|
// Convert the members of the element array.
|
|
for (int i = 0; i < count; ++i) {
|
|
switch (elements[i].type) {
|
|
|
|
case QPainterPath::MoveToElement:
|
|
{
|
|
if (haveStart && haveEnd && startx == endx && starty == endy) {
|
|
// Implicitly close the previous sub-path.
|
|
segments.append(VG_CLOSE_PATH);
|
|
}
|
|
startx = elements[i].x;
|
|
starty = elements[i].y;
|
|
coords.append(startx);
|
|
coords.append(starty);
|
|
haveStart = true;
|
|
haveEnd = false;
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::LineToElement:
|
|
{
|
|
endx = elements[i].x;
|
|
endy = elements[i].y;
|
|
coords.append(endx);
|
|
coords.append(endy);
|
|
haveEnd = true;
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToElement:
|
|
{
|
|
coords.append(elements[i].x);
|
|
coords.append(elements[i].y);
|
|
haveEnd = false;
|
|
curvePos = 2;
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToDataElement:
|
|
{
|
|
coords.append(elements[i].x);
|
|
coords.append(elements[i].y);
|
|
haveEnd = false;
|
|
curvePos += 2;
|
|
if (curvePos == 6) {
|
|
curvePos = 0;
|
|
segments.append(VG_CUBIC_TO_ABS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
} else {
|
|
// Convert the members of the element array after applying the
|
|
// current transform to the path locally.
|
|
for (int i = 0; i < count; ++i) {
|
|
switch (elements[i].type) {
|
|
|
|
case QPainterPath::MoveToElement:
|
|
{
|
|
if (haveStart && haveEnd && startx == endx && starty == endy) {
|
|
// Implicitly close the previous sub-path.
|
|
segments.append(VG_CLOSE_PATH);
|
|
}
|
|
temp = transform.map(QPointF(elements[i].x, elements[i].y));
|
|
startx = temp.x();
|
|
starty = temp.y();
|
|
coords.append(startx);
|
|
coords.append(starty);
|
|
haveStart = true;
|
|
haveEnd = false;
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::LineToElement:
|
|
{
|
|
temp = transform.map(QPointF(elements[i].x, elements[i].y));
|
|
endx = temp.x();
|
|
endy = temp.y();
|
|
coords.append(endx);
|
|
coords.append(endy);
|
|
haveEnd = true;
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToElement:
|
|
{
|
|
temp = transform.map(QPointF(elements[i].x, elements[i].y));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
haveEnd = false;
|
|
curvePos = 2;
|
|
}
|
|
break;
|
|
|
|
case QPainterPath::CurveToDataElement:
|
|
{
|
|
temp = transform.map(QPointF(elements[i].x, elements[i].y));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
haveEnd = false;
|
|
curvePos += 2;
|
|
if (curvePos == 6) {
|
|
curvePos = 0;
|
|
segments.append(VG_CUBIC_TO_ABS);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
if (haveStart && haveEnd && startx == endx && starty == endy) {
|
|
// Implicitly close the last sub-path.
|
|
segments.append(VG_CLOSE_PATH);
|
|
}
|
|
|
|
vgAppendPathData(vgpath, segments.count(),
|
|
segments.constData(), coords.constData());
|
|
|
|
return vgpath;
|
|
}
|
|
|
|
VGPath QVGPaintEnginePrivate::roundedRectPath(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
|
|
{
|
|
static VGubyte roundedrect_types[] = {
|
|
VG_MOVE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CUBIC_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CUBIC_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CUBIC_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CUBIC_TO_ABS,
|
|
VG_CLOSE_PATH
|
|
};
|
|
|
|
qreal x1 = rect.left();
|
|
qreal x2 = rect.right();
|
|
qreal y1 = rect.top();
|
|
qreal y2 = rect.bottom();
|
|
|
|
if (mode == Qt::RelativeSize) {
|
|
xRadius = xRadius * rect.width() / 200.;
|
|
yRadius = yRadius * rect.height() / 200.;
|
|
}
|
|
|
|
xRadius = qMin(xRadius, rect.width() / 2);
|
|
yRadius = qMin(yRadius, rect.height() / 2);
|
|
|
|
VGfloat pts[] = {
|
|
x1 + xRadius, y1, // MoveTo
|
|
x2 - xRadius, y1, // LineTo
|
|
x2 - (1 - KAPPA) * xRadius, y1, // CurveTo
|
|
x2, y1 + (1 - KAPPA) * yRadius,
|
|
x2, y1 + yRadius,
|
|
x2, y2 - yRadius, // LineTo
|
|
x2, y2 - (1 - KAPPA) * yRadius, // CurveTo
|
|
x2 - (1 - KAPPA) * xRadius, y2,
|
|
x2 - xRadius, y2,
|
|
x1 + xRadius, y2, // LineTo
|
|
x1 + (1 - KAPPA) * xRadius, y2, // CurveTo
|
|
x1, y2 - (1 - KAPPA) * yRadius,
|
|
x1, y2 - yRadius,
|
|
x1, y1 + yRadius, // LineTo
|
|
x1, y1 + (1 - KAPPA) * yRadius, // CurveTo
|
|
x1 + (1 - KAPPA) * xRadius, y1,
|
|
x1 + xRadius, y1
|
|
};
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
VGPath vgpath = roundRectPath;
|
|
if (!vgpath) {
|
|
vgpath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
10, // segmentCapacityHint
|
|
17 * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
vgAppendPathData(vgpath, 10, roundedrect_types, pts);
|
|
roundRectPath = vgpath;
|
|
} else {
|
|
vgModifyPathCoords(vgpath, 0, 9, pts);
|
|
}
|
|
#else
|
|
VGPath vgpath = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
10, // segmentCapacityHint
|
|
17 * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
vgAppendPathData(vgpath, 10, roundedrect_types, pts);
|
|
#endif
|
|
|
|
return vgpath;
|
|
}
|
|
|
|
Q_GUI_EXPORT QImage qt_imageForBrush(int style, bool invert);
|
|
|
|
static QImage colorizeBitmap(const QImage &image, const QColor &color)
|
|
{
|
|
QImage sourceImage = image.convertToFormat(QImage::Format_MonoLSB);
|
|
QImage dest = QImage(sourceImage.size(), QImage::Format_ARGB32_Premultiplied);
|
|
|
|
QRgb fg = PREMUL(color.rgba());
|
|
QRgb bg = 0;
|
|
|
|
int height = sourceImage.height();
|
|
int width = sourceImage.width();
|
|
for (int y=0; y<height; ++y) {
|
|
const uchar *source = sourceImage.constScanLine(y);
|
|
QRgb *target = reinterpret_cast<QRgb *>(dest.scanLine(y));
|
|
for (int x=0; x < width; ++x)
|
|
target[x] = (source[x>>3] >> (x&7)) & 1 ? fg : bg;
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
static VGImage toVGImage
|
|
(const QImage & image, Qt::ImageConversionFlags flags = Qt::AutoColor)
|
|
{
|
|
QImage img(image);
|
|
|
|
VGImageFormat format;
|
|
switch (img.format()) {
|
|
case QImage::Format_Mono:
|
|
img = image.convertToFormat(QImage::Format_MonoLSB, flags);
|
|
img.invertPixels();
|
|
format = VG_BW_1;
|
|
break;
|
|
case QImage::Format_MonoLSB:
|
|
img.invertPixels();
|
|
format = VG_BW_1;
|
|
break;
|
|
case QImage::Format_RGB32:
|
|
format = VG_sXRGB_8888;
|
|
break;
|
|
case QImage::Format_ARGB32:
|
|
format = VG_sARGB_8888;
|
|
break;
|
|
case QImage::Format_ARGB32_Premultiplied:
|
|
format = VG_sARGB_8888_PRE;
|
|
break;
|
|
case QImage::Format_RGB16:
|
|
format = VG_sRGB_565;
|
|
break;
|
|
default:
|
|
// Convert everything else into ARGB32_Premultiplied.
|
|
img = image.convertToFormat(QImage::Format_ARGB32_Premultiplied, flags);
|
|
format = VG_sARGB_8888_PRE;
|
|
break;
|
|
}
|
|
|
|
const uchar *pixels = img.constBits();
|
|
|
|
VGImage vgImg = QVGImagePool::instance()->createPermanentImage
|
|
(format, img.width(), img.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData
|
|
(vgImg, pixels, img.bytesPerLine(), format, 0, 0,
|
|
img.width(), img.height());
|
|
|
|
return vgImg;
|
|
}
|
|
|
|
static VGImage toVGImageSubRect
|
|
(const QImage & image, const QRect& sr,
|
|
Qt::ImageConversionFlags flags = Qt::AutoColor)
|
|
{
|
|
QImage img(image);
|
|
|
|
VGImageFormat format;
|
|
int bpp = 4;
|
|
|
|
switch (img.format()) {
|
|
case QImage::Format_Mono:
|
|
case QImage::Format_MonoLSB:
|
|
return VG_INVALID_HANDLE;
|
|
case QImage::Format_RGB32:
|
|
format = VG_sXRGB_8888;
|
|
break;
|
|
case QImage::Format_ARGB32:
|
|
format = VG_sARGB_8888;
|
|
break;
|
|
case QImage::Format_ARGB32_Premultiplied:
|
|
format = VG_sARGB_8888_PRE;
|
|
break;
|
|
case QImage::Format_RGB16:
|
|
format = VG_sRGB_565;
|
|
bpp = 2;
|
|
break;
|
|
default:
|
|
// Convert everything else into ARGB32_Premultiplied.
|
|
img = image.convertToFormat(QImage::Format_ARGB32_Premultiplied, flags);
|
|
format = VG_sARGB_8888_PRE;
|
|
break;
|
|
}
|
|
|
|
const uchar *pixels = img.constBits() + bpp * sr.x() +
|
|
img.bytesPerLine() * sr.y();
|
|
|
|
VGImage vgImg = QVGImagePool::instance()->createPermanentImage
|
|
(format, sr.width(), sr.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData
|
|
(vgImg, pixels, img.bytesPerLine(), format, 0, 0,
|
|
sr.width(), sr.height());
|
|
|
|
return vgImg;
|
|
}
|
|
|
|
static VGImage toVGImageWithOpacity(const QImage & image, qreal opacity)
|
|
{
|
|
QImage img(image.size(), QImage::Format_ARGB32_Premultiplied);
|
|
img.fill(0);
|
|
QPainter painter;
|
|
painter.begin(&img);
|
|
painter.setOpacity(opacity);
|
|
painter.drawImage(0, 0, image);
|
|
painter.end();
|
|
|
|
const uchar *pixels = img.constBits();
|
|
|
|
VGImage vgImg = QVGImagePool::instance()->createPermanentImage
|
|
(VG_sARGB_8888_PRE, img.width(), img.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData
|
|
(vgImg, pixels, img.bytesPerLine(), VG_sARGB_8888_PRE, 0, 0,
|
|
img.width(), img.height());
|
|
|
|
return vgImg;
|
|
}
|
|
|
|
static VGImage toVGImageWithOpacitySubRect
|
|
(const QImage & image, qreal opacity, const QRect& sr)
|
|
{
|
|
QImage img(sr.size(), QImage::Format_ARGB32_Premultiplied);
|
|
img.fill(0);
|
|
QPainter painter;
|
|
painter.begin(&img);
|
|
painter.setOpacity(opacity);
|
|
painter.drawImage(QPoint(0, 0), image, sr);
|
|
painter.end();
|
|
|
|
const uchar *pixels = img.constBits();
|
|
|
|
VGImage vgImg = QVGImagePool::instance()->createPermanentImage
|
|
(VG_sARGB_8888_PRE, img.width(), img.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData
|
|
(vgImg, pixels, img.bytesPerLine(), VG_sARGB_8888_PRE, 0, 0,
|
|
img.width(), img.height());
|
|
|
|
return vgImg;
|
|
}
|
|
|
|
VGPaintType QVGPaintEnginePrivate::setBrush
|
|
(VGPaint paint, const QBrush& brush, VGMatrixMode mode,
|
|
VGPaintType prevType)
|
|
{
|
|
VGfloat values[5];
|
|
setBrushTransform(brush, mode);
|
|
|
|
// Reset the paint pattern on the brush, which will discard
|
|
// the previous VGImage if one was set.
|
|
if (prevType == VG_PAINT_TYPE_PATTERN || prevType == (VGPaintType)0)
|
|
vgPaintPattern(paint, VG_INVALID_HANDLE);
|
|
|
|
switch (brush.style()) {
|
|
|
|
case Qt::SolidPattern: {
|
|
// The brush is a solid color.
|
|
QColor color(brush.color());
|
|
values[0] = color.redF();
|
|
values[1] = color.greenF();
|
|
values[2] = color.blueF();
|
|
values[3] = color.alphaF() * opacity;
|
|
if (prevType != VG_PAINT_TYPE_COLOR)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
|
|
vgSetParameterfv(paint, VG_PAINT_COLOR, 4, values);
|
|
return VG_PAINT_TYPE_COLOR;
|
|
}
|
|
|
|
case Qt::LinearGradientPattern: {
|
|
// The brush is a linear gradient.
|
|
Q_ASSERT(brush.gradient()->type() == QGradient::LinearGradient);
|
|
const QLinearGradient *grad =
|
|
static_cast<const QLinearGradient*>(brush.gradient());
|
|
values[0] = grad->start().x();
|
|
values[1] = grad->start().y();
|
|
values[2] = grad->finalStop().x();
|
|
values[3] = grad->finalStop().y();
|
|
if (prevType != VG_PAINT_TYPE_LINEAR_GRADIENT)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_LINEAR_GRADIENT);
|
|
vgSetParameterfv(paint, VG_PAINT_LINEAR_GRADIENT, 4, values);
|
|
setupColorRamp(grad, paint);
|
|
return VG_PAINT_TYPE_LINEAR_GRADIENT;
|
|
}
|
|
|
|
case Qt::RadialGradientPattern: {
|
|
// The brush is a radial gradient.
|
|
Q_ASSERT(brush.gradient()->type() == QGradient::RadialGradient);
|
|
const QRadialGradient *grad =
|
|
static_cast<const QRadialGradient*>(brush.gradient());
|
|
values[0] = grad->center().x();
|
|
values[1] = grad->center().y();
|
|
values[2] = grad->focalPoint().x();
|
|
values[3] = grad->focalPoint().y();
|
|
values[4] = grad->radius();
|
|
if (prevType != VG_PAINT_TYPE_RADIAL_GRADIENT)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_RADIAL_GRADIENT);
|
|
vgSetParameterfv(paint, VG_PAINT_RADIAL_GRADIENT, 5, values);
|
|
setupColorRamp(grad, paint);
|
|
return VG_PAINT_TYPE_RADIAL_GRADIENT;
|
|
}
|
|
|
|
case Qt::TexturePattern: {
|
|
// The brush is a texture specified by a QPixmap/QImage.
|
|
QPixmapData *pd = brush.texture().pixmapData();
|
|
if (!pd)
|
|
break; // null QPixmap
|
|
VGImage vgImg;
|
|
bool deref = false;
|
|
if (pd->pixelType() == QPixmapData::BitmapType) {
|
|
// Colorize bitmaps using the brush color and opacity.
|
|
QColor color = brush.color();
|
|
if (opacity != 1.0)
|
|
color.setAlphaF(color.alphaF() * opacity);
|
|
QImage image = colorizeBitmap(*(pd->buffer()), color);
|
|
vgImg = toVGImage(image);
|
|
deref = true;
|
|
} else if (opacity == 1.0) {
|
|
if (pd->classId() == QPixmapData::OpenVGClass) {
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
vgImg = vgpd->toVGImage();
|
|
|
|
// We don't want the pool to reclaim this image
|
|
// because we cannot predict when the paint object
|
|
// will stop using it. Replacing the image with
|
|
// new data will make the paint object invalid.
|
|
vgpd->detachImageFromPool();
|
|
} else {
|
|
vgImg = toVGImage(*(pd->buffer()));
|
|
deref = true;
|
|
}
|
|
} else if (pd->classId() == QPixmapData::OpenVGClass) {
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
vgImg = vgpd->toVGImage(opacity);
|
|
vgpd->detachImageFromPool();
|
|
} else {
|
|
vgImg = toVGImageWithOpacity(*(pd->buffer()), opacity);
|
|
deref = true;
|
|
}
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
break;
|
|
if (prevType != VG_PAINT_TYPE_PATTERN)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_PATTERN);
|
|
vgSetParameteri(paint, VG_PAINT_PATTERN_TILING_MODE, VG_TILE_REPEAT);
|
|
vgPaintPattern(paint, vgImg);
|
|
if (deref)
|
|
vgDestroyImage(vgImg); // Will be valid until pattern is destroyed.
|
|
return VG_PAINT_TYPE_PATTERN;
|
|
}
|
|
|
|
case Qt::ConicalGradientPattern: {
|
|
// Convert conical gradients into the first stop color.
|
|
qWarning() << "QVGPaintEnginePrivate::setBrush: conical gradients are not supported by OpenVG";
|
|
Q_ASSERT(brush.gradient()->type() == QGradient::ConicalGradient);
|
|
const QConicalGradient *grad =
|
|
static_cast<const QConicalGradient*>(brush.gradient());
|
|
const QGradientStops stops = grad->stops();
|
|
QColor color;
|
|
if (stops.size() > 0)
|
|
color = stops[0].second;
|
|
values[0] = color.redF();
|
|
values[1] = color.greenF();
|
|
values[2] = color.blueF();
|
|
values[3] = color.alphaF() * opacity;
|
|
if (prevType != VG_PAINT_TYPE_COLOR)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
|
|
vgSetParameterfv(paint, VG_PAINT_COLOR, 4, values);
|
|
return VG_PAINT_TYPE_COLOR;
|
|
}
|
|
|
|
case Qt::Dense1Pattern:
|
|
case Qt::Dense2Pattern:
|
|
case Qt::Dense3Pattern:
|
|
case Qt::Dense4Pattern:
|
|
case Qt::Dense5Pattern:
|
|
case Qt::Dense6Pattern:
|
|
case Qt::Dense7Pattern:
|
|
case Qt::HorPattern:
|
|
case Qt::VerPattern:
|
|
case Qt::CrossPattern:
|
|
case Qt::BDiagPattern:
|
|
case Qt::FDiagPattern:
|
|
case Qt::DiagCrossPattern: {
|
|
// The brush is a traditional dotted or cross-hatched pattern brush.
|
|
QColor color = brush.color();
|
|
if (opacity != 1.0)
|
|
color.setAlphaF(color.alphaF() * opacity);
|
|
QImage image = colorizeBitmap
|
|
(qt_imageForBrush(brush.style(), true), color);
|
|
VGImage vgImg = toVGImage(image);
|
|
if (prevType != VG_PAINT_TYPE_PATTERN)
|
|
vgSetParameteri(paint, VG_PAINT_TYPE, VG_PAINT_TYPE_PATTERN);
|
|
vgSetParameteri(paint, VG_PAINT_PATTERN_TILING_MODE, VG_TILE_REPEAT);
|
|
vgPaintPattern(paint, vgImg);
|
|
vgDestroyImage(vgImg); // Will stay valid until pattern is destroyed.
|
|
return VG_PAINT_TYPE_PATTERN;
|
|
}
|
|
|
|
default: break;
|
|
}
|
|
return (VGPaintType)0;
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::setPenParams(const QPen& pen)
|
|
{
|
|
// Note: OpenVG does not support zero-width or cosmetic pens,
|
|
// so we have to simulate cosmetic pens by reversing the scale.
|
|
VGfloat width = pen.widthF();
|
|
if (width <= 0.0f)
|
|
width = 1.0f;
|
|
if (pen.isCosmetic()) {
|
|
if (penScale != 1.0 && penScale != 0.0)
|
|
width /= penScale;
|
|
}
|
|
vgSetf(VG_STROKE_LINE_WIDTH, width);
|
|
|
|
if (pen.capStyle() == Qt::FlatCap)
|
|
vgSetf(VG_STROKE_CAP_STYLE, VG_CAP_BUTT);
|
|
else if (pen.capStyle() == Qt::SquareCap)
|
|
vgSetf(VG_STROKE_CAP_STYLE, VG_CAP_SQUARE);
|
|
else
|
|
vgSetf(VG_STROKE_CAP_STYLE, VG_CAP_ROUND);
|
|
|
|
if (pen.joinStyle() == Qt::MiterJoin) {
|
|
vgSetf(VG_STROKE_JOIN_STYLE, VG_JOIN_MITER);
|
|
vgSetf(VG_STROKE_MITER_LIMIT, pen.miterLimit());
|
|
} else if (pen.joinStyle() == Qt::BevelJoin) {
|
|
vgSetf(VG_STROKE_JOIN_STYLE, VG_JOIN_BEVEL);
|
|
} else {
|
|
vgSetf(VG_STROKE_JOIN_STYLE, VG_JOIN_ROUND);
|
|
}
|
|
|
|
if (pen.style() == Qt::SolidLine) {
|
|
vgSetfv(VG_STROKE_DASH_PATTERN, 0, NULL);
|
|
} else {
|
|
const QVector<qreal> dashPattern = pen.dashPattern();
|
|
QVector<VGfloat> currentDashPattern(dashPattern.count());
|
|
for (int i = 0; i < dashPattern.count(); ++i)
|
|
currentDashPattern[i] = dashPattern[i] * width;
|
|
vgSetfv(VG_STROKE_DASH_PATTERN, currentDashPattern.count(), currentDashPattern.data());
|
|
vgSetf(VG_STROKE_DASH_PHASE, pen.dashOffset());
|
|
vgSetf(VG_STROKE_DASH_PHASE_RESET, VG_FALSE);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::setBrushTransform
|
|
(const QBrush& brush, VGMatrixMode mode)
|
|
{
|
|
// Compute the new brush transformation matrix.
|
|
QTransform transform(brush.transform());
|
|
if (brushOrigin.x() != 0.0f || brushOrigin.y() != 0.0f)
|
|
transform.translate(brushOrigin.x(), brushOrigin.y());
|
|
|
|
// Bail out if the matrix is the same as last time, to avoid
|
|
// updating the VG context state unless absolutely necessary.
|
|
// Most applications won't have a brush transformation set,
|
|
// which will leave the VG setting at its default of identity.
|
|
// Always change the transform if coming out of raw VG mode.
|
|
if (mode == VG_MATRIX_FILL_PAINT_TO_USER) {
|
|
if (!rawVG && transform == brushTransform)
|
|
return;
|
|
brushTransform = transform;
|
|
} else {
|
|
if (!rawVG && transform == penTransform)
|
|
return;
|
|
penTransform = transform;
|
|
}
|
|
|
|
// Set the brush transformation matrix.
|
|
if (mode != matrixMode) {
|
|
vgSeti(VG_MATRIX_MODE, mode);
|
|
matrixMode = mode;
|
|
}
|
|
if (transform.isIdentity()) {
|
|
vgLoadIdentity();
|
|
} else {
|
|
VGfloat mat[9];
|
|
mat[0] = transform.m11();
|
|
mat[1] = transform.m12();
|
|
mat[2] = transform.m13();
|
|
mat[3] = transform.m21();
|
|
mat[4] = transform.m22();
|
|
mat[5] = transform.m23();
|
|
mat[6] = transform.m31();
|
|
mat[7] = transform.m32();
|
|
mat[8] = transform.m33();
|
|
vgLoadMatrix(mat);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::setupColorRamp(const QGradient *grad, VGPaint paint)
|
|
{
|
|
QGradient::Spread spread = grad->spread();
|
|
VGColorRampSpreadMode spreadMode;
|
|
if (spread == QGradient::ReflectSpread)
|
|
spreadMode = VG_COLOR_RAMP_SPREAD_REFLECT;
|
|
else if (spread == QGradient::RepeatSpread)
|
|
spreadMode = VG_COLOR_RAMP_SPREAD_REPEAT;
|
|
else
|
|
spreadMode = VG_COLOR_RAMP_SPREAD_PAD;
|
|
|
|
const QGradientStops stops = grad->stops();
|
|
int n = 5*stops.size();
|
|
QVector<VGfloat> fill_stops(n);
|
|
|
|
for (int i = 0; i < stops.size(); ++i ) {
|
|
QColor col = stops[i].second;
|
|
fill_stops[i*5] = stops[i].first;
|
|
fill_stops[i*5 + 1] = col.redF();
|
|
fill_stops[i*5 + 2] = col.greenF();
|
|
fill_stops[i*5 + 3] = col.blueF();
|
|
fill_stops[i*5 + 4] = col.alphaF() * opacity;
|
|
}
|
|
|
|
vgSetParameteri(paint, VG_PAINT_COLOR_RAMP_SPREAD_MODE, spreadMode);
|
|
vgSetParameteri(paint, VG_PAINT_COLOR_RAMP_PREMULTIPLIED, VG_FALSE);
|
|
vgSetParameterfv(paint, VG_PAINT_COLOR_RAMP_STOPS, n, fill_stops.data());
|
|
}
|
|
|
|
QVGPainterState::QVGPainterState(QVGPainterState& other)
|
|
: QPainterState(other),
|
|
isNew(true), clipRegion(other.clipRegion),
|
|
savedDirty(0)
|
|
{
|
|
}
|
|
|
|
QVGPainterState::QVGPainterState()
|
|
: isNew(true), savedDirty(0)
|
|
{
|
|
}
|
|
|
|
QVGPainterState::~QVGPainterState()
|
|
{
|
|
}
|
|
|
|
QVGPaintEngine::QVGPaintEngine()
|
|
: QPaintEngineEx(*new QVGPaintEnginePrivate(this))
|
|
{
|
|
}
|
|
|
|
QVGPaintEngine::QVGPaintEngine(QVGPaintEnginePrivate &data)
|
|
: QPaintEngineEx(data)
|
|
{
|
|
}
|
|
|
|
QVGPaintEngine::~QVGPaintEngine()
|
|
{
|
|
}
|
|
|
|
QPainterState *QVGPaintEngine::createState(QPainterState *orig) const
|
|
{
|
|
if (!orig) {
|
|
return new QVGPainterState();
|
|
} else {
|
|
Q_D(const QVGPaintEngine);
|
|
QVGPaintEnginePrivate *d2 = const_cast<QVGPaintEnginePrivate*>(d);
|
|
QVGPainterState *origState = static_cast<QVGPainterState *>(orig);
|
|
origState->savedDirty = d2->dirty;
|
|
d2->dirty = 0;
|
|
return new QVGPainterState(*origState);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::draw
|
|
(VGPath path, const QPen& pen, const QBrush& brush, VGint rule)
|
|
{
|
|
VGbitfield mode = 0;
|
|
if (qpen_style(pen) != Qt::NoPen && qbrush_style(qpen_brush(pen)) != Qt::NoBrush) {
|
|
ensurePen(pen);
|
|
mode |= VG_STROKE_PATH;
|
|
}
|
|
if (brush.style() != Qt::NoBrush) {
|
|
ensureBrush(brush);
|
|
setFillRule(rule);
|
|
mode |= VG_FILL_PATH;
|
|
}
|
|
if (mode != 0) {
|
|
ensurePathTransform();
|
|
vgDrawPath(path, mode);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::stroke(VGPath path, const QPen& pen)
|
|
{
|
|
if (pen.style() == Qt::NoPen)
|
|
return;
|
|
ensurePen(pen);
|
|
ensurePathTransform();
|
|
vgDrawPath(path, VG_STROKE_PATH);
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::fill(VGPath path, const QBrush& brush, VGint rule)
|
|
{
|
|
if (brush.style() == Qt::NoBrush)
|
|
return;
|
|
ensureBrush(brush);
|
|
setFillRule(rule);
|
|
QPen savedPen = currentPen;
|
|
currentPen = Qt::NoPen;
|
|
ensurePathTransform();
|
|
currentPen = savedPen;
|
|
vgDrawPath(path, VG_FILL_PATH);
|
|
}
|
|
|
|
bool QVGPaintEngine::begin(QPaintDevice *pdev)
|
|
{
|
|
Q_UNUSED(pdev);
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// Initialize the VG painting objects if we haven't done it yet.
|
|
if (!d->penPaint)
|
|
d->initObjects();
|
|
|
|
// The initial clip region is the entire device area.
|
|
QVGPainterState *s = state();
|
|
s->clipRegion = defaultClipRegion();
|
|
|
|
// Initialize the VG state for this paint operation.
|
|
restoreState(QPaintEngine::AllDirty);
|
|
d->dirty = 0;
|
|
d->rawVG = false;
|
|
return true;
|
|
}
|
|
|
|
bool QVGPaintEngine::end()
|
|
{
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
return true;
|
|
}
|
|
|
|
void QVGPaintEngine::draw(const QVectorPath &path)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::draw(path);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
VGPath vgpath = d->vectorPathToVGPath(path);
|
|
if (!path.hasWindingFill())
|
|
d->draw(vgpath, s->pen, s->brush, VG_EVEN_ODD);
|
|
else
|
|
d->draw(vgpath, s->pen, s->brush, VG_NON_ZERO);
|
|
vgDestroyPath(vgpath);
|
|
}
|
|
|
|
extern QPainterPath qt_painterPathFromVectorPath(const QVectorPath &path);
|
|
|
|
void QVGPaintEngine::fill(const QVectorPath &path, const QBrush &brush)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation(brush)) {
|
|
QPainter *p = painter();
|
|
QBrush oldBrush = p->brush();
|
|
p->setBrush(brush);
|
|
qt_draw_helper(p->d_ptr.data(), qt_painterPathFromVectorPath(path), QPainterPrivate::FillDraw);
|
|
p->setBrush(oldBrush);
|
|
return;
|
|
}
|
|
VGPath vgpath = d->vectorPathToVGPath(path);
|
|
if (!path.hasWindingFill())
|
|
d->fill(vgpath, brush, VG_EVEN_ODD);
|
|
else
|
|
d->fill(vgpath, brush, VG_NON_ZERO);
|
|
vgDestroyPath(vgpath);
|
|
}
|
|
|
|
void QVGPaintEngine::stroke(const QVectorPath &path, const QPen &pen)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation(pen.brush())) {
|
|
QPaintEngineEx::stroke(path, pen);
|
|
return;
|
|
}
|
|
VGPath vgpath = d->vectorPathToVGPath(path);
|
|
d->stroke(vgpath, pen);
|
|
vgDestroyPath(vgpath);
|
|
}
|
|
|
|
// Determine if a co-ordinate transform is simple enough to allow
|
|
// rectangle-based clipping with vgMask(). Simple transforms most
|
|
// often result from origin translations.
|
|
static inline bool clipTransformIsSimple(const QTransform& transform)
|
|
{
|
|
QTransform::TransformationType type = transform.type();
|
|
if (type == QTransform::TxNone || type == QTransform::TxTranslate)
|
|
return true;
|
|
if (type == QTransform::TxRotate) {
|
|
// Check for 0, 90, 180, and 270 degree rotations.
|
|
// (0 might happen after 4 rotations of 90 degrees).
|
|
qreal m11 = transform.m11();
|
|
qreal m12 = transform.m12();
|
|
qreal m21 = transform.m21();
|
|
qreal m22 = transform.m22();
|
|
if (m11 == 0.0f && m22 == 0.0f) {
|
|
if (m12 == 1.0f && m21 == -1.0f)
|
|
return true; // 90 degrees.
|
|
else if (m12 == -1.0f && m21 == 1.0f)
|
|
return true; // 270 degrees.
|
|
} else if (m12 == 0.0f && m21 == 0.0f) {
|
|
if (m11 == -1.0f && m22 == -1.0f)
|
|
return true; // 180 degrees.
|
|
else if (m11 == 1.0f && m22 == 1.0f)
|
|
return true; // 0 degrees.
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if defined(QVG_SCISSOR_CLIP)
|
|
|
|
void QVGPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
if (op == Qt::NoClip) {
|
|
s->clipRegion = defaultClipRegion();
|
|
updateScissor();
|
|
return;
|
|
}
|
|
|
|
// We aren't using masking, so handle simple QRectF's only.
|
|
if (path.shape() == QVectorPath::RectangleHint &&
|
|
path.elementCount() == 4 && clipTransformIsSimple(d->transform)) {
|
|
// Clipping region that resulted from QPainter::setClipRect(QRectF).
|
|
// Convert it into a QRect and apply.
|
|
const qreal *points = path.points();
|
|
QRectF rect(points[0], points[1], points[2] - points[0],
|
|
points[5] - points[1]);
|
|
clip(rect.toRect(), op);
|
|
return;
|
|
}
|
|
|
|
// Try converting the path into a QRegion that tightly follows
|
|
// the outline of the path we want to clip with.
|
|
QRegion region;
|
|
if (!path.isEmpty())
|
|
region = QRegion(path.convertToPainterPath().toFillPolygon(QTransform()).toPolygon());
|
|
|
|
switch (op) {
|
|
case Qt::NoClip:
|
|
{
|
|
region = defaultClipRegion();
|
|
}
|
|
break;
|
|
|
|
case Qt::ReplaceClip:
|
|
{
|
|
region = d->transform.map(region);
|
|
}
|
|
break;
|
|
|
|
case Qt::IntersectClip:
|
|
{
|
|
region = s->clipRegion.intersect(d->transform.map(region));
|
|
}
|
|
break;
|
|
}
|
|
if (region.numRects() <= d->maxScissorRects) {
|
|
// We haven't reached the maximum scissor count yet, so we can
|
|
// still make use of this region.
|
|
s->clipRegion = region;
|
|
updateScissor();
|
|
return;
|
|
}
|
|
|
|
// The best we can do is clip to the bounding rectangle
|
|
// of all control points.
|
|
clip(path.controlPointRect().toRect(), op);
|
|
}
|
|
|
|
void QVGPaintEngine::clip(const QRect &rect, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
switch (op) {
|
|
case Qt::NoClip:
|
|
{
|
|
s->clipRegion = defaultClipRegion();
|
|
}
|
|
break;
|
|
|
|
case Qt::ReplaceClip:
|
|
{
|
|
s->clipRegion = d->transform.map(QRegion(rect));
|
|
}
|
|
break;
|
|
|
|
case Qt::IntersectClip:
|
|
{
|
|
s->clipRegion = s->clipRegion.intersect(d->transform.map(QRegion(rect)));
|
|
}
|
|
break;
|
|
}
|
|
|
|
updateScissor();
|
|
}
|
|
|
|
void QVGPaintEngine::clip(const QRegion ®ion, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
switch (op) {
|
|
case Qt::NoClip:
|
|
{
|
|
s->clipRegion = defaultClipRegion();
|
|
}
|
|
break;
|
|
|
|
case Qt::ReplaceClip:
|
|
{
|
|
s->clipRegion = d->transform.map(region);
|
|
}
|
|
break;
|
|
|
|
case Qt::IntersectClip:
|
|
{
|
|
s->clipRegion = s->clipRegion.intersect(d->transform.map(region));
|
|
}
|
|
break;
|
|
}
|
|
|
|
updateScissor();
|
|
}
|
|
|
|
void QVGPaintEngine::clip(const QPainterPath &path, Qt::ClipOperation op)
|
|
{
|
|
QPaintEngineEx::clip(path, op);
|
|
}
|
|
|
|
#else // !QVG_SCISSOR_CLIP
|
|
|
|
void QVGPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
if (op == Qt::NoClip) {
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
return;
|
|
}
|
|
|
|
// We don't have vgRenderToMask(), so handle simple QRectF's only.
|
|
if (path.shape() == QVectorPath::RectangleHint &&
|
|
path.elementCount() == 4 && clipTransformIsSimple(d->transform)) {
|
|
// Clipping region that resulted from QPainter::setClipRect(QRectF).
|
|
// Convert it into a QRect and apply.
|
|
const qreal *points = path.points();
|
|
QRectF rect(points[0], points[1], points[2] - points[0],
|
|
points[5] - points[1]);
|
|
clip(rect.toRect(), op);
|
|
return;
|
|
}
|
|
|
|
#if !defined(QVG_NO_RENDER_TO_MASK)
|
|
QPaintDevice *pdev = paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
if (op == Qt::ReplaceClip) {
|
|
vgMask(VG_INVALID_HANDLE, VG_CLEAR_MASK, 0, 0, width, height);
|
|
d->maskRect = QRect();
|
|
} else if (!d->maskValid) {
|
|
d->ensureMask(this, width, height);
|
|
}
|
|
|
|
d->ensurePathTransform();
|
|
VGPath vgpath = d->vectorPathToVGPath(path);
|
|
switch (op) {
|
|
case Qt::ReplaceClip:
|
|
case Qt::IntersectClip:
|
|
vgRenderToMask(vgpath, VG_FILL_PATH, VG_INTERSECT_MASK);
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
vgDestroyPath(vgpath);
|
|
|
|
vgSeti(VG_MASKING, VG_TRUE);
|
|
d->maskValid = true;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::clip(const QRect &rect, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
// If we have a non-simple transform, then use path-based clipping.
|
|
if (op != Qt::NoClip && !clipTransformIsSimple(d->transform)) {
|
|
QPaintEngineEx::clip(rect, op);
|
|
return;
|
|
}
|
|
|
|
switch (op) {
|
|
case Qt::NoClip:
|
|
{
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
}
|
|
break;
|
|
|
|
case Qt::ReplaceClip:
|
|
{
|
|
QRect r = d->transform.mapRect(rect);
|
|
if (isDefaultClipRect(r)) {
|
|
// Replacing the clip with a full-window region is the
|
|
// same as turning off clipping.
|
|
if (d->maskValid)
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
} else {
|
|
// Special case: if the intersection of the system
|
|
// clip and "r" is a single rectangle, then use the
|
|
// scissor for clipping. We try to avoid allocating a
|
|
// QRegion copy on the heap for the test if we can.
|
|
QRegion clip = d->systemClip; // Reference-counted, no alloc.
|
|
QRect clipRect;
|
|
if (clip.rectCount() == 1) {
|
|
clipRect = clip.boundingRect().intersected(r);
|
|
} else if (clip.isEmpty()) {
|
|
clipRect = r;
|
|
} else {
|
|
clip = clip.intersect(r);
|
|
if (clip.rectCount() != 1) {
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
d->modifyMask(this, VG_FILL_MASK, r);
|
|
break;
|
|
}
|
|
clipRect = clip.boundingRect();
|
|
}
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = true;
|
|
d->maskRect = clipRect;
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
updateScissor();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Qt::IntersectClip:
|
|
{
|
|
QRect r = d->transform.mapRect(rect);
|
|
if (!d->maskValid) {
|
|
// Mask has not been used yet, so intersect with
|
|
// the previous scissor-based region in maskRect.
|
|
if (d->scissorMask)
|
|
r = r.intersect(d->maskRect);
|
|
if (isDefaultClipRect(r)) {
|
|
// The clip is the full window, so turn off clipping.
|
|
d->maskIsSet = true;
|
|
d->maskRect = QRect();
|
|
} else {
|
|
// Activate the scissor on a smaller maskRect.
|
|
d->maskIsSet = false;
|
|
d->maskRect = r;
|
|
}
|
|
d->scissorMask = true;
|
|
updateScissor();
|
|
} else if (d->maskIsSet && isDefaultClipRect(r)) {
|
|
// Intersecting a full-window clip with a full-window
|
|
// region is the same as turning off clipping.
|
|
if (d->maskValid)
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
} else {
|
|
d->modifyMask(this, VG_INTERSECT_MASK, r);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::clip(const QRegion ®ion, Qt::ClipOperation op)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// Use the QRect case if the region consists of a single rectangle.
|
|
if (region.rectCount() == 1) {
|
|
clip(region.boundingRect(), op);
|
|
return;
|
|
}
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
// If we have a non-simple transform, then use path-based clipping.
|
|
if (op != Qt::NoClip && !clipTransformIsSimple(d->transform)) {
|
|
QPaintEngineEx::clip(region, op);
|
|
return;
|
|
}
|
|
|
|
switch (op) {
|
|
case Qt::NoClip:
|
|
{
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
}
|
|
break;
|
|
|
|
case Qt::ReplaceClip:
|
|
{
|
|
QRegion r = d->transform.map(region);
|
|
if (isDefaultClipRegion(r)) {
|
|
// Replacing the clip with a full-window region is the
|
|
// same as turning off clipping.
|
|
if (d->maskValid)
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
} else {
|
|
// Special case: if the intersection of the system
|
|
// clip and the region is a single rectangle, then
|
|
// use the scissor for clipping.
|
|
QRegion clip = d->systemClip;
|
|
if (clip.isEmpty())
|
|
clip = r;
|
|
else
|
|
clip = clip.intersect(r);
|
|
if (clip.rectCount() == 1) {
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = true;
|
|
d->maskRect = clip.boundingRect();
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
updateScissor();
|
|
} else {
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
d->modifyMask(this, VG_FILL_MASK, r);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Qt::IntersectClip:
|
|
{
|
|
if (region.rectCount() != 1) {
|
|
// If there is more than one rectangle, then intersecting
|
|
// the rectangles one by one in modifyMask() will not give
|
|
// the desired result. So fall back to path-based clipping.
|
|
QPaintEngineEx::clip(region, op);
|
|
return;
|
|
}
|
|
QRegion r = d->transform.map(region);
|
|
if (d->maskIsSet && isDefaultClipRegion(r)) {
|
|
// Intersecting a full-window clip with a full-window
|
|
// region is the same as turning off clipping.
|
|
if (d->maskValid)
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
} else {
|
|
d->modifyMask(this, VG_INTERSECT_MASK, r);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if !defined(QVG_NO_RENDER_TO_MASK)
|
|
|
|
// Copied from qpathclipper.cpp.
|
|
static bool qt_vg_pathToRect(const QPainterPath &path, QRectF *rect)
|
|
{
|
|
if (path.elementCount() != 5)
|
|
return false;
|
|
|
|
const bool mightBeRect = path.elementAt(0).isMoveTo()
|
|
&& path.elementAt(1).isLineTo()
|
|
&& path.elementAt(2).isLineTo()
|
|
&& path.elementAt(3).isLineTo()
|
|
&& path.elementAt(4).isLineTo();
|
|
|
|
if (!mightBeRect)
|
|
return false;
|
|
|
|
const qreal x1 = path.elementAt(0).x;
|
|
const qreal y1 = path.elementAt(0).y;
|
|
|
|
const qreal x2 = path.elementAt(1).x;
|
|
const qreal y2 = path.elementAt(2).y;
|
|
|
|
if (path.elementAt(1).y != y1)
|
|
return false;
|
|
|
|
if (path.elementAt(2).x != x2)
|
|
return false;
|
|
|
|
if (path.elementAt(3).x != x1 || path.elementAt(3).y != y2)
|
|
return false;
|
|
|
|
if (path.elementAt(4).x != x1 || path.elementAt(4).y != y1)
|
|
return false;
|
|
|
|
if (rect)
|
|
*rect = QRectF(QPointF(x1, y1), QPointF(x2, y2));
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
void QVGPaintEngine::clip(const QPainterPath &path, Qt::ClipOperation op)
|
|
{
|
|
#if !defined(QVG_NO_RENDER_TO_MASK)
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// If the path is a simple rectangle, then use clip(QRect) instead.
|
|
QRectF simpleRect;
|
|
if (qt_vg_pathToRect(path, &simpleRect)) {
|
|
clip(simpleRect.toRect(), op);
|
|
return;
|
|
}
|
|
|
|
d->dirty |= QPaintEngine::DirtyClipRegion;
|
|
|
|
if (op == Qt::NoClip) {
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
return;
|
|
}
|
|
|
|
QPaintDevice *pdev = paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
if (op == Qt::ReplaceClip) {
|
|
vgMask(VG_INVALID_HANDLE, VG_CLEAR_MASK, 0, 0, width, height);
|
|
d->maskRect = QRect();
|
|
} else if (!d->maskValid) {
|
|
d->ensureMask(this, width, height);
|
|
}
|
|
|
|
d->ensurePathTransform();
|
|
VGPath vgpath = d->painterPathToVGPath(path);
|
|
switch (op) {
|
|
case Qt::ReplaceClip:
|
|
case Qt::IntersectClip:
|
|
vgRenderToMask(vgpath, VG_FILL_PATH, VG_INTERSECT_MASK);
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
vgDestroyPath(vgpath);
|
|
|
|
vgSeti(VG_MASKING, VG_TRUE);
|
|
d->maskValid = true;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
#else
|
|
QPaintEngineEx::clip(path, op);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::ensureMask
|
|
(QVGPaintEngine *engine, int width, int height)
|
|
{
|
|
scissorMask = false;
|
|
if (maskIsSet) {
|
|
vgMask(VG_INVALID_HANDLE, VG_FILL_MASK, 0, 0, width, height);
|
|
maskRect = QRect();
|
|
} else {
|
|
vgMask(VG_INVALID_HANDLE, VG_CLEAR_MASK, 0, 0, width, height);
|
|
if (maskRect.isValid()) {
|
|
vgMask(VG_INVALID_HANDLE, VG_FILL_MASK,
|
|
maskRect.x(), height - maskRect.y() - maskRect.height(),
|
|
maskRect.width(), maskRect.height());
|
|
maskRect = QRect();
|
|
engine->updateScissor();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::modifyMask
|
|
(QVGPaintEngine *engine, VGMaskOperation op, const QRegion& region)
|
|
{
|
|
QPaintDevice *pdev = engine->paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
if (!maskValid)
|
|
ensureMask(engine, width, height);
|
|
|
|
QVector<QRect> rects = region.rects();
|
|
for (int i = 0; i < rects.size(); ++i) {
|
|
vgMask(VG_INVALID_HANDLE, op,
|
|
rects[i].x(), height - rects[i].y() - rects[i].height(),
|
|
rects[i].width(), rects[i].height());
|
|
}
|
|
|
|
vgSeti(VG_MASKING, VG_TRUE);
|
|
maskValid = true;
|
|
maskIsSet = false;
|
|
scissorMask = false;
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::modifyMask
|
|
(QVGPaintEngine *engine, VGMaskOperation op, const QRect& rect)
|
|
{
|
|
QPaintDevice *pdev = engine->paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
if (!maskValid)
|
|
ensureMask(engine, width, height);
|
|
|
|
if (rect.isValid()) {
|
|
vgMask(VG_INVALID_HANDLE, op,
|
|
rect.x(), height - rect.y() - rect.height(),
|
|
rect.width(), rect.height());
|
|
}
|
|
|
|
vgSeti(VG_MASKING, VG_TRUE);
|
|
maskValid = true;
|
|
maskIsSet = false;
|
|
scissorMask = false;
|
|
}
|
|
|
|
#endif // !QVG_SCISSOR_CLIP
|
|
|
|
void QVGPaintEngine::updateScissor()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
QRegion region = d->systemClip;
|
|
|
|
#if defined(QVG_SCISSOR_CLIP)
|
|
// Using the scissor to do clipping, so combine the systemClip
|
|
// with the current painting clipRegion.
|
|
|
|
if (d->maskValid) {
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
}
|
|
|
|
QVGPainterState *s = state();
|
|
if (s->clipEnabled) {
|
|
if (region.isEmpty())
|
|
region = s->clipRegion;
|
|
else
|
|
region = region.intersect(s->clipRegion);
|
|
if (isDefaultClipRegion(region)) {
|
|
// The scissor region is the entire drawing surface,
|
|
// so there is no point doing any scissoring.
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
d->scissorActive = false;
|
|
d->scissorDirty = false;
|
|
return;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
#if !defined(QVG_SCISSOR_CLIP)
|
|
// Combine the system clip with the simple mask rectangle.
|
|
if (d->scissorMask) {
|
|
if (region.isEmpty())
|
|
region = d->maskRect;
|
|
else
|
|
region = region.intersect(d->maskRect);
|
|
if (isDefaultClipRegion(region)) {
|
|
// The scissor region is the entire drawing surface,
|
|
// so there is no point doing any scissoring.
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
d->scissorActive = false;
|
|
d->scissorDirty = false;
|
|
return;
|
|
}
|
|
} else
|
|
#endif
|
|
|
|
// Disable the scissor completely if the system clip is empty.
|
|
if (region.isEmpty()) {
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
d->scissorActive = false;
|
|
d->scissorDirty = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (d->scissorActive && region == d->scissorRegion && !d->scissorDirty)
|
|
return;
|
|
|
|
QVector<QRect> rects = region.rects();
|
|
int count = rects.count();
|
|
if (count > d->maxScissorRects) {
|
|
#if !defined(QVG_SCISSOR_CLIP)
|
|
count = d->maxScissorRects;
|
|
#else
|
|
// Use masking
|
|
int width = paintDevice()->width();
|
|
int height = paintDevice()->height();
|
|
vgMask(VG_INVALID_HANDLE, VG_CLEAR_MASK,
|
|
0, 0, width, height);
|
|
for (int i = 0; i < rects.size(); ++i) {
|
|
vgMask(VG_INVALID_HANDLE, VG_FILL_MASK,
|
|
rects[i].x(), height - rects[i].y() - rects[i].height(),
|
|
rects[i].width(), rects[i].height());
|
|
}
|
|
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
vgSeti(VG_MASKING, VG_TRUE);
|
|
d->maskValid = true;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
d->scissorActive = false;
|
|
d->scissorDirty = false;
|
|
d->scissorRegion = region;
|
|
return;
|
|
#endif
|
|
}
|
|
|
|
QVarLengthArray<VGint> params(count * 4);
|
|
int height = paintDevice()->height();
|
|
for (int i = 0; i < count; ++i) {
|
|
params[i * 4 + 0] = rects[i].x();
|
|
params[i * 4 + 1] = height - rects[i].y() - rects[i].height();
|
|
params[i * 4 + 2] = rects[i].width();
|
|
params[i * 4 + 3] = rects[i].height();
|
|
}
|
|
|
|
vgSetiv(VG_SCISSOR_RECTS, count * 4, params.data());
|
|
vgSeti(VG_SCISSORING, VG_TRUE);
|
|
d->scissorDirty = false;
|
|
d->scissorActive = true;
|
|
d->scissorRegion = region;
|
|
}
|
|
|
|
QRegion QVGPaintEngine::defaultClipRegion()
|
|
{
|
|
// The default clip region for a paint device is the whole drawing area.
|
|
QPaintDevice *pdev = paintDevice();
|
|
return QRegion(0, 0, pdev->width(), pdev->height());
|
|
}
|
|
|
|
bool QVGPaintEngine::isDefaultClipRegion(const QRegion& region)
|
|
{
|
|
if (region.rectCount() != 1)
|
|
return false;
|
|
|
|
QPaintDevice *pdev = paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
QRect rect = region.boundingRect();
|
|
return (rect.x() == 0 && rect.y() == 0 &&
|
|
rect.width() == width && rect.height() == height);
|
|
}
|
|
|
|
bool QVGPaintEngine::isDefaultClipRect(const QRect& rect)
|
|
{
|
|
QPaintDevice *pdev = paintDevice();
|
|
int width = pdev->width();
|
|
int height = pdev->height();
|
|
|
|
return (rect.x() == 0 && rect.y() == 0 &&
|
|
rect.width() == width && rect.height() == height);
|
|
}
|
|
|
|
void QVGPaintEngine::clipEnabledChanged()
|
|
{
|
|
#if defined(QVG_SCISSOR_CLIP)
|
|
vgSeti(VG_MASKING, VG_FALSE); // disable mask fallback
|
|
updateScissor();
|
|
#else
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
d->dirty |= QPaintEngine::DirtyClipEnabled;
|
|
if (s->clipEnabled && s->clipOperation != Qt::NoClip) {
|
|
// Replay the entire clip stack to put the mask into the right state.
|
|
d->maskValid = false;
|
|
d->maskIsSet = true;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
s->clipRegion = defaultClipRegion();
|
|
d->replayClipOperations();
|
|
d->transform = s->transform();
|
|
d->updateTransform(paintDevice());
|
|
} else {
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::penChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyPen;
|
|
|
|
d->hasExtendedRadialGradientPen =
|
|
state()->pen.style() != Qt::NoPen && d->needsEmulation(state()->pen.brush());
|
|
}
|
|
|
|
void QVGPaintEngine::brushChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyBrush;
|
|
|
|
d->hasExtendedRadialGradientPen = d->needsEmulation(state()->brush);
|
|
}
|
|
|
|
void QVGPaintEngine::brushOriginChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyBrushOrigin;
|
|
d->brushOrigin = state()->brushOrigin;
|
|
d->forcePenChange = true;
|
|
d->forceBrushChange = true;
|
|
}
|
|
|
|
void QVGPaintEngine::opacityChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyOpacity;
|
|
d->opacity = state()->opacity;
|
|
d->forcePenChange = true;
|
|
d->forceBrushChange = true;
|
|
}
|
|
|
|
void QVGPaintEngine::compositionModeChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyCompositionMode;
|
|
|
|
VGint vgMode = VG_BLEND_SRC_OVER;
|
|
|
|
switch (state()->composition_mode) {
|
|
case QPainter::CompositionMode_SourceOver:
|
|
vgMode = VG_BLEND_SRC_OVER;
|
|
break;
|
|
case QPainter::CompositionMode_DestinationOver:
|
|
vgMode = VG_BLEND_DST_OVER;
|
|
break;
|
|
case QPainter::CompositionMode_Source:
|
|
vgMode = VG_BLEND_SRC;
|
|
break;
|
|
case QPainter::CompositionMode_SourceIn:
|
|
vgMode = VG_BLEND_SRC_IN;
|
|
break;
|
|
case QPainter::CompositionMode_DestinationIn:
|
|
vgMode = VG_BLEND_DST_IN;
|
|
break;
|
|
case QPainter::CompositionMode_Plus:
|
|
vgMode = VG_BLEND_ADDITIVE;
|
|
break;
|
|
case QPainter::CompositionMode_Multiply:
|
|
vgMode = VG_BLEND_MULTIPLY;
|
|
break;
|
|
case QPainter::CompositionMode_Screen:
|
|
vgMode = VG_BLEND_SCREEN;
|
|
break;
|
|
case QPainter::CompositionMode_Darken:
|
|
vgMode = VG_BLEND_DARKEN;
|
|
break;
|
|
case QPainter::CompositionMode_Lighten:
|
|
vgMode = VG_BLEND_LIGHTEN;
|
|
break;
|
|
default:
|
|
if (d->hasAdvancedBlending) {
|
|
switch (state()->composition_mode) {
|
|
case QPainter::CompositionMode_Overlay:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_OVERLAY_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_ColorDodge:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_COLORDODGE_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_ColorBurn:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_COLORBURN_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_HardLight:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_HARDLIGHT_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_SoftLight:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_SOFTLIGHT_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_Difference:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_DIFFERENCE_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_Exclusion:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_EXCLUSION_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_SourceOut:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_SRC_OUT_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_DestinationOut:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_DST_OUT_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_SourceAtop:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_SRC_ATOP_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_DestinationAtop:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_DST_ATOP_KHR;
|
|
break;
|
|
case QPainter::CompositionMode_Xor:
|
|
vgMode = QVGPaintEnginePrivate::QT_VG_BLEND_XOR_KHR;
|
|
break;
|
|
default: break; // Fall back to VG_BLEND_SRC_OVER.
|
|
}
|
|
}
|
|
if (vgMode == VG_BLEND_SRC_OVER)
|
|
qWarning() << "QVGPaintEngine::compositionModeChanged unsupported mode" << state()->composition_mode;
|
|
break;
|
|
}
|
|
|
|
d->setBlendMode(VGBlendMode(vgMode));
|
|
}
|
|
|
|
void QVGPaintEngine::renderHintsChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
d->dirty |= QPaintEngine::DirtyHints;
|
|
|
|
QPainter::RenderHints hints = state()->renderHints;
|
|
|
|
VGRenderingQuality rq =
|
|
(hints & QPainter::Antialiasing)
|
|
? VG_RENDERING_QUALITY_BETTER
|
|
: VG_RENDERING_QUALITY_NONANTIALIASED;
|
|
VGImageQuality iq =
|
|
(hints & QPainter::SmoothPixmapTransform)
|
|
? VG_IMAGE_QUALITY_BETTER
|
|
: VG_IMAGE_QUALITY_NONANTIALIASED;
|
|
|
|
d->setRenderingQuality(rq);
|
|
d->setImageQuality(iq);
|
|
}
|
|
|
|
void QVGPaintEngine::transformChanged()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
d->dirty |= QPaintEngine::DirtyTransform;
|
|
d->transform = s->transform();
|
|
qreal oldPenScale = d->penScale;
|
|
d->updateTransform(paintDevice());
|
|
if (d->penScale != oldPenScale)
|
|
d->forcePenChange = true;
|
|
}
|
|
|
|
bool QVGPaintEngine::clearRect(const QRectF &rect, const QColor &color)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QVGPainterState *s = state();
|
|
if (!s->clipEnabled || s->clipOperation == Qt::NoClip) {
|
|
QRect r = d->transform.mapRect(rect).toRect();
|
|
int height = paintDevice()->height();
|
|
if (d->clearColor != color || d->clearOpacity != s->opacity) {
|
|
VGfloat values[4];
|
|
values[0] = color.redF();
|
|
values[1] = color.greenF();
|
|
values[2] = color.blueF();
|
|
values[3] = color.alphaF() * s->opacity;
|
|
vgSetfv(VG_CLEAR_COLOR, 4, values);
|
|
d->clearColor = color;
|
|
d->clearOpacity = s->opacity;
|
|
}
|
|
vgClear(r.x(), height - r.y() - r.height(),
|
|
r.width(), r.height());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void QVGPaintEngine::fillRect(const QRectF &rect, const QBrush &brush)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
if (brush.style() == Qt::NoBrush)
|
|
return;
|
|
|
|
// Check to see if we can use vgClear() for faster filling.
|
|
if (brush.style() == Qt::SolidPattern && brush.isOpaque() &&
|
|
clipTransformIsSimple(d->transform) && d->opacity == 1.0f &&
|
|
clearRect(rect, brush.color())) {
|
|
return;
|
|
}
|
|
|
|
if (d->needsEmulation(brush)) {
|
|
QPaintEngineEx::fillRect(rect, brush);
|
|
return;
|
|
}
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
VGfloat coords[8];
|
|
if (d->simpleTransform) {
|
|
coords[0] = rect.x();
|
|
coords[1] = rect.y();
|
|
coords[2] = rect.x() + rect.width();
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = rect.y() + rect.height();
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
} else {
|
|
QPointF tl = d->transform.map(rect.topLeft());
|
|
QPointF tr = d->transform.map(rect.topRight());
|
|
QPointF bl = d->transform.map(rect.bottomLeft());
|
|
QPointF br = d->transform.map(rect.bottomRight());
|
|
coords[0] = tl.x();
|
|
coords[1] = tl.y();
|
|
coords[2] = tr.x();
|
|
coords[3] = tr.y();
|
|
coords[4] = br.x();
|
|
coords[5] = br.y();
|
|
coords[6] = bl.x();
|
|
coords[7] = bl.y();
|
|
}
|
|
vgModifyPathCoords(d->rectPath, 0, 4, coords);
|
|
d->fill(d->rectPath, brush);
|
|
#else
|
|
QPaintEngineEx::fillRect(rect, brush);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::fillRect(const QRectF &rect, const QColor &color)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// Check to see if we can use vgClear() for faster filling.
|
|
if (clipTransformIsSimple(d->transform) && d->opacity == 1.0f && color.alpha() == 255 &&
|
|
clearRect(rect, color)) {
|
|
return;
|
|
}
|
|
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
VGfloat coords[8];
|
|
if (d->simpleTransform) {
|
|
coords[0] = rect.x();
|
|
coords[1] = rect.y();
|
|
coords[2] = rect.x() + rect.width();
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = rect.y() + rect.height();
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
} else {
|
|
QPointF tl = d->transform.map(rect.topLeft());
|
|
QPointF tr = d->transform.map(rect.topRight());
|
|
QPointF bl = d->transform.map(rect.bottomLeft());
|
|
QPointF br = d->transform.map(rect.bottomRight());
|
|
coords[0] = tl.x();
|
|
coords[1] = tl.y();
|
|
coords[2] = tr.x();
|
|
coords[3] = tr.y();
|
|
coords[4] = br.x();
|
|
coords[5] = br.y();
|
|
coords[6] = bl.x();
|
|
coords[7] = bl.y();
|
|
}
|
|
vgModifyPathCoords(d->rectPath, 0, 4, coords);
|
|
d->fill(d->rectPath, QBrush(color));
|
|
#else
|
|
QPaintEngineEx::fillRect(rect, QBrush(color));
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawRoundedRect(const QRectF &rect, qreal xrad, qreal yrad, Qt::SizeMode mode)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawRoundedRect(rect, xrad, yrad, mode);
|
|
return;
|
|
}
|
|
if (d->simpleTransform) {
|
|
QVGPainterState *s = state();
|
|
VGPath vgpath = d->roundedRectPath(rect, xrad, yrad, mode);
|
|
d->draw(vgpath, s->pen, s->brush);
|
|
#if defined(QVG_NO_MODIFY_PATH)
|
|
vgDestroyPath(vgpath);
|
|
#endif
|
|
} else {
|
|
QPaintEngineEx::drawRoundedRect(rect, xrad, yrad, mode);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::drawRects(const QRect *rects, int rectCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawRects(rects, rectCount);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
for (int i = 0; i < rectCount; ++i, ++rects) {
|
|
VGfloat coords[8];
|
|
if (d->simpleTransform) {
|
|
coords[0] = rects->x();
|
|
coords[1] = rects->y();
|
|
coords[2] = rects->x() + rects->width();
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = rects->y() + rects->height();
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
} else {
|
|
QPointF tl = d->transform.map(QPointF(rects->x(), rects->y()));
|
|
QPointF tr = d->transform.map(QPointF(rects->x() + rects->width(),
|
|
rects->y()));
|
|
QPointF bl = d->transform.map(QPointF(rects->x(),
|
|
rects->y() + rects->height()));
|
|
QPointF br = d->transform.map(QPointF(rects->x() + rects->width(),
|
|
rects->y() + rects->height()));
|
|
coords[0] = tl.x();
|
|
coords[1] = tl.y();
|
|
coords[2] = tr.x();
|
|
coords[3] = tr.y();
|
|
coords[4] = br.x();
|
|
coords[5] = br.y();
|
|
coords[6] = bl.x();
|
|
coords[7] = bl.y();
|
|
}
|
|
vgModifyPathCoords(d->rectPath, 0, 4, coords);
|
|
d->draw(d->rectPath, s->pen, s->brush);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawRects(rects, rectCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawRects(const QRectF *rects, int rectCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawRects(rects, rectCount);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
for (int i = 0; i < rectCount; ++i, ++rects) {
|
|
VGfloat coords[8];
|
|
if (d->simpleTransform) {
|
|
coords[0] = rects->x();
|
|
coords[1] = rects->y();
|
|
coords[2] = rects->x() + rects->width();
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = rects->y() + rects->height();
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
} else {
|
|
QPointF tl = d->transform.map(rects->topLeft());
|
|
QPointF tr = d->transform.map(rects->topRight());
|
|
QPointF bl = d->transform.map(rects->bottomLeft());
|
|
QPointF br = d->transform.map(rects->bottomRight());
|
|
coords[0] = tl.x();
|
|
coords[1] = tl.y();
|
|
coords[2] = tr.x();
|
|
coords[3] = tr.y();
|
|
coords[4] = br.x();
|
|
coords[5] = br.y();
|
|
coords[6] = bl.x();
|
|
coords[7] = bl.y();
|
|
}
|
|
vgModifyPathCoords(d->rectPath, 0, 4, coords);
|
|
d->draw(d->rectPath, s->pen, s->brush);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawRects(rects, rectCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawLines(const QLine *lines, int lineCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawLines(lines, lineCount);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
for (int i = 0; i < lineCount; ++i, ++lines) {
|
|
VGfloat coords[4];
|
|
if (d->simpleTransform) {
|
|
coords[0] = lines->x1();
|
|
coords[1] = lines->y1();
|
|
coords[2] = lines->x2();
|
|
coords[3] = lines->y2();
|
|
} else {
|
|
QPointF p1 = d->transform.map(QPointF(lines->x1(), lines->y1()));
|
|
QPointF p2 = d->transform.map(QPointF(lines->x2(), lines->y2()));
|
|
coords[0] = p1.x();
|
|
coords[1] = p1.y();
|
|
coords[2] = p2.x();
|
|
coords[3] = p2.y();
|
|
}
|
|
vgModifyPathCoords(d->linePath, 0, 2, coords);
|
|
d->stroke(d->linePath, s->pen);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawLines(lines, lineCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawLines(const QLineF *lines, int lineCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawLines(lines, lineCount);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
for (int i = 0; i < lineCount; ++i, ++lines) {
|
|
VGfloat coords[4];
|
|
if (d->simpleTransform) {
|
|
coords[0] = lines->x1();
|
|
coords[1] = lines->y1();
|
|
coords[2] = lines->x2();
|
|
coords[3] = lines->y2();
|
|
} else {
|
|
QPointF p1 = d->transform.map(lines->p1());
|
|
QPointF p2 = d->transform.map(lines->p2());
|
|
coords[0] = p1.x();
|
|
coords[1] = p1.y();
|
|
coords[2] = p2.x();
|
|
coords[3] = p2.y();
|
|
}
|
|
vgModifyPathCoords(d->linePath, 0, 2, coords);
|
|
d->stroke(d->linePath, s->pen);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawLines(lines, lineCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawEllipse(const QRectF &r)
|
|
{
|
|
// Based on the description of vguEllipse() in the OpenVG specification.
|
|
// We don't use vguEllipse(), to avoid unnecessary library dependencies.
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawEllipse(r);
|
|
return;
|
|
}
|
|
if (d->simpleTransform) {
|
|
QVGPainterState *s = state();
|
|
VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
4, // segmentCapacityHint
|
|
12, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
static VGubyte segments[4] = {
|
|
VG_MOVE_TO_ABS,
|
|
VG_SCCWARC_TO_REL,
|
|
VG_SCCWARC_TO_REL,
|
|
VG_CLOSE_PATH
|
|
};
|
|
VGfloat coords[12];
|
|
VGfloat halfwid = r.width() / 2;
|
|
VGfloat halfht = r.height() / 2;
|
|
coords[0] = r.x() + r.width();
|
|
coords[1] = r.y() + halfht;
|
|
coords[2] = halfwid;
|
|
coords[3] = halfht;
|
|
coords[4] = 0.0f;
|
|
coords[5] = -r.width();
|
|
coords[6] = 0.0f;
|
|
coords[7] = halfwid;
|
|
coords[8] = halfht;
|
|
coords[9] = 0.0f;
|
|
coords[10] = r.width();
|
|
coords[11] = 0.0f;
|
|
vgAppendPathData(path, 4, segments, coords);
|
|
d->draw(path, s->pen, s->brush);
|
|
vgDestroyPath(path);
|
|
} else {
|
|
// The projective transform version of an ellipse is difficult.
|
|
// Generate a QVectorPath containing cubic curves and transform that.
|
|
QPaintEngineEx::drawEllipse(r);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::drawEllipse(const QRect &r)
|
|
{
|
|
drawEllipse(QRectF(r));
|
|
}
|
|
|
|
void QVGPaintEngine::drawPath(const QPainterPath &path)
|
|
{
|
|
// Shortcut past the QPainterPath -> QVectorPath conversion,
|
|
// converting the QPainterPath directly into a VGPath.
|
|
Q_D(QVGPaintEngine);
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawPath(path);
|
|
return;
|
|
}
|
|
QVGPainterState *s = state();
|
|
VGPath vgpath = d->painterPathToVGPath(path);
|
|
if (path.fillRule() == Qt::OddEvenFill)
|
|
d->draw(vgpath, s->pen, s->brush, VG_EVEN_ODD);
|
|
else
|
|
d->draw(vgpath, s->pen, s->brush, VG_NON_ZERO);
|
|
vgDestroyPath(vgpath);
|
|
}
|
|
|
|
void QVGPaintEngine::drawPoints(const QPointF *points, int pointCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
|
|
if (d->needsPenEmulation()) {
|
|
QPaintEngineEx::drawPoints(points, pointCount);
|
|
return;
|
|
}
|
|
|
|
// Set up a new pen if necessary.
|
|
QPen pen = state()->pen;
|
|
if (pen.style() == Qt::NoPen)
|
|
return;
|
|
if (pen.capStyle() == Qt::FlatCap)
|
|
pen.setCapStyle(Qt::SquareCap);
|
|
|
|
for (int i = 0; i < pointCount; ++i, ++points) {
|
|
VGfloat coords[4];
|
|
if (d->simpleTransform) {
|
|
coords[0] = points->x();
|
|
coords[1] = points->y();
|
|
coords[2] = coords[0];
|
|
coords[3] = coords[1];
|
|
} else {
|
|
QPointF p = d->transform.map(*points);
|
|
coords[0] = p.x();
|
|
coords[1] = p.y();
|
|
coords[2] = coords[0];
|
|
coords[3] = coords[1];
|
|
}
|
|
vgModifyPathCoords(d->linePath, 0, 2, coords);
|
|
d->stroke(d->linePath, pen);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawPoints(points, pointCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawPoints(const QPoint *points, int pointCount)
|
|
{
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
Q_D(QVGPaintEngine);
|
|
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawPoints(points, pointCount);
|
|
return;
|
|
}
|
|
|
|
// Set up a new pen if necessary.
|
|
QPen pen = state()->pen;
|
|
if (pen.style() == Qt::NoPen)
|
|
return;
|
|
if (pen.capStyle() == Qt::FlatCap)
|
|
pen.setCapStyle(Qt::SquareCap);
|
|
|
|
for (int i = 0; i < pointCount; ++i, ++points) {
|
|
VGfloat coords[4];
|
|
if (d->simpleTransform) {
|
|
coords[0] = points->x();
|
|
coords[1] = points->y();
|
|
coords[2] = coords[0];
|
|
coords[3] = coords[1];
|
|
} else {
|
|
QPointF p = d->transform.map(QPointF(*points));
|
|
coords[0] = p.x();
|
|
coords[1] = p.y();
|
|
coords[2] = coords[0];
|
|
coords[3] = coords[1];
|
|
}
|
|
vgModifyPathCoords(d->linePath, 0, 2, coords);
|
|
d->stroke(d->linePath, pen);
|
|
}
|
|
#else
|
|
QPaintEngineEx::drawPoints(points, pointCount);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawPolygon(points, pointCount, mode);
|
|
return;
|
|
}
|
|
|
|
QVGPainterState *s = state();
|
|
VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
pointCount + 1, // segmentCapacityHint
|
|
pointCount * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
QVarLengthArray<VGfloat, 16> coords;
|
|
QVarLengthArray<VGubyte, 10> segments;
|
|
for (int i = 0; i < pointCount; ++i, ++points) {
|
|
if (d->simpleTransform) {
|
|
coords.append(points->x());
|
|
coords.append(points->y());
|
|
} else {
|
|
QPointF temp = d->transform.map(*points);
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
}
|
|
if (i == 0)
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
else
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
if (mode != QPaintEngine::PolylineMode)
|
|
segments.append(VG_CLOSE_PATH);
|
|
vgAppendPathData(path, segments.count(),
|
|
segments.constData(), coords.constData());
|
|
switch (mode) {
|
|
case QPaintEngine::WindingMode:
|
|
d->draw(path, s->pen, s->brush, VG_NON_ZERO);
|
|
break;
|
|
|
|
case QPaintEngine::PolylineMode:
|
|
d->stroke(path, s->pen);
|
|
break;
|
|
|
|
default:
|
|
d->draw(path, s->pen, s->brush, VG_EVEN_ODD);
|
|
break;
|
|
}
|
|
vgDestroyPath(path);
|
|
}
|
|
|
|
void QVGPaintEngine::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
if (d->needsEmulation()) {
|
|
QPaintEngineEx::drawPolygon(points, pointCount, mode);
|
|
return;
|
|
}
|
|
|
|
QVGPainterState *s = state();
|
|
VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
pointCount + 1, // segmentCapacityHint
|
|
pointCount * 2, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
QVarLengthArray<VGfloat, 16> coords;
|
|
QVarLengthArray<VGubyte, 10> segments;
|
|
for (int i = 0; i < pointCount; ++i, ++points) {
|
|
if (d->simpleTransform) {
|
|
coords.append(points->x());
|
|
coords.append(points->y());
|
|
} else {
|
|
QPointF temp = d->transform.map(QPointF(*points));
|
|
coords.append(temp.x());
|
|
coords.append(temp.y());
|
|
}
|
|
if (i == 0)
|
|
segments.append(VG_MOVE_TO_ABS);
|
|
else
|
|
segments.append(VG_LINE_TO_ABS);
|
|
}
|
|
if (mode != QPaintEngine::PolylineMode)
|
|
segments.append(VG_CLOSE_PATH);
|
|
vgAppendPathData(path, segments.count(),
|
|
segments.constData(), coords.constData());
|
|
switch (mode) {
|
|
case QPaintEngine::WindingMode:
|
|
d->draw(path, s->pen, s->brush, VG_NON_ZERO);
|
|
break;
|
|
|
|
case QPaintEngine::PolylineMode:
|
|
d->stroke(path, s->pen);
|
|
break;
|
|
|
|
default:
|
|
d->draw(path, s->pen, s->brush, VG_EVEN_ODD);
|
|
break;
|
|
}
|
|
vgDestroyPath(path);
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::setImageOptions()
|
|
{
|
|
if (opacity != 1.0f && simpleTransform) {
|
|
if (opacity != paintOpacity) {
|
|
VGfloat values[4];
|
|
values[0] = 1.0f;
|
|
values[1] = 1.0f;
|
|
values[2] = 1.0f;
|
|
values[3] = opacity;
|
|
vgSetParameterfv(opacityPaint, VG_PAINT_COLOR, 4, values);
|
|
paintOpacity = opacity;
|
|
}
|
|
if (fillPaint != opacityPaint) {
|
|
vgSetPaint(opacityPaint, VG_FILL_PATH);
|
|
fillPaint = opacityPaint;
|
|
}
|
|
setImageMode(VG_DRAW_IMAGE_MULTIPLY);
|
|
} else {
|
|
setImageMode(VG_DRAW_IMAGE_NORMAL);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEnginePrivate::systemStateChanged()
|
|
{
|
|
q->updateScissor();
|
|
}
|
|
|
|
static void drawVGImage(QVGPaintEnginePrivate *d,
|
|
const QRectF& r, VGImage vgImg,
|
|
const QSize& imageSize, const QRectF& sr)
|
|
{
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
return;
|
|
VGImage child = VG_INVALID_HANDLE;
|
|
|
|
if (sr.topLeft().isNull() && sr.size() == imageSize) {
|
|
child = vgImg;
|
|
} else {
|
|
QRect src = sr.toRect();
|
|
#if !defined(QT_SHIVAVG)
|
|
child = vgChildImage(vgImg, src.x(), src.y(), src.width(), src.height());
|
|
#else
|
|
child = vgImg; // XXX: ShivaVG doesn't have vgChildImage().
|
|
#endif
|
|
}
|
|
|
|
QTransform transform(d->imageTransform);
|
|
VGfloat scaleX = sr.width() == 0.0f ? 0.0f : r.width() / sr.width();
|
|
VGfloat scaleY = sr.height() == 0.0f ? 0.0f : r.height() / sr.height();
|
|
transform.translate(r.x(), r.y());
|
|
transform.scale(scaleX, scaleY);
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
d->setImageOptions();
|
|
vgDrawImage(child);
|
|
|
|
if(child != vgImg)
|
|
vgDestroyImage(child);
|
|
}
|
|
|
|
static void drawVGImage(QVGPaintEnginePrivate *d,
|
|
const QPointF& pos, VGImage vgImg)
|
|
{
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
return;
|
|
|
|
QTransform transform(d->imageTransform);
|
|
transform.translate(pos.x(), pos.y());
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
d->setImageOptions();
|
|
vgDrawImage(vgImg);
|
|
}
|
|
|
|
static void drawImageTiled(QVGPaintEnginePrivate *d,
|
|
const QRectF &r,
|
|
const QImage &image,
|
|
const QRectF &sr = QRectF())
|
|
{
|
|
const int minTileSize = 16;
|
|
int tileWidth = 512;
|
|
int tileHeight = tileWidth;
|
|
|
|
VGImageFormat tileFormat = qt_vg_image_to_vg_format(image.format());
|
|
VGImage tile = VG_INVALID_HANDLE;
|
|
QVGImagePool *pool = QVGImagePool::instance();
|
|
while (tile == VG_INVALID_HANDLE && tileWidth >= minTileSize) {
|
|
tile = pool->createPermanentImage(tileFormat, tileWidth, tileHeight,
|
|
VG_IMAGE_QUALITY_FASTER);
|
|
if (tile == VG_INVALID_HANDLE) {
|
|
tileWidth /= 2;
|
|
tileHeight /= 2;
|
|
}
|
|
}
|
|
if (tile == VG_INVALID_HANDLE) {
|
|
qWarning("drawImageTiled: Failed to create %dx%d tile, giving up", tileWidth, tileHeight);
|
|
return;
|
|
}
|
|
|
|
VGfloat opacityMatrix[20] = {
|
|
1.0f, 0.0f, 0.0f, 0.0f,
|
|
0.0f, 1.0f, 0.0f, 0.0f,
|
|
0.0f, 0.0f, 1.0f, 0.0f,
|
|
0.0f, 0.0f, 0.0f, d->opacity,
|
|
0.0f, 0.0f, 0.0f, 0.0f
|
|
};
|
|
VGImage tileWithOpacity = VG_INVALID_HANDLE;
|
|
if (d->opacity != 1) {
|
|
tileWithOpacity = pool->createPermanentImage(VG_sARGB_8888_PRE,
|
|
tileWidth, tileHeight, VG_IMAGE_QUALITY_FASTER);
|
|
if (tileWithOpacity == VG_INVALID_HANDLE)
|
|
qWarning("drawImageTiled: Failed to create extra tile, ignoring opacity");
|
|
}
|
|
|
|
QRect sourceRect = sr.toRect();
|
|
if (sourceRect.isNull())
|
|
sourceRect = QRect(0, 0, image.width(), image.height());
|
|
|
|
VGfloat scaleX = r.width() / sourceRect.width();
|
|
VGfloat scaleY = r.height() / sourceRect.height();
|
|
|
|
d->setImageOptions();
|
|
|
|
for (int y = sourceRect.y(); y < sourceRect.height(); y += tileHeight) {
|
|
int h = qMin(tileHeight, sourceRect.height() - y);
|
|
if (h < 1)
|
|
break;
|
|
for (int x = sourceRect.x(); x < sourceRect.width(); x += tileWidth) {
|
|
int w = qMin(tileWidth, sourceRect.width() - x);
|
|
if (w < 1)
|
|
break;
|
|
|
|
int bytesPerPixel = image.depth() / 8;
|
|
const uchar *sptr = image.constBits() + x * bytesPerPixel + y * image.bytesPerLine();
|
|
vgImageSubData(tile, sptr, image.bytesPerLine(), tileFormat, 0, 0, w, h);
|
|
|
|
QTransform transform(d->imageTransform);
|
|
transform.translate(r.x() + x, r.y() + y);
|
|
transform.scale(scaleX, scaleY);
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
VGImage actualTile = tile;
|
|
if (tileWithOpacity != VG_INVALID_HANDLE) {
|
|
vgColorMatrix(tileWithOpacity, actualTile, opacityMatrix);
|
|
if (w < tileWidth || h < tileHeight)
|
|
actualTile = vgChildImage(tileWithOpacity, 0, 0, w, h);
|
|
else
|
|
actualTile = tileWithOpacity;
|
|
} else if (w < tileWidth || h < tileHeight) {
|
|
actualTile = vgChildImage(tile, 0, 0, w, h);
|
|
}
|
|
vgDrawImage(actualTile);
|
|
|
|
if (actualTile != tile && actualTile != tileWithOpacity)
|
|
vgDestroyImage(actualTile);
|
|
}
|
|
}
|
|
|
|
vgDestroyImage(tile);
|
|
if (tileWithOpacity != VG_INVALID_HANDLE)
|
|
vgDestroyImage(tileWithOpacity);
|
|
}
|
|
|
|
// Used by qpixmapfilter_vg.cpp to draw filtered VGImage's.
|
|
void qt_vg_drawVGImage(QPainter *painter, const QPointF& pos, VGImage vgImg)
|
|
{
|
|
QVGPaintEngine *engine =
|
|
static_cast<QVGPaintEngine *>(painter->paintEngine());
|
|
drawVGImage(engine->vgPrivate(), pos, vgImg);
|
|
}
|
|
|
|
// Used by qpixmapfilter_vg.cpp to draw filtered VGImage's as a stencil.
|
|
void qt_vg_drawVGImageStencil
|
|
(QPainter *painter, const QPointF& pos, VGImage vgImg, const QBrush& brush)
|
|
{
|
|
QVGPaintEngine *engine =
|
|
static_cast<QVGPaintEngine *>(painter->paintEngine());
|
|
|
|
QVGPaintEnginePrivate *d = engine->vgPrivate();
|
|
|
|
QTransform transform(d->imageTransform);
|
|
transform.translate(pos.x(), pos.y());
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
d->ensureBrush(brush);
|
|
d->setImageMode(VG_DRAW_IMAGE_STENCIL);
|
|
vgDrawImage(vgImg);
|
|
}
|
|
|
|
bool QVGPaintEngine::canVgWritePixels(const QImage &image) const
|
|
{
|
|
Q_D(const QVGPaintEngine);
|
|
|
|
// qt_vg_image_to_vg_format returns VG_sARGB_8888 as
|
|
// fallback case if no matching VG format is found.
|
|
// If given image format is not Format_ARGB32 and returned
|
|
// format is VG_sARGB_8888, it means that no match was
|
|
// found. In that case vgWritePixels cannot be used.
|
|
// Also 1-bit formats cannot be used directly either.
|
|
if ((image.format() != QImage::Format_ARGB32
|
|
&& qt_vg_image_to_vg_format(image.format()) == VG_sARGB_8888)
|
|
|| image.depth() == 1) {
|
|
return false;
|
|
}
|
|
|
|
// vgWritePixels ignores masking, blending and xforms so we can only use it if
|
|
// ALL of the following conditions are true:
|
|
// - It is a simple translate, or a scale of -1 on the y-axis (inverted)
|
|
// - The opacity is totally opaque
|
|
// - The composition mode is "source" OR "source over" provided the image is opaque
|
|
return ( d->imageTransform.type() <= QTransform::TxScale
|
|
&& d->imageTransform.m11() == 1.0 && qAbs(d->imageTransform.m22()) == 1.0)
|
|
&& d->opacity == 1.0f
|
|
&& (d->blendMode == VG_BLEND_SRC || (d->blendMode == VG_BLEND_SRC_OVER &&
|
|
!image.hasAlphaChannel()));
|
|
}
|
|
|
|
void QVGPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr)
|
|
{
|
|
QPixmapData *pd = pm.pixmapData();
|
|
if (!pd)
|
|
return; // null QPixmap
|
|
if (pd->classId() == QPixmapData::OpenVGClass) {
|
|
Q_D(QVGPaintEngine);
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
if (!vgpd->isValid())
|
|
return;
|
|
if (d->simpleTransform)
|
|
drawVGImage(d, r, vgpd->toVGImage(), vgpd->size(), sr);
|
|
else
|
|
drawVGImage(d, r, vgpd->toVGImage(d->opacity), vgpd->size(), sr);
|
|
|
|
if(!vgpd->failedToAlloc)
|
|
return;
|
|
|
|
// try to reallocate next time if reasonable small pixmap
|
|
QSize screenSize = QApplication::desktop()->screenGeometry().size();
|
|
if (pm.size().width() <= screenSize.width()
|
|
&& pm.size().height() <= screenSize.height())
|
|
vgpd->failedToAlloc = false;
|
|
|
|
vgpd->source.beginDataAccess();
|
|
drawImage(r, vgpd->source.imageRef(), sr, Qt::AutoColor);
|
|
vgpd->source.endDataAccess(true);
|
|
} else {
|
|
drawImage(r, *(pd->buffer()), sr, Qt::AutoColor);
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::drawPixmap(const QPointF &pos, const QPixmap &pm)
|
|
{
|
|
QPixmapData *pd = pm.pixmapData();
|
|
if (!pd)
|
|
return; // null QPixmap
|
|
if (pd->classId() == QPixmapData::OpenVGClass) {
|
|
Q_D(QVGPaintEngine);
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
if (!vgpd->isValid())
|
|
return;
|
|
if (d->simpleTransform)
|
|
drawVGImage(d, pos, vgpd->toVGImage());
|
|
else
|
|
drawVGImage(d, pos, vgpd->toVGImage(d->opacity));
|
|
|
|
if (!vgpd->failedToAlloc)
|
|
return;
|
|
|
|
// try to reallocate next time if reasonable small pixmap
|
|
QSize screenSize = QApplication::desktop()->screenGeometry().size();
|
|
if (pm.size().width() <= screenSize.width()
|
|
&& pm.size().height() <= screenSize.height())
|
|
vgpd->failedToAlloc = false;
|
|
|
|
vgpd->source.beginDataAccess();
|
|
drawImage(pos, vgpd->source.imageRef());
|
|
vgpd->source.endDataAccess(true);
|
|
} else {
|
|
drawImage(pos, *(pd->buffer()));
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::drawImage
|
|
(const QRectF &r, const QImage &image, const QRectF &sr,
|
|
Qt::ImageConversionFlags flags)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (image.isNull())
|
|
return;
|
|
VGImage vgImg;
|
|
if (d->simpleTransform || d->opacity == 1.0f)
|
|
vgImg = toVGImageSubRect(image, sr.toRect(), flags);
|
|
else
|
|
vgImg = toVGImageWithOpacitySubRect(image, d->opacity, sr.toRect());
|
|
if (vgImg != VG_INVALID_HANDLE) {
|
|
if (r.size() == sr.size()) {
|
|
drawVGImage(d, r.topLeft(), vgImg);
|
|
} else {
|
|
drawVGImage(d, r, vgImg, sr.size().toSize(),
|
|
QRectF(QPointF(0, 0), sr.size()));
|
|
}
|
|
} else {
|
|
if (canVgWritePixels(image) && (r.size() == sr.size()) && !flags) {
|
|
// Optimization for straight blits, no blending
|
|
int x = sr.x();
|
|
int y = sr.y();
|
|
int bpp = image.depth() >> 3; // bytes
|
|
int offset = 0;
|
|
int bpl = image.bytesPerLine();
|
|
if (d->imageTransform.m22() < 0) {
|
|
// inverted
|
|
offset = ((y + sr.height()) * bpl) - ((image.width() - x) * bpp);
|
|
bpl = -bpl;
|
|
} else {
|
|
offset = (y * bpl) + (x * bpp);
|
|
}
|
|
const uchar *bits = image.constBits() + offset;
|
|
|
|
QPointF mapped = d->imageTransform.map(r.topLeft());
|
|
vgWritePixels(bits, bpl, qt_vg_image_to_vg_format(image.format()),
|
|
mapped.x(), mapped.y() - sr.height(), r.width(), r.height());
|
|
return;
|
|
} else {
|
|
// Monochrome images need to use the vgChildImage() path.
|
|
vgImg = toVGImage(image, flags);
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
drawImageTiled(d, r, image, sr);
|
|
else
|
|
drawVGImage(d, r, vgImg, image.size(), sr);
|
|
}
|
|
}
|
|
vgDestroyImage(vgImg);
|
|
}
|
|
|
|
void QVGPaintEngine::drawImage(const QPointF &pos, const QImage &image)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (image.isNull())
|
|
return;
|
|
VGImage vgImg;
|
|
if (canVgWritePixels(image)) {
|
|
// Optimization for straight blits, no blending
|
|
bool inverted = (d->imageTransform.m22() < 0);
|
|
const uchar *bits = inverted ? image.constBits() + image.byteCount() : image.constBits();
|
|
int bpl = inverted ? -image.bytesPerLine() : image.bytesPerLine();
|
|
|
|
QPointF mapped = d->imageTransform.map(pos);
|
|
vgWritePixels(bits, bpl, qt_vg_image_to_vg_format(image.format()),
|
|
mapped.x(), mapped.y() - image.height(), image.width(), image.height());
|
|
return;
|
|
} else if (d->simpleTransform || d->opacity == 1.0f) {
|
|
vgImg = toVGImage(image);
|
|
} else {
|
|
vgImg = toVGImageWithOpacity(image, d->opacity);
|
|
}
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
drawImageTiled(d, QRectF(pos, image.size()), image);
|
|
else
|
|
drawVGImage(d, pos, vgImg);
|
|
vgDestroyImage(vgImg);
|
|
}
|
|
|
|
void QVGPaintEngine::drawTiledPixmap
|
|
(const QRectF &r, const QPixmap &pixmap, const QPointF &s)
|
|
{
|
|
QBrush brush(state()->pen.color(), pixmap);
|
|
QTransform xform = QTransform::fromTranslate(r.x() - s.x(), r.y() - s.y());
|
|
brush.setTransform(xform);
|
|
fillRect(r, brush);
|
|
}
|
|
|
|
// Best performance will be achieved with QDrawPixmaps::OpaqueHint
|
|
// (i.e. no opacity), no rotation or scaling, and drawing the full
|
|
// pixmap rather than parts of the pixmap. Even having just one of
|
|
// these conditions will improve performance.
|
|
void QVGPaintEngine::drawPixmapFragments(const QPainter::PixmapFragment *drawingData, int dataCount,
|
|
const QPixmap &pixmap, QFlags<QPainter::PixmapFragmentHint> hints)
|
|
{
|
|
#if !defined(QT_SHIVAVG)
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// If the pixmap is not VG, or the transformation is projective,
|
|
// then fall back to the default implementation.
|
|
QPixmapData *pd = pixmap.pixmapData();
|
|
if (!pd)
|
|
return; // null QPixmap
|
|
if (pd->classId() != QPixmapData::OpenVGClass || !d->simpleTransform) {
|
|
QPaintEngineEx::drawPixmapFragments(drawingData, dataCount, pixmap, hints);
|
|
return;
|
|
}
|
|
|
|
// Bail out if nothing to do.
|
|
if (dataCount <= 0)
|
|
return;
|
|
|
|
// Bail out if we don't have a usable VGImage for the pixmap.
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
if (!vgpd->isValid())
|
|
return;
|
|
VGImage vgImg = vgpd->toVGImage();
|
|
if (vgImg == VG_INVALID_HANDLE)
|
|
return;
|
|
|
|
// We cache the results of any vgChildImage() calls because the
|
|
// same child is very likely to be used over and over in particle
|
|
// systems. However, performance is even better if vgChildImage()
|
|
// isn't needed at all, so use full source rects where possible.
|
|
QVarLengthArray<VGImage> cachedImages;
|
|
QVarLengthArray<QRect> cachedSources;
|
|
|
|
// Select the opacity paint object.
|
|
if ((hints & QPainter::OpaqueHint) != 0 && d->opacity == 1.0f) {
|
|
d->setImageMode(VG_DRAW_IMAGE_NORMAL);
|
|
} else {
|
|
hints = 0;
|
|
if (d->fillPaint != d->opacityPaint) {
|
|
vgSetPaint(d->opacityPaint, VG_FILL_PATH);
|
|
d->fillPaint = d->opacityPaint;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < dataCount; ++i) {
|
|
QTransform transform(d->imageTransform);
|
|
transform.translate(drawingData[i].x, drawingData[i].y);
|
|
transform.rotate(drawingData[i].rotation);
|
|
|
|
VGImage child;
|
|
QSize imageSize = vgpd->size();
|
|
QRectF sr(drawingData[i].sourceLeft, drawingData[i].sourceTop,
|
|
drawingData[i].width, drawingData[i].height);
|
|
if (sr.topLeft().isNull() && sr.size() == imageSize) {
|
|
child = vgImg;
|
|
} else {
|
|
// Look for a previous child with the same source rectangle
|
|
// to avoid constantly calling vgChildImage()/vgDestroyImage().
|
|
QRect src = sr.toRect();
|
|
int j;
|
|
for (j = 0; j < cachedSources.size(); ++j) {
|
|
if (cachedSources[j] == src)
|
|
break;
|
|
}
|
|
if (j < cachedSources.size()) {
|
|
child = cachedImages[j];
|
|
} else {
|
|
child = vgChildImage
|
|
(vgImg, src.x(), src.y(), src.width(), src.height());
|
|
cachedImages.append(child);
|
|
cachedSources.append(src);
|
|
}
|
|
}
|
|
|
|
VGfloat scaleX = drawingData[i].scaleX;
|
|
VGfloat scaleY = drawingData[i].scaleY;
|
|
transform.translate(-0.5 * scaleX * sr.width(),
|
|
-0.5 * scaleY * sr.height());
|
|
transform.scale(scaleX, scaleY);
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
if ((hints & QPainter::OpaqueHint) == 0) {
|
|
qreal opacity = d->opacity * drawingData[i].opacity;
|
|
if (opacity != 1.0f) {
|
|
if (d->paintOpacity != opacity) {
|
|
VGfloat values[4];
|
|
values[0] = 1.0f;
|
|
values[1] = 1.0f;
|
|
values[2] = 1.0f;
|
|
values[3] = opacity;
|
|
d->paintOpacity = opacity;
|
|
vgSetParameterfv
|
|
(d->opacityPaint, VG_PAINT_COLOR, 4, values);
|
|
}
|
|
d->setImageMode(VG_DRAW_IMAGE_MULTIPLY);
|
|
} else {
|
|
d->setImageMode(VG_DRAW_IMAGE_NORMAL);
|
|
}
|
|
}
|
|
|
|
vgDrawImage(child);
|
|
}
|
|
|
|
// Destroy the cached child sub-images.
|
|
for (int i = 0; i < cachedImages.size(); ++i)
|
|
vgDestroyImage(cachedImages[i]);
|
|
#else
|
|
QPaintEngineEx::drawPixmapFragments(drawingData, dataCount, pixmap, hints);
|
|
#endif
|
|
}
|
|
|
|
QVGFontEngineCleaner::QVGFontEngineCleaner(QVGPaintEnginePrivate *d)
|
|
: QObject(), d_ptr(d)
|
|
{
|
|
}
|
|
|
|
QVGFontEngineCleaner::~QVGFontEngineCleaner()
|
|
{
|
|
}
|
|
|
|
void QVGFontEngineCleaner::fontEngineDestroyed()
|
|
{
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
QFontEngine *engine = static_cast<QFontEngine *>(sender());
|
|
QVGFontCache::Iterator it = d_ptr->fontCache.find(engine);
|
|
if (it != d_ptr->fontCache.end()) {
|
|
delete it.value();
|
|
d_ptr->fontCache.erase(it);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
|
|
QVGFontGlyphCache::QVGFontGlyphCache()
|
|
{
|
|
font = vgCreateFont(0);
|
|
scaleX = scaleY = 0.0;
|
|
invertedGlyphs = false;
|
|
memset(cachedGlyphsMask, 0, sizeof(cachedGlyphsMask));
|
|
}
|
|
|
|
QVGFontGlyphCache::~QVGFontGlyphCache()
|
|
{
|
|
if (font != VG_INVALID_HANDLE)
|
|
vgDestroyFont(font);
|
|
}
|
|
|
|
void QVGFontGlyphCache::setScaleFromText(const QFont &font, QFontEngine *fontEngine)
|
|
{
|
|
QFontInfo fi(font);
|
|
qreal pixelSize = fi.pixelSize();
|
|
qreal emSquare = fontEngine->properties().emSquare.toReal();
|
|
scaleX = scaleY = static_cast<VGfloat>(pixelSize / emSquare);
|
|
}
|
|
|
|
void QVGFontGlyphCache::cacheGlyphs(QVGPaintEnginePrivate *d,
|
|
QFontEngine *fontEngine,
|
|
const glyph_t *g, int count)
|
|
{
|
|
VGfloat origin[2];
|
|
VGfloat escapement[2];
|
|
glyph_metrics_t metrics;
|
|
// Some Qt font engines don't set yoff in getUnscaledGlyph().
|
|
// Zero the metric structure so that everything has a default value.
|
|
memset(&metrics, 0, sizeof(metrics));
|
|
while (count-- > 0) {
|
|
// Skip this glyph if we have already cached it before.
|
|
glyph_t glyph = *g++;
|
|
if (glyph < 256) {
|
|
if ((cachedGlyphsMask[glyph / 32] & (1 << (glyph % 32))) != 0)
|
|
continue;
|
|
cachedGlyphsMask[glyph / 32] |= (1 << (glyph % 32));
|
|
} else if (cachedGlyphs.contains(glyph)) {
|
|
continue;
|
|
} else {
|
|
cachedGlyphs.insert(glyph);
|
|
}
|
|
#if !defined(QVG_NO_IMAGE_GLYPHS)
|
|
Q_UNUSED(d);
|
|
QImage scaledImage = fontEngine->alphaMapForGlyph(glyph);
|
|
VGImage vgImage = VG_INVALID_HANDLE;
|
|
metrics = fontEngine->boundingBox(glyph);
|
|
if (!scaledImage.isNull()) { // Not a space character
|
|
if (scaledImage.format() == QImage::Format_Indexed8) {
|
|
vgImage = vgCreateImage(VG_A_8, scaledImage.width(), scaledImage.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData(vgImage, scaledImage.constBits(), scaledImage.bytesPerLine(), VG_A_8, 0, 0, scaledImage.width(), scaledImage.height());
|
|
} else if (scaledImage.format() == QImage::Format_Mono) {
|
|
QImage img = scaledImage.convertToFormat(QImage::Format_Indexed8);
|
|
vgImage = vgCreateImage(VG_A_8, img.width(), img.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData(vgImage, img.constBits(), img.bytesPerLine(), VG_A_8, 0, 0, img.width(), img.height());
|
|
} else {
|
|
QImage img = scaledImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
|
vgImage = vgCreateImage(VG_sARGB_8888_PRE, img.width(), img.height(), VG_IMAGE_QUALITY_FASTER);
|
|
vgImageSubData(vgImage, img.constBits(), img.bytesPerLine(), VG_sARGB_8888_PRE, 0, 0, img.width(), img.height());
|
|
}
|
|
}
|
|
origin[0] = -metrics.x.toReal();
|
|
origin[1] = -metrics.y.toReal();
|
|
escapement[0] = 0;
|
|
escapement[1] = 0;
|
|
vgSetGlyphToImage(font, glyph, vgImage, origin, escapement);
|
|
vgDestroyImage(vgImage); // Reduce reference count.
|
|
#else
|
|
// Calculate the path for the glyph and cache it.
|
|
QPainterPath path;
|
|
fontEngine->getUnscaledGlyph(glyph, &path, &metrics);
|
|
VGPath vgPath;
|
|
if (!path.isEmpty()) {
|
|
vgPath = d->painterPathToVGPath(path);
|
|
} else {
|
|
// Probably a "space" character with no visible outline.
|
|
vgPath = VG_INVALID_HANDLE;
|
|
}
|
|
origin[0] = 0;
|
|
origin[1] = 0;
|
|
escapement[0] = 0;
|
|
escapement[1] = 0;
|
|
vgSetGlyphToPath(font, glyph, vgPath, VG_FALSE, origin, escapement);
|
|
vgDestroyPath(vgPath); // Reduce reference count.
|
|
#endif // !defined(QVG_NO_IMAGE_GLYPHS)
|
|
}
|
|
}
|
|
|
|
#endif // !defined(QVG_NO_DRAW_GLYPHS)
|
|
|
|
void QVGPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
|
|
{
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
Q_D(QVGPaintEngine);
|
|
const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
|
|
|
|
// If we are not using a simple transform, then fall back
|
|
// to the default Qt path stroking algorithm.
|
|
if (!d->simpleTransform) {
|
|
QPaintEngineEx::drawTextItem(p, textItem);
|
|
return;
|
|
}
|
|
|
|
if (d->needsPenEmulation()) {
|
|
QPaintEngineEx::drawTextItem(p, textItem);
|
|
return;
|
|
}
|
|
|
|
// Get the glyphs and positions associated with the text item.
|
|
QVarLengthArray<QFixedPoint> positions;
|
|
QVarLengthArray<glyph_t> glyphs;
|
|
QTransform matrix;
|
|
ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
|
|
|
|
if (!drawCachedGlyphs(glyphs.size(), glyphs.data(), ti.font(), ti.fontEngine, p, positions.data()))
|
|
QPaintEngineEx::drawTextItem(p, textItem);
|
|
#else
|
|
// OpenGL 1.0 does not have support for VGFont and glyphs,
|
|
// so fall back to the default Qt path stroking algorithm.
|
|
QPaintEngineEx::drawTextItem(p, textItem);
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::drawStaticTextItem(QStaticTextItem *textItem)
|
|
{
|
|
drawCachedGlyphs(textItem->numGlyphs, textItem->glyphs, textItem->font, textItem->fontEngine(),
|
|
QPointF(0, 0), textItem->glyphPositions);
|
|
}
|
|
|
|
bool QVGPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs, const QFont &font,
|
|
QFontEngine *fontEngine, const QPointF &p,
|
|
const QFixedPoint *positions)
|
|
{
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// Find the glyph cache for this font.
|
|
QVGFontCache::ConstIterator it = d->fontCache.constFind(fontEngine);
|
|
QVGFontGlyphCache *glyphCache;
|
|
if (it != d->fontCache.constEnd()) {
|
|
glyphCache = it.value();
|
|
} else {
|
|
#ifdef Q_OS_SYMBIAN
|
|
glyphCache = new QSymbianVGFontGlyphCache();
|
|
#else
|
|
glyphCache = new QVGFontGlyphCache();
|
|
#endif
|
|
if (glyphCache->font == VG_INVALID_HANDLE) {
|
|
qWarning("QVGPaintEngine::drawTextItem: OpenVG fonts are not supported by the OpenVG engine");
|
|
delete glyphCache;
|
|
return false;
|
|
}
|
|
glyphCache->setScaleFromText(font, fontEngine);
|
|
d->fontCache.insert(fontEngine, glyphCache);
|
|
if (!d->fontEngineCleaner)
|
|
d->fontEngineCleaner = new QVGFontEngineCleaner(d);
|
|
QObject::connect(fontEngine, SIGNAL(destroyed()),
|
|
d->fontEngineCleaner, SLOT(fontEngineDestroyed()));
|
|
}
|
|
|
|
// Set the transformation to use for drawing the current glyphs.
|
|
QTransform glyphTransform(d->pathTransform);
|
|
if (d->transform.type() <= QTransform::TxTranslate) {
|
|
// Prevent blurriness of unscaled, unrotated text by forcing integer coordinates.
|
|
glyphTransform.translate(
|
|
floor(p.x() + glyphTransform.dx() + aliasedCoordinateDelta) - glyphTransform.dx(),
|
|
floor(p.y() - glyphTransform.dy() + aliasedCoordinateDelta) + glyphTransform.dy());
|
|
} else {
|
|
glyphTransform.translate(p.x(), p.y());
|
|
}
|
|
#if defined(QVG_NO_IMAGE_GLYPHS)
|
|
glyphTransform.scale(glyphCache->scaleX, glyphCache->scaleY);
|
|
#endif
|
|
|
|
// Some glyph caches can create the VGImage upright
|
|
if (glyphCache->invertedGlyphs)
|
|
glyphTransform.scale(1, -1);
|
|
|
|
d->setTransform(VG_MATRIX_GLYPH_USER_TO_SURFACE, glyphTransform);
|
|
|
|
// Add the glyphs from the text item into the glyph cache.
|
|
glyphCache->cacheGlyphs(d, fontEngine, glyphs, numGlyphs);
|
|
|
|
// Create the array of adjustments between glyphs
|
|
QVarLengthArray<VGfloat> adjustments_x(numGlyphs);
|
|
QVarLengthArray<VGfloat> adjustments_y(numGlyphs);
|
|
for (int i = 1; i < numGlyphs; ++i) {
|
|
adjustments_x[i-1] = (positions[i].x - positions[i-1].x).round().toReal();
|
|
adjustments_y[i-1] = (positions[i].y - positions[i-1].y).round().toReal();
|
|
}
|
|
|
|
// Set the glyph drawing origin.
|
|
VGfloat origin[2];
|
|
origin[0] = positions[0].x.round().toReal();
|
|
origin[1] = positions[0].y.round().toReal();
|
|
vgSetfv(VG_GLYPH_ORIGIN, 2, origin);
|
|
|
|
// Fast anti-aliasing for paths, better for images.
|
|
#if !defined(QVG_NO_IMAGE_GLYPHS)
|
|
d->setImageQuality(VG_IMAGE_QUALITY_BETTER);
|
|
d->setImageMode(VG_DRAW_IMAGE_STENCIL);
|
|
#else
|
|
d->setRenderingQuality(VG_RENDERING_QUALITY_FASTER);
|
|
#endif
|
|
|
|
// Draw the glyphs. We need to fill with the brush associated with
|
|
// the Qt pen, not the Qt brush.
|
|
d->ensureBrush(state()->pen.brush());
|
|
vgDrawGlyphs(glyphCache->font, numGlyphs, (VGuint*)glyphs,
|
|
adjustments_x.data(), adjustments_y.data(), VG_FILL_PATH, VG_TRUE);
|
|
return true;
|
|
#else
|
|
Q_UNUSED(numGlyphs);
|
|
Q_UNUSED(glyphs);
|
|
Q_UNUSED(font);
|
|
Q_UNUSED(fontEngine);
|
|
Q_UNUSED(p);
|
|
Q_UNUSED(positions);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::setState(QPainterState *s)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
QPaintEngineEx::setState(s);
|
|
QVGPainterState *ps = static_cast<QVGPainterState *>(s);
|
|
if (ps->isNew) {
|
|
// Newly created state object. The call to setState()
|
|
// will either be followed by a call to begin(), or we are
|
|
// setting the state as part of a save().
|
|
ps->isNew = false;
|
|
} else {
|
|
// This state object was set as part of a restore().
|
|
restoreState(d->dirty);
|
|
d->dirty = ps->savedDirty;
|
|
}
|
|
}
|
|
|
|
void QVGPaintEngine::beginNativePainting()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// About to enter raw VG mode: flush pending changes and make
|
|
// sure that all matrices are set to the current transformation.
|
|
QVGPainterState *s = this->state();
|
|
d->ensurePen(s->pen);
|
|
d->ensureBrush(s->brush);
|
|
d->ensurePathTransform();
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, d->imageTransform);
|
|
#if !defined(QVG_NO_DRAW_GLYPHS)
|
|
d->setTransform(VG_MATRIX_GLYPH_USER_TO_SURFACE, d->pathTransform);
|
|
#endif
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
vgSeti(VG_MASKING, VG_FALSE);
|
|
d->rawVG = true;
|
|
}
|
|
|
|
void QVGPaintEngine::endNativePainting()
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
// Exiting raw VG mode: force all state values to be
|
|
// explicitly set on the VG engine to undo any changes
|
|
// that were made by the raw VG function calls.
|
|
QPaintEngine::DirtyFlags dirty = d->dirty;
|
|
d->clearModes();
|
|
d->forcePenChange = true;
|
|
d->forceBrushChange = true;
|
|
d->penType = (VGPaintType)0;
|
|
d->brushType = (VGPaintType)0;
|
|
d->clearColor = QColor();
|
|
d->fillPaint = d->brushPaint;
|
|
d->scissorDirty = true;
|
|
restoreState(QPaintEngine::AllDirty);
|
|
d->dirty = dirty;
|
|
d->rawVG = false;
|
|
vgSetPaint(d->penPaint, VG_STROKE_PATH);
|
|
vgSetPaint(d->brushPaint, VG_FILL_PATH);
|
|
}
|
|
|
|
QPixmapFilter *QVGPaintEngine::pixmapFilter(int type, const QPixmapFilter *prototype)
|
|
{
|
|
#if !defined(QT_SHIVAVG)
|
|
Q_D(QVGPaintEngine);
|
|
switch (type) {
|
|
case QPixmapFilter::ConvolutionFilter:
|
|
if (!d->convolutionFilter)
|
|
d->convolutionFilter.reset(new QVGPixmapConvolutionFilter);
|
|
return d->convolutionFilter.data();
|
|
case QPixmapFilter::ColorizeFilter:
|
|
if (!d->colorizeFilter)
|
|
d->colorizeFilter.reset(new QVGPixmapColorizeFilter);
|
|
return d->colorizeFilter.data();
|
|
case QPixmapFilter::DropShadowFilter:
|
|
if (!d->dropShadowFilter)
|
|
d->dropShadowFilter.reset(new QVGPixmapDropShadowFilter);
|
|
return d->dropShadowFilter.data();
|
|
case QPixmapFilter::BlurFilter:
|
|
if (!d->blurFilter)
|
|
d->blurFilter.reset(new QVGPixmapBlurFilter);
|
|
return d->blurFilter.data();
|
|
default: break;
|
|
}
|
|
#endif
|
|
return QPaintEngineEx::pixmapFilter(type, prototype);
|
|
}
|
|
|
|
void QVGPaintEngine::restoreState(QPaintEngine::DirtyFlags dirty)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
|
|
// Restore the pen, brush, and other settings.
|
|
if ((dirty & QPaintEngine::DirtyBrushOrigin) != 0)
|
|
brushOriginChanged();
|
|
d->fillRule = 0;
|
|
d->clearColor = QColor();
|
|
if ((dirty & QPaintEngine::DirtyOpacity) != 0)
|
|
opacityChanged();
|
|
if ((dirty & QPaintEngine::DirtyTransform) != 0)
|
|
transformChanged();
|
|
if ((dirty & QPaintEngine::DirtyCompositionMode) != 0)
|
|
compositionModeChanged();
|
|
if ((dirty & QPaintEngine::DirtyHints) != 0)
|
|
renderHintsChanged();
|
|
if ((dirty & (QPaintEngine::DirtyClipRegion |
|
|
QPaintEngine::DirtyClipPath |
|
|
QPaintEngine::DirtyClipEnabled)) != 0) {
|
|
d->maskValid = false;
|
|
d->maskIsSet = false;
|
|
d->scissorMask = false;
|
|
d->maskRect = QRect();
|
|
d->scissorDirty = true;
|
|
clipEnabledChanged();
|
|
}
|
|
|
|
#if defined(QVG_SCISSOR_CLIP)
|
|
if ((dirty & (QPaintEngine::DirtyClipRegion |
|
|
QPaintEngine::DirtyClipPath |
|
|
QPaintEngine::DirtyClipEnabled)) == 0) {
|
|
updateScissor();
|
|
}
|
|
#else
|
|
updateScissor();
|
|
#endif
|
|
}
|
|
|
|
void QVGPaintEngine::fillRegion
|
|
(const QRegion& region, const QColor& color, const QSize& surfaceSize)
|
|
{
|
|
Q_D(QVGPaintEngine);
|
|
if (d->clearColor != color || d->clearOpacity != 1.0f) {
|
|
VGfloat values[4];
|
|
values[0] = color.redF();
|
|
values[1] = color.greenF();
|
|
values[2] = color.blueF();
|
|
values[3] = color.alphaF();
|
|
vgSetfv(VG_CLEAR_COLOR, 4, values);
|
|
d->clearColor = color;
|
|
d->clearOpacity = 1.0f;
|
|
}
|
|
if (region.rectCount() == 1) {
|
|
QRect r = region.boundingRect();
|
|
vgClear(r.x(), surfaceSize.height() - r.y() - r.height(),
|
|
r.width(), r.height());
|
|
} else {
|
|
const QVector<QRect> rects = region.rects();
|
|
for (int i = 0; i < rects.size(); ++i) {
|
|
QRect r = rects.at(i);
|
|
vgClear(r.x(), surfaceSize.height() - r.y() - r.height(),
|
|
r.width(), r.height());
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !defined(QVG_NO_SINGLE_CONTEXT) && !defined(QT_NO_EGL)
|
|
|
|
QVGCompositionHelper::QVGCompositionHelper()
|
|
{
|
|
d = qt_vg_create_paint_engine()->vgPrivate();
|
|
}
|
|
|
|
QVGCompositionHelper::~QVGCompositionHelper()
|
|
{
|
|
}
|
|
|
|
void QVGCompositionHelper::startCompositing(const QSize& screenSize)
|
|
{
|
|
this->screenSize = screenSize;
|
|
clearScissor();
|
|
d->setBlendMode(VG_BLEND_SRC_OVER);
|
|
}
|
|
|
|
void QVGCompositionHelper::endCompositing()
|
|
{
|
|
clearScissor();
|
|
}
|
|
|
|
void QVGCompositionHelper::blitWindow
|
|
(VGImage image, const QSize& imageSize,
|
|
const QRect& rect, const QPoint& topLeft, int opacity)
|
|
{
|
|
if (image == VG_INVALID_HANDLE)
|
|
return;
|
|
|
|
// Determine which sub rectangle of the window to draw.
|
|
QRect sr = rect.translated(-topLeft);
|
|
|
|
if (opacity >= 255) {
|
|
// Fully opaque: use vgSetPixels() to directly copy the sub-region.
|
|
int y = screenSize.height() - (rect.bottom() + 1);
|
|
vgSetPixels(rect.x(), y, image, sr.x(),
|
|
imageSize.height() - (sr.y() + sr.height()),
|
|
sr.width(), sr.height());
|
|
} else {
|
|
// Extract the child image that we want to draw.
|
|
VGImage child;
|
|
if (sr.topLeft().isNull() && sr.size() == imageSize)
|
|
child = image;
|
|
else {
|
|
child = vgChildImage
|
|
(image, sr.x(), imageSize.height() - (sr.y() + sr.height()),
|
|
sr.width(), sr.height());
|
|
}
|
|
|
|
// Set the image transform.
|
|
QTransform transform;
|
|
int y = screenSize.height() - (rect.bottom() + 1);
|
|
transform.translate(rect.x() - 0.5f, y - 0.5f);
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
|
|
// Enable opacity for image drawing if necessary.
|
|
if (opacity != d->paintOpacity) {
|
|
VGfloat values[4];
|
|
values[0] = 1.0f;
|
|
values[1] = 1.0f;
|
|
values[2] = 1.0f;
|
|
values[3] = ((VGfloat)opacity) / 255.0f;
|
|
vgSetParameterfv(d->opacityPaint, VG_PAINT_COLOR, 4, values);
|
|
d->paintOpacity = values[3];
|
|
}
|
|
if (d->fillPaint != d->opacityPaint) {
|
|
vgSetPaint(d->opacityPaint, VG_FILL_PATH);
|
|
d->fillPaint = d->opacityPaint;
|
|
}
|
|
d->setImageMode(VG_DRAW_IMAGE_MULTIPLY);
|
|
|
|
// Draw the child image.
|
|
vgDrawImage(child);
|
|
|
|
// Destroy the child image.
|
|
if(child != image)
|
|
vgDestroyImage(child);
|
|
}
|
|
}
|
|
|
|
static void fillBackgroundRect(const QRect& rect, QVGPaintEnginePrivate *d)
|
|
{
|
|
VGfloat coords[8];
|
|
coords[0] = rect.x();
|
|
coords[1] = rect.y();
|
|
coords[2] = rect.x() + rect.width();
|
|
coords[3] = coords[1];
|
|
coords[4] = coords[2];
|
|
coords[5] = rect.y() + rect.height();
|
|
coords[6] = coords[0];
|
|
coords[7] = coords[5];
|
|
#if !defined(QVG_NO_MODIFY_PATH)
|
|
vgModifyPathCoords(d->rectPath, 0, 4, coords);
|
|
vgDrawPath(d->rectPath, VG_FILL_PATH);
|
|
#else
|
|
Q_UNUSED(d);
|
|
VGPath rectPath = vgCreatePath
|
|
(VG_PATH_FORMAT_STANDARD,
|
|
VG_PATH_DATATYPE_F,
|
|
1.0f, // scale
|
|
0.0f, // bias
|
|
5, // segmentCapacityHint
|
|
8, // coordCapacityHint
|
|
VG_PATH_CAPABILITY_ALL);
|
|
static VGubyte const segments[5] = {
|
|
VG_MOVE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_LINE_TO_ABS,
|
|
VG_CLOSE_PATH
|
|
};
|
|
vgAppendPathData(rectPath, 5, segments, coords);
|
|
vgDrawPath(rectPath, VG_FILL_PATH);
|
|
vgDestroyPath(rectPath);
|
|
#endif
|
|
}
|
|
|
|
void QVGCompositionHelper::fillBackground
|
|
(const QRegion& region, const QBrush& brush)
|
|
{
|
|
if (brush.style() == Qt::SolidPattern) {
|
|
// Use vgClear() to quickly fill the background.
|
|
QColor color = brush.color();
|
|
if (d->clearColor != color || d->clearOpacity != 1.0f) {
|
|
VGfloat values[4];
|
|
values[0] = color.redF();
|
|
values[1] = color.greenF();
|
|
values[2] = color.blueF();
|
|
values[3] = color.alphaF();
|
|
vgSetfv(VG_CLEAR_COLOR, 4, values);
|
|
d->clearColor = color;
|
|
d->clearOpacity = 1.0f;
|
|
}
|
|
if (region.rectCount() == 1) {
|
|
QRect r = region.boundingRect();
|
|
vgClear(r.x(), screenSize.height() - r.y() - r.height(),
|
|
r.width(), r.height());
|
|
} else {
|
|
const QVector<QRect> rects = region.rects();
|
|
for (int i = 0; i < rects.size(); ++i) {
|
|
QRect r = rects.at(i);
|
|
vgClear(r.x(), screenSize.height() - r.y() - r.height(),
|
|
r.width(), r.height());
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Set the path transform to the default viewport transformation.
|
|
VGfloat devh = screenSize.height();
|
|
QTransform viewport(1.0f, 0.0f, 0.0f,
|
|
0.0f, -1.0f, 0.0f,
|
|
0.0f, devh, 1.0f);
|
|
d->setTransform(VG_MATRIX_PATH_USER_TO_SURFACE, viewport);
|
|
|
|
// Set the brush to use to fill the background.
|
|
d->ensureBrush(brush);
|
|
d->setFillRule(VG_EVEN_ODD);
|
|
|
|
if (region.rectCount() == 1) {
|
|
fillBackgroundRect(region.boundingRect(), d);
|
|
} else {
|
|
const QVector<QRect> rects = region.rects();
|
|
for (int i = 0; i < rects.size(); ++i)
|
|
fillBackgroundRect(rects.at(i), d);
|
|
}
|
|
|
|
// We will need to reset the path transform during the next paint.
|
|
d->pathTransformSet = false;
|
|
}
|
|
}
|
|
|
|
void QVGCompositionHelper::drawCursorPixmap
|
|
(const QPixmap& pixmap, const QPoint& offset)
|
|
{
|
|
VGImage vgImage = VG_INVALID_HANDLE;
|
|
|
|
// Fetch the VGImage from the pixmap if possible.
|
|
QPixmapData *pd = pixmap.pixmapData();
|
|
if (!pd)
|
|
return; // null QPixmap
|
|
if (pd->classId() == QPixmapData::OpenVGClass) {
|
|
QVGPixmapData *vgpd = static_cast<QVGPixmapData *>(pd);
|
|
if (vgpd->isValid())
|
|
vgImage = vgpd->toVGImage();
|
|
}
|
|
|
|
// Set the image transformation and modes.
|
|
VGfloat devh = screenSize.height();
|
|
QTransform transform(1.0f, 0.0f, 0.0f,
|
|
0.0f, -1.0f, 0.0f,
|
|
0.0f, devh, 1.0f);
|
|
transform.translate(offset.x(), offset.y());
|
|
d->setTransform(VG_MATRIX_IMAGE_USER_TO_SURFACE, transform);
|
|
d->setImageMode(VG_DRAW_IMAGE_NORMAL);
|
|
|
|
// Draw the VGImage.
|
|
if (vgImage != VG_INVALID_HANDLE) {
|
|
vgDrawImage(vgImage);
|
|
} else {
|
|
QImage img = pixmap.toImage().convertToFormat
|
|
(QImage::Format_ARGB32_Premultiplied);
|
|
|
|
vgImage = vgCreateImage
|
|
(VG_sARGB_8888_PRE, img.width(), img.height(),
|
|
VG_IMAGE_QUALITY_FASTER);
|
|
if (vgImage == VG_INVALID_HANDLE)
|
|
return;
|
|
vgImageSubData
|
|
(vgImage, img.constBits() + img.bytesPerLine() * (img.height() - 1),
|
|
-(img.bytesPerLine()), VG_sARGB_8888_PRE, 0, 0,
|
|
img.width(), img.height());
|
|
|
|
vgDrawImage(vgImage);
|
|
vgDestroyImage(vgImage);
|
|
}
|
|
}
|
|
|
|
void QVGCompositionHelper::setScissor(const QRegion& region)
|
|
{
|
|
QVector<QRect> rects = region.rects();
|
|
int count = rects.count();
|
|
if (count > d->maxScissorRects)
|
|
count = d->maxScissorRects;
|
|
QVarLengthArray<VGint> params(count * 4);
|
|
int height = screenSize.height();
|
|
for (int i = 0; i < count; ++i) {
|
|
params[i * 4 + 0] = rects[i].x();
|
|
params[i * 4 + 1] = height - rects[i].y() - rects[i].height();
|
|
params[i * 4 + 2] = rects[i].width();
|
|
params[i * 4 + 3] = rects[i].height();
|
|
}
|
|
|
|
vgSetiv(VG_SCISSOR_RECTS, count * 4, params.data());
|
|
vgSeti(VG_SCISSORING, VG_TRUE);
|
|
d->scissorDirty = false;
|
|
d->scissorActive = true;
|
|
d->scissorRegion = region;
|
|
}
|
|
|
|
void QVGCompositionHelper::clearScissor()
|
|
{
|
|
if (d->scissorActive || d->scissorDirty) {
|
|
vgSeti(VG_SCISSORING, VG_FALSE);
|
|
d->scissorActive = false;
|
|
d->scissorDirty = false;
|
|
}
|
|
}
|
|
|
|
#endif // !QVG_NO_SINGLE_CONTEXT && !QT_NO_EGL
|
|
|
|
VGImageFormat qt_vg_image_to_vg_format(QImage::Format format)
|
|
{
|
|
switch (format) {
|
|
case QImage::Format_MonoLSB:
|
|
return VG_BW_1;
|
|
case QImage::Format_Indexed8:
|
|
return VG_sL_8;
|
|
case QImage::Format_ARGB32_Premultiplied:
|
|
return VG_sARGB_8888_PRE;
|
|
case QImage::Format_RGB32:
|
|
return VG_sXRGB_8888;
|
|
case QImage::Format_ARGB32:
|
|
return VG_sARGB_8888;
|
|
case QImage::Format_RGB16:
|
|
return VG_sRGB_565;
|
|
case QImage::Format_ARGB4444_Premultiplied:
|
|
return VG_sARGB_4444;
|
|
default:
|
|
break;
|
|
}
|
|
return VG_sARGB_8888; // XXX
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "qpaintengine_vg.moc"
|