feat: video wallpapers #163
+3
-5
@@ -34,7 +34,7 @@ MouseArea {
|
|||||||
cursorShape: expanded ? Qt.ArrowCursor : undefined
|
cursorShape: expanded ? Qt.ArrowCursor : undefined
|
||||||
enabled: expanded
|
enabled: expanded
|
||||||
hoverEnabled: expanded
|
hoverEnabled: expanded
|
||||||
layer.enabled: opacity < 1
|
layer.enabled: false
|
||||||
opacity: expanded ? 1 : 0
|
opacity: expanded ? 1 : 0
|
||||||
parent: {
|
parent: {
|
||||||
const win = QsWindow.window;
|
const win = QsWindow.window;
|
||||||
@@ -101,8 +101,7 @@ MouseArea {
|
|||||||
yScale: root.expanded ? 1 : 0.1
|
yScale: root.expanded ? 1 : 0.1
|
||||||
|
|
||||||
Behavior on yScale {
|
Behavior on yScale {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,8 +136,7 @@ MouseArea {
|
|||||||
radius: menu.radius - column.anchors.margins
|
radius: menu.radius - column.anchors.margins
|
||||||
|
|
||||||
Behavior on radius {
|
Behavior on radius {
|
||||||
Anim {
|
Anim {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StateLayer {
|
StateLayer {
|
||||||
|
|||||||
+64
-38
@@ -16,16 +16,15 @@ Searcher {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||||
property alias crops: adapter.monitorCrops
|
|
||||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||||
readonly property string currentThumbnail: thumbnailFor(current)
|
readonly property string currentThumbnail: thumbnailFor(current)
|
||||||
readonly property bool currentIsVideo: isVideoPath(current)
|
readonly property bool currentIsVideo: isVideoPath(current)
|
||||||
property alias monitorCrops: monitorCrops
|
|
||||||
property bool pendingPreviewClear
|
property bool pendingPreviewClear
|
||||||
property bool previewColorLock
|
property bool previewColorLock
|
||||||
property string previewPath
|
property string previewPath
|
||||||
property bool recentlyChanged
|
property bool recentlyChanged
|
||||||
property bool showPreview: false
|
property bool showPreview: false
|
||||||
|
property int cropVersion: 0
|
||||||
|
|
||||||
function getCategoryFor(w: WallpaperEntry): string {
|
function getCategoryFor(w: WallpaperEntry): string {
|
||||||
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
|
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
|
||||||
@@ -58,7 +57,7 @@ Searcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCrop(screen: string): var {
|
function getCrop(screen: string): var {
|
||||||
return root.crops[screen];
|
return wallpapers.crop(root.current, screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
function preview(path: string): void {
|
function preview(path: string): void {
|
||||||
@@ -75,19 +74,18 @@ Searcher {
|
|||||||
else if (zoom > 5.0)
|
else if (zoom > 5.0)
|
||||||
zoom = 5.0;
|
zoom = 5.0;
|
||||||
|
|
||||||
root.crops[screen] = {
|
wallpapers.setCrop(root.current, screen, {
|
||||||
x: rect.x,
|
x: rect.x,
|
||||||
y: rect.y,
|
y: rect.y,
|
||||||
width: rect.width,
|
width: rect.width,
|
||||||
height: rect.height,
|
height: rect.height,
|
||||||
zoom: zoom
|
zoom: zoom
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWallpaper(path: string): void {
|
function setWallpaper(path: string): void {
|
||||||
actualCurrent = path;
|
actualCurrent = path;
|
||||||
WallpaperPath.currentWallpaperPath = path;
|
WallpaperPath.currentWallpaperPath = path;
|
||||||
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0));
|
|
||||||
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
|
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
|
||||||
if (Config.colors.schemeGen)
|
if (Config.colors.schemeGen)
|
||||||
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]);
|
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.colors.mode}`]);
|
||||||
@@ -121,32 +119,6 @@ Searcher {
|
|||||||
target: "wallpaper"
|
target: "wallpaper"
|
||||||
}
|
}
|
||||||
|
|
||||||
FileView {
|
|
||||||
id: monitorCrops
|
|
||||||
|
|
||||||
path: `${Paths.state}/wallpaper-crops.json`
|
|
||||||
watchChanges: true
|
|
||||||
|
|
||||||
onAdapterUpdated: cropWriteDelay.restart()
|
|
||||||
onFileChanged: reload()
|
|
||||||
|
|
||||||
JsonAdapter {
|
|
||||||
id: adapter
|
|
||||||
|
|
||||||
property var monitorCrops: ({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: cropWriteDelay
|
|
||||||
|
|
||||||
interval: 100
|
|
||||||
repeat: false
|
|
||||||
running: false
|
|
||||||
|
|
||||||
onTriggered: monitorCrops.writeAdapter()
|
|
||||||
}
|
|
||||||
|
|
||||||
WallpaperDb {
|
WallpaperDb {
|
||||||
id: wallpapers
|
id: wallpapers
|
||||||
|
|
||||||
@@ -183,35 +155,89 @@ Searcher {
|
|||||||
if (path === root.current)
|
if (path === root.current)
|
||||||
wallpaper.invalidateCache();
|
wallpaper.invalidateCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCropsChanged(path: string): void {
|
||||||
|
if (path === root.current)
|
||||||
|
root.cropVersion++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
|
id: screenVariants
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
||||||
WallpaperObject {
|
WallpaperObject {
|
||||||
readonly property var crops: root.getCrop(modelData.name)
|
id: obj
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
|
||||||
cropHeight: crops?.height ?? 1.0
|
// Crop animation only for edits, never for wallpaper swaps.
|
||||||
cropWidth: crops?.width ?? 1.0
|
property bool animateCrop: false
|
||||||
cropX: crops?.x ?? 0.0
|
|
||||||
cropY: crops?.y ?? 0.0
|
|
||||||
screen: modelData.name
|
screen: modelData.name
|
||||||
service: wallpaper
|
service: wallpaper
|
||||||
zoom: crops?.zoom ?? 1.0
|
|
||||||
|
// Apply the wallpaper's own crop. Driven off the service's pathChanged
|
||||||
|
// (which fires after applyPath has snapshotted the outgoing frame with
|
||||||
|
// the outgoing crop) and off crop edits, never as a reactive binding on
|
||||||
|
// the wallpaper path — so a swap applies the incoming crop atomically
|
||||||
|
// after the base is captured.
|
||||||
|
function seedCrop(): void {
|
||||||
|
// Load the current wallpaper's crop without pushing: the service
|
||||||
|
// already read it from the database. This only seeds the values so
|
||||||
|
// a later edit animates from the right starting point.
|
||||||
|
obj.syncCrop(root.getCrop(modelData.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply an edited crop: assign via the setters so the Behaviors animate
|
||||||
|
// the numbers and push each intermediate value to the service.
|
||||||
|
function editCrop(): void {
|
||||||
|
const c = root.getCrop(modelData.name);
|
||||||
|
obj.cropX = c?.x ?? 0.0;
|
||||||
|
obj.cropY = c?.y ?? 0.0;
|
||||||
|
obj.cropWidth = c?.width ?? 1.0;
|
||||||
|
obj.cropHeight = c?.height ?? 1.0;
|
||||||
|
obj.zoom = c?.zoom ?? 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: seedCrop()
|
||||||
|
onScreenChanged: seedCrop()
|
||||||
|
|
||||||
Behavior on cropHeight {
|
Behavior on cropHeight {
|
||||||
|
enabled: obj.animateCrop
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
Behavior on cropWidth {
|
Behavior on cropWidth {
|
||||||
|
enabled: obj.animateCrop
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
Behavior on cropX {
|
Behavior on cropX {
|
||||||
|
enabled: obj.animateCrop
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
Behavior on cropY {
|
Behavior on cropY {
|
||||||
|
enabled: obj.animateCrop
|
||||||
Anim {}
|
Anim {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: wallpaper
|
||||||
|
function onPathChanged(): void {
|
||||||
|
for (const obj of screenVariants.instances) {
|
||||||
|
obj.animateCrop = false;
|
||||||
|
obj.seedCrop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onCropVersionChanged(): void {
|
||||||
|
for (const obj of screenVariants.instances) {
|
||||||
|
obj.animateCrop = true;
|
||||||
|
obj.editCrop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,13 @@ Item {
|
|||||||
id: wrapper
|
id: wrapper
|
||||||
|
|
||||||
readonly property bool isVideo: Wallpapers.currentIsVideo
|
readonly property bool isVideo: Wallpapers.currentIsVideo
|
||||||
|
readonly property string currentWallpaper: Wallpapers.current
|
||||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||||
readonly property real maxZoom: 5.0
|
readonly property real maxZoom: 5.0
|
||||||
readonly property bool changesMade: {
|
readonly property bool changesMade: {
|
||||||
cropRevision;
|
cropRevision;
|
||||||
|
Wallpapers.cropVersion;
|
||||||
|
wrapper.currentWallpaper;
|
||||||
|
|
||||||
const r = cropRectLoader.item;
|
const r = cropRectLoader.item;
|
||||||
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
||||||
@@ -115,6 +118,9 @@ Item {
|
|||||||
refreshScreens();
|
refreshScreens();
|
||||||
Qt.callLater(syncCropToScreen);
|
Qt.callLater(syncCropToScreen);
|
||||||
}
|
}
|
||||||
|
onCurrentWallpaperChanged: {
|
||||||
|
Qt.callLater(syncCropToScreen);
|
||||||
|
}
|
||||||
onSelectedScreenIndexChanged: {
|
onSelectedScreenIndexChanged: {
|
||||||
Qt.callLater(syncCropToScreen);
|
Qt.callLater(syncCropToScreen);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,42 @@ import qs.Services
|
|||||||
PageBase {
|
PageBase {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property list<MenuItem> wallTransitions: [
|
||||||
|
MenuItem {
|
||||||
|
text: "Bowtie"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "Fade"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "LinearBlur"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "WindowSlices"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "WipeRight"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "WipeDown"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "TilesWave"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "SplitSlideInHorizontal"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "Hexagonalize"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "DoomMelt"
|
||||||
|
},
|
||||||
|
MenuItem {
|
||||||
|
text: "Random"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
title: qsTr("Wallpaper & style")
|
title: qsTr("Wallpaper & style")
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -73,6 +109,18 @@ PageBase {
|
|||||||
onToggled: Config.background.enabled = checked
|
onToggled: Config.background.enabled = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectRow {
|
||||||
|
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||||
|
active: root.wallTransitions[Config.background.wallTransition]
|
||||||
|
menuItems: root.wallTransitions
|
||||||
|
menuOnTop: true
|
||||||
|
settingAnchor: "style-wallpaper-transition"
|
||||||
|
subtext: qsTr("Currently selected: %1").arg(active.text)
|
||||||
|
text: qsTr("Wallpaper transition")
|
||||||
|
|
||||||
|
onSelected: item => Config.background.wallTransition = root.wallTransitions.indexOf(item)
|
||||||
|
}
|
||||||
|
|
||||||
ToggleRow {
|
ToggleRow {
|
||||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||||
checked: Colors.transparency.enabled
|
checked: Colors.transparency.enabled
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.hpp"
|
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include "enums.hpp"
|
||||||
|
|
||||||
namespace ZShell::config {
|
namespace ZShell::config {
|
||||||
|
|
||||||
class Background : public settings::ObjectNode {
|
class Background : public settings::ObjectNode {
|
||||||
@@ -9,6 +12,8 @@ class Background : public settings::ObjectNode {
|
|||||||
|
|
||||||
CONFIG_PROPERTY(bool, enabled, true)
|
CONFIG_PROPERTY(bool, enabled, true)
|
||||||
CONFIG_PROPERTY(int, wallFadeDuration, 300)
|
CONFIG_PROPERTY(int, wallFadeDuration, 300)
|
||||||
|
CONFIG_ENUM_PROPERTY(
|
||||||
|
WallpaperTransition, wallTransition, WallpaperTransition::Fade)
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::config
|
} // namespace ZShell::config
|
||||||
|
|||||||
@@ -23,6 +23,19 @@ ENUM(NotifsFullscreen, On, Off)
|
|||||||
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
|
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
|
||||||
ENUM(DataUnit, Binary, Decimal)
|
ENUM(DataUnit, Binary, Decimal)
|
||||||
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
|
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
|
||||||
|
ENUM(
|
||||||
|
WallpaperTransition,
|
||||||
|
Bowtie,
|
||||||
|
Fade,
|
||||||
|
LinearBlur,
|
||||||
|
WindowSlices,
|
||||||
|
WipeRight,
|
||||||
|
WipeDown,
|
||||||
|
TilesWave,
|
||||||
|
SplitSlideInHorizontal,
|
||||||
|
Hexagonalize,
|
||||||
|
DoomMelt,
|
||||||
|
Random)
|
||||||
|
|
||||||
#undef ENUM
|
#undef ENUM
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ qml_module(ZShell-internal
|
|||||||
PkgConfig::AVCODEC
|
PkgConfig::AVCODEC
|
||||||
PkgConfig::AVFORMAT
|
PkgConfig::AVFORMAT
|
||||||
PkgConfig::AVUTIL
|
PkgConfig::AVUTIL
|
||||||
|
ZShell-config
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(ZShell-internal "wallpaper_shaders"
|
qt_add_resources(ZShell-internal "wallpaper_shaders"
|
||||||
@@ -53,6 +54,16 @@ qt_add_resources(ZShell-internal "wallpaper_shaders"
|
|||||||
shaders/wallpaper.vert
|
shaders/wallpaper.vert
|
||||||
shaders/wallpaper.frag
|
shaders/wallpaper.frag
|
||||||
shaders/wallpaper_video.frag
|
shaders/wallpaper_video.frag
|
||||||
|
shaders/wallpaper_trans_bowtie.frag
|
||||||
|
shaders/wallpaper_trans_fade.frag
|
||||||
|
shaders/wallpaper_trans_linear_blur.frag
|
||||||
|
shaders/wallpaper_trans_window_slices.frag
|
||||||
|
shaders/wallpaper_trans_wipe_right.frag
|
||||||
|
shaders/wallpaper_trans_wipe_down.frag
|
||||||
|
shaders/wallpaper_trans_tiles_wave.frag
|
||||||
|
shaders/wallpaper_trans_split_slide_in_horizontal.frag
|
||||||
|
shaders/wallpaper_trans_hexagonalize.frag
|
||||||
|
shaders/wallpaper_trans_doom_melt.frag
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CUDAToolkit_FOUND)
|
if(CUDAToolkit_FOUND)
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// Bow-tie wipe transition adapted from gl-transitions.com
|
||||||
|
// Author: huynx (MIT)
|
||||||
|
// Original: two triangles grow from the top and bottom edges into a bow tie
|
||||||
|
// that reveals the incoming image.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
float check(vec2 p1, vec2 p2, vec2 p3) {
|
||||||
|
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pointInTriangle(vec2 pt, vec2 p1, vec2 p2, vec2 p3) {
|
||||||
|
bool b1 = check(pt, p1, p2) < 0.0;
|
||||||
|
bool b2 = check(pt, p2, p3) < 0.0;
|
||||||
|
bool b3 = check(pt, p3, p1) < 0.0;
|
||||||
|
return (b1 == b2) && (b2 == b3);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool in_top_triangle(vec2 p) {
|
||||||
|
vec2 vertex1 = vec2(0.5, progress);
|
||||||
|
vec2 vertex2 = vec2(0.5 - progress, 0.0);
|
||||||
|
vec2 vertex3 = vec2(0.5 + progress, 0.0);
|
||||||
|
return pointInTriangle(p, vertex1, vertex2, vertex3);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool in_bottom_triangle(vec2 p) {
|
||||||
|
vec2 vertex1 = vec2(0.5, 1.0 - progress);
|
||||||
|
vec2 vertex2 = vec2(0.5 - progress, 1.0);
|
||||||
|
vec2 vertex3 = vec2(0.5 + progress, 1.0);
|
||||||
|
return pointInTriangle(p, vertex1, vertex2, vertex3);
|
||||||
|
}
|
||||||
|
|
||||||
|
float blur_edge(vec2 bot1, vec2 bot2, vec2 top, vec2 testPt) {
|
||||||
|
vec2 lineDir = bot1 - top;
|
||||||
|
vec2 perpDir = vec2(lineDir.y, -lineDir.x);
|
||||||
|
vec2 dirToPt1 = bot1 - testPt;
|
||||||
|
float dist1 = abs(dot(normalize(perpDir), dirToPt1));
|
||||||
|
|
||||||
|
lineDir = bot2 - top;
|
||||||
|
perpDir = vec2(lineDir.y, -lineDir.x);
|
||||||
|
dirToPt1 = bot2 - testPt;
|
||||||
|
float min_dist = min(abs(dot(normalize(perpDir), dirToPt1)), dist1);
|
||||||
|
|
||||||
|
if (min_dist < 0.005)
|
||||||
|
return min_dist / 0.005;
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
if (in_top_triangle(uv)) {
|
||||||
|
if (progress < 0.1)
|
||||||
|
return getFromColor(uv);
|
||||||
|
|
||||||
|
if (uv.y < 0.5) {
|
||||||
|
vec2 vertex1 = vec2(0.5, progress);
|
||||||
|
vec2 vertex2 = vec2(0.5 - progress, 0.0);
|
||||||
|
vec2 vertex3 = vec2(0.5 + progress, 0.0);
|
||||||
|
return mix(
|
||||||
|
getFromColor(uv),
|
||||||
|
getToColor(uv),
|
||||||
|
blur_edge(vertex2, vertex3, vertex1, uv));
|
||||||
|
}
|
||||||
|
if (progress > 0.0)
|
||||||
|
return getToColor(uv);
|
||||||
|
return getFromColor(uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_bottom_triangle(uv)) {
|
||||||
|
if (uv.y >= 0.5) {
|
||||||
|
vec2 vertex1 = vec2(0.5, 1.0 - progress);
|
||||||
|
vec2 vertex2 = vec2(0.5 - progress, 1.0);
|
||||||
|
vec2 vertex3 = vec2(0.5 + progress, 1.0);
|
||||||
|
return mix(
|
||||||
|
getFromColor(uv),
|
||||||
|
getToColor(uv),
|
||||||
|
blur_edge(vertex2, vertex3, vertex1, uv));
|
||||||
|
}
|
||||||
|
return getFromColor(uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFromColor(uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Doom-style melt wipe, from gl-transitions.com
|
||||||
|
// Author: Zeh Fernando (MIT)
|
||||||
|
// Description: the outgoing image melts down in uneven columns, like the Doom
|
||||||
|
// screen wipe. Fully procedural (no displacement texture).
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). Optional
|
||||||
|
// gl-transitions params are fixed here: bars = 30, amplitude = 2.0,
|
||||||
|
// noise = 0.1, frequency = 0.5, dripScale = 0.5.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const int bars = 30;
|
||||||
|
const float amplitude = 2.0;
|
||||||
|
const float noise = 0.1;
|
||||||
|
const float frequency = 0.5;
|
||||||
|
const float dripScale = 0.5;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rand(int num) {
|
||||||
|
return fract(mod(float(num) * 67123.313, 12.0) * sin(float(num) * 10.3) *
|
||||||
|
cos(float(num)));
|
||||||
|
}
|
||||||
|
|
||||||
|
float wave(int num) {
|
||||||
|
float fn = float(num) * frequency * 0.1 * float(bars);
|
||||||
|
return cos(fn * 0.5) * cos(fn * 0.13) * sin((fn + 10.0) * 0.3) / 2.0 + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
float drip(int num) {
|
||||||
|
return sin(float(num) / float(bars - 1) * 3.141592) * dripScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
float pos(int num) {
|
||||||
|
return (noise == 0.0 ? wave(num)
|
||||||
|
: mix(wave(num), rand(num), noise)) +
|
||||||
|
(dripScale == 0.0 ? 0.0 : drip(num));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
int bar = int(uv.x * float(bars));
|
||||||
|
float scale = 1.0 + pos(bar) * amplitude;
|
||||||
|
float phase = progress * scale;
|
||||||
|
float posY = uv.y;
|
||||||
|
|
||||||
|
if (phase + posY < 1.0) {
|
||||||
|
return getFromColor(vec2(uv.x, uv.y + phase));
|
||||||
|
}
|
||||||
|
return getToColor(uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Fade (crossfade) transition, from gl-transitions.com.
|
||||||
|
//
|
||||||
|
// Ported to ZShell's own GLSL ABI (same contract as wallpaper_trans_bowtie.frag):
|
||||||
|
// version/precision header is prepended by WallpaperService::shaderSource, the
|
||||||
|
// fullscreen triangle comes from shaders/wallpaper.vert, and getFromColor /
|
||||||
|
// getToColor sample with the crop + per-layer flip convention.
|
||||||
|
//
|
||||||
|
// uniforms: samplers `from`/`to`, vec2 size, vec4 fromCrop, vec4 toCrop,
|
||||||
|
// float fromFlip, float toFlip, float progress (0..1).
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
uniform float progress;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
return mix(getFromColor(uv), getToColor(uv), progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// Hexagonalize crossfade, from gl-transitions.com
|
||||||
|
// Author: Fernando Kuteken (MIT)
|
||||||
|
// Description: both images turn into hexagons, crossfade, then sharpen again.
|
||||||
|
// Hexagonal math from http://www.redblobgames.com/grids/hexagons/
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). The
|
||||||
|
// gl-transitions `ratio` contextual value is derived from `size`. Optional
|
||||||
|
// params are fixed here: steps = 50, horizontalHexagons = 20.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const int steps = 50;
|
||||||
|
const float horizontalHexagons = 20.0;
|
||||||
|
float ratio;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Hexagon {
|
||||||
|
float q;
|
||||||
|
float r;
|
||||||
|
float s;
|
||||||
|
};
|
||||||
|
|
||||||
|
Hexagon createHexagon(float q, float r) {
|
||||||
|
Hexagon hex;
|
||||||
|
hex.q = q;
|
||||||
|
hex.r = r;
|
||||||
|
hex.s = -q - r;
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hexagon roundHexagon(Hexagon hex) {
|
||||||
|
float q = floor(hex.q + 0.5);
|
||||||
|
float r = floor(hex.r + 0.5);
|
||||||
|
float s = floor(hex.s + 0.5);
|
||||||
|
|
||||||
|
float deltaQ = abs(q - hex.q);
|
||||||
|
float deltaR = abs(r - hex.r);
|
||||||
|
float deltaS = abs(s - hex.s);
|
||||||
|
|
||||||
|
if (deltaQ > deltaR && deltaQ > deltaS)
|
||||||
|
q = -r - s;
|
||||||
|
else if (deltaR > deltaS)
|
||||||
|
r = -q - s;
|
||||||
|
else
|
||||||
|
s = -q - r;
|
||||||
|
|
||||||
|
return createHexagon(q, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hexagon hexagonFromPoint(vec2 point, float size) {
|
||||||
|
point.y /= ratio;
|
||||||
|
point = (point - 0.5) / size;
|
||||||
|
|
||||||
|
float q = (sqrt(3.0) / 3.0) * point.x + (-1.0 / 3.0) * point.y;
|
||||||
|
float r = 0.0 * point.x + 2.0 / 3.0 * point.y;
|
||||||
|
|
||||||
|
Hexagon hex = createHexagon(q, r);
|
||||||
|
return roundHexagon(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 pointFromHexagon(Hexagon hex, float size) {
|
||||||
|
float x = (sqrt(3.0) * hex.q + (sqrt(3.0) / 2.0) * hex.r) * size + 0.5;
|
||||||
|
float y = (0.0 * hex.q + (3.0 / 2.0) * hex.r) * size + 0.5;
|
||||||
|
return vec2(x, y * ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
float dist = 2.0 * min(progress, 1.0 - progress);
|
||||||
|
dist = steps > 0 ? ceil(dist * float(steps)) / float(steps) : dist;
|
||||||
|
|
||||||
|
float size = (sqrt(3.0) / 3.0) * dist / horizontalHexagons;
|
||||||
|
|
||||||
|
vec2 point = dist > 0.0 ? pointFromHexagon(hexagonFromPoint(uv, size), size)
|
||||||
|
: uv;
|
||||||
|
|
||||||
|
return mix(getFromColor(point), getToColor(point), progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
ratio = size.x / size.y;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// Linear blur crossfade, from gl-transitions.com
|
||||||
|
// Author: gre (MIT)
|
||||||
|
// Description: a crossfade that passes through a directional blur.
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag): the version
|
||||||
|
// header is prepended by WallpaperService::shaderSource, the fullscreen
|
||||||
|
// triangle comes from wallpaper.vert, and getFromColor/getToColor apply the
|
||||||
|
// crop + flip convention. `uv` is bottom-left origin per gl-transitions.
|
||||||
|
//
|
||||||
|
// Optional gl-transitions params are fixed here: intensity = 0.1.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const float intensity = 0.1;
|
||||||
|
const int passes = 6;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
vec4 c1 = vec4(0.0);
|
||||||
|
vec4 c2 = vec4(0.0);
|
||||||
|
|
||||||
|
float disp = intensity * (0.5 - distance(0.5, progress));
|
||||||
|
for (int xi = 0; xi < passes; xi++) {
|
||||||
|
float x = float(xi) / float(passes) - 0.5;
|
||||||
|
for (int yi = 0; yi < passes; yi++) {
|
||||||
|
float y = float(yi) / float(passes) - 0.5;
|
||||||
|
vec2 v = vec2(x, y);
|
||||||
|
c1 += getFromColor(uv + disp * v);
|
||||||
|
c2 += getToColor(uv + disp * v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c1 /= float(passes * passes);
|
||||||
|
c2 /= float(passes * passes);
|
||||||
|
return mix(c1, c2, progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Split slide-in (horizontal), from gl-transitions.com
|
||||||
|
// Author: OllyOllyOlly (MIT)
|
||||||
|
// Description: the incoming image slides in as two halves moving horizontally
|
||||||
|
// in opposite directions.
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||||
|
// Optional gl-transitions params are fixed here: reverse = false.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const vec2 boundMin = vec2(0.0, 0.0);
|
||||||
|
const vec2 boundMax = vec2(1.0, 1.0);
|
||||||
|
|
||||||
|
bool inBounds(vec2 p) {
|
||||||
|
return all(lessThan(boundMin, p)) && all(lessThan(p, boundMax));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
float modifier = 1.0;
|
||||||
|
vec2 toP = (uv.y > 0.5)
|
||||||
|
? vec2((uv.x - (progress * modifier)) + modifier, uv.y)
|
||||||
|
: vec2((uv.x + (progress * modifier)) - modifier, uv.y);
|
||||||
|
|
||||||
|
return inBounds(toP) ? getToColor(toP) : getFromColor(uv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Tiles wave reveal, from gl-transitions.com
|
||||||
|
// Author: numb3r23 (MIT)
|
||||||
|
// Description: the incoming image is revealed tile by tile in a diagonal wave.
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||||
|
// Optional gl-transitions params are fixed here: tileCount = (8, 8),
|
||||||
|
// flipX = true, flipY = false.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const ivec2 tileCount = ivec2(8, 8);
|
||||||
|
const bool flipX = true;
|
||||||
|
const bool flipY = false;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
vec2 tileSize = 1.0 / vec2(tileCount);
|
||||||
|
vec2 posInTile = fract(uv * vec2(tileCount));
|
||||||
|
vec2 tileNum = floor(uv * vec2(tileCount));
|
||||||
|
float countTiles = float(tileCount.x * tileCount.y);
|
||||||
|
|
||||||
|
// Diagonal wave from bottom-left to top-right.
|
||||||
|
float offset = (tileNum.y + tileNum.x * float(tileCount.y)) / countTiles;
|
||||||
|
float timeOffset = clamp((progress - offset) * countTiles, 0.0, 0.5);
|
||||||
|
float sinTime = 1.0 - abs(cos(fract(timeOffset) * 3.1415926));
|
||||||
|
|
||||||
|
vec2 texC = posInTile;
|
||||||
|
|
||||||
|
if (sinTime <= 0.5) {
|
||||||
|
if (flipX) {
|
||||||
|
if (texC.x < sinTime || texC.x > 1.0 - sinTime)
|
||||||
|
return getFromColor(uv);
|
||||||
|
texC.x = texC.x < 0.5
|
||||||
|
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
|
||||||
|
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||||
|
}
|
||||||
|
if (flipY) {
|
||||||
|
if (texC.y < sinTime || texC.y > 1.0 - sinTime)
|
||||||
|
return getFromColor(uv);
|
||||||
|
texC.y = texC.y < 0.5
|
||||||
|
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
|
||||||
|
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||||
|
}
|
||||||
|
vec2 globalUV = tileNum * tileSize + texC * tileSize;
|
||||||
|
return getFromColor(globalUV);
|
||||||
|
} else {
|
||||||
|
if (flipX) {
|
||||||
|
if (texC.x > sinTime || texC.x < 1.0 - sinTime)
|
||||||
|
return getToColor(uv);
|
||||||
|
texC.x = texC.x < 0.5
|
||||||
|
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
|
||||||
|
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||||
|
texC.x = 1.0 - texC.x;
|
||||||
|
}
|
||||||
|
if (flipY) {
|
||||||
|
if (texC.y > sinTime || texC.y < 1.0 - sinTime)
|
||||||
|
return getToColor(uv);
|
||||||
|
texC.y = texC.y < 0.5
|
||||||
|
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
|
||||||
|
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
|
||||||
|
texC.y = 1.0 - texC.y;
|
||||||
|
}
|
||||||
|
vec2 globalUV = tileNum * tileSize + texC * tileSize;
|
||||||
|
return getToColor(globalUV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Window slices wipe, from gl-transitions.com
|
||||||
|
// Author: gre (MIT)
|
||||||
|
// Description: the incoming image appears through vertical slices that widen.
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||||
|
// Optional gl-transitions params are fixed here: count = 10.0,
|
||||||
|
// smoothness = 0.5.
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
const float count = 10.0;
|
||||||
|
const float smoothness = 0.5;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
float pr = smoothstep(-smoothness, 0.0, uv.x - progress * (1.0 + smoothness));
|
||||||
|
float s = step(pr, fract(count * uv.x));
|
||||||
|
return mix(getFromColor(uv), getToColor(uv), s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Hard-edged wipe from top to bottom, from gl-transitions.com
|
||||||
|
// Author: Jake Nelson (MIT)
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
return mix(getFromColor(uv), getToColor(uv), step(1.0 - uv.y, progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Hard-edged wipe from left to right, from gl-transitions.com
|
||||||
|
// Author: Jake Nelson (MIT)
|
||||||
|
//
|
||||||
|
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
|
||||||
|
|
||||||
|
uniform sampler2D from;
|
||||||
|
uniform sampler2D to;
|
||||||
|
uniform vec2 size;
|
||||||
|
uniform vec4 fromCrop;
|
||||||
|
uniform vec4 toCrop;
|
||||||
|
uniform float progress;
|
||||||
|
uniform float fromFlip;
|
||||||
|
uniform float toFlip;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
vec4 getFromColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(from, fromCrop.xy + s * fromCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 getToColor(vec2 uv) {
|
||||||
|
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
|
||||||
|
return texture(to, toCrop.xy + s * toCrop.zw);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 transition(vec2 uv) {
|
||||||
|
return mix(getFromColor(uv), getToColor(uv), step(0.0 + uv.x, progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = gl_FragCoord.xy / size;
|
||||||
|
outColor = transition(uv);
|
||||||
|
}
|
||||||
@@ -184,9 +184,13 @@ void VideoPlayer::closeDecoder() {
|
|||||||
m_streamIndex = -1;
|
m_streamIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::decodeLoop(const QString& path) {
|
void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
||||||
|
if (!m_threadRunning.load(std::memory_order_acquire) ||
|
||||||
|
m_generation.load(std::memory_order_acquire) != generation)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!openDecoder(path)) {
|
if (!openDecoder(path)) {
|
||||||
m_threadRunning = false;
|
m_threadRunning.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,13 +202,14 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
|||||||
|
|
||||||
AVFrame* frame = av_frame_alloc();
|
AVFrame* frame = av_frame_alloc();
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
m_threadRunning = false;
|
m_threadRunning.store(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AVPacket pkt{};
|
AVPacket pkt{};
|
||||||
|
|
||||||
while (m_threadRunning) {
|
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||||
|
m_generation.load(std::memory_order_acquire) == generation) {
|
||||||
int ret = av_read_frame(m_fmt, &pkt);
|
int ret = av_read_frame(m_fmt, &pkt);
|
||||||
if (ret == AVERROR_EOF) {
|
if (ret == AVERROR_EOF) {
|
||||||
if (avformat_seek_file(
|
if (avformat_seek_file(
|
||||||
@@ -230,7 +235,9 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
|||||||
}
|
}
|
||||||
av_packet_unref(&pkt);
|
av_packet_unref(&pkt);
|
||||||
|
|
||||||
while (m_threadRunning && avcodec_receive_frame(m_codec, frame) == 0) {
|
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||||
|
m_generation.load(std::memory_order_acquire) == generation &&
|
||||||
|
avcodec_receive_frame(m_codec, frame) == 0) {
|
||||||
bool wasEmpty = false;
|
bool wasEmpty = false;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
@@ -251,21 +258,34 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
|||||||
|
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
closeDecoder();
|
closeDecoder();
|
||||||
m_threadRunning = false;
|
m_threadRunning.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoPlayer::start(const QString& path) {
|
bool VideoPlayer::start(const QString& path) {
|
||||||
stop();
|
const uint32_t generation =
|
||||||
|
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||||
|
m_threadRunning.store(true);
|
||||||
|
|
||||||
if (!ensureCudaContext()) return false;
|
std::thread previous = std::move(m_thread);
|
||||||
|
m_thread = std::thread(
|
||||||
|
[this, path, generation, previous = std::move(previous)]() mutable {
|
||||||
|
if (previous.joinable()) previous.join();
|
||||||
|
closeDecoder();
|
||||||
|
|
||||||
|
if (!ensureCudaContext()) {
|
||||||
|
m_threadRunning.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeLoop(path, generation);
|
||||||
|
});
|
||||||
|
|
||||||
m_threadRunning = true;
|
|
||||||
m_thread = std::thread([this, path]() { decodeLoop(path); });
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::stop() {
|
void VideoPlayer::stop() {
|
||||||
m_threadRunning = false;
|
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
m_threadRunning.store(false);
|
||||||
if (m_thread.joinable()) m_thread.join();
|
if (m_thread.joinable()) m_thread.join();
|
||||||
|
|
||||||
closeDecoder();
|
closeDecoder();
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class VideoPlayer {
|
|||||||
[[nodiscard]] QSize frameSize() const;
|
[[nodiscard]] QSize frameSize() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void decodeLoop(const QString& path);
|
void decodeLoop(const QString& path, uint32_t generation);
|
||||||
bool openDecoder(const QString& path);
|
bool openDecoder(const QString& path);
|
||||||
void closeDecoder();
|
void closeDecoder();
|
||||||
|
|
||||||
@@ -60,6 +60,7 @@ class VideoPlayer {
|
|||||||
|
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
std::atomic<bool> m_threadRunning{false};
|
std::atomic<bool> m_threadRunning{false};
|
||||||
|
std::atomic<uint32_t> m_generation{0};
|
||||||
|
|
||||||
mutable std::mutex m_mtx;
|
mutable std::mutex m_mtx;
|
||||||
AVFrame* m_pending = nullptr;
|
AVFrame* m_pending = nullptr;
|
||||||
|
|||||||
@@ -67,8 +67,46 @@ void WallpaperObject::setZoom(qreal zoom) {
|
|||||||
push();
|
push();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WallpaperObject::syncCrop(const QVariantMap& crop) {
|
||||||
|
const qreal x = crop.value("x").toDouble();
|
||||||
|
const qreal y = crop.value("y").toDouble();
|
||||||
|
const qreal width = crop.contains("width") ? crop.value("width").toDouble()
|
||||||
|
: 1.0;
|
||||||
|
const qreal height = crop.contains("height")
|
||||||
|
? crop.value("height").toDouble()
|
||||||
|
: 1.0;
|
||||||
|
const qreal z =
|
||||||
|
crop.contains("zoom") ? crop.value("zoom").toDouble() : 1.0;
|
||||||
|
|
||||||
|
const bool prev = m_pushEnabled;
|
||||||
|
m_pushEnabled = false;
|
||||||
|
|
||||||
|
if (!qFuzzyCompare(m_cropX, x)) {
|
||||||
|
m_cropX = x;
|
||||||
|
emit cropXChanged();
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(m_cropY, y)) {
|
||||||
|
m_cropY = y;
|
||||||
|
emit cropYChanged();
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(m_cropWidth, width)) {
|
||||||
|
m_cropWidth = width;
|
||||||
|
emit cropWidthChanged();
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(m_cropHeight, height)) {
|
||||||
|
m_cropHeight = height;
|
||||||
|
emit cropHeightChanged();
|
||||||
|
}
|
||||||
|
if (!qFuzzyCompare(m_zoom, z)) {
|
||||||
|
m_zoom = z;
|
||||||
|
emit zoomChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pushEnabled = prev;
|
||||||
|
}
|
||||||
|
|
||||||
void WallpaperObject::push() {
|
void WallpaperObject::push() {
|
||||||
if (!m_service || m_screen.isEmpty()) return;
|
if (!m_pushEnabled || !m_service || m_screen.isEmpty()) return;
|
||||||
|
|
||||||
QVariantMap crop;
|
QVariantMap crop;
|
||||||
crop.insert("x", m_cropX);
|
crop.insert("x", m_cropX);
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ class WallpaperObject : public QObject {
|
|||||||
qreal zoom() const { return m_zoom; }
|
qreal zoom() const { return m_zoom; }
|
||||||
void setZoom(qreal zoom);
|
void setZoom(qreal zoom);
|
||||||
|
|
||||||
|
Q_INVOKABLE void syncCrop(const QVariantMap& crop);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void serviceChanged();
|
void serviceChanged();
|
||||||
void screenChanged();
|
void screenChanged();
|
||||||
@@ -77,6 +79,8 @@ class WallpaperObject : public QObject {
|
|||||||
qreal m_cropWidth = 1.0;
|
qreal m_cropWidth = 1.0;
|
||||||
qreal m_cropHeight = 1.0;
|
qreal m_cropHeight = 1.0;
|
||||||
qreal m_zoom = 1.0;
|
qreal m_zoom = 1.0;
|
||||||
|
|
||||||
|
bool m_pushEnabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QHash>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QRectF>
|
#include <QRectF>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
#include <QVector>
|
||||||
#include <QtQml/qqml.h>
|
#include <QtQml/qqml.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
#include <wayland-client-protocol.h>
|
#include <wayland-client-protocol.h>
|
||||||
|
|
||||||
#include "wallpapersurface.hpp"
|
#include "wallpapersurface.hpp"
|
||||||
|
#include "config/enums.hpp"
|
||||||
|
|
||||||
struct mpv_handle;
|
struct mpv_handle;
|
||||||
struct mpv_render_context;
|
struct mpv_render_context;
|
||||||
@@ -131,8 +134,76 @@ class WallpaperService : public QObject {
|
|||||||
|
|
||||||
void storeGlobal(const char* interface, void* proxy);
|
void storeGlobal(const char* interface, void* proxy);
|
||||||
|
|
||||||
|
struct TransProgram {
|
||||||
|
uint32_t id = 0;
|
||||||
|
int size = -1;
|
||||||
|
int progress = -1;
|
||||||
|
int from = -1;
|
||||||
|
int to = -1;
|
||||||
|
int fromCrop = -1;
|
||||||
|
int toCrop = -1;
|
||||||
|
int fromFlip = -1;
|
||||||
|
int toFlip = -1;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] bool setupGl();
|
[[nodiscard]] bool setupGl();
|
||||||
[[nodiscard]] uint32_t createProgram();
|
[[nodiscard]] uint32_t createProgram();
|
||||||
|
[[nodiscard]] TransProgram createTransitionProgram(const QString& file);
|
||||||
|
[[nodiscard]] TransProgram programFor(const QString& file);
|
||||||
|
bool ensureTransitionProgram();
|
||||||
|
QString transitionFileFor(ZShell::config::WallpaperTransition::Enum value);
|
||||||
|
[[nodiscard]] QString randomTransitionFile();
|
||||||
|
|
||||||
|
void ensureCapture(const QSize& size);
|
||||||
|
uint32_t renderVideoIntoTexture(const QSize& size);
|
||||||
|
|
||||||
|
void drawSurfaceTransition(WallpaperSurface* surface);
|
||||||
|
|
||||||
|
struct TransitionLayer {
|
||||||
|
uint32_t tex = 0; // frozen image target (older layers)
|
||||||
|
QImage image; // CPU copy for crops/sizing (image layers)
|
||||||
|
QVariantMap crops; // crop map captured when the layer was pushed
|
||||||
|
QString transitionFile; // this layer's own transition shader
|
||||||
|
bool isVideo = false; // only the newest layer may be video
|
||||||
|
bool ready = false; // target is sampleable
|
||||||
|
bool started = false; // its own clock has begun
|
||||||
|
QElapsedTimer timer;
|
||||||
|
qreal progress = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ensureBaseFbo();
|
||||||
|
void ensureAccum(const QSize& size);
|
||||||
|
void bindRtt(uint32_t fbo, uint32_t tex, const QSize& size);
|
||||||
|
void drawTransitionPair(
|
||||||
|
const TransProgram& program,
|
||||||
|
uint32_t fromTex,
|
||||||
|
float fromFlip,
|
||||||
|
const QRectF& fromCrop,
|
||||||
|
uint32_t toTex,
|
||||||
|
float toFlip,
|
||||||
|
const QRectF& toCrop,
|
||||||
|
qreal progress,
|
||||||
|
const QSize& size);
|
||||||
|
uint32_t targetTexFor(const TransitionLayer& layer, int index) const;
|
||||||
|
QRectF cropForLayer(
|
||||||
|
const TransitionLayer& layer,
|
||||||
|
const QString& screen,
|
||||||
|
const QSize& size) const;
|
||||||
|
void captureBase(
|
||||||
|
const QSize& size,
|
||||||
|
const QString& screen,
|
||||||
|
uint32_t oldTex,
|
||||||
|
float oldFlip,
|
||||||
|
const QRectF& oldCrop);
|
||||||
|
void renderComposite(
|
||||||
|
const QSize& size,
|
||||||
|
const QString& screen,
|
||||||
|
uint32_t outFbo,
|
||||||
|
uint32_t outTex,
|
||||||
|
int first,
|
||||||
|
int last,
|
||||||
|
uint32_t videoTex,
|
||||||
|
uint32_t posterTex);
|
||||||
|
|
||||||
void uploadTexture(uint32_t& texture, const QImage& image);
|
void uploadTexture(uint32_t& texture, const QImage& image);
|
||||||
void uploadImage();
|
void uploadImage();
|
||||||
@@ -150,8 +221,14 @@ class WallpaperService : public QObject {
|
|||||||
|
|
||||||
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
|
||||||
|
|
||||||
void ensureImage();
|
void ensureImage();
|
||||||
|
|
||||||
|
void ensurePoster();
|
||||||
|
|
||||||
void notifyBufferSizes();
|
void notifyBufferSizes();
|
||||||
|
|
||||||
void queue(WallpaperSurface* surface);
|
void queue(WallpaperSurface* surface);
|
||||||
@@ -187,7 +264,6 @@ class WallpaperService : public QObject {
|
|||||||
|
|
||||||
bool m_isVideo = false;
|
bool m_isVideo = false;
|
||||||
bool m_videoReload = false;
|
bool m_videoReload = false;
|
||||||
bool m_previousVideo = false;
|
|
||||||
mpv_handle* m_mpv = nullptr;
|
mpv_handle* m_mpv = nullptr;
|
||||||
mpv_render_context* m_mpvRender = nullptr;
|
mpv_render_context* m_mpvRender = nullptr;
|
||||||
uint32_t m_blackTex = 0;
|
uint32_t m_blackTex = 0;
|
||||||
@@ -223,6 +299,45 @@ class WallpaperService : public QObject {
|
|||||||
int m_alphaUniform = -1;
|
int m_alphaUniform = -1;
|
||||||
int m_sourceUniform = -1;
|
int m_sourceUniform = -1;
|
||||||
|
|
||||||
|
bool m_randomTransition = false; // wallTransition == Random
|
||||||
|
QString m_transitionFile; // the transition stamped onto new layers
|
||||||
|
bool m_transitionDirty = false;
|
||||||
|
QHash<QString, TransProgram>
|
||||||
|
m_transPrograms; // compiled programs, keyed by shader
|
||||||
|
TransProgram
|
||||||
|
m_neutralProgram; // fade program for identity copies (seed/base/final)
|
||||||
|
QVector<int>
|
||||||
|
m_transitionUses; // usage counts, parallel to the transition list
|
||||||
|
QString m_lastTransition; // last random pick, so it is never repeated
|
||||||
|
|
||||||
|
uint32_t m_captureFbo = 0;
|
||||||
|
uint32_t m_captureTex = 0;
|
||||||
|
QSize m_captureSize;
|
||||||
|
|
||||||
|
uint32_t m_baseFbo = 0;
|
||||||
|
uint32_t m_baseTex = 0;
|
||||||
|
QSize m_baseSize;
|
||||||
|
bool m_baseReady = false;
|
||||||
|
|
||||||
|
uint32_t m_accumFbo = 0;
|
||||||
|
uint32_t m_accumA = 0;
|
||||||
|
uint32_t m_accumB = 0;
|
||||||
|
QSize m_accumSize;
|
||||||
|
|
||||||
|
std::vector<TransitionLayer> m_transitions; // oldest .. newest
|
||||||
|
|
||||||
|
bool m_needCaptureBase = false;
|
||||||
|
QImage m_baseImage; // old settled image, for the base capture crop
|
||||||
|
QVariantMap m_baseCrops; // old settled crop map, for the base capture crop
|
||||||
|
bool m_baseIsVideo = false; // old settled frame was a live video
|
||||||
|
bool m_baseIsBlack = false; // shell start / first load: fade in from black
|
||||||
|
bool m_collapseVideo =
|
||||||
|
false; // video pushed mid-stack: bake older into base
|
||||||
|
bool m_promoteOldest = false; // stack over the cap: promote oldest to base
|
||||||
|
bool m_newestDone = false; // newest layer finished: settle into base
|
||||||
|
|
||||||
|
static constexpr int kMaxTransitions = 4;
|
||||||
|
|
||||||
uint32_t m_videoProgram = 0;
|
uint32_t m_videoProgram = 0;
|
||||||
uint32_t m_vao = 0;
|
uint32_t m_vao = 0;
|
||||||
int m_videoSizeUniform = -1;
|
int m_videoSizeUniform = -1;
|
||||||
@@ -240,14 +355,7 @@ class WallpaperService : public QObject {
|
|||||||
bool m_usePlayer = true;
|
bool m_usePlayer = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QImage m_previousImage;
|
|
||||||
QVariantMap m_previousCrops;
|
|
||||||
uint32_t m_previousTexture = 0;
|
|
||||||
std::vector<uint32_t> m_orphanTextures;
|
std::vector<uint32_t> m_orphanTextures;
|
||||||
bool m_fading = false;
|
|
||||||
bool m_fadeClockStarted = false;
|
|
||||||
bool m_firstFadeDone = false;
|
|
||||||
QElapsedTimer m_fadeTimer;
|
|
||||||
|
|
||||||
QList<WallpaperSurface*> m_surfaces;
|
QList<WallpaperSurface*> m_surfaces;
|
||||||
|
|
||||||
@@ -264,6 +372,13 @@ class WallpaperService : public QObject {
|
|||||||
|
|
||||||
bool m_forceDecode = false;
|
bool m_forceDecode = false;
|
||||||
bool m_imageChanged = false;
|
bool m_imageChanged = false;
|
||||||
|
|
||||||
|
QImage m_posterImage;
|
||||||
|
uint32_t m_posterTex = 0;
|
||||||
|
QString m_posterPath;
|
||||||
|
bool m_posterReady = false;
|
||||||
|
bool m_posterLoading = false;
|
||||||
|
bool m_posterChanged = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <qfuturewatcher.h>
|
#include <qfuturewatcher.h>
|
||||||
#include <qimage.h>
|
#include <qimage.h>
|
||||||
#include <qimagereader.h>
|
#include <qimagereader.h>
|
||||||
|
#include <qjsondocument.h>
|
||||||
|
#include <qjsonobject.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qsqldatabase.h>
|
#include <qsqldatabase.h>
|
||||||
@@ -343,8 +345,8 @@ void WallpaperDb::setCacheSize(const QSize& cacheSize) {
|
|||||||
if (db.isOpen()) {
|
if (db.isOpen()) {
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
query.prepare(
|
query.prepare(
|
||||||
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL"
|
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL,"
|
||||||
" WHERE path = ?");
|
" poster = NULL WHERE path = ?");
|
||||||
query.addBindValue(entry->path());
|
query.addBindValue(entry->path());
|
||||||
query.exec();
|
query.exec();
|
||||||
}
|
}
|
||||||
@@ -413,7 +415,8 @@ void WallpaperDb::openDb() {
|
|||||||
query.exec(
|
query.exec(
|
||||||
"CREATE TABLE IF NOT EXISTS wallpapers("
|
"CREATE TABLE IF NOT EXISTS wallpapers("
|
||||||
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
|
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
|
||||||
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB)");
|
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB,"
|
||||||
|
" poster BLOB, crop TEXT)");
|
||||||
|
|
||||||
query.exec("SELECT cache FROM wallpapers LIMIT 1");
|
query.exec("SELECT cache FROM wallpapers LIMIT 1");
|
||||||
if (query.lastError().isValid()) {
|
if (query.lastError().isValid()) {
|
||||||
@@ -421,9 +424,12 @@ void WallpaperDb::openDb() {
|
|||||||
query.exec(
|
query.exec(
|
||||||
"CREATE TABLE wallpapers("
|
"CREATE TABLE wallpapers("
|
||||||
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
|
"path TEXT PRIMARY KEY, kind TEXT, codec TEXT, hardware INTEGER,"
|
||||||
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB)");
|
" size INTEGER, mtime INTEGER, cache BLOB, scheme_thumb BLOB,"
|
||||||
|
" poster BLOB, crop TEXT)");
|
||||||
} else {
|
} else {
|
||||||
query.exec("ALTER TABLE wallpapers ADD COLUMN scheme_thumb BLOB");
|
query.exec("ALTER TABLE wallpapers ADD COLUMN scheme_thumb BLOB");
|
||||||
|
query.exec("ALTER TABLE wallpapers ADD COLUMN poster BLOB");
|
||||||
|
query.exec("ALTER TABLE wallpapers ADD COLUMN crop TEXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
registerProvider();
|
registerProvider();
|
||||||
@@ -538,6 +544,7 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete it.value();
|
delete it.value();
|
||||||
|
m_crops.remove(it.key());
|
||||||
it = m_byPath.erase(it);
|
it = m_byPath.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
@@ -575,24 +582,28 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
|||||||
if (db.isOpen()) {
|
if (db.isOpen()) {
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
query.prepare(
|
query.prepare(
|
||||||
"SELECT cache, scheme_thumb, size, mtime FROM wallpapers"
|
"SELECT cache, scheme_thumb, poster, size, mtime, crop"
|
||||||
" WHERE path = ?");
|
" FROM wallpapers WHERE path = ?");
|
||||||
query.addBindValue(path);
|
query.addBindValue(path);
|
||||||
query.exec();
|
query.exec();
|
||||||
|
|
||||||
if (query.next()) {
|
if (query.next()) {
|
||||||
const auto cache = query.value(0).toByteArray();
|
const auto cache = query.value(0).toByteArray();
|
||||||
const auto scheme = query.value(1).toByteArray();
|
const auto scheme = query.value(1).toByteArray();
|
||||||
const auto storedSize = query.value(2).toLongLong();
|
const auto poster = query.value(2).toByteArray();
|
||||||
const auto storedMtime = query.value(3).toLongLong();
|
const auto storedSize = query.value(3).toLongLong();
|
||||||
|
const auto storedMtime = query.value(4).toLongLong();
|
||||||
|
loadCrop(path, query.value(5).toString());
|
||||||
|
|
||||||
|
const bool posterOk = !isVideo || !poster.isEmpty();
|
||||||
|
|
||||||
if (storedSize == size && storedMtime == mtime &&
|
if (storedSize == size && storedMtime == mtime &&
|
||||||
!cache.isEmpty()) {
|
!cache.isEmpty()) {
|
||||||
reused = true;
|
reused = true;
|
||||||
entry->setThumbnailPath(thumbnailUrl(path));
|
entry->setThumbnailPath(thumbnailUrl(path));
|
||||||
|
|
||||||
if (scheme.isEmpty()) {
|
if (scheme.isEmpty() || !posterOk) {
|
||||||
queueMetadata(entry, isVideo, cache);
|
queueMetadata(entry, isVideo, cache, poster);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,9 +613,13 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
|||||||
if (db.isOpen()) {
|
if (db.isOpen()) {
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
query.prepare(
|
query.prepare(
|
||||||
"INSERT OR REPLACE INTO wallpapers"
|
"INSERT INTO wallpapers"
|
||||||
" (path, kind, hardware, size, mtime, cache)"
|
" (path, kind, hardware, size, mtime, cache)"
|
||||||
" VALUES (?, ?, ?, ?, ?, NULL)");
|
" VALUES (?, ?, ?, ?, ?, NULL)"
|
||||||
|
" ON CONFLICT(path) DO UPDATE SET kind = excluded.kind,"
|
||||||
|
" hardware = excluded.hardware, size = excluded.size,"
|
||||||
|
" mtime = excluded.mtime, cache = NULL,"
|
||||||
|
" scheme_thumb = NULL, poster = NULL");
|
||||||
query.addBindValue(path);
|
query.addBindValue(path);
|
||||||
query.addBindValue(isVideo ? "video" : "image");
|
query.addBindValue(isVideo ? "video" : "image");
|
||||||
query.addBindValue(
|
query.addBindValue(
|
||||||
@@ -626,7 +641,10 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperDb::queueMetadata(
|
void WallpaperDb::queueMetadata(
|
||||||
WallpaperEntry* entry, bool isVideo, const QByteArray& existingCache) {
|
WallpaperEntry* entry,
|
||||||
|
bool isVideo,
|
||||||
|
const QByteArray& existingCache,
|
||||||
|
const QByteArray& existingPoster) {
|
||||||
qCInfo(
|
qCInfo(
|
||||||
dbLog,
|
dbLog,
|
||||||
"WallpaperDb: queued cache job for %s (%s%s)",
|
"WallpaperDb: queued cache job for %s (%s%s)",
|
||||||
@@ -634,7 +652,8 @@ void WallpaperDb::queueMetadata(
|
|||||||
isVideo ? "video" : "image",
|
isVideo ? "video" : "image",
|
||||||
existingCache.isEmpty() ? "" : ", existing blob");
|
existingCache.isEmpty() ? "" : ", existing blob");
|
||||||
|
|
||||||
m_queue.append(MetadataJob{entry, entry->path(), isVideo, existingCache});
|
m_queue.append(
|
||||||
|
MetadataJob{entry, entry->path(), isVideo, existingCache, existingPoster});
|
||||||
|
|
||||||
if (!m_queueTimer.isActive()) {
|
if (!m_queueTimer.isActive()) {
|
||||||
m_queueTimer.start();
|
m_queueTimer.start();
|
||||||
@@ -672,10 +691,12 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
|||||||
struct CacheResult {
|
struct CacheResult {
|
||||||
QString path;
|
QString path;
|
||||||
QByteArray cache;
|
QByteArray cache;
|
||||||
|
QByteArray poster;
|
||||||
QByteArray scheme;
|
QByteArray scheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
const QSize cacheSize = m_cacheSize;
|
const QSize cacheSize = m_cacheSize;
|
||||||
|
const bool isVideoJob = job.isVideo;
|
||||||
|
|
||||||
QElapsedTimer clock;
|
QElapsedTimer clock;
|
||||||
clock.start();
|
clock.start();
|
||||||
@@ -685,7 +706,7 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
|||||||
watcher,
|
watcher,
|
||||||
&QFutureWatcher<CacheResult>::finished,
|
&QFutureWatcher<CacheResult>::finished,
|
||||||
this,
|
this,
|
||||||
[this, watcher, clock]() {
|
[this, watcher, clock, isVideoJob]() {
|
||||||
const auto result = watcher->result();
|
const auto result = watcher->result();
|
||||||
|
|
||||||
qCInfo(
|
qCInfo(
|
||||||
@@ -695,15 +716,27 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
|||||||
int(result.cache.size()),
|
int(result.cache.size()),
|
||||||
int(clock.elapsed()));
|
int(clock.elapsed()));
|
||||||
|
|
||||||
|
qCInfo(
|
||||||
|
dbLog,
|
||||||
|
"WallpaperDb: %s poster %d bytes",
|
||||||
|
result.path.toUtf8().constData(),
|
||||||
|
int(result.poster.size()));
|
||||||
|
|
||||||
|
if (isVideoJob && result.poster.isEmpty()) {
|
||||||
|
qWarning(dbLog)
|
||||||
|
<< "WallpaperDb: no poster produced for" << result.path;
|
||||||
|
}
|
||||||
|
|
||||||
if (QSqlDatabase::contains(m_uuid)) {
|
if (QSqlDatabase::contains(m_uuid)) {
|
||||||
const auto db = QSqlDatabase::database(m_uuid, false);
|
const auto db = QSqlDatabase::database(m_uuid, false);
|
||||||
if (db.isOpen()) {
|
if (db.isOpen()) {
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
query.prepare(
|
query.prepare(
|
||||||
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?"
|
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?,"
|
||||||
" WHERE path = ?");
|
" poster = ? WHERE path = ?");
|
||||||
query.addBindValue(result.cache);
|
query.addBindValue(result.cache);
|
||||||
query.addBindValue(result.scheme);
|
query.addBindValue(result.scheme);
|
||||||
|
query.addBindValue(result.poster);
|
||||||
query.addBindValue(result.path);
|
query.addBindValue(result.path);
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning(dbLog)
|
qWarning(dbLog)
|
||||||
@@ -736,7 +769,14 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
|||||||
: imageCacheBytes(job.path, cacheSize);
|
: imageCacheBytes(job.path, cacheSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray poster = job.existingPoster;
|
||||||
|
if (poster.isEmpty()) {
|
||||||
|
poster =
|
||||||
|
job.isVideo ? videoPosterBytes(job.path, cacheSize) : cache;
|
||||||
|
}
|
||||||
|
|
||||||
result.cache = cache;
|
result.cache = cache;
|
||||||
|
result.poster = poster;
|
||||||
result.scheme = cache.isEmpty() ? QByteArray()
|
result.scheme = cache.isEmpty() ? QByteArray()
|
||||||
: schemeThumbBytes(cache, 128);
|
: schemeThumbBytes(cache, 128);
|
||||||
|
|
||||||
@@ -781,4 +821,56 @@ bool WallpaperDb::entryAllowed(const WallpaperEntry* entry) const {
|
|||||||
return entry->hardware();
|
return entry->hardware();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantMap WallpaperDb::crop(const QString& path, const QString& screen) const {
|
||||||
|
const auto pathCrops = m_crops.constFind(path);
|
||||||
|
if (pathCrops == m_crops.constEnd()) return {};
|
||||||
|
return pathCrops.value().value(screen).toMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WallpaperDb::setCrop(
|
||||||
|
const QString& path, const QString& screen, const QVariantMap& crop) {
|
||||||
|
if (path.isEmpty() || screen.isEmpty()) return;
|
||||||
|
|
||||||
|
QVariantMap& pathCrops = m_crops[path];
|
||||||
|
pathCrops[screen] = crop;
|
||||||
|
storeCropJson(path);
|
||||||
|
emit cropsChanged(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WallpaperDb::loadCrop(const QString& path, const QString& json) {
|
||||||
|
if (json.isEmpty()) {
|
||||||
|
m_crops.remove(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto doc = QJsonDocument::fromJson(json.toUtf8());
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
m_crops.remove(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_crops[path] = doc.object().toVariantMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WallpaperDb::storeCropJson(const QString& path) {
|
||||||
|
const auto db = QSqlDatabase::database(m_uuid, false);
|
||||||
|
if (!db.isOpen()) return;
|
||||||
|
|
||||||
|
const auto pathCrops = m_crops.value(path);
|
||||||
|
const QByteArray json =
|
||||||
|
pathCrops.isEmpty()
|
||||||
|
? QByteArray()
|
||||||
|
: QJsonDocument(QJsonObject::fromVariantMap(pathCrops))
|
||||||
|
.toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
|
QSqlQuery query(db);
|
||||||
|
query.prepare("UPDATE wallpapers SET crop = ? WHERE path = ?");
|
||||||
|
query.addBindValue(json.isEmpty() ? QVariant() : QVariant(json));
|
||||||
|
query.addBindValue(path);
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning(dbLog) << "WallpaperDb: could not store the crop for" << path
|
||||||
|
<< query.lastError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ZShell::models
|
} // namespace ZShell::models
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qsize.h>
|
#include <qsize.h>
|
||||||
#include <qtimer.h>
|
#include <qtimer.h>
|
||||||
|
#include <qvariant.h>
|
||||||
#include <qvector.h>
|
#include <qvector.h>
|
||||||
|
|
||||||
namespace ZShell::models {
|
namespace ZShell::models {
|
||||||
@@ -109,6 +110,11 @@ class WallpaperDb : public QObject {
|
|||||||
|
|
||||||
[[nodiscard]] static QString thumbnailUrl(const QString& path);
|
[[nodiscard]] static QString thumbnailUrl(const QString& path);
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariantMap crop(const QString& path, const QString& screen)
|
||||||
|
const;
|
||||||
|
Q_INVOKABLE void setCrop(
|
||||||
|
const QString& path, const QString& screen, const QVariantMap& crop);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void dbPathChanged();
|
void dbPathChanged();
|
||||||
@@ -117,6 +123,7 @@ class WallpaperDb : public QObject {
|
|||||||
void cacheSizeChanged();
|
void cacheSizeChanged();
|
||||||
|
|
||||||
void cacheStored(const QString& path);
|
void cacheStored(const QString& path);
|
||||||
|
void cropsChanged(const QString& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FileEntry {
|
struct FileEntry {
|
||||||
@@ -134,6 +141,7 @@ class WallpaperDb : public QObject {
|
|||||||
QString path;
|
QString path;
|
||||||
bool isVideo;
|
bool isVideo;
|
||||||
QByteArray existingCache;
|
QByteArray existingCache;
|
||||||
|
QByteArray existingPoster;
|
||||||
};
|
};
|
||||||
|
|
||||||
void openDb();
|
void openDb();
|
||||||
@@ -144,11 +152,15 @@ class WallpaperDb : public QObject {
|
|||||||
void queueMetadata(
|
void queueMetadata(
|
||||||
WallpaperEntry* entry,
|
WallpaperEntry* entry,
|
||||||
bool isVideo,
|
bool isVideo,
|
||||||
const QByteArray& existingCache = {});
|
const QByteArray& existingCache = {},
|
||||||
|
const QByteArray& existingPoster = {});
|
||||||
void processQueue();
|
void processQueue();
|
||||||
void runMetadataJob(const MetadataJob& job);
|
void runMetadataJob(const MetadataJob& job);
|
||||||
void rebuildEntries();
|
void rebuildEntries();
|
||||||
|
|
||||||
|
void loadCrop(const QString& path, const QString& json);
|
||||||
|
void storeCropJson(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] bool entryAllowed(const WallpaperEntry* entry) const;
|
[[nodiscard]] bool entryAllowed(const WallpaperEntry* entry) const;
|
||||||
|
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
@@ -163,6 +175,8 @@ class WallpaperDb : public QObject {
|
|||||||
QList<WallpaperEntry*> m_entries;
|
QList<WallpaperEntry*> m_entries;
|
||||||
QHash<QString, WallpaperEntry*> m_byPath;
|
QHash<QString, WallpaperEntry*> m_byPath;
|
||||||
|
|
||||||
|
QHash<QString, QVariantMap> m_crops;
|
||||||
|
|
||||||
QVector<MetadataJob> m_queue;
|
QVector<MetadataJob> m_queue;
|
||||||
QTimer m_queueTimer;
|
QTimer m_queueTimer;
|
||||||
bool m_jobRunning = false;
|
bool m_jobRunning = false;
|
||||||
|
|||||||
@@ -285,6 +285,91 @@ inline QByteArray videoCacheBytes(const QString& src, const QSize& fit) {
|
|||||||
return frame.readAll();
|
return frame.readAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline QByteArray videoPosterBytes(const QString& src, const QSize& fit) {
|
||||||
|
auto runProcess = [](const QString& program, const QStringList& args) {
|
||||||
|
QProcess process;
|
||||||
|
process.start(program, args);
|
||||||
|
if (!process.waitForFinished(15000)) {
|
||||||
|
process.kill();
|
||||||
|
process.waitForFinished(1000);
|
||||||
|
}
|
||||||
|
return qMakePair(process.exitCode(), process.readAllStandardOutput());
|
||||||
|
};
|
||||||
|
|
||||||
|
QSize source;
|
||||||
|
const auto resProbe = runProcess(
|
||||||
|
"ffprobe",
|
||||||
|
{
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=width,height",
|
||||||
|
"-of",
|
||||||
|
"csv=s=x:p=0",
|
||||||
|
src,
|
||||||
|
});
|
||||||
|
if (resProbe.first == 0) {
|
||||||
|
const auto parts =
|
||||||
|
QString::fromUtf8(resProbe.second).trimmed().split('x');
|
||||||
|
if (parts.size() == 2) {
|
||||||
|
source = QSize(parts[0].toInt(), parts[1].toInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize target = coverSizeNoUpscale(source, fit);
|
||||||
|
if (target.isEmpty()) {
|
||||||
|
target = fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.setWidth(target.width() & ~1);
|
||||||
|
target.setHeight(target.height() & ~1);
|
||||||
|
|
||||||
|
const QString scale =
|
||||||
|
QStringLiteral(
|
||||||
|
"scale=%1:%2:force_original_aspect_ratio=decrease,"
|
||||||
|
"scale=trunc(iw/2)*2:trunc(ih/2)*2")
|
||||||
|
.arg(target.width())
|
||||||
|
.arg(target.height());
|
||||||
|
|
||||||
|
QTemporaryDir tmpDir;
|
||||||
|
if (!tmpDir.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString framePath = tmpDir.filePath(QStringLiteral("poster.jpg"));
|
||||||
|
|
||||||
|
runProcess(
|
||||||
|
"ffmpeg",
|
||||||
|
{
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"error",
|
||||||
|
"-ss",
|
||||||
|
"0",
|
||||||
|
"-i",
|
||||||
|
src,
|
||||||
|
"-frames:v",
|
||||||
|
"1",
|
||||||
|
"-vf",
|
||||||
|
scale,
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuvj420p",
|
||||||
|
"-q:v",
|
||||||
|
"2",
|
||||||
|
"-y",
|
||||||
|
framePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
QFile frame(framePath);
|
||||||
|
if (!frame.open(QIODevice::ReadOnly)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame.readAll();
|
||||||
|
}
|
||||||
|
|
||||||
inline QByteArray schemeThumbBytes(const QByteArray& source, int maxSide) {
|
inline QByteArray schemeThumbBytes(const QByteArray& source, int maxSide) {
|
||||||
QImage image = QImage::fromData(source, "JPEG");
|
QImage image = QImage::fromData(source, "JPEG");
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user