| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick 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 https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qsgopenvginternalrectanglenode.h" |
| #include "qsgopenvghelpers.h" |
| #include <cmath> |
| #include <VG/vgu.h> |
| |
| QSGOpenVGInternalRectangleNode::QSGOpenVGInternalRectangleNode() |
| { |
| // Set Dummy material and geometry to avoid asserts |
| setMaterial((QSGMaterial*)1); |
| setGeometry((QSGGeometry*)1); |
| createVGResources(); |
| } |
| |
| QSGOpenVGInternalRectangleNode::~QSGOpenVGInternalRectangleNode() |
| { |
| destroyVGResources(); |
| } |
| |
| |
| void QSGOpenVGInternalRectangleNode::setRect(const QRectF &rect) |
| { |
| m_rect = rect; |
| m_pathDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setColor(const QColor &color) |
| { |
| m_fillColor = color; |
| m_fillDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setPenColor(const QColor &color) |
| { |
| m_strokeColor = color; |
| m_strokeDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setPenWidth(qreal width) |
| { |
| m_penWidth = width; |
| m_strokeDirty = true; |
| m_pathDirty = true; |
| } |
| |
| //Move first stop by pos relative to seconds |
| static QGradientStop interpolateStop(const QGradientStop &firstStop, const QGradientStop &secondStop, double newPos) |
| { |
| double distance = secondStop.first - firstStop.first; |
| double distanceDelta = newPos - firstStop.first; |
| double modifierValue = distanceDelta / distance; |
| int redDelta = (secondStop.second.red() - firstStop.second.red()) * modifierValue; |
| int greenDelta = (secondStop.second.green() - firstStop.second.green()) * modifierValue; |
| int blueDelta = (secondStop.second.blue() - firstStop.second.blue()) * modifierValue; |
| int alphaDelta = (secondStop.second.alpha() - firstStop.second.alpha()) * modifierValue; |
| |
| QGradientStop newStop; |
| newStop.first = newPos; |
| newStop.second = QColor(firstStop.second.red() + redDelta, |
| firstStop.second.green() + greenDelta, |
| firstStop.second.blue() + blueDelta, |
| firstStop.second.alpha() + alphaDelta); |
| |
| return newStop; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setGradientStops(const QGradientStops &stops) |
| { |
| |
| //normalize stops |
| bool needsNormalization = false; |
| for (const QGradientStop &stop : qAsConst(stops)) { |
| if (stop.first < 0.0 || stop.first > 1.0) { |
| needsNormalization = true; |
| continue; |
| } |
| } |
| |
| if (needsNormalization) { |
| QGradientStops normalizedStops; |
| if (stops.count() == 1) { |
| //If there is only one stop, then the position does not matter |
| //It is just treated as a color |
| QGradientStop stop = stops.at(0); |
| stop.first = 0.0; |
| normalizedStops.append(stop); |
| } else { |
| //Clip stops to only the first below 0.0 and above 1.0 |
| int below = -1; |
| int above = -1; |
| QVector<int> between; |
| for (int i = 0; i < stops.count(); ++i) { |
| if (stops.at(i).first < 0.0) { |
| below = i; |
| } else if (stops.at(i).first > 1.0) { |
| above = i; |
| break; |
| } else { |
| between.append(i); |
| } |
| } |
| |
| //Interpoloate new color values for above and below |
| if (below != -1 ) { |
| //If there are more than one stops left, interpolate |
| if (below + 1 < stops.count()) { |
| normalizedStops.append(interpolateStop(stops.at(below), stops.at(below + 1), 0.0)); |
| } else { |
| QGradientStop singleStop; |
| singleStop.first = 0.0; |
| singleStop.second = stops.at(below).second; |
| normalizedStops.append(singleStop); |
| } |
| } |
| |
| for (int i = 0; i < between.count(); ++i) |
| normalizedStops.append(stops.at(between.at(i))); |
| |
| if (above != -1) { |
| //If there stops before above, interpolate |
| if (above >= 1) { |
| normalizedStops.append(interpolateStop(stops.at(above), stops.at(above - 1), 1.0)); |
| } else { |
| QGradientStop singleStop; |
| singleStop.first = 1.0; |
| singleStop.second = stops.at(above).second; |
| normalizedStops.append(singleStop); |
| } |
| } |
| } |
| |
| m_gradientStops = normalizedStops; |
| |
| } else { |
| m_gradientStops = stops; |
| } |
| |
| m_fillDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setGradientVertical(bool vertical) |
| { |
| m_vertical = vertical; |
| m_fillDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setRadius(qreal radius) |
| { |
| m_radius = radius; |
| m_pathDirty = true; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setAligned(bool aligned) |
| { |
| m_aligned = aligned; |
| } |
| |
| void QSGOpenVGInternalRectangleNode::update() |
| { |
| } |
| |
| void QSGOpenVGInternalRectangleNode::render() |
| { |
| // Set Transform |
| if (transform().isAffine()) { |
| // Use current transform matrix |
| vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE); |
| vgLoadMatrix(transform().constData()); |
| if (m_offscreenSurface) { |
| delete m_offscreenSurface; |
| m_offscreenSurface = nullptr; |
| } |
| } else { |
| vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE); |
| vgLoadIdentity(); |
| // Fallback to rendering to an image for rounded rects with perspective transforms |
| if (m_offscreenSurface == nullptr || m_offscreenSurface->size() != QSize(std::ceil(m_rect.width()), std::ceil(m_rect.height()))) { |
| delete m_offscreenSurface; |
| m_offscreenSurface = new QOpenVGOffscreenSurface(QSize(std::ceil(m_rect.width()), std::ceil(m_rect.height()))); |
| } |
| m_offscreenSurface->makeCurrent(); |
| } |
| |
| // If path is dirty |
| if (m_pathDirty) { |
| vgClearPath(m_rectanglePath, VG_PATH_CAPABILITY_APPEND_TO); |
| vgClearPath(m_borderPath, VG_PATH_CAPABILITY_APPEND_TO); |
| |
| if (m_penWidth == 0) { |
| generateRectanglePath(m_rect, m_radius, m_rectanglePath); |
| } else { |
| generateRectangleAndBorderPaths(m_rect, m_penWidth, m_radius, m_rectanglePath, m_borderPath); |
| } |
| |
| m_pathDirty = false; |
| } |
| |
| //If fill is drity |
| if (m_fillDirty) { |
| if (m_gradientStops.isEmpty()) { |
| vgSetParameteri(m_rectanglePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); |
| vgSetParameterfv(m_rectanglePaint, VG_PAINT_COLOR, 4, QSGOpenVGHelpers::qColorToVGColor(m_fillColor, opacity()).constData()); |
| } else { |
| // Linear Gradient |
| vgSetParameteri(m_rectanglePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_LINEAR_GRADIENT); |
| const VGfloat linearGradient[] = { |
| 0.0f, |
| 0.0f, |
| m_vertical ? 0.0f : static_cast<VGfloat>(m_rect.width()), |
| m_vertical ? static_cast<VGfloat>(m_rect.height()) : 0.0f |
| }; |
| vgSetParameterfv(m_rectanglePaint, VG_PAINT_LINEAR_GRADIENT, 4, linearGradient); |
| vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_SPREAD_MODE, VG_COLOR_RAMP_SPREAD_PAD); |
| vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_PREMULTIPLIED, false); |
| |
| QVector<VGfloat> stops; |
| for (const QGradientStop &stop : qAsConst(m_gradientStops)) { |
| // offset |
| stops.append(stop.first); |
| // color |
| stops.append(QSGOpenVGHelpers::qColorToVGColor(stop.second, opacity())); |
| } |
| |
| vgSetParameterfv(m_rectanglePaint, VG_PAINT_COLOR_RAMP_STOPS, stops.length(), stops.constData()); |
| } |
| |
| m_fillDirty = false; |
| } |
| |
| //If stroke is dirty |
| if (m_strokeDirty) { |
| vgSetParameteri(m_borderPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR); |
| vgSetParameterfv(m_borderPaint, VG_PAINT_COLOR, 4, QSGOpenVGHelpers::qColorToVGColor(m_strokeColor, opacity()).constData()); |
| |
| m_strokeDirty = false; |
| } |
| |
| //Draw |
| if (m_penWidth > 0) { |
| vgSetPaint(m_borderPaint, VG_FILL_PATH); |
| vgDrawPath(m_borderPath, VG_FILL_PATH); |
| vgSetPaint(m_rectanglePaint, VG_FILL_PATH); |
| vgDrawPath(m_rectanglePath, VG_FILL_PATH); |
| } else { |
| vgSetPaint(m_rectanglePaint, VG_FILL_PATH); |
| vgDrawPath(m_rectanglePath, VG_FILL_PATH); |
| } |
| |
| if (!transform().isAffine()) { |
| m_offscreenSurface->doneCurrent(); |
| // Render offscreen surface |
| vgSeti(VG_MATRIX_MODE, VG_MATRIX_IMAGE_USER_TO_SURFACE); |
| vgLoadMatrix(transform().constData()); |
| vgDrawImage(m_offscreenSurface->image()); |
| } |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setOpacity(float opacity) |
| { |
| if (opacity != QSGOpenVGRenderable::opacity()) { |
| QSGOpenVGRenderable::setOpacity(opacity); |
| m_strokeDirty = true; |
| m_fillDirty = true; |
| } |
| } |
| |
| void QSGOpenVGInternalRectangleNode::setTransform(const QOpenVGMatrix &transform) |
| { |
| // if there transform matrix is not affine, regenerate the path |
| if (transform.isAffine()) |
| m_pathDirty = true; |
| |
| QSGOpenVGRenderable::setTransform(transform); |
| } |
| |
| void QSGOpenVGInternalRectangleNode::createVGResources() |
| { |
| m_rectanglePaint = vgCreatePaint(); |
| m_borderPaint = vgCreatePaint(); |
| m_rectanglePath = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1, 0, 0, 0, |
| VG_PATH_CAPABILITY_APPEND_TO); |
| m_borderPath = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1, 0, 0, 0, |
| VG_PATH_CAPABILITY_APPEND_TO); |
| } |
| |
| void QSGOpenVGInternalRectangleNode::destroyVGResources() |
| { |
| if (m_offscreenSurface) |
| delete m_offscreenSurface; |
| |
| vgDestroyPaint(m_rectanglePaint); |
| vgDestroyPaint(m_borderPaint); |
| vgDestroyPath(m_rectanglePath); |
| vgDestroyPath(m_borderPath); |
| } |
| |
| void QSGOpenVGInternalRectangleNode::generateRectanglePath(const QRectF &rect, float radius, VGPath path) const |
| { |
| if (radius == 0) { |
| // Generate a rectangle |
| if (transform().isAffine()) { |
| // Create command list |
| static const VGubyte rectCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| |
| // Create command data |
| QVector<VGfloat> coordinates(5); |
| coordinates[0] = rect.x(); |
| coordinates[1] = rect.y(); |
| coordinates[2] = rect.width(); |
| coordinates[3] = rect.height(); |
| coordinates[4] = -rect.width(); |
| vgAppendPathData(path, 5, rectCommands, coordinates.constData()); |
| } else { |
| // Pre-transform path |
| static const VGubyte rectCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_CLOSE_PATH |
| }; |
| |
| QVector<VGfloat> coordinates(8); |
| const QPointF topLeft = transform().map(rect.topLeft()); |
| const QPointF topRight = transform().map(rect.topRight()); |
| const QPointF bottomLeft = transform().map(rect.bottomLeft()); |
| const QPointF bottomRight = transform().map(rect.bottomRight()); |
| coordinates[0] = bottomLeft.x(); |
| coordinates[1] = bottomLeft.y(); |
| coordinates[2] = bottomRight.x(); |
| coordinates[3] = bottomRight.y(); |
| coordinates[4] = topRight.x(); |
| coordinates[5] = topRight.y(); |
| coordinates[6] = topLeft.x(); |
| coordinates[7] = topLeft.y(); |
| |
| vgAppendPathData(path, 5, rectCommands, coordinates.constData()); |
| } |
| } else { |
| // Generate a rounded rectangle |
| //Radius should never exceeds half of the width or half of the height |
| float adjustedRadius = qMin((float)qMin(rect.width(), rect.height()) * 0.5f, radius); |
| |
| // OpenVG expectes radius to be 2x what we expect |
| adjustedRadius *= 2; |
| |
| // Create command list |
| static const VGubyte roundedRectCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| |
| // Create command data |
| QVector<VGfloat> coordinates(26); |
| |
| coordinates[0] = rect.x() + adjustedRadius / 2; |
| coordinates[1] = rect.y(); |
| |
| coordinates[2] = rect.width() - adjustedRadius; |
| |
| coordinates[3] = adjustedRadius / 2; |
| coordinates[4] = adjustedRadius / 2; |
| coordinates[5] = 0; |
| coordinates[6] = adjustedRadius / 2; |
| coordinates[7] = adjustedRadius / 2; |
| |
| coordinates[8] = rect.height() - adjustedRadius; |
| |
| coordinates[9] = adjustedRadius / 2; |
| coordinates[10] = adjustedRadius / 2; |
| coordinates[11] = 0; |
| coordinates[12] = -adjustedRadius / 2; |
| coordinates[13] = adjustedRadius / 2; |
| |
| coordinates[14] = -(rect.width() - adjustedRadius); |
| |
| coordinates[15] = adjustedRadius / 2; |
| coordinates[16] = adjustedRadius / 2; |
| coordinates[17] = 0; |
| coordinates[18] = -adjustedRadius / 2; |
| coordinates[19] = -adjustedRadius / 2; |
| |
| coordinates[20] = -(rect.height() - adjustedRadius); |
| |
| coordinates[21] = adjustedRadius / 2; |
| coordinates[22] = adjustedRadius / 2; |
| coordinates[23] = 0; |
| coordinates[24] = adjustedRadius / 2; |
| coordinates[25] = -adjustedRadius / 2; |
| |
| vgAppendPathData(path, 10, roundedRectCommands, coordinates.constData()); |
| } |
| } |
| |
| void QSGOpenVGInternalRectangleNode::generateBorderPath(const QRectF &rect, float borderWidth, float borderHeight, float radius, VGPath path) const |
| { |
| if (radius == 0) { |
| // squared frame |
| if (transform().isAffine()) { |
| // Create command list |
| static const VGubyte squaredBorderCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_MOVE_TO_ABS, |
| VG_VLINE_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| |
| // Create command data |
| QVector<VGfloat> coordinates(10); |
| // Outside Square |
| coordinates[0] = rect.x(); |
| coordinates[1] = rect.y(); |
| coordinates[2] = rect.width(); |
| coordinates[3] = rect.height(); |
| coordinates[4] = -rect.width(); |
| // Inside Square (opposite direction) |
| coordinates[5] = rect.x() + borderWidth; |
| coordinates[6] = rect.y() + borderHeight; |
| coordinates[7] = rect.height() - (borderHeight * 2); |
| coordinates[8] = rect.width() - (borderWidth * 2); |
| coordinates[9] = -(rect.height() - (borderHeight * 2)); |
| |
| vgAppendPathData(path, 9, squaredBorderCommands, coordinates.constData()); |
| } else { |
| // persepective transform |
| static const VGubyte squaredBorderCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_MOVE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_LINE_TO_ABS, |
| VG_CLOSE_PATH |
| }; |
| |
| QVector<VGfloat> coordinates(16); |
| QRectF insideRect = rect.marginsRemoved(QMarginsF(borderWidth, borderHeight, borderWidth, borderHeight)); |
| QPointF outsideBottomLeft = transform().map(rect.bottomLeft()); |
| QPointF outsideBottomRight = transform().map(rect.bottomRight()); |
| QPointF outsideTopRight = transform().map(rect.topRight()); |
| QPointF outsideTopLeft = transform().map(rect.topLeft()); |
| QPointF insideBottomLeft = transform().map(insideRect.bottomLeft()); |
| QPointF insideTopLeft = transform().map(insideRect.topLeft()); |
| QPointF insideTopRight = transform().map(insideRect.topRight()); |
| QPointF insideBottomRight = transform().map(insideRect.bottomRight()); |
| |
| // Outside |
| coordinates[0] = outsideBottomLeft.x(); |
| coordinates[1] = outsideBottomLeft.y(); |
| coordinates[2] = outsideBottomRight.x(); |
| coordinates[3] = outsideBottomRight.y(); |
| coordinates[4] = outsideTopRight.x(); |
| coordinates[5] = outsideTopRight.y(); |
| coordinates[6] = outsideTopLeft.x(); |
| coordinates[7] = outsideTopLeft.y(); |
| // Inside |
| coordinates[8] = insideBottomLeft.x(); |
| coordinates[9] = insideBottomLeft.y(); |
| coordinates[10] = insideTopLeft.x(); |
| coordinates[11] = insideTopLeft.y(); |
| coordinates[12] = insideTopRight.x(); |
| coordinates[13] = insideTopRight.y(); |
| coordinates[14] = insideBottomRight.x(); |
| coordinates[15] = insideBottomRight.y(); |
| |
| vgAppendPathData(path, 9, squaredBorderCommands, coordinates.constData()); |
| } |
| } else if (radius < qMax(borderWidth, borderHeight)){ |
| // rounded outside, squared inside |
| // Create command list |
| static const VGubyte roundedRectCommands[] = { |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_MOVE_TO_ABS, |
| VG_VLINE_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| |
| // Ajust for OpenVG's usage or radius |
| float adjustedRadius = radius * 2; |
| |
| // Create command data |
| QVector<VGfloat> coordinates(31); |
| // Outside Rounded Rect |
| coordinates[0] = rect.x() + adjustedRadius / 2; |
| coordinates[1] = rect.y(); |
| |
| coordinates[2] = rect.width() - adjustedRadius; |
| |
| coordinates[3] = adjustedRadius / 2; |
| coordinates[4] = adjustedRadius / 2; |
| coordinates[5] = 0; |
| coordinates[6] = adjustedRadius / 2; |
| coordinates[7] = adjustedRadius / 2; |
| |
| coordinates[8] = rect.height() - adjustedRadius; |
| |
| coordinates[9] = adjustedRadius / 2; |
| coordinates[10] = adjustedRadius / 2; |
| coordinates[11] = 0; |
| coordinates[12] = -adjustedRadius / 2; |
| coordinates[13] = adjustedRadius / 2; |
| |
| coordinates[14] = -(rect.width() - adjustedRadius); |
| |
| coordinates[15] = adjustedRadius / 2; |
| coordinates[16] = adjustedRadius / 2; |
| coordinates[17] = 0; |
| coordinates[18] = -adjustedRadius / 2; |
| coordinates[19] = -adjustedRadius / 2; |
| |
| coordinates[20] = -(rect.height() - adjustedRadius); |
| |
| coordinates[21] = adjustedRadius / 2; |
| coordinates[22] = adjustedRadius / 2; |
| coordinates[23] = 0; |
| coordinates[24] = adjustedRadius / 2; |
| coordinates[25] = -adjustedRadius / 2; |
| |
| // Inside Square (opposite direction) |
| coordinates[26] = rect.x() + borderWidth; |
| coordinates[27] = rect.y() + borderHeight; |
| coordinates[28] = rect.height() - (borderHeight * 2); |
| coordinates[29] = rect.width() - (borderWidth * 2); |
| coordinates[30] = -(rect.height() - (borderHeight * 2)); |
| |
| vgAppendPathData(path, 14, roundedRectCommands, coordinates.constData()); |
| } else { |
| // rounded outside, rounded inside |
| |
| static const VGubyte roundedBorderCommands[] = { |
| // Outer |
| VG_MOVE_TO_ABS, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCCWARC_TO_REL, |
| // Inner |
| VG_MOVE_TO_ABS, |
| VG_SCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCWARC_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_SCWARC_TO_REL, |
| VG_VLINE_TO_REL, |
| VG_SCWARC_TO_REL, |
| VG_HLINE_TO_REL, |
| VG_CLOSE_PATH |
| }; |
| |
| // Adjust for OpenVG's usage or radius |
| float adjustedRadius = radius * 2; |
| float adjustedInnerRadius = (radius - qMax(borderWidth, borderHeight)) * 2; |
| |
| // Create command data |
| QVector<VGfloat> coordinates(52); |
| |
| // Outer |
| coordinates[0] = rect.x() + adjustedRadius / 2; |
| coordinates[1] = rect.y(); |
| |
| coordinates[2] = rect.width() - adjustedRadius; |
| |
| coordinates[3] = adjustedRadius / 2; |
| coordinates[4] = adjustedRadius / 2; |
| coordinates[5] = 0; |
| coordinates[6] = adjustedRadius / 2; |
| coordinates[7] = adjustedRadius / 2; |
| |
| coordinates[8] = rect.height() - adjustedRadius; |
| |
| coordinates[9] = adjustedRadius / 2; |
| coordinates[10] = adjustedRadius / 2; |
| coordinates[11] = 0; |
| coordinates[12] = -adjustedRadius / 2; |
| coordinates[13] = adjustedRadius / 2; |
| |
| coordinates[14] = -(rect.width() - adjustedRadius); |
| |
| coordinates[15] = adjustedRadius / 2; |
| coordinates[16] = adjustedRadius / 2; |
| coordinates[17] = 0; |
| coordinates[18] = -adjustedRadius / 2; |
| coordinates[19] = -adjustedRadius / 2; |
| |
| coordinates[20] = -(rect.height() - adjustedRadius); |
| |
| coordinates[21] = adjustedRadius / 2; |
| coordinates[22] = adjustedRadius / 2; |
| coordinates[23] = 0; |
| coordinates[24] = adjustedRadius / 2; |
| coordinates[25] = -adjustedRadius / 2; |
| |
| // Inner |
| coordinates[26] = rect.width() - (adjustedInnerRadius / 2 + borderWidth); |
| coordinates[27] = rect.height() - borderHeight; |
| |
| coordinates[28] = adjustedInnerRadius / 2; |
| coordinates[29] = adjustedInnerRadius / 2; |
| coordinates[30] = 0; |
| coordinates[31] = adjustedInnerRadius / 2; |
| coordinates[32] = -adjustedInnerRadius / 2; |
| |
| coordinates[33] = -((rect.height() - borderHeight * 2) - adjustedInnerRadius); |
| |
| coordinates[34] = adjustedInnerRadius / 2; |
| coordinates[35] = adjustedInnerRadius / 2; |
| coordinates[36] = 0; |
| coordinates[37] = -adjustedInnerRadius / 2; |
| coordinates[38] = -adjustedInnerRadius / 2; |
| |
| coordinates[39] = -((rect.width() - borderWidth * 2) - adjustedInnerRadius); |
| |
| coordinates[40] = adjustedInnerRadius / 2; |
| coordinates[41] = adjustedInnerRadius / 2; |
| coordinates[42] = 0; |
| coordinates[43] = -adjustedInnerRadius / 2; |
| coordinates[44] = adjustedInnerRadius / 2; |
| |
| coordinates[45] = (rect.height() - borderHeight * 2) - adjustedInnerRadius; |
| |
| coordinates[46] = adjustedInnerRadius / 2; |
| coordinates[47] = adjustedInnerRadius / 2; |
| coordinates[48] = 0; |
| coordinates[49] = adjustedInnerRadius / 2; |
| coordinates[50] = adjustedInnerRadius / 2; |
| |
| coordinates[51] = (rect.width() - borderWidth * 2) - adjustedInnerRadius; |
| |
| vgAppendPathData(path, 19, roundedBorderCommands, coordinates.constData()); |
| } |
| } |
| |
| void QSGOpenVGInternalRectangleNode::generateRectangleAndBorderPaths(const QRectF &rect, float penWidth, float radius, VGPath inside, VGPath outside) const |
| { |
| //Borders can not be more than half the height/width of a rect |
| float borderWidth = qMin(penWidth, (float)rect.width() * 0.5f); |
| float borderHeight = qMin(penWidth, (float)rect.height() * 0.5f); |
| |
| //Radius should never exceeds half of the width or half of the height |
| float adjustedRadius = qMin((float)qMin(rect.width(), rect.height()) * 0.5f, radius); |
| |
| QRectF innerRect = rect; |
| innerRect.adjust(borderWidth, borderHeight, -borderWidth, -borderHeight); |
| |
| if (radius == 0) { |
| // Regular rect with border |
| generateRectanglePath(innerRect, 0, inside); |
| generateBorderPath(rect, borderWidth, borderHeight, 0, outside); |
| } else { |
| // Rounded Rect with border |
| float innerRadius = radius - qMax(borderWidth, borderHeight); |
| if (innerRadius < 0) |
| innerRadius = 0.0f; |
| |
| generateRectanglePath(innerRect, innerRadius, inside); |
| generateBorderPath(rect, borderWidth, borderHeight, adjustedRadius, outside); |
| } |
| } |