From f9194dd741bccb40f35f06702e9e53506156b073 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Mon, 1 Sep 2025 21:30:38 -0400 Subject: [PATCH] Wallpaper: added fillMode to all shaders (no, crop, fit, stretch) --- Commons/Settings.qml | 1 + Modules/Background/Background.qml | 57 +++++++- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 145 ++++++++++++-------- Services/WallpaperService.qml | 45 ++++++ Shaders/frag/wp_disc.frag | 75 +++++++++- Shaders/frag/wp_fade.frag | 77 ++++++++++- Shaders/frag/wp_stripes.frag | 69 +++++++++- Shaders/frag/wp_wipe.frag | 70 +++++++++- Shaders/qsb/wp_disc.frag.qsb | Bin 2532 -> 5385 bytes Shaders/qsb/wp_fade.frag.qsb | Bin 1470 -> 4333 bytes Shaders/qsb/wp_stripes.frag.qsb | Bin 5320 -> 8072 bytes Shaders/qsb/wp_wipe.frag.qsb | Bin 2677 -> 5457 bytes 12 files changed, 463 insertions(+), 76 deletions(-) diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 7eac008..605aac6 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -174,6 +174,7 @@ Singleton { property string directory: "/usr/share/wallpapers" property bool enableMultiMonitorDirectories: false property bool setWallpaperOnAllMonitors: true + property string fillMode: "crop" property bool randomEnabled: false property int randomIntervalSec: 300 // 5 min property int transitionDuration: 1500 // 1500 ms diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 594425f..5b5112b 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -39,12 +39,24 @@ Variants { // Used to debounce wallpaper changes property string futureWallpaper: "" + // Fillmode default is "crop" + property real fillMode: 1.0 + // On startup assign wallpaper immediately Component.onCompleted: { + fillMode = WallpaperService.getFillModeUniform() + var path = modelData ? WallpaperService.getWallpaper(modelData.name) : "" setWallpaperImmediate(path) } + Connections { + target: Settings.data.wallpaper + function onFillModeChanged() { + fillMode = WallpaperService.getFillModeUniform() + } + } + // External state management Connections { target: WallpaperService @@ -84,8 +96,6 @@ Variants { Image { id: currentWallpaper - anchors.fill: parent - fillMode: Image.PreserveAspectCrop source: "" smooth: true mipmap: false @@ -97,8 +107,6 @@ Variants { Image { id: nextWallpaper - anchors.fill: parent - fillMode: Image.PreserveAspectCrop source: "" smooth: true mipmap: false @@ -116,6 +124,17 @@ Variants { property variant source1: currentWallpaper property variant source2: nextWallpaper property real progress: root.transitionProgress + + // Fill mode properties + property real fillMode: root.fillMode + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb") } @@ -131,6 +150,16 @@ Variants { property real smoothness: root.edgeSmoothness property real direction: root.wipeDirection + // Fill mode properties + property real fillMode: root.fillMode + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb") } @@ -148,6 +177,16 @@ Variants { property real centerX: root.discCenterX property real centerY: root.discCenterY + // Fill mode properties + property real fillMode: root.fillMode + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb") } @@ -165,6 +204,16 @@ Variants { property real stripeCount: root.stripesCount property real angle: root.stripesAngle + // Fill mode properties + property real fillMode: root.fillMode + property real imageWidth1: source1.sourceSize.width + property real imageHeight1: source1.sourceSize.height + property real imageWidth2: source2.sourceSize.width + property real imageHeight2: source2.sourceSize.height + property real screenWidth: width + property real screenHeight: height + property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb") } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index fcf715b..b758cf9 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -84,6 +84,93 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + ColumnLayout { + visible: Settings.data.wallpaper.enabled + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "Look & Feel" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + // Fill Mode + NComboBox { + label: "Fill Mode" + description: "Select how the image should scale to match your monitor's resolution." + model: WallpaperService.fillModeModel + currentKey: Settings.data.wallpaper.fillMode + onSelected: key => Settings.data.wallpaper.fillMode = key + } + + // Transition Type + NComboBox { + label: "Transition Type" + description: "Animation type when switching between wallpapers." + model: WallpaperService.transitionsModel + currentKey: Settings.data.wallpaper.transitionType + onSelected: key => Settings.data.wallpaper.transitionType = key + } + + // Transition Duration + ColumnLayout { + NLabel { + label: "Transition Duration" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 100 + to: 5000 + stepSize: 100 + value: Settings.data.wallpaper.transitionDuration + onMoved: Settings.data.wallpaper.transitionDuration = value + cutoutColor: Color.mSurface + } + NText { + text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + + // Edge Smoothness + ColumnLayout { + NLabel { + label: "Transition Edge Smoothness" + description: "Duration of transition animations in seconds." + } + + RowLayout { + spacing: Style.marginL * scaling + NSlider { + Layout.fillWidth: true + from: 0.0 + to: 1.0 + value: Settings.data.wallpaper.transitionEdgeSmoothness + onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value + cutoutColor: Color.mSurface + } + NText { + text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + } + } + } + } + + NDivider { + visible: Settings.data.wallpaper.enabled + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + ColumnLayout { visible: Settings.data.wallpaper.enabled spacing: Style.marginL * scaling @@ -202,64 +289,6 @@ ColumnLayout { } } } - - // Transition Type - NComboBox { - label: "Transition Type" - description: "Animation type when switching between wallpapers." - model: WallpaperService.transitionsModel - currentKey: Settings.data.wallpaper.transitionType - onSelected: key => Settings.data.wallpaper.transitionType = key - } - - // Transition Duration - ColumnLayout { - NLabel { - label: "Transition Duration" - description: "Duration of transition animations in seconds." - } - - RowLayout { - spacing: Style.marginL * scaling - NSlider { - Layout.fillWidth: true - from: 100 - to: 5000 - stepSize: 100 - value: Settings.data.wallpaper.transitionDuration - onMoved: Settings.data.wallpaper.transitionDuration = value - cutoutColor: Color.mSurface - } - NText { - text: (Settings.data.wallpaper.transitionDuration / 1000).toFixed(2) + "s" - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - } - - // Edge Smoothness - ColumnLayout { - NLabel { - label: "Transition Edge Smoothness" - description: "Duration of transition animations in seconds." - } - - RowLayout { - spacing: Style.marginL * scaling - NSlider { - Layout.fillWidth: true - from: 0.0 - to: 1.0 - value: Settings.data.wallpaper.transitionEdgeSmoothness - onMoved: Settings.data.wallpaper.transitionEdgeSmoothness = value - cutoutColor: Color.mSurface - } - NText { - text: Math.round(Settings.data.wallpaper.transitionEdgeSmoothness * 100) + "%" - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - } - } - } } // Reusable component for interval preset chips diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 7e9408e..a8a5e19 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -49,6 +49,51 @@ Singleton { } } + readonly property ListModel fillModeModel: ListModel { + // Centers image without resizing + // Pads with fillColor if image is smaller than screen + ListElement { + key: "center" + name: "Center" + uniform: 0.0 + } + // Scales image to fill entire screen + // Crops portions that exceed screen bounds + // Maintains aspect ratio + ListElement { + key: "crop" + name: "Crop (Fill/Cover)" + uniform: 1.0 + } + // Scales image to fit entirely within screen + // Maintains aspect ratio + // May show fillColor bars on sides + ListElement { + key: "fit" + name: "Fit (Contain)" + uniform: 2.0 + } + // Stretches image to exact screen dimensions + // Does NOT maintain aspect ratio + // May distort the image + ListElement { + key: "stretch" + name: "Stretch" + uniform: 3.0 + } + } + + function getFillModeUniform() { + for (var i = 0; i < fillModeModel.count; i++) { + const mode = fillModeModel.get(i) + if (mode.key === Settings.data.wallpaper.fillMode) { + return mode.uniform + } + } + // Fallback to crop + return 1.0 + } + // All transition keys but filter out "none" and "random" so we are left with the real transitions readonly property var allTransitions: Array.from({ "length": transitionsModel.count diff --git a/Shaders/frag/wp_disc.frag b/Shaders/frag/wp_disc.frag index 4bde549..0fc2122 100644 --- a/Shaders/frag/wp_disc.frag +++ b/Shaders/frag/wp_disc.frag @@ -15,17 +15,82 @@ layout(std140, binding = 0) uniform buf { float centerY; // Y coordinate of disc center (0.0 to 1.0) float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) float aspectRatio; // Width / Height of the screen + + // Fill mode parameters + float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) } ubuf; +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight) { + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + void main() { vec2 uv = qt_TexCoord0; - vec4 color1 = texture(source1, uv); // Current (old) wallpaper - vec4 color2 = texture(source2, uv); // Next (new) wallpaper - + + // Sample textures with fill mode + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2); + // Map smoothness from 0.0-1.0 to 0.001-0.5 range // Using a non-linear mapping for better control float mappedSmoothness = mix(0.001, 0.5, ubuf.smoothness * ubuf.smoothness); - + // Adjust UV coordinates to compensate for aspect ratio // This makes distances circular instead of elliptical vec2 adjustedUV = vec2(uv.x * ubuf.aspectRatio, uv.y); @@ -54,4 +119,4 @@ void main() { fragColor = mix(color2, color1, factor); fragColor *= ubuf.qt_Opacity; -} +} \ No newline at end of file diff --git a/Shaders/frag/wp_fade.frag b/Shaders/frag/wp_fade.frag index 24917a5..47aa7c6 100644 --- a/Shaders/frag/wp_fade.frag +++ b/Shaders/frag/wp_fade.frag @@ -1,19 +1,88 @@ +// ===== wp_fade.frag ===== #version 450 layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; + layout(binding = 1) uniform sampler2D source1; layout(binding = 2) uniform sampler2D source2; + layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; float progress; -}; + + // Fill mode parameters + float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) +} ubuf; + +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight) { + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} void main() { - vec4 color1 = texture(source1, qt_TexCoord0); - vec4 color2 = texture(source2, qt_TexCoord0); + vec2 uv = qt_TexCoord0; + + // Sample textures with fill mode + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2); // Mix the two textures based on progress value - fragColor = mix(color1, color2, progress) * qt_Opacity; + fragColor = mix(color1, color2, ubuf.progress) * ubuf.qt_Opacity; } \ No newline at end of file diff --git a/Shaders/frag/wp_stripes.frag b/Shaders/frag/wp_stripes.frag index f141227..c2684ce 100644 --- a/Shaders/frag/wp_stripes.frag +++ b/Shaders/frag/wp_stripes.frag @@ -15,12 +15,77 @@ layout(std140, binding = 0) uniform buf { float angle; // Angle of stripes in degrees (default 30.0) float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) float aspectRatio; // Width / Height of the screen + + // Fill mode parameters + float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) } ubuf; +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight) { + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + void main() { vec2 uv = qt_TexCoord0; - vec4 color1 = texture(source1, uv); // Current (old) wallpaper - vec4 color2 = texture(source2, uv); // Next (new) wallpaper + + // Sample textures with fill mode + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2); // Map smoothness from 0.0-1.0 to 0.001-0.3 range // Using a non-linear mapping for better control at low values diff --git a/Shaders/frag/wp_wipe.frag b/Shaders/frag/wp_wipe.frag index 10b948a..46b21e6 100644 --- a/Shaders/frag/wp_wipe.frag +++ b/Shaders/frag/wp_wipe.frag @@ -1,4 +1,3 @@ - // ===== wp_wipe.frag ===== #version 450 @@ -14,12 +13,77 @@ layout(std140, binding = 0) uniform buf { float progress; // Transition progress (0.0 to 1.0) float direction; // 0=left, 1=right, 2=up, 3=down float smoothness; // Edge smoothness (0.0 to 1.0, 0=sharp, 1=very smooth) + + // Fill mode parameters + float fillMode; // 0=no(center), 1=crop(fill), 2=fit(contain), 3=stretch + float imageWidth1; // Width of source1 image + float imageHeight1; // Height of source1 image + float imageWidth2; // Width of source2 image + float imageHeight2; // Height of source2 image + float screenWidth; // Screen width + float screenHeight; // Screen height + vec4 fillColor; // Fill color for empty areas (default: black) } ubuf; +// Calculate UV coordinates based on fill mode +vec2 calculateUV(vec2 uv, float imgWidth, float imgHeight) { + float imageAspect = imgWidth / imgHeight; + float screenAspect = ubuf.screenWidth / ubuf.screenHeight; + vec2 transformedUV = uv; + + if (ubuf.fillMode < 0.5) { + // Mode 0: no (center) - No resize, center image at original size + // Convert UV to pixel coordinates, offset, then back to UV in image space + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + vec2 imageOffset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - vec2(imgWidth, imgHeight)) * 0.5; + vec2 imagePixel = screenPixel - imageOffset; + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + else if (ubuf.fillMode < 1.5) { + // Mode 1: crop (fill/cover) - Fill screen, crop excess (default) + float scale = max(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (scaledImageSize - vec2(ubuf.screenWidth, ubuf.screenHeight)) / scaledImageSize; + transformedUV = uv * (vec2(1.0) - offset) + offset * 0.5; + } + else if (ubuf.fillMode < 2.5) { + // Mode 2: fit (contain) - Fit inside screen, maintain aspect ratio + float scale = min(ubuf.screenWidth / imgWidth, ubuf.screenHeight / imgHeight); + vec2 scaledImageSize = vec2(imgWidth, imgHeight) * scale; + vec2 offset = (vec2(ubuf.screenWidth, ubuf.screenHeight) - scaledImageSize) * 0.5; + + // Convert screen UV to pixel coordinates + vec2 screenPixel = uv * vec2(ubuf.screenWidth, ubuf.screenHeight); + // Adjust for offset and scale + vec2 imagePixel = (screenPixel - offset) / scale; + // Convert back to UV coordinates in image space + transformedUV = imagePixel / vec2(imgWidth, imgHeight); + } + // Mode 3: stretch - Use original UV (stretches to fit) + // No transformation needed for stretch mode + + return transformedUV; +} + +// Sample texture with fill mode and handle out-of-bounds +vec4 sampleWithFillMode(sampler2D tex, vec2 uv, float imgWidth, float imgHeight) { + vec2 transformedUV = calculateUV(uv, imgWidth, imgHeight); + + // Check if UV is out of bounds + if (transformedUV.x < 0.0 || transformedUV.x > 1.0 || + transformedUV.y < 0.0 || transformedUV.y > 1.0) { + return ubuf.fillColor; + } + + return texture(tex, transformedUV); +} + void main() { vec2 uv = qt_TexCoord0; - vec4 color1 = texture(source1, uv); // Current (old) wallpaper - vec4 color2 = texture(source2, uv); // Next (new) wallpaper + + // Sample textures with fill mode + vec4 color1 = sampleWithFillMode(source1, uv, ubuf.imageWidth1, ubuf.imageHeight1); + vec4 color2 = sampleWithFillMode(source2, uv, ubuf.imageWidth2, ubuf.imageHeight2); // Map smoothness from 0.0-1.0 to 0.001-0.5 range // Using a non-linear mapping for better control diff --git a/Shaders/qsb/wp_disc.frag.qsb b/Shaders/qsb/wp_disc.frag.qsb index 9c66bfb94229f7df7cd1831b23c20033749d497a..8e1705e73a0dbca41b23da0cf4982d3efe75fc9b 100644 GIT binary patch literal 5385 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 literal 2532 zcmV}sVPyep=d%yIwXJ&sRjYcbQ8`>nN-g9K_z4N<|xpVj4 z*^!B89}%U9Xb%ycq$k9wO3UO>hvvv9p8~2;o`^<>i2jL-5-y?O2-PVdiyBh6DaB*; zuM{1pb&9A?M0+Xf-mhq=327kq@E%#jDIk|RM6@4>>l*qEb#oLV3)CiyY;uXyGZ?+D zyfd#is7(R+)S!SI3MquZ{4jw-WRpW4adIe-ZVUMBoxxWDzdH0hIUY|>g z!-S5LMIp7xA)7eeml1QxhuHEkF?ERrKSUw<6j31EEf7%#Yq%$QRyw?>!DaS**J~W# zwH|B#pGQYdPY8GR(_Z=mxNp*kbZ|e7%kNuvm+t05UdxrHMJm%F8kIOPc9^7z3y|~6CJ=Ucs zkjpynG`hmVA>tdyG4?^wr@=c$Df$pP#GKB6{s`+S9QYjY1iYZnX|#|p zYI73f7YzMnQ#>_N;&&|8MS7qEAM#-yC8SpxHS8j*7= zBVYDu`61T-utA^DXt7?WG+M}?HRu@wf777v81y59{@kE#gLV!23Fuio4+oIrS7}tv z?EuC^P98-*D(o@L{}GMJcpeAl7~~Iwr{v#8{wn!*4Ec9~c?a?*z`H?XGLL?S-1`VR z#9C*-SD48J%oOs)VhC)yjS!s0FD z7*qOJ65_uKzQX(}0aF2A`TlG8UeSD5(|lM3M)>duV@g*Qbx6^_K^;=-Zvi9t2WVVk z)}i|oj0?S=gRk_6P1Y%G9!^3m>) z6)iSvLeC4>@PY9(yKU(4Vb=vu=@2g{u11H@sn&)A6Ka^qkl8f+cnZv>h7s>6H?Lhn-abM!W)`?u&Fi!>p}zXnhL2)knJ8?Cl}1zlgm?iR+? zn*0uH@%L8oJKe4lT4hO8B~9h={`xa*hG&&%m_w$pRy>i=YPFkAP@HF>9|g8kD8B+wET_|(R`AUA8f??Ci(+QWPG`>dgIZpU z`EHGgP-i9)6p4y9*{lJ@Sc%zw(+>)4mT{-UqrfT7XW&ehMVpf*vY3D@YREVgEvwyj zYD?WPv02t~JDGegpD*O&t8}q+`C@6ha6UhM{?g@AzEr$?;nKzFEX(Jnvoc({UYOaG z$>}*(WLE8Q6mqBb;31opoJ_Qt>nIs*S%jatXGx-Sa*?~@LJp~rtfmK8?VEVb4LO^Y zUzTS(nVxG|!Taw|CIFLctPa{(&9VV~t4XqKmNgx( z!8bB8Ov>wQSNCL}l$vmV9+;^yCEY3H^4XXBA6PXv5*j2*J-lp+DL>em1dDwt>XyxY zvC45Ch1_Xpp!RHnY*yJ!Qk<2G4E2p>_0zy=%v;>53Ptq%UJR|lv6&+4G- zC2a-$h{wCOdSEr?LVQoX@DAj{ah= zNArX)1L}(nhsg9oYi*O+5}67Md@BvufJE~ z0A5vfT8}puVOxsJ^%A$Z!>T}4QN(q0DA2?77W?!ThUhIO>J{j8$Sv;L@ifgop^x=9 z3-r*X#XI*OR_6jg46Ap%cEpiJ3EJ2UTfVt6x3F-3J|BlFLB~SG4JDdcHp7-4R+p`y z;c%#sJxc(peT@s|hqNx}?KUrjJ{JcrV=jexexbE91lN%4274zbA5AA{@8syCF3&#d z*@3K`GC8_MCkeMb$V3ss0vTGuVePJJw7bFb5pQA#N)rpY z)o`k=xL1XuuRzV}h+IP5=+&8+kzhL3;7cL~>71M%0Ii(L&g<|f@C*gd^=_WRFTS|O zw@&o>2wr|qhZf#Ibqwf9VVrc4$Hf} z|A+Ka1TRG}@*+UPxPkZ1K##<-a&8elZqYw{(e!|SyRn@ zho<^{{jyNELOwI|zxaJ2(b9kLE5mDUuRXVZ+`l&XMw@9RyjI4~n(<=UFPQXlskJFT u?oOgTjmbiRw@u@X6aQCi diff --git a/Shaders/qsb/wp_fade.frag.qsb b/Shaders/qsb/wp_fade.frag.qsb index 89641d8b0148d85f48da59479b767e133b282537..3081ea8e74f8db9e0050785b607d7be861cd4dcf 100644 GIT binary patch literal 4333 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 Yge99kx%qnMxa?PVK3*Sx0@5c_2-dd!CjbBd diff --git a/Shaders/qsb/wp_stripes.frag.qsb b/Shaders/qsb/wp_stripes.frag.qsb index 7f739ae7fa38d28d3478c6370a6c913152a358aa..db496eaa5b10c2627de673c1ad7bec01da2bce11 100644 GIT binary patch literal 8072 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 W7GU_(vfOp^!N-qZeEuJ!6b#9356u7o literal 5320 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