Qt Quick 3D - Principled Material Example

 // Copyright (C) 2023 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

 import QtQuick
 import QtQuick.Controls
 import QtQuick.Layouts
 import QtQuick3D
 import QtQuick.Dialogs

 import Example

 RowLayout {
     id: root
     property Texture targetTexture: null
     property bool stampMode: false
     required property url defaultTexture
     property url stampSource: ""
     property alias defaultClearColor: drawer.clearColor
     property bool envMapMode: false

     GroupBox {
         title: "Image Source Mode"
         ColumnLayout {
             RadioButton {
                 id: noTextureChoice
                 text: "None"
                 checked: true
                 onCheckedChanged: {
                     targetTexture = null
                 }
             }
             RadioButton {
                 id: selectTextureChoice
                 text: "Texture"
                 checked: false
                 onCheckedChanged: targetTexture = selectedTexture
             }
             RadioButton {
                 id: loadImageChoice
                 text: "Load Image"
                 onCheckedChanged: targetTexture = loadTextureTexture
             }
             RadioButton {
                 id: drawerChoice
                 text: "Draw Texture"
                 onCheckedChanged: targetTexture = drawerTexture
             }
         }
     }
     Item {
         visible: selectTextureChoice.checked
         implicitWidth: 256
         implicitHeight: 256
         Image {
             id: previewImage
             anchors.fill: parent
             sourceSize.width: width
             sourceSize.height: height
             fillMode: Image.PreserveAspectFit
             source: root.defaultTexture
             Texture {
                 id: selectedTexture
                 source: root.defaultTexture
                 mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
             }
         }
     }

     Rectangle {
         id: loadTextureFrame
         implicitWidth: 256
         implicitHeight: 256
         color: "transparent"
         border.color: "black"
         visible: loadImageChoice.checked
         property url textureSource: ""
         Text {
             anchors.centerIn: parent
             text: "[Load Image]"
         }
         Image {
             anchors.fill: parent
             sourceSize.width: width
             sourceSize.height: height
             fillMode: Image.PreserveAspectFit
             visible: loadTextureFrame.textureSource !== null
             source: loadTextureFrame.textureSource
         }

         MouseArea {
             anchors.fill: parent
             onClicked: {
                 textureSourceDialog.open()
             }
         }

         Texture {
             id: loadTextureTexture
             source: loadTextureFrame.textureSource
             mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
         }

         ImageHelper {
             id: imageHelper
         }

         FileDialog {
             id: textureSourceDialog
             title: "Open an Image File"
             nameFilters: [ imageHelper.getSupportedImageFormatsFilter()]
             onAccepted: {
                 if (textureSourceDialog.selectedFile !== null) {
                     loadTextureFrame.textureSource = textureSourceDialog.selectedFile
                 }
             }
         }

     }
     ColumnLayout {
         visible: drawerChoice.checked
         Rectangle {
             implicitWidth: 260
             implicitHeight: 260
             color: "transparent"
             border.color: "black"
             Canvas {
                 id: drawer
                 width: 256
                 height: 256
                 x: 2
                 y: 2

                 property color penColor: "blue"
                 property real penWidth: penWidthSlider.value
                 property bool needsClear: true
                 property color clearColor: "white"
                 property var commands: []
                 property var stampCommands: []
                 property bool stampMode: root.stampMode
                 //property point prevPoint : Qt.point(0, 0)

                 onPaint: {
                     let ctx = getContext('2d');
                     if (needsClear) {
                         ctx.fillStyle = Qt.rgba(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
                         ctx.fillRect(0, 0, width, height)
                         needsClear = false;
                     }
                     if (!stampMode) {
                         ctx.strokeStyle = Qt.rgba(penColor.r, penColor.g, penColor.b, penColor.a)
                         ctx.lineCap = "round"
                         ctx.lineWidth = penWidth;

                         for (let i = 0; i < commands.length; ++i) {
                             let command = commands[i];
                             ctx.beginPath()
                             ctx.moveTo(command.start.x, command.start.y);
                             ctx.lineTo(command.end.x, command.end.y);
                             ctx.stroke();
                         }
                         commands = [];
                     } else {
                         for (let i = 0; i < stampCommands.length; ++i) {
                             let stampCommand = stampCommands[i]
                             // get offset
                             let dX = stampCommand.x - stampcursor.width * 0.5
                             let dY = stampCommand.y - stampcursor.height * 0.5
                             ctx.drawImage(stampcursor, dX, dY)
                         }
                         stampCommands = [];
                     }
                 }
             }

             MouseArea {
                 id: mouseArea
                 anchors.fill: drawer
                 enabled: drawerChoice.checked
                 hoverEnabled: true
                 //acceptedButtons: Qt.LeftButton
                 property bool isDrawing: false
                 property var lastPosition: Qt.point(0, 0)
                 preventStealing: true
                 clip: true

                 Item {
                     id: cursor
                     Rectangle {
                         anchors.centerIn: parent
                         visible: !root.stampMode
                         width: drawer.penWidth
                         height: drawer.penWidth
                         radius: width * 0.5
                         color: drawer.penColor
                     }

                     Image {
                        id: stampcursor
                        anchors.centerIn: parent
                        visible: root.stampMode
                        source: root.stampSource
                     }
                 }

                 onEntered: cursor.visible = true
                 onExited: cursor.visible = false

                 onPressed: (mouse)=> {
                                if (mouse.button === Qt.LeftButton && !root.stampMode) {
                                    lastPosition = Qt.point(mouse.x, mouse.y)
                                    isDrawing = true
                                }
                            }
                 onPositionChanged: (mouse)=> {
                                        if (isDrawing) {
                                            let pos = Qt.point(mouse.x, mouse.y);
                                            let command = {"start": lastPosition, "end": pos}
                                            drawer.commands.push(command)
                                            lastPosition = pos;
                                            drawer.requestPaint();
                                        }
                                        cursor.x = mouse.x
                                        cursor.y = mouse.y
                                    }
                 onReleased: (mouse)=> {
                                 if (mouse.button === Qt.LeftButton && isDrawing) {
                                     let pos = Qt.point(mouse.x, mouse.y);
                                     let command = {"start": lastPosition, "end": pos}
                                     drawer.commands.push(command)
                                     isDrawing = false;
                                     drawer.requestPaint();
                                 } else if (root.stampMode) {
                                     drawer.stampCommands.push(Qt.point(mouse.x, mouse.y));
                                     drawer.requestPaint();
                                 }
                             }
             }

         }
         RowLayout {
             visible: !root.stampMode
             spacing: 0
             Rectangle {
                 id: whiteBrush
                 implicitWidth: 25
                 implicitHeight: 25
                 color: "white"
                 border.color: "black"
                 MouseArea {
                     anchors.fill: parent
                     onClicked: {
                         drawer.penColor = parent.color;
                     }
                 }
             }
             Rectangle {
                 id: blackBrush
                 implicitWidth: 25
                 implicitHeight: 25
                 color: "black"
                 border.color: "black"
                 MouseArea {
                     anchors.fill: parent
                     onClicked: {
                         drawer.penColor = parent.color;
                     }
                 }
             }
             Rectangle {
                 id: redBrush
                 implicitWidth: 25
                 implicitHeight: 25
                 color: "red"
                 border.color: "black"
                 MouseArea {
                     anchors.fill: parent
                     onClicked: {
                         drawer.penColor = parent.color;
                     }
                 }
             }
             Rectangle {
                 id: greenBrush
                 implicitWidth: 25
                 implicitHeight: 25
                 color: "green"
                 border.color: "black"
                 MouseArea {
                     anchors.fill: parent
                     onClicked: {
                         drawer.penColor = parent.color;
                     }
                 }
             }
             Rectangle {
                 id: blueBrush
                 implicitWidth: 25
                 implicitHeight: 25
                 color: "blue"
                 border.color: "black"
                 MouseArea {
                     anchors.fill: parent
                     onClicked: {
                         drawer.penColor = parent.color;
                     }
                 }
             }
             Label {
                 text: " "

             }

             Button {
                 text: "Clear"
                 onClicked: {

                     drawer.needsClear = true
                     drawer.requestPaint()
                 }
             }

         }
         RowLayout {
             visible: !root.stampMode
             Slider {
                 id: penWidthSlider
                 from: 1
                 to: 50
                 value: 5
             }
             Label {
                 Layout.fillWidth: true
                 text: "Pen Width"
             }
         }

     }

     Texture {
         id: drawerTexture
         sourceItem: drawerChoice.checked ? drawer : null
         mappingMode: root.envMapMode ? Texture.Environment : Texture.UV
     }

 }