Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #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;
++ const auto firstStopRgbColor = firstStop.second.toRgb();
++ const auto secondStopRgbColor = secondStop.second.toRgb();
++ int redDelta = (secondStopRgbColor.red() - firstStopRgbColor.red()) * modifierValue;
++ int greenDelta = (secondStopRgbColor.green() - firstStopRgbColor.green()) * modifierValue;
++ int blueDelta = (secondStopRgbColor.blue() - firstStopRgbColor.blue()) * modifierValue;
++ int alphaDelta = (secondStopRgbColor.alpha() - firstStopRgbColor.alpha()) * modifierValue;
++
++ QGradientStop newStop;
++ newStop.first = newPos;
++ newStop.second = QColor(firstStopRgbColor.red() + redDelta,
++ firstStopRgbColor.green() + greenDelta,
++ firstStopRgbColor.blue() + blueDelta,
++ firstStopRgbColor.alpha() + alphaDelta);
++
++ return newStop;
++ }
++
++ void QSGOpenVGInternalRectangleNode::setGradientStops(const QGradientStops &stops)
++ {
++
++ //normalize stops
++ bool needsNormalization = false;
++ for (const QGradientStop &stop : std::as_const(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;
++ QList<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);
++
++ QList<VGfloat> stops;
++ for (const QGradientStop &stop : std::as_const(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
++ QList<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
++ };
++
++ QList<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
++ QList<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
++ QList<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
++ };
++
++ QList<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
++ QList<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
++ QList<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);
++ }
++ }