Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Changing the graphic engine changes the way a path is drawn



  • Using Qt Quick 5.15.2, I need to draw a dashed rounded rectangle outline. As unfortunately it's not possible to reach a such drawing with the Rectangle.border properties, I created my own shape path to reach the rendering I needed.

    However the path I created is sometimes broken, and so inconsistent. First of all, I needed to introduce kind of compensatory values in my path, because sometimes the position was shifted by 1 pixel in relation to the expected one, for a reason I cannot figure out. And after reaching the expected drawing, it may become broken again if I change the graphic engine used by Qt, e.g by using or not OpenGLES.

    For that reason I wrote 2 different paths, which I switch depending on which graphic engine I use. However, although all is fine on my computer, several of my clients complain that the rectangle is drawn completely broken, which makes me suppose that Qt may change the graphic engine internally, depending on the target computer capabilities.

    This is a very annoying issue, and I would like to know what I'm doing wrong, as well as how a such path should be written to work correctly on ALL computers.

    Here is my qml code:

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    import QtQuick.Shapes 1.15
    import QtGraphicalEffects 1.15
    import QtQuick.Templates 2.15 as T
    
    /**
    * Dashed border
    *@author JMR
    */
    T.Control
    {
        // advanced properties
        property int    m_StrokeWidth: 1
        property int    m_Radius:      5
        property string m_FillColor:   "transparent"
        property string m_StrokeColor: "#bac1db"
        property bool   m_UseOpenGLES: true//false
    
        /**
        * Background rectangle
        */
        Rectangle
        {
            // common properties
            anchors.fill: parent
            color: m_FillColor
            radius: m_Radius
    
            /**
            * Dashed outline shape (OpenGLES version)
            */
            Shape
            {
                // common properties
                id: shDashedBorderOGLES
                anchors.fill: parent
                anchors.margins: 0
                //layer.enabled: true
                //layer.samples: 8
                smooth: true
                visible: m_UseOpenGLES
    
                /**
                * Dashed outline shape path
                */
                ShapePath
                {
                    // common properties
                    fillColor: "transparent"
                    strokeColor: m_StrokeColor
                    strokeWidth: m_StrokeWidth
                    strokeStyle: ShapePath.DashLine
                    dashPattern: [5, 5]
                    startX: m_Radius
                    startY: 0
    
                    // path commands
                    PathLine {x: shDashedBorderOGLES.width - m_Radius; y: 0;}
                    PathQuad {x: shDashedBorderOGLES.width;            y: m_Radius;                              controlX: shDashedBorderOGLES.width; controlY: 0;}
                    PathLine {x: shDashedBorderOGLES.width;            y: shDashedBorderOGLES.height - m_Radius;}
                    PathQuad {x: shDashedBorderOGLES.width - m_Radius; y: shDashedBorderOGLES.height - 1;        controlX: shDashedBorderOGLES.width; controlY: shDashedBorderOGLES.height;}
                    PathLine {x: m_Radius;                             y: shDashedBorderOGLES.height - 1;}
                    PathQuad {x: 0;                                    y: shDashedBorderOGLES.height - m_Radius; controlX: 0;                         controlY: shDashedBorderOGLES.height;}
                    PathLine {x: 1;                                    y: m_Radius;}
                    PathQuad {x: m_Radius;                             y: 0;                                     controlX: 0;                         controlY: 0;}
                }
            }
    
            /**
            * Dashed outline shape (non-OpenGLES version)
            */
            Shape
            {
                // common properties
                id: shDashedBorderNOGLES
                anchors.fill: parent
                anchors.margins: 0
                //layer.enabled: true
                //layer.samples: 8
                smooth: true
                visible: !m_UseOpenGLES
    
                /**
                * Dashed outline shape path
                */
                ShapePath
                {
                    // common properties
                    fillColor: "transparent"
                    strokeColor: m_StrokeColor
                    strokeWidth: m_StrokeWidth
                    strokeStyle: ShapePath.DashLine
                    dashPattern: [5, 5]
                    startX: m_Radius
                    startY: 1
    
                    // path commands
                    PathLine {x: shDashedBorderNOGLES.width - m_Radius; y: 1;}
                    PathQuad {x: shDashedBorderNOGLES.width;            y: m_Radius;                               controlX: shDashedBorderNOGLES.width; controlY: 0;}
                    PathLine {x: shDashedBorderNOGLES.width;            y: shDashedBorderNOGLES.height - m_Radius;}
                    PathQuad {x: shDashedBorderNOGLES.width - m_Radius; y: shDashedBorderNOGLES.height;            controlX: shDashedBorderNOGLES.width; controlY: shDashedBorderNOGLES.height;}
                    PathLine {x: m_Radius;                              y: shDashedBorderNOGLES.height;}
                    PathQuad {x: 0;                                     y: shDashedBorderNOGLES.height - m_Radius; controlX: 0;                          controlY: shDashedBorderNOGLES.height;}
                    PathLine {x: 1;                                     y: m_Radius;}
                    PathQuad {x: m_Radius;                              y: 0;                                      controlX: 0;                          controlY: 0;}
                }
            }
        }
    }
    

    And below is the graphic engine I may enable or disable:

    QCoreApplication::setAttribute(Qt::ApplicationAttribute::AA_UseOpenGLES, true);
    


  • So as nobody answered this question, I found a workaround which seems to resolve my issue. I changed the stroke width from 1 to 1.5. However this unfortunately doesn't explain what happened here, and why the ShapePath object behaves this way. I have my own idea about that: I think it's a kind of pen alignment, as in GDI+ (https://docs.microsoft.com/en-us/windows/win32/api/gdiplusenums/ne-gdiplusenums-penalignment), however I found absolutely no way to configure that.

    So below is my solution, which works correctly in all situations on my side:

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    import QtQuick.Shapes 1.15
    import QtGraphicalEffects 1.15
    import QtQuick.Templates 2.15 as T
    
    /**
    * Dashed border
    *@author JMR
    */
    T.Control
    {
        // advanced properties
        property real   m_StrokeWidth: 1.5
        property int    m_Radius:      5
        property string m_FillColor:   "transparent"
        property string m_StrokeColor: "#bac1db"
    
        /**
        * Background rectangle
        */
        Rectangle
        {
            // common properties
            anchors.fill: parent
            color: m_FillColor
            radius: m_Radius
    
            /**
            * Dashed outline shape
            */
            Shape
            {
                // common properties
                id: shDashedBorder
                anchors.fill: parent
                anchors.margins: 0
                //layer.enabled: true
                //layer.samples: 8
                smooth: true
                clip: true
    
                /**
                * Dashed outline shape path
                */
                ShapePath
                {
                    // common properties
                    fillColor: "transparent"
                    strokeColor: m_StrokeColor
                    strokeWidth: m_StrokeWidth
                    strokeStyle: ShapePath.DashLine
                    dashPattern: [5, 5]
                    startX: m_Radius
                    startY: 0
    
                    // path commands
                    PathLine {x: shDashedBorder.width - m_Radius; y: 0;}
                    PathQuad {x: shDashedBorder.width;            y: m_Radius;                         controlX: shDashedBorder.width; controlY: 0;}
                    PathLine {x: shDashedBorder.width;            y: shDashedBorder.height - m_Radius;}
                    PathQuad {x: shDashedBorder.width - m_Radius; y: shDashedBorder.height;            controlX: shDashedBorder.width; controlY: shDashedBorder.height;}
                    PathLine {x: m_Radius;                        y: shDashedBorder.height;}
                    PathQuad {x: 0;                               y: shDashedBorder.height - m_Radius; controlX: 0;                    controlY: shDashedBorder.height;}
                    PathLine {x: 0;                               y: m_Radius;}
                    PathQuad {x: m_Radius;                        y: 0;                                controlX: 0;                    controlY: 0;}
                }
            }
        }
    }
    


  • So as nobody answered this question, I found a workaround which seems to resolve my issue. I changed the stroke width from 1 to 1.5. However this unfortunately doesn't explain what happened here, and why the ShapePath object behaves this way. I have my own idea about that: I think it's a kind of pen alignment, as in GDI+ (https://docs.microsoft.com/en-us/windows/win32/api/gdiplusenums/ne-gdiplusenums-penalignment), however I found absolutely no way to configure that.

    So below is my solution, which works correctly in all situations on my side:

    import QtQuick 2.15
    import QtQuick.Window 2.15
    import QtQuick.Controls 2.15
    import QtQuick.Shapes 1.15
    import QtGraphicalEffects 1.15
    import QtQuick.Templates 2.15 as T
    
    /**
    * Dashed border
    *@author JMR
    */
    T.Control
    {
        // advanced properties
        property real   m_StrokeWidth: 1.5
        property int    m_Radius:      5
        property string m_FillColor:   "transparent"
        property string m_StrokeColor: "#bac1db"
    
        /**
        * Background rectangle
        */
        Rectangle
        {
            // common properties
            anchors.fill: parent
            color: m_FillColor
            radius: m_Radius
    
            /**
            * Dashed outline shape
            */
            Shape
            {
                // common properties
                id: shDashedBorder
                anchors.fill: parent
                anchors.margins: 0
                //layer.enabled: true
                //layer.samples: 8
                smooth: true
                clip: true
    
                /**
                * Dashed outline shape path
                */
                ShapePath
                {
                    // common properties
                    fillColor: "transparent"
                    strokeColor: m_StrokeColor
                    strokeWidth: m_StrokeWidth
                    strokeStyle: ShapePath.DashLine
                    dashPattern: [5, 5]
                    startX: m_Radius
                    startY: 0
    
                    // path commands
                    PathLine {x: shDashedBorder.width - m_Radius; y: 0;}
                    PathQuad {x: shDashedBorder.width;            y: m_Radius;                         controlX: shDashedBorder.width; controlY: 0;}
                    PathLine {x: shDashedBorder.width;            y: shDashedBorder.height - m_Radius;}
                    PathQuad {x: shDashedBorder.width - m_Radius; y: shDashedBorder.height;            controlX: shDashedBorder.width; controlY: shDashedBorder.height;}
                    PathLine {x: m_Radius;                        y: shDashedBorder.height;}
                    PathQuad {x: 0;                               y: shDashedBorder.height - m_Radius; controlX: 0;                    controlY: shDashedBorder.height;}
                    PathLine {x: 0;                               y: m_Radius;}
                    PathQuad {x: m_Radius;                        y: 0;                                controlX: 0;                    controlY: 0;}
                }
            }
        }
    }