aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/appearance/Wallpapers.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/modules/appearance/Wallpapers.qml')
-rw-r--r--desktop/modules/appearance/Wallpapers.qml87
1 files changed, 87 insertions, 0 deletions
diff --git a/desktop/modules/appearance/Wallpapers.qml b/desktop/modules/appearance/Wallpapers.qml
index 4d06a2e..73c1f25 100644
--- a/desktop/modules/appearance/Wallpapers.qml
+++ b/desktop/modules/appearance/Wallpapers.qml
@@ -150,6 +150,93 @@ Singleton {
: "each image pixel becomes a visible block, so it will look pixelated.");
}
+ // Real-ESRGAN through stable-diffusion.cpp's sd-cli, for an image too
+ // small for its screen. The file is replaced in place, so wallp's state
+ // and the engine's lists, which name it by path, still find it. sd-cli
+ // writes to a dot-named sibling first, which the scan skips, and only a
+ // successful run is moved over the original, so a failure leaves it
+ // untouched. The name keeps its extension because sd-cli writes the
+ // format the extension names. Only files under `dir`: the system ones
+ // are not ours to rewrite. Success is no [E] line as well as exit 0: a
+ // model that fails to load logs "[E] new_upscaler_ctx failed", saves
+ // the input unscaled and exits 0 (measured).
+ //
+ // Which Real-ESRGAN model is decided first, as its own step, so the page
+ // can show the choice before anything is rewritten. Imajev-4B makes it,
+ // a small Jev-style vision decision model, run once through llama-mtmd-cli on a
+ // 512 px thumbnail: the answer is held to one token, A or B, by a
+ // grammar, so greedy decoding returns the option with the highest logit,
+ // which is the decision. About 1.7 s. Anime goes to the anime model,
+ // which keeps line art clean; everything else, and any failure of the
+ // decider, goes to the general one, safe on any image. Measured on 22
+ // wallpapers it got every anime and photo right and called 4 of 6
+ // painted 3D renders anime, which then come out smoother than painted;
+ // the 2B, and a three-way prompt adding "painted or render", did worse.
+ // ponytail: argmax only, so Imajev's calibration temperature is not
+ // needed. One 4x pass, which covers both screens unless the image is
+ // under 640 px wide or 480 px tall; --upscale-repeats if that matters.
+ readonly property string upscalers: "/data/LLM-models/SD/upscalers"
+ readonly property string decider: "/data/LLM-models/SD/imajev/imajev-4b"
+ property string upscaling: ""
+ property string upscaleError: ""
+ function canUpscale(path, which) {
+ return path.startsWith(dir + "/") && upscale(path, which) > 1;
+ }
+ function sibling(path) { return path.slice(0, path.lastIndexOf("/") + 1) + "." + basename(path); }
+
+ // The decision for one image: decidedFor is the path it was made for, so
+ // staging another image leaves it standing for the first and unread.
+ property string deciding: ""
+ property string decidedFor: ""
+ property string decidedKind: "" // "anime" or "photo"
+ function decide(path) {
+ if (deciding !== "" || upscaling !== "" || path === "") return;
+ deciding = path;
+ upscaleError = "";
+ decideProc.command = ["sh", "-c",
+ 'magick "$1" -resize 512x512 "$2" 2>/dev/null; '
+ + 'llama-mtmd-cli -m "$3-Q4_K_M.gguf" --mmproj "$3-mmproj-f16.gguf" --image "$2" '
+ + '-ngl 99 --temp 0 -n 1 --grammar "$5" -p "$4" 2>/dev/null; rm -f "$2"',
+ "sh", path, sibling(path) + ".jpg", decider,
+ "Is this image A) anime, cartoon or illustration art, or B) a photograph or realistic render? Answer A or B.",
+ 'root ::= "A" | "B"'];
+ decideProc.running = true;
+ }
+ Process {
+ id: decideProc
+ stdout: StdioCollector { id: decideOut }
+ onExited: {
+ root.decidedKind = decideOut.text.trim().endsWith("A") ? "anime" : "photo";
+ root.decidedFor = root.deciding;
+ root.deciding = "";
+ }
+ }
+
+ function upscaleFile(path) {
+ if (upscaling !== "" || path === "" || path !== decidedFor) return;
+ upscaling = path;
+ upscaleError = "";
+ upscaleProc.command = ["sh", "-c",
+ 'log=$(sd-cli -M upscale --upscale-model "$1" -i "$2" -o "$3" 2>&1); s=$?; '
+ + 'err=$(printf "%s\\n" "$log" | grep -F "[E]"); '
+ + 'if [ $s -eq 0 ] && [ -z "$err" ]; then mv -f "$3" "$2"; '
+ + 'else rm -f "$3"; echo "${err:-sd-cli exited $s}" >&2; exit 1; fi',
+ "sh", `${upscalers}/RealESRGAN_x4plus${decidedKind === "anime" ? "_anime_6B" : ""}.pth`,
+ path, sibling(path)];
+ upscaleProc.running = true;
+ }
+ Process {
+ id: upscaleProc
+ stderr: StdioCollector { id: upscaleErr }
+ onExited: code => {
+ if (code !== 0) root.upscaleError = `upscaling ${root.basename(root.upscaling)} failed: ${upscaleErr.text.trim()}`;
+ root.decidedFor = "";
+ root.upscaling = "";
+ // The new size clears the quality note and the red border.
+ root.refresh();
+ }
+ }
+
function basename(path) { return path.slice(path.lastIndexOf("/") + 1); }
// The files by the subfolder they sit in, folders by name, each keeping