From a2f90b03f4e1b93126ffc40ef1d10c6177a68191 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sun, 30 Sep 2018 18:51:35 -0500 Subject: [PATCH] Add "Remove Spot" filter. --- src/qml/filters/delogo/meta.qml | 28 ++++ src/qml/filters/delogo/ui.qml | 150 ++++++++++++++++++ src/qml/filters/delogo/vui.qml | 81 ++++++++++ src/qml/modules/Shotcut/Controls/RectVui.qml | 70 ++++++++ .../Shotcut/Controls/RectangleControl.qml | 29 ++++ src/qml/modules/Shotcut/Controls/qmldir | 1 + 6 files changed, 359 insertions(+) create mode 100644 src/qml/filters/delogo/meta.qml create mode 100644 src/qml/filters/delogo/ui.qml create mode 100644 src/qml/filters/delogo/vui.qml create mode 100644 src/qml/modules/Shotcut/Controls/RectVui.qml diff --git a/src/qml/filters/delogo/meta.qml b/src/qml/filters/delogo/meta.qml new file mode 100644 index 0000000000..a0fb0d7afb --- /dev/null +++ b/src/qml/filters/delogo/meta.qml @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.0 +import org.shotcut.qml 1.0 + +Metadata { + type: Metadata.Filter + name: qsTr("Remove Spot") + mlt_service: 'avfilter.delogo' + qml: 'ui.qml' + vui: 'vui.qml' + allowMultiple: true +} diff --git a/src/qml/filters/delogo/ui.qml b/src/qml/filters/delogo/ui.qml new file mode 100644 index 0000000000..cba9eababb --- /dev/null +++ b/src/qml/filters/delogo/ui.qml @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import Shotcut.Controls 1.0 + +Item { + // Center the box in the image by default + property double wDefault: profile.width / 5 + property double hDefault: profile.height / 5 + property double xDefault: profile.width / 2 - wDefault / 2 + property double yDefault: profile.height / 2 - hDefault / 2 + + property var defaultParameters: ["av.x", "av.y", "av.w", "av.h"] + + width: 200 + height: 125 + Component.onCompleted: { + if (filter.isNew) { + filter.set("av.x", xDefault) + filter.set("av.y", yDefault) + filter.set("av.w", wDefault) + filter.set("av.h", hDefault) + filter.savePreset(defaultParameters) + } + setControls() + } + + function setControls() { + rectX.text = filter.get("av.x") + rectY.text = filter.get("av.y") + rectW.text = filter.get("av.w") + rectH.text = filter.get("av.h") + } + + function setFilter() { + filter.set("av.x", parseInt(rectX.text)) + filter.set("av.y", parseInt(rectY.text)) + filter.set("av.w", parseInt(rectW.text)) + filter.set("av.h", parseInt(rectH.text)) + } + + GridLayout { + columns: 5 + anchors.fill: parent + anchors.margins: 8 + + Label { + text: qsTr('Preset') + Layout.alignment: Qt.AlignRight + } + Preset { + id: preset + parameters: defaultParameters + Layout.columnSpan: 4 + onPresetSelected: setControls() + } + + Label { + text: qsTr('Position') + Layout.alignment: Qt.AlignRight + } + RowLayout { + Layout.columnSpan: 4 + TextField { + id: rectX + horizontalAlignment: Qt.AlignRight + validator: IntValidator { + bottom: 0 + } + onEditingFinished: { + text = Math.min(parseInt(text), profile.width - parseInt(rectW.text) - 1) + text = Math.max(parseInt(text), 1) + if (filter.get("av.x") !== parseInt(text)) setFilter() + } + } + Label { text: ',' } + TextField { + id: rectY + horizontalAlignment: Qt.AlignRight + validator: IntValidator { + bottom: 0 + } + onEditingFinished: { + text = Math.min(parseInt(text), profile.height - parseInt(rectH.text) - 1) + text = Math.max(parseInt(text), 1) + if (filter.get("av.y") !== parseInt(text)) setFilter() + } + } + } + + Label { + text: qsTr('Size') + Layout.alignment: Qt.AlignRight + } + RowLayout { + Layout.columnSpan: 4 + TextField { + id: rectW + horizontalAlignment: Qt.AlignRight + validator: IntValidator { + bottom: 0 + } + onEditingFinished: { + text = Math.min(parseInt(text), profile.width - parseInt(rectX.text) - 1) + text = Math.max(parseInt(text), 1) + if (filter.get("av.w") !== parseInt(text)) setFilter() + } + } + Label { text: 'x' } + TextField { + id: rectH + horizontalAlignment: Qt.AlignRight + validator: IntValidator { + bottom: 0 + } + onEditingFinished: { + text = Math.min(parseInt(text), profile.height - parseInt(rectY.text) - 1) + text = Math.max(parseInt(text), 1) + if (filter.get("av.h") !== parseInt(text)) setFilter() + } + } + } + + Item { Layout.fillHeight: true } + } + + Connections { + target: filter + onChanged: { + setControls() + } + } +} diff --git a/src/qml/filters/delogo/vui.qml b/src/qml/filters/delogo/vui.qml new file mode 100644 index 0000000000..cb35b70726 --- /dev/null +++ b/src/qml/filters/delogo/vui.qml @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.1 +import Shotcut.Controls 1.0 + +RectVui { + id: rectVui + boundary: Qt.rect(1, 1, profile.width - 2, profile.height - 2) + + property bool _blockUpdates: false; + + onFilterRectChanged: { + if (_blockUpdates) return + + rectVui.filterRect.x = Math.min(rectVui.filterRect.x, profile.width - rectVui.filterRect.width - 1) + rectVui.filterRect.x = Math.max(rectVui.filterRect.x, 1) + rectVui.filterRect.y = Math.min(rectVui.filterRect.y, profile.height - rectVui.filterRect.height - 1) + rectVui.filterRect.y = Math.max(rectVui.filterRect.y, 1) + rectVui.filterRect.width = Math.min(rectVui.filterRect.width, profile.width - rectVui.filterRect.x - 1) + rectVui.filterRect.width = Math.max(rectVui.filterRect.width, 1) + rectVui.filterRect.height = Math.min(rectVui.filterRect.height, profile.width - rectVui.filterRect.y - 1) + rectVui.filterRect.height = Math.max(rectVui.filterRect.height, 1) + + if (rectVui.filterRect.x != filter.get("av.x") || + rectVui.filterRect.y != filter.get("av.y") || + rectVui.filterRect.width != filter.get("av.w") || + rectVui.filterRect.height != filter.get("av.h") ) + { + _blockUpdates = true + filter.set("av.x", rectVui.filterRect.x) + filter.set("av.y", rectVui.filterRect.y) + filter.set("av.w", rectVui.filterRect.width) + filter.set("av.h", rectVui.filterRect.height) + _blockUpdates = false + } + } + + Component.onCompleted: { + if (_blockUpdates) return + if (rectVui.filterRect.x != filter.get("av.x") || + rectVui.filterRect.y != filter.get("av.y") || + rectVui.filterRect.width != filter.get("av.w") || + rectVui.filterRect.height != filter.get("av.h") ) + { + _blockUpdates = true + rectVui.filterRect = Qt.rect(filter.get("av.x"), filter.get("av.y"), filter.get("av.w"), filter.get("av.h")) + _blockUpdates = false + } + } + + Connections { + target: filter + onChanged: { + if (_blockUpdates) return + if (rectVui.filterRect.x != filter.get("av.x") || + rectVui.filterRect.y != filter.get("av.y") || + rectVui.filterRect.width != filter.get("av.w") || + rectVui.filterRect.height != filter.get("av.h") ) + { + _blockUpdates = true + rectVui.filterRect = Qt.rect(filter.get("av.x"), filter.get("av.y"), filter.get("av.w"), filter.get("av.h")) + _blockUpdates = false + } + } + } +} diff --git a/src/qml/modules/Shotcut/Controls/RectVui.qml b/src/qml/modules/Shotcut/Controls/RectVui.qml new file mode 100644 index 0000000000..a07c802fc8 --- /dev/null +++ b/src/qml/modules/Shotcut/Controls/RectVui.qml @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017-2018 Meltytech, LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.1 +import Shotcut.Controls 1.0 + +VuiBase { + property rect filterRect + property rect boundary: Qt.rect(-10000, -10000, 20000, 20000) + property real _zoom: (video.zoom > 0)? video.zoom : 1.0 + + onFilterRectChanged: { + rectangle.setHandles(filterRect) + } + + Flickable { + anchors.fill: parent + interactive: false + clip: true + contentWidth: video.rect.width * _zoom + contentHeight: video.rect.height * _zoom + contentX: video.offset.x + contentY: video.offset.y + + Item { + id: videoItem + x: video.rect.x + y: video.rect.y + width: video.rect.width + height: video.rect.height + scale: _zoom + + RectangleControl { + id: rectangle + widthScale: video.rect.width / profile.width + heightScale: video.rect.height / profile.height + handleSize: Math.max(Math.round(8 / _zoom), 4) + borderSize: Math.max(Math.round(1.33 / _zoom), 1) + onWidthScaleChanged: { + setHandles(filterRect) + setBoundary(boundary) + } + onHeightScaleChanged: { + setHandles(filterRect) + setBoundary(boundary) + } + onRectChanged: { + filterRect = Qt.rect(Math.round(rect.x / rectangle.widthScale), + Math.round(rect.y / rectangle.heightScale), + Math.round(rect.width / rectangle.widthScale), + Math.round(rect.height / rectangle.heightScale)) + } + } + } + } +} diff --git a/src/qml/modules/Shotcut/Controls/RectangleControl.qml b/src/qml/modules/Shotcut/Controls/RectangleControl.qml index e8f08587ba..4a69b465c2 100644 --- a/src/qml/modules/Shotcut/Controls/RectangleControl.qml +++ b/src/qml/modules/Shotcut/Controls/RectangleControl.qml @@ -30,6 +30,7 @@ Item { property alias rectangle: rectangle property color handleColor: Qt.rgba(1, 1, 1, enabled? 0.9 : 0.2) property int snapMargin: 10 + property rect _boundary: Qt.rect(-10000, -10000, 20000, 20000) signal rectChanged(Rectangle rect) @@ -52,6 +53,10 @@ Item { bottomLeftHandle.y = bottomRightHandle.y } + function setBoundary(boundary) { + _boundary = Qt.rect(boundary.x * widthScale, boundary.y * heightScale, boundary.width * widthScale, boundary.height * heightScale) + } + function snapX(x) { if (!video.snapToGrid || video.grid === 0) { return x @@ -116,6 +121,18 @@ Item { return y } + function boundaryX(x) { + x = Math.max(x, _boundary.x) + x = Math.min(x, _boundary.x + _boundary.width) + return x + } + + function boundaryY(y) { + y = Math.max(y, _boundary.y) + y = Math.min(y, _boundary.y + _boundary.height) + return y + } + Rectangle { id: rectangle color: 'transparent' @@ -167,6 +184,10 @@ Item { onPositionChanged: { rectangle.x = snapX(rectangle.x + rectangle.width / 2) - rectangle.width / 2 rectangle.y = snapY(rectangle.y + rectangle.height / 2) - rectangle.height / 2 + rectangle.x = Math.max(rectangle.x, _boundary.x) + rectangle.x = Math.min(rectangle.x, _boundary.x + _boundary.width - rectangle.width) + rectangle.y = Math.max(rectangle.y, _boundary.y) + rectangle.y = Math.min(rectangle.y, _boundary.y + _boundary.height - rectangle.height) rectChanged(rectangle) } onReleased: { @@ -205,6 +226,8 @@ Item { onPositionChanged: { topLeftHandle.x = snapX(topLeftHandle.x) topLeftHandle.y = snapY(topLeftHandle.y) + topLeftHandle.x = boundaryX(topLeftHandle.x) + topLeftHandle.y = boundaryY(topLeftHandle.y) if (aspectRatio !== 0.0) parent.x = topRightHandle.x + handleSize - rectangle.height * aspectRatio parent.x = Math.min(parent.x, bottomRightHandle.x) @@ -240,6 +263,8 @@ Item { onPositionChanged: { topRightHandle.x = snapX(topRightHandle.x + handleSize) - handleSize topRightHandle.y = snapY(topRightHandle.y) + topRightHandle.x = boundaryX(topRightHandle.x + handleSize) - handleSize + topRightHandle.y = boundaryY(topRightHandle.y) if (aspectRatio !== 0.0) parent.x = topLeftHandle.x + rectangle.height * aspectRatio - handleSize parent.x = Math.max(parent.x, bottomLeftHandle.x) @@ -275,6 +300,8 @@ Item { onPositionChanged: { bottomLeftHandle.x = snapX(bottomLeftHandle.x) bottomLeftHandle.y = snapY(bottomLeftHandle.y + handleSize) - handleSize + bottomLeftHandle.x = boundaryX(bottomLeftHandle.x) + bottomLeftHandle.y = boundaryY(bottomLeftHandle.y + handleSize) - handleSize if (aspectRatio !== 0.0) parent.x = topRightHandle.x + handleSize - rectangle.height * aspectRatio parent.x = Math.min(parent.x, topRightHandle.x) @@ -308,6 +335,8 @@ Item { onPositionChanged: { bottomRightHandle.x = snapX(bottomRightHandle.x + handleSize) - handleSize bottomRightHandle.y = snapY(bottomRightHandle.y + handleSize) - handleSize + bottomRightHandle.x = boundaryX(bottomRightHandle.x + handleSize) - handleSize + bottomRightHandle.y = boundaryY(bottomRightHandle.y + handleSize) - handleSize if (aspectRatio !== 0.0) parent.x = topLeftHandle.x + rectangle.height * aspectRatio - handleSize parent.x = Math.max(parent.x, topLeftHandle.x) diff --git a/src/qml/modules/Shotcut/Controls/qmldir b/src/qml/modules/Shotcut/Controls/qmldir index 33a05bac99..02829222b2 100644 --- a/src/qml/modules/Shotcut/Controls/qmldir +++ b/src/qml/modules/Shotcut/Controls/qmldir @@ -13,3 +13,4 @@ TextFilterUi 1.0 TextFilterUi.qml TextFilterVui 1.0 TextFilterVui.qml ToggleButton 1.0 ToggleButton.qml VuiBase 1.0 VuiBase.qml +RectVui 1.0 RectVui.qml