/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtOpenVG module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** 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. ** ** $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 #include "qwindowsurface_vgegl_p.h" #endif #include #include #include #include #include #include #include #include #include #include #include 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 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 . 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 convolutionFilter; QScopedPointer colorizeFilter; QScopedPointer dropShadowFilter; QScopedPointer 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 { Q_GUI_EXPORT 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(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 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(points)); return vgpath; } // Sizes chosen so that drawRoundedRect() paths fit in these arrays. QVarLengthArray 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 coords; QVarLengthArray 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(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(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(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(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(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(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 dashPattern = pen.dashPattern(); QVector 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 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(d); QVGPainterState *origState = static_cast(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); } Q_GUI_EXPORT 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; case Qt::UniteClip: { region = s->clipRegion.unite(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; case Qt::UniteClip: { s->clipRegion = s->clipRegion.unite(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; case Qt::UniteClip: { s->clipRegion = s->clipRegion.unite(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::UniteClip: vgRenderToMask(vgpath, VG_FILL_PATH, VG_UNION_MASK); break; 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; case Qt::UniteClip: { // If we already have a full-window clip, then uniting a // region with it will do nothing. Otherwise union. if (!(d->maskIsSet)) d->modifyMask(this, VG_UNION_MASK, d->transform.mapRect(rect)); } 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; case Qt::UniteClip: { // If we already have a full-window clip, then uniting a // region with it will do nothing. Otherwise union. if (!(d->maskIsSet)) d->modifyMask(this, VG_UNION_MASK, d->transform.map(region)); } 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::UniteClip: vgRenderToMask(vgpath, VG_FILL_PATH, VG_UNION_MASK); break; 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 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 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 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 coords; QVarLengthArray 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 coords; QVarLengthArray 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_NONANTIALIASED); 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(); VGImageQuality oldImageQuality = d->imageQuality; VGRenderingQuality oldRenderingQuality = d->renderingQuality; d->setImageQuality(VG_IMAGE_QUALITY_NONANTIALIASED); d->setRenderingQuality(VG_RENDERING_QUALITY_NONANTIALIASED); 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); d->setImageQuality(oldImageQuality); d->setRenderingQuality(oldRenderingQuality); } // 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(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(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(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->vgImage != VG_INVALID_HANDLE) return; 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(pd); if (!vgpd->isValid()) return; if (d->simpleTransform) drawVGImage(d, pos, vgpd->toVGImage()); else drawVGImage(d, pos, vgpd->toVGImage(d->opacity)); if (vgpd->vgImage != VG_INVALID_HANDLE) return; 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.bytesPerLine() : 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 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(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 cachedImages; QVarLengthArray 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(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(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(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 positions; QVarLengthArray 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 adjustments_x(numGlyphs); QVarLengthArray 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(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 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 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 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(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 rects = region.rects(); int count = rects.count(); if (count > d->maxScissorRects) count = d->maxScissorRects; QVarLengthArray 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"