Created
January 10, 2026 12:40
-
-
Save namikiri/84b092891c2717ca70402054968c3b15 to your computer and use it in GitHub Desktop.
Pixel sorting effect for Shotcut
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import QtQuick | |
| import org.shotcut.qml | |
| Metadata { | |
| type: Metadata.Filter | |
| name: qsTr("Pixels0rt") | |
| keywords: qsTr('glitch pixelsort', 'search keywords for the video filter') + ' pixels0rt' | |
| mlt_service: "frei0r.pixels0rt" | |
| qml: "ui.qml" | |
| icon: 'icon.webp' | |
| keyframes { | |
| allowAnimateIn: true | |
| allowAnimateOut: true | |
| simpleProperties: ['0', '1', '2', '3'] | |
| parameters: [ | |
| Parameter { | |
| name: qsTr('Threshold') | |
| property: '0' | |
| isCurve: true | |
| minimum: 0 | |
| maximum: 1 | |
| }, | |
| Parameter { | |
| name: qsTr('Direction') | |
| property: '1' | |
| isCurve: false | |
| minimum: 0 | |
| maximum: 1 | |
| }, | |
| Parameter { | |
| name: qsTr('Reversed') | |
| property: '2' | |
| isCurve: false | |
| minimum: 0 | |
| maximum: 1 | |
| }, | |
| Parameter { | |
| name: qsTr('Transparency threshold') | |
| property: '3' | |
| isCurve: true | |
| minimum: 0 | |
| maximum: 1 | |
| } | |
| ] | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import QtQuick | |
| import QtQuick.Controls | |
| import QtQuick.Layouts | |
| import Shotcut.Controls as Shotcut | |
| Shotcut.KeyframableFilter { | |
| property string threshold: '0' | |
| property string direction: '1' | |
| property string reversed: '2' | |
| property string alphaThr: '3' | |
| property double thresholdDefault: 0.4 | |
| property double directionDefault: 0 | |
| property double reversedDefault: 0 | |
| property double alphaThrDefault: 1.0 | |
| function setControls() { | |
| var position = getPosition(); | |
| blockUpdate = true; | |
| thresholdSlider.value = filter.getDouble(threshold, position) * thresholdSlider.maximumValue; | |
| thresholdKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(threshold) > 0; | |
| alphaThrSlider.value = filter.getDouble(alphaThr, position) * alphaThrSlider.maximumValue; | |
| alphaThrKeyframesButton.checked = filter.animateIn <= 0 && filter.animateOut <= 0 && filter.keyframeCount(alphaThr) > 0; | |
| blockUpdate = false; | |
| enableControls(isSimpleKeyframesActive()); | |
| } | |
| function enableControls(enabled) { | |
| thresholdSlider.enabled = enabled; | |
| directionCombo.enabled = enabled; | |
| reversedCheckbox.enabled = enabled; | |
| alphaThrSlider.enabled = enabled; | |
| } | |
| function updateSimpleKeyframes() { | |
| setControls(); | |
| updateFilter(threshold, thresholdSlider.value / thresholdSlider.maximumValue, thresholdKeyframesButton, null); | |
| updateFilter(alphaThr, alphaThrSlider.value / alphaThrSlider.maximumValue, alphaThrKeyframesButton, null); | |
| } | |
| keyframableParameters: [threshold, alphaThr] | |
| startValues: [0.5] | |
| middleValues: [thresholdDefault, alphaThrDefault] | |
| endValues: [0.5] | |
| width: 350 | |
| height: 100 | |
| Component.onCompleted: { | |
| if (filter.isNew) { | |
| filter.set(threshold, thresholdDefault); | |
| filter.set(direction, directionDefault / 4); | |
| filter.set(reversed, reversedDefault); | |
| filter.set(alphaThr, alphaThrDefault); | |
| filter.savePreset(preset.parameters); | |
| } | |
| setControls(); | |
| } | |
| GridLayout { | |
| anchors.fill: parent | |
| anchors.margins: 8 | |
| columns: 4 | |
| Label { | |
| text: qsTr('Preset') | |
| Layout.alignment: Qt.AlignRight | |
| } | |
| Shotcut.Preset { | |
| id: preset | |
| parameters: [threshold, direction] | |
| Layout.columnSpan: 3 | |
| onBeforePresetLoaded: resetSimpleKeyframes() | |
| onPresetSelected: { | |
| setControls(); | |
| initializeSimpleKeyframes(); | |
| } | |
| } | |
| Label { | |
| text: qsTr('Threshold') | |
| Layout.alignment: Qt.AlignRight | |
| } | |
| Shotcut.SliderSpinner { | |
| id: thresholdSlider | |
| minimumValue: 0 | |
| maximumValue: 100 | |
| stepSize: 0.1 | |
| decimals: 1 | |
| suffix: ' %' | |
| onValueChanged: updateFilter(threshold, value / maximumValue, thresholdKeyframesButton, getPosition()) | |
| } | |
| Shotcut.UndoButton { | |
| onClicked: thresholdSlider.value = thresholdDefault * thresholdSlider.maximumValue | |
| } | |
| Shotcut.KeyframesButton { | |
| id: thresholdKeyframesButton | |
| onToggled: { | |
| enableControls(true); | |
| toggleKeyframes(checked, threshold, thresholdSlider.value / thresholdSlider.maximumValue); | |
| } | |
| } | |
| Label { | |
| text: qsTr('Direction') | |
| Layout.alignment: Qt.AlignRight | |
| } | |
| Shotcut.ComboBox { | |
| id: directionCombo | |
| Layout.columnSpan: 2 | |
| Layout.fillWidth: true | |
| function setCurrentValue(value) { | |
| var modelIndex = indexOfValue(value); | |
| if (modelIndex >= 0) { | |
| currentIndex = modelIndex; | |
| } else { | |
| console.log("Invalid value for direction", value); | |
| currentIndex = 0; | |
| } | |
| } | |
| onCurrentIndexChanged: filter.set(direction, directionModel.get(currentIndex).value / 4) | |
| model: ListModel { | |
| id: directionModel | |
| ListElement { | |
| text: qsTr('Top to bottom') | |
| value: 0 | |
| } | |
| ListElement { | |
| text: qsTr('Bottom to top') | |
| value: 1 | |
| } | |
| ListElement { | |
| text: qsTr('Left to right') | |
| value: 2 | |
| } | |
| ListElement { | |
| text: qsTr('Right to left') | |
| value: 3 | |
| } | |
| } | |
| textRole: "text" | |
| valueRole: "value" | |
| } | |
| Shotcut.UndoButton { | |
| onClicked: directionCombo.currentIndex = directionDefault | |
| } | |
| Item { | |
| width: 1 | |
| } | |
| CheckBox { | |
| id: reversedCheckbox | |
| Layout.columnSpan: 2 | |
| text: qsTr('Reversed') | |
| onCheckedChanged: filter.set(reversed, checked ? 1.0 : 0.0) | |
| } | |
| Shotcut.UndoButton { | |
| onClicked: reversedCheckbox.checked = false | |
| } | |
| Label { | |
| text: qsTr('Transparency threshold') | |
| Layout.alignment: Qt.AlignRight | |
| } | |
| Shotcut.SliderSpinner { | |
| id: alphaThrSlider | |
| minimumValue: 0 | |
| maximumValue: 100 | |
| stepSize: 0.1 | |
| decimals: 1 | |
| suffix: ' %' | |
| onValueChanged: updateFilter(alphaThr, value / maximumValue, alphaThrKeyframesButton, getPosition()) | |
| } | |
| Shotcut.UndoButton { | |
| onClicked: alphaThrSlider.value = alphaThrDefault * alphaThrSlider.maximumValue | |
| } | |
| Shotcut.KeyframesButton { | |
| id: alphaThrKeyframesButton | |
| onToggled: { | |
| enableControls(true); | |
| toggleKeyframes(checked, alphaThr, alphaThrSlider.value / alphaThrSlider.maximumValue); | |
| } | |
| } | |
| Item { | |
| Layout.fillHeight: true | |
| } | |
| } | |
| Connections { | |
| function onChanged() { | |
| setControls(); | |
| } | |
| function onInChanged() { | |
| updateSimpleKeyframes(); | |
| } | |
| function onOutChanged() { | |
| updateSimpleKeyframes(); | |
| } | |
| function onAnimateInChanged() { | |
| updateSimpleKeyframes(); | |
| } | |
| function onAnimateOutChanged() { | |
| updateSimpleKeyframes(); | |
| } | |
| function onPropertyChanged(name) { | |
| setControls(); | |
| } | |
| target: filter | |
| } | |
| Connections { | |
| function onPositionChanged() { | |
| setControls(); | |
| } | |
| target: producer | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment