diff options
Diffstat (limited to 'desktop/modules/appearance/Wallpapers.qml')
| -rw-r--r-- | desktop/modules/appearance/Wallpapers.qml | 87 |
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 |
