ephemera
ephemera

Reputation: 29

Changing single contour svg image fill color in QML

I have made a single contour .svg cursor (basically a triangle curved at the bottom) without any fill, and I want to be able to change its fill color. I can't just make few different svgs for this, due to fill color being picked by user.

QtGraphicalEffects library is not supported since Qt6, so all I could find on this topic was of little use. The solution for this might be to just drop my svg over a colored rectangle, but then I'll have to mask out the area outside of my cursor to make it transparent(or vice-versa), and I'm not sure if it's even possible. I also found some shader-based solutions but none of then seem to work for my case (might just be me being new to QML) SVG:

<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
  <defs>
    <style>
      .cls-1 {
        fill: none;
        stroke: #414141;
        stroke-width: 44px;
        fill-rule: evenodd;
      }
    </style>
  </defs>
  <path id="Pointer_1" data-name="Pointer 1" class="cls-1" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>

Upvotes: 0

Views: 617

Answers (1)

Stephen Quan
Stephen Quan

Reputation: 25946

[MULTIPLE EDITS]

Firstly, the latest builds of Qt6 do support QtGraphicalEffects via Qt5Compat.GraphicalEffects. https://doc.qt.io/qt-6/qtgraphicaleffects5-

The approach I prefer to use is to leverage the fact that most components have an icon property which has a built-in mechanism for recoloring an SVG. You set the following properties:

  • icon.source
  • icon.width
  • icon.height
  • icon.color

By creating two SVGs we can use the above approach to control the fill and stroke independently.

The typical component I use is Button, but, I strip off the default Button UI/UX and just leverage from the icon property as follows:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    SplitView {
        anchors.fill: parent
        orientation: Qt.Horizontal
        Item {
            SplitView.preferredWidth: parent.width / 2
            SplitView.fillHeight: true
            Item {
                 anchors.centerIn: parent
                 width: parent.width * 8 / 10
                 height: parent.height * 8 / 10
                 Button {
                     anchors.centerIn: parent
                     visible: fillCombo.currentText !== 'none'
                     background: Item { }
                     icon.source: "shape-fill.svg"
                     icon.width: Math.min(parent.width, parent.height)
                     icon.height: icon.width
                     icon.color: fillCombo.currentText
                 }
                 Button {
                     anchors.centerIn: parent
                     visible: strokeCombo.currentText !== 'none'
                     background: Item { }
                     icon.source: "shape-outline.svg"
                     icon.width: Math.min(parent.width, parent.height)
                     icon.height: icon.width
                     icon.color: strokeCombo.currentText
                 }
            }
        }
        Item {
            SplitView.fillWidth: true
            SplitView.fillHeight: true
            ColumnLayout {
                anchors.centerIn: parent
                Label {
                    text: qsTr("Stroke")
                }
                ComboBox {
                    id: strokeCombo
                    model: [ "lightsteelblue", "red", "orange", "yellow", "green", "blue" ]
                }
                Label {
                    Layout.topMargin: 20
                    text: qsTr("Fill")
                }
                ComboBox {
                    id: fillCombo
                    model: [ "none", "red", "orange", "yellow", "green", "blue" ]
                }
            }
        }
    }
}

// shape-outline.svg
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
  <path stroke="red" stroke-width="44" fill="none" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>

// shape-fill.svg
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
  <path stroke="none" fill="black" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>
</svg>

You can Try it Online!

Alternatively, another approach is you can build your SVG as a data uri by adding a "data:image/svg+xml," prefix to your SVG string. Because you're using a data uri, you can make a function that generates the SVG based on use input:

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    SplitView {
        anchors.fill: parent
        orientation: Qt.Horizontal
        Item {
            SplitView.preferredWidth: parent.width / 2
            SplitView.fillHeight: true
            Image {
                 anchors.centerIn: parent
                 width: parent.width * 8 / 10
                 height: parent.height * 8 / 10
                 source: todatauri(getsvg(strokeCombo.currentText, fillCombo.currentText))
                 fillMode: Image.PreserveAspectFit
            }
        }
        Item {
            SplitView.fillWidth: true
            SplitView.fillHeight: true
            ColumnLayout {
                anchors.centerIn: parent
                Label {
                    text: qsTr("Stroke")
                }
                ComboBox {
                    id: strokeCombo
                    model: [ "black", "red", "orange", "yellow", "green", "blue" ]
                }
                Label {
                    Layout.topMargin: 20
                    text: qsTr("Fill")
                }
                ComboBox {
                    id: fillCombo
                    model: [ "none", "red", "orange", "yellow", "green", "blue" ]
                }
            }
        }
    }
    function todatauri(svg) {
        return "data:image/svg+xml," + svg;
    }
    function getsvg(stroke, fill) {
        return `<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="0 0 600 600">
  <defs>
    <style>
      .cls-1 {
        fill: none;
        stroke: #414141;
        stroke-width: 44px;
        fill-rule: evenodd;
      }
    </style>
  </defs>
  <path id="Pointer_1" data-name="Pointer 1" class="cls-1" stroke="${stroke}" stroke-width="44" fill="${fill}" d="M300.291,48.248L556.347,560.4s-127.234-47.825-255.133-47.825c-128.157,0-256.979,47.825-256.979,47.825Z"/>

</svg>
`;
    }
}

You can Try it Online!

Upvotes: 1

Related Questions