WIP: feat: video wallpapers #163
+3
-5
@@ -34,7 +34,7 @@ MouseArea {
|
||||
cursorShape: expanded ? Qt.ArrowCursor : undefined
|
||||
enabled: expanded
|
||||
hoverEnabled: expanded
|
||||
layer.enabled: opacity < 1
|
||||
layer.enabled: false
|
||||
opacity: expanded ? 1 : 0
|
||||
parent: {
|
||||
const win = QsWindow.window;
|
||||
@@ -101,8 +101,7 @@ MouseArea {
|
||||
yScale: root.expanded ? 1 : 0.1
|
||||
|
||||
Behavior on yScale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +136,7 @@ MouseArea {
|
||||
radius: menu.radius - column.anchors.margins
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
|
||||
+64
-38
@@ -16,16 +16,15 @@ Searcher {
|
||||
id: root
|
||||
|
||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||
property alias crops: adapter.monitorCrops
|
||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||
readonly property string currentThumbnail: thumbnailFor(current)
|
||||
readonly property bool currentIsVideo: isVideoPath(current)
|
||||
property alias monitorCrops: monitorCrops
|
||||
property bool pendingPreviewClear
|
||||
property bool previewColorLock
|
||||
property string previewPath
|
||||
property bool recentlyChanged
|
||||
property bool showPreview: false
|
||||
property int cropVersion: 0
|
||||
|
||||
function getCategoryFor(w: WallpaperEntry): string {
|
||||
let category = w.parentDir.slice(Paths.wallsdir.length + 1);
|
||||
@@ -58,7 +57,7 @@ Searcher {
|
||||
}
|
||||
|
||||
function getCrop(screen: string): var {
|
||||
return root.crops[screen];
|
||||
return wallpapers.crop(root.current, screen);
|
||||
}
|
||||
|
||||
function preview(path: string): void {
|
||||
@@ -75,19 +74,18 @@ Searcher {
|
||||
else if (zoom > 5.0)
|
||||
zoom = 5.0;
|
||||
|
||||
root.crops[screen] = {
|
||||
wallpapers.setCrop(root.current, screen, {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
zoom: zoom
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setWallpaper(path: string): void {
|
||||
actualCurrent = 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}`]);
|
||||
if (Config.colors.schemeGen)
|
||||
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"
|
||||
}
|
||||
|
||||
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 {
|
||||
id: wallpapers
|
||||
|
||||
@@ -183,35 +155,89 @@ Searcher {
|
||||
if (path === root.current)
|
||||
wallpaper.invalidateCache();
|
||||
}
|
||||
|
||||
function onCropsChanged(path: string): void {
|
||||
if (path === root.current)
|
||||
root.cropVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: screenVariants
|
||||
model: Quickshell.screens
|
||||
|
||||
WallpaperObject {
|
||||
readonly property var crops: root.getCrop(modelData.name)
|
||||
id: obj
|
||||
required property var modelData
|
||||
|
||||
cropHeight: crops?.height ?? 1.0
|
||||
cropWidth: crops?.width ?? 1.0
|
||||
cropX: crops?.x ?? 0.0
|
||||
cropY: crops?.y ?? 0.0
|
||||
// Crop animation only for edits, never for wallpaper swaps.
|
||||
property bool animateCrop: false
|
||||
|
||||
screen: modelData.name
|
||||
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 {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropWidth {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropX {
|
||||
enabled: obj.animateCrop
|
||||
Anim {}
|
||||
}
|
||||
Behavior on cropY {
|
||||
enabled: obj.animateCrop
|
||||
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
|
||||
|
||||
readonly property bool isVideo: Wallpapers.currentIsVideo
|
||||
readonly property string currentWallpaper: Wallpapers.current
|
||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||
readonly property real maxZoom: 5.0
|
||||
readonly property bool changesMade: {
|
||||
cropRevision;
|
||||
Wallpapers.cropVersion;
|
||||
wrapper.currentWallpaper;
|
||||
|
||||
const r = cropRectLoader.item;
|
||||
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
||||
@@ -115,6 +118,9 @@ Item {
|
||||
refreshScreens();
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onCurrentWallpaperChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
onSelectedScreenIndexChanged: {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,42 @@ import qs.Services
|
||||
PageBase {
|
||||
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")
|
||||
|
||||
ColumnLayout {
|
||||
@@ -73,6 +109,18 @@ PageBase {
|
||||
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 {
|
||||
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
||||
checked: Colors.transparency.enabled
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#pragma once
|
||||
#include "common.hpp"
|
||||
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "common.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace ZShell::config {
|
||||
|
||||
class Background : public settings::ObjectNode {
|
||||
@@ -9,6 +12,8 @@ class Background : public settings::ObjectNode {
|
||||
|
||||
CONFIG_PROPERTY(bool, enabled, true)
|
||||
CONFIG_PROPERTY(int, wallFadeDuration, 300)
|
||||
CONFIG_ENUM_PROPERTY(
|
||||
WallpaperTransition, wallTransition, WallpaperTransition::Fade)
|
||||
};
|
||||
|
||||
} // namespace ZShell::config
|
||||
|
||||
@@ -23,6 +23,19 @@ ENUM(NotifsFullscreen, On, Off)
|
||||
ENUM(TemperatureUnit, Auto, Celsius, Fahrenheit, Kelvin)
|
||||
ENUM(DataUnit, Binary, Decimal)
|
||||
ENUM(ClockFormat, Auto, TwelveHour, TwentyFourHour)
|
||||
ENUM(
|
||||
WallpaperTransition,
|
||||
Bowtie,
|
||||
Fade,
|
||||
LinearBlur,
|
||||
WindowSlices,
|
||||
WipeRight,
|
||||
WipeDown,
|
||||
TilesWave,
|
||||
SplitSlideInHorizontal,
|
||||
Hexagonalize,
|
||||
DoomMelt,
|
||||
Random)
|
||||
|
||||
#undef ENUM
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ qml_module(ZShell-internal
|
||||
PkgConfig::AVCODEC
|
||||
PkgConfig::AVFORMAT
|
||||
PkgConfig::AVUTIL
|
||||
ZShell-config
|
||||
)
|
||||
|
||||
qt_add_resources(ZShell-internal "wallpaper_shaders"
|
||||
@@ -53,6 +54,16 @@ qt_add_resources(ZShell-internal "wallpaper_shaders"
|
||||
shaders/wallpaper.vert
|
||||
shaders/wallpaper.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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
m_threadRunning = false;
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,13 +202,14 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
m_threadRunning = false;
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
if (ret == AVERROR_EOF) {
|
||||
if (avformat_seek_file(
|
||||
@@ -230,7 +235,9 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
||||
}
|
||||
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;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
@@ -251,21 +258,34 @@ void VideoPlayer::decodeLoop(const QString& path) {
|
||||
|
||||
av_frame_free(&frame);
|
||||
closeDecoder();
|
||||
m_threadRunning = false;
|
||||
m_threadRunning.store(false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
closeDecoder();
|
||||
|
||||
@@ -46,7 +46,7 @@ class VideoPlayer {
|
||||
[[nodiscard]] QSize frameSize() const;
|
||||
|
||||
private:
|
||||
void decodeLoop(const QString& path);
|
||||
void decodeLoop(const QString& path, uint32_t generation);
|
||||
bool openDecoder(const QString& path);
|
||||
void closeDecoder();
|
||||
|
||||
@@ -60,6 +60,7 @@ class VideoPlayer {
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_threadRunning{false};
|
||||
std::atomic<uint32_t> m_generation{0};
|
||||
|
||||
mutable std::mutex m_mtx;
|
||||
AVFrame* m_pending = nullptr;
|
||||
|
||||
@@ -67,8 +67,46 @@ void WallpaperObject::setZoom(qreal zoom) {
|
||||
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() {
|
||||
if (!m_service || m_screen.isEmpty()) return;
|
||||
if (!m_pushEnabled || !m_service || m_screen.isEmpty()) return;
|
||||
|
||||
QVariantMap crop;
|
||||
crop.insert("x", m_cropX);
|
||||
|
||||
@@ -55,6 +55,8 @@ class WallpaperObject : public QObject {
|
||||
qreal zoom() const { return m_zoom; }
|
||||
void setZoom(qreal zoom);
|
||||
|
||||
Q_INVOKABLE void syncCrop(const QVariantMap& crop);
|
||||
|
||||
signals:
|
||||
void serviceChanged();
|
||||
void screenChanged();
|
||||
@@ -77,6 +79,8 @@ class WallpaperObject : public QObject {
|
||||
qreal m_cropWidth = 1.0;
|
||||
qreal m_cropHeight = 1.0;
|
||||
qreal m_zoom = 1.0;
|
||||
|
||||
bool m_pushEnabled = true;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <QImage>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QElapsedTimer>
|
||||
#include <QRectF>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
#include <QVector>
|
||||
#include <QtQml/qqml.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <atomic>
|
||||
@@ -28,6 +30,7 @@
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
#include "wallpapersurface.hpp"
|
||||
#include "config/enums.hpp"
|
||||
|
||||
struct mpv_handle;
|
||||
struct mpv_render_context;
|
||||
@@ -131,8 +134,76 @@ class WallpaperService : public QObject {
|
||||
|
||||
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]] 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 uploadImage();
|
||||
@@ -150,8 +221,14 @@ class WallpaperService : public QObject {
|
||||
|
||||
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
||||
|
||||
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
|
||||
|
||||
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
|
||||
|
||||
void ensureImage();
|
||||
|
||||
void ensurePoster();
|
||||
|
||||
void notifyBufferSizes();
|
||||
|
||||
void queue(WallpaperSurface* surface);
|
||||
@@ -187,7 +264,6 @@ class WallpaperService : public QObject {
|
||||
|
||||
bool m_isVideo = false;
|
||||
bool m_videoReload = false;
|
||||
bool m_previousVideo = false;
|
||||
mpv_handle* m_mpv = nullptr;
|
||||
mpv_render_context* m_mpvRender = nullptr;
|
||||
uint32_t m_blackTex = 0;
|
||||
@@ -223,6 +299,45 @@ class WallpaperService : public QObject {
|
||||
int m_alphaUniform = -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_vao = 0;
|
||||
int m_videoSizeUniform = -1;
|
||||
@@ -240,14 +355,7 @@ class WallpaperService : public QObject {
|
||||
bool m_usePlayer = true;
|
||||
#endif
|
||||
|
||||
QImage m_previousImage;
|
||||
QVariantMap m_previousCrops;
|
||||
uint32_t m_previousTexture = 0;
|
||||
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;
|
||||
|
||||
@@ -264,6 +372,13 @@ class WallpaperService : public QObject {
|
||||
|
||||
bool m_forceDecode = 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
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <qfuturewatcher.h>
|
||||
#include <qimage.h>
|
||||
#include <qimagereader.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qlogging.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qsqldatabase.h>
|
||||
@@ -343,8 +345,8 @@ void WallpaperDb::setCacheSize(const QSize& cacheSize) {
|
||||
if (db.isOpen()) {
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL"
|
||||
" WHERE path = ?");
|
||||
"UPDATE wallpapers SET cache = NULL, scheme_thumb = NULL,"
|
||||
" poster = NULL WHERE path = ?");
|
||||
query.addBindValue(entry->path());
|
||||
query.exec();
|
||||
}
|
||||
@@ -413,7 +415,8 @@ void WallpaperDb::openDb() {
|
||||
query.exec(
|
||||
"CREATE TABLE IF NOT EXISTS wallpapers("
|
||||
"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");
|
||||
if (query.lastError().isValid()) {
|
||||
@@ -421,9 +424,12 @@ void WallpaperDb::openDb() {
|
||||
query.exec(
|
||||
"CREATE TABLE wallpapers("
|
||||
"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 {
|
||||
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();
|
||||
@@ -538,6 +544,7 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
||||
}
|
||||
|
||||
delete it.value();
|
||||
m_crops.remove(it.key());
|
||||
it = m_byPath.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@@ -575,24 +582,28 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
||||
if (db.isOpen()) {
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"SELECT cache, scheme_thumb, size, mtime FROM wallpapers"
|
||||
" WHERE path = ?");
|
||||
"SELECT cache, scheme_thumb, poster, size, mtime, crop"
|
||||
" FROM wallpapers WHERE path = ?");
|
||||
query.addBindValue(path);
|
||||
query.exec();
|
||||
|
||||
if (query.next()) {
|
||||
const auto cache = query.value(0).toByteArray();
|
||||
const auto scheme = query.value(1).toByteArray();
|
||||
const auto storedSize = query.value(2).toLongLong();
|
||||
const auto storedMtime = query.value(3).toLongLong();
|
||||
const auto poster = query.value(2).toByteArray();
|
||||
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 &&
|
||||
!cache.isEmpty()) {
|
||||
reused = true;
|
||||
entry->setThumbnailPath(thumbnailUrl(path));
|
||||
|
||||
if (scheme.isEmpty()) {
|
||||
queueMetadata(entry, isVideo, cache);
|
||||
if (scheme.isEmpty() || !posterOk) {
|
||||
queueMetadata(entry, isVideo, cache, poster);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,9 +613,13 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
||||
if (db.isOpen()) {
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT OR REPLACE INTO wallpapers"
|
||||
"INSERT INTO wallpapers"
|
||||
" (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(isVideo ? "video" : "image");
|
||||
query.addBindValue(
|
||||
@@ -626,7 +641,10 @@ void WallpaperDb::applyScan(const ScanResult& result) {
|
||||
}
|
||||
|
||||
void WallpaperDb::queueMetadata(
|
||||
WallpaperEntry* entry, bool isVideo, const QByteArray& existingCache) {
|
||||
WallpaperEntry* entry,
|
||||
bool isVideo,
|
||||
const QByteArray& existingCache,
|
||||
const QByteArray& existingPoster) {
|
||||
qCInfo(
|
||||
dbLog,
|
||||
"WallpaperDb: queued cache job for %s (%s%s)",
|
||||
@@ -634,7 +652,8 @@ void WallpaperDb::queueMetadata(
|
||||
isVideo ? "video" : "image",
|
||||
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()) {
|
||||
m_queueTimer.start();
|
||||
@@ -672,10 +691,12 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
||||
struct CacheResult {
|
||||
QString path;
|
||||
QByteArray cache;
|
||||
QByteArray poster;
|
||||
QByteArray scheme;
|
||||
};
|
||||
|
||||
const QSize cacheSize = m_cacheSize;
|
||||
const bool isVideoJob = job.isVideo;
|
||||
|
||||
QElapsedTimer clock;
|
||||
clock.start();
|
||||
@@ -685,7 +706,7 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
||||
watcher,
|
||||
&QFutureWatcher<CacheResult>::finished,
|
||||
this,
|
||||
[this, watcher, clock]() {
|
||||
[this, watcher, clock, isVideoJob]() {
|
||||
const auto result = watcher->result();
|
||||
|
||||
qCInfo(
|
||||
@@ -695,15 +716,27 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
||||
int(result.cache.size()),
|
||||
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)) {
|
||||
const auto db = QSqlDatabase::database(m_uuid, false);
|
||||
if (db.isOpen()) {
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?"
|
||||
" WHERE path = ?");
|
||||
"UPDATE wallpapers SET cache = ?, scheme_thumb = ?,"
|
||||
" poster = ? WHERE path = ?");
|
||||
query.addBindValue(result.cache);
|
||||
query.addBindValue(result.scheme);
|
||||
query.addBindValue(result.poster);
|
||||
query.addBindValue(result.path);
|
||||
if (!query.exec()) {
|
||||
qWarning(dbLog)
|
||||
@@ -736,7 +769,14 @@ void WallpaperDb::runMetadataJob(const MetadataJob& job) {
|
||||
: imageCacheBytes(job.path, cacheSize);
|
||||
}
|
||||
|
||||
QByteArray poster = job.existingPoster;
|
||||
if (poster.isEmpty()) {
|
||||
poster =
|
||||
job.isVideo ? videoPosterBytes(job.path, cacheSize) : cache;
|
||||
}
|
||||
|
||||
result.cache = cache;
|
||||
result.poster = poster;
|
||||
result.scheme = cache.isEmpty() ? QByteArray()
|
||||
: schemeThumbBytes(cache, 128);
|
||||
|
||||
@@ -781,4 +821,56 @@ bool WallpaperDb::entryAllowed(const WallpaperEntry* entry) const {
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <qqmllist.h>
|
||||
#include <qsize.h>
|
||||
#include <qtimer.h>
|
||||
#include <qvariant.h>
|
||||
#include <qvector.h>
|
||||
|
||||
namespace ZShell::models {
|
||||
@@ -109,6 +110,11 @@ class WallpaperDb : public QObject {
|
||||
|
||||
[[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:
|
||||
void pathChanged();
|
||||
void dbPathChanged();
|
||||
@@ -117,6 +123,7 @@ class WallpaperDb : public QObject {
|
||||
void cacheSizeChanged();
|
||||
|
||||
void cacheStored(const QString& path);
|
||||
void cropsChanged(const QString& path);
|
||||
|
||||
private:
|
||||
struct FileEntry {
|
||||
@@ -134,6 +141,7 @@ class WallpaperDb : public QObject {
|
||||
QString path;
|
||||
bool isVideo;
|
||||
QByteArray existingCache;
|
||||
QByteArray existingPoster;
|
||||
};
|
||||
|
||||
void openDb();
|
||||
@@ -144,11 +152,15 @@ class WallpaperDb : public QObject {
|
||||
void queueMetadata(
|
||||
WallpaperEntry* entry,
|
||||
bool isVideo,
|
||||
const QByteArray& existingCache = {});
|
||||
const QByteArray& existingCache = {},
|
||||
const QByteArray& existingPoster = {});
|
||||
void processQueue();
|
||||
void runMetadataJob(const MetadataJob& job);
|
||||
void rebuildEntries();
|
||||
|
||||
void loadCrop(const QString& path, const QString& json);
|
||||
void storeCropJson(const QString& path);
|
||||
|
||||
[[nodiscard]] bool entryAllowed(const WallpaperEntry* entry) const;
|
||||
|
||||
QDir m_dir;
|
||||
@@ -163,6 +175,8 @@ class WallpaperDb : public QObject {
|
||||
QList<WallpaperEntry*> m_entries;
|
||||
QHash<QString, WallpaperEntry*> m_byPath;
|
||||
|
||||
QHash<QString, QVariantMap> m_crops;
|
||||
|
||||
QVector<MetadataJob> m_queue;
|
||||
QTimer m_queueTimer;
|
||||
bool m_jobRunning = false;
|
||||
|
||||
@@ -285,6 +285,91 @@ inline QByteArray videoCacheBytes(const QString& src, const QSize& fit) {
|
||||
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) {
|
||||
QImage image = QImage::fromData(source, "JPEG");
|
||||
if (image.isNull()) {
|
||||
|
||||
Reference in New Issue
Block a user