feat(wallpaper): support JPEG XL wallpapers
This commit is contained in:
parent
6a753f5c12
commit
1b5e9aa317
3 changed files with 294 additions and 8 deletions
173
Cargo.lock
generated
173
Cargo.lock
generated
|
|
@ -1699,11 +1699,14 @@ dependencies = [
|
|||
"cosmic-config",
|
||||
"cosmic-randr-shell",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"fast_image_resize",
|
||||
"freedesktop-icons",
|
||||
"futures-lite 2.3.0",
|
||||
"futures-util",
|
||||
"image 0.25.2",
|
||||
"infer",
|
||||
"jxl-oxide",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
|
@ -2096,6 +2099,15 @@ dependencies = [
|
|||
"smithay-clipboard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
|
|
@ -2296,6 +2308,20 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||
|
||||
[[package]]
|
||||
name = "fast_image_resize"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a66a61fbfc84ef99a839499cf9e5a7c2951d2da874ea00f29ee938bc50d1b396"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"document-features",
|
||||
"image 0.25.2",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
|
|
@ -4018,6 +4044,147 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-bitstream"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5855ff16398ffbcf81fee52c41ca65326499c8764b21bb9952c367ace98995fb"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-coding"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5b5093904e940bc11ef50e872c7bdf7b6e88653f012b925f8479daf212b5c9"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-color"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97f0dd548fabf9c094f9f2304059c86764f606b9040c0bfcfac55f155f423b55"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-coding",
|
||||
"jxl-grid",
|
||||
"jxl-threadpool",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-frame"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4719f285ebfff5e64f352d0ef149a5244aef4f8e6b5aa666ba6241e90b50632f"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-coding",
|
||||
"jxl-grid",
|
||||
"jxl-image",
|
||||
"jxl-modular",
|
||||
"jxl-threadpool",
|
||||
"jxl-vardct",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-grid"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e941628e8de1dc6ba1d2bba8ebc68a69f8ff50cc7ddce5bc821658d1f4ea6e59"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-image"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3824c81613c05c19a9e4329d569145d3f460c0fcadb3965bd8418162d43f7f4"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-color",
|
||||
"jxl-grid",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-modular"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f48a5d003627f380004c29d35e51672da06ae343a2e6fe8d9c84295b9a3e843"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-coding",
|
||||
"jxl-grid",
|
||||
"jxl-threadpool",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-oxide"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c209f66ef0fe72df77b44ee6aae98eb87bc2dd236d6981e44e143cc37f33f6e"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-color",
|
||||
"jxl-frame",
|
||||
"jxl-grid",
|
||||
"jxl-image",
|
||||
"jxl-render",
|
||||
"jxl-threadpool",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-render"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aec53c004c9171e89f15ad1f029d6b638cbd70d3a70276746bb8c75f9393bb64"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-coding",
|
||||
"jxl-color",
|
||||
"jxl-frame",
|
||||
"jxl-grid",
|
||||
"jxl-image",
|
||||
"jxl-modular",
|
||||
"jxl-threadpool",
|
||||
"jxl-vardct",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-threadpool"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d2860c68899a3c6266044fc26c6a0041e9f27145f58cc69b6eedc1b77f5ee13"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
"rayon-core",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jxl-vardct"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15da4b49b832b3d8a67329f47e2a1732e0847667938bb9b4a37d99a4668775c2"
|
||||
dependencies = [
|
||||
"jxl-bitstream",
|
||||
"jxl-coding",
|
||||
"jxl-grid",
|
||||
"jxl-modular",
|
||||
"jxl-threadpool",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kamadak-exif"
|
||||
version = "0.5.5"
|
||||
|
|
@ -4307,6 +4474,12 @@ version = "0.7.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||
|
||||
[[package]]
|
||||
name = "locale_config"
|
||||
version = "0.3.0"
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ cosmic-bg-config = { workspace = true }
|
|||
cosmic-config = { workspace = true }
|
||||
cosmic-randr-shell = { workspace = true }
|
||||
dirs = "5.0.1"
|
||||
eyre = "0.6.12"
|
||||
fast_image_resize = { version = "5.0.0", features = ["image"] }
|
||||
freedesktop-icons = "0.2.6"
|
||||
futures-lite = "2.3.0"
|
||||
futures-util = "0.3.30"
|
||||
image = "0.25.2"
|
||||
infer = "0.16.0"
|
||||
jxl-oxide = "0.9.0"
|
||||
tokio = { version = "1.40.0", features = ["sync"] }
|
||||
tracing = "0.1.40"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
pub use cosmic_bg_config::{Color, Config, Entry, Gradient, ScalingMode, Source};
|
||||
|
||||
use eyre::{eyre, OptionExt};
|
||||
use fast_image_resize::SrcCropping;
|
||||
use futures_lite::Stream;
|
||||
use image::imageops::FilterType;
|
||||
use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
|
||||
use jxl_oxide::{EnumColourEncoding, JxlImage, PixelFormat};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{hash_map::DefaultHasher, BTreeSet, HashMap},
|
||||
|
|
@ -115,16 +118,22 @@ pub async fn load_each_from_path(
|
|||
if recurse && file_type.is_dir() {
|
||||
paths.push(path);
|
||||
} else if file_type.is_file() {
|
||||
let Ok(Some(kind)) = infer::get_from_path(&path) else {
|
||||
let path = if path.extension().map_or(false, |ext| ext == "jxl") {
|
||||
path
|
||||
} else if let Ok(Some(kind)) = infer::get_from_path(&path) {
|
||||
if infer::MatcherType::Image == kind.matcher_type() {
|
||||
path
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if infer::MatcherType::Image == kind.matcher_type() {
|
||||
wallpapers.insert(path);
|
||||
wallpapers.insert(path);
|
||||
|
||||
if wallpapers.len() > 99 {
|
||||
break;
|
||||
}
|
||||
if wallpapers.len() > 99 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -165,7 +174,7 @@ pub fn load_image_with_thumbnail(
|
|||
ImageOperation::Cached(thumbnail) => thumbnail.to_rgba8(),
|
||||
|
||||
ImageOperation::GenerateThumbnail { path, image } => {
|
||||
let image = image.thumbnail(300, 169).to_rgba8();
|
||||
let image = resize_thumbnail(&image, 300, 169).to_rgba8();
|
||||
|
||||
if let Some(path) = path {
|
||||
// Save thumbnail to disk without blocking.
|
||||
|
|
@ -251,6 +260,16 @@ fn load_thumbnail(
|
|||
}
|
||||
|
||||
fn open_image(input_buffer: &mut Vec<u8>, path: &Path) -> Option<DynamicImage> {
|
||||
if path.extension().map_or(false, |ext| ext == "jxl") {
|
||||
return match decode_jpegxl(path) {
|
||||
Ok(image) => Some(image),
|
||||
Err(why) => {
|
||||
tracing::error!(?path, ?why, "image decode failed");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let capacity = match path.metadata() {
|
||||
Ok(metadata) => metadata.len() as usize,
|
||||
Err(why) => {
|
||||
|
|
@ -409,3 +428,94 @@ fn border_radius(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes JPEG XL image files into `image::DynamicImage` via `jxl-oxide`.
|
||||
pub fn decode_jpegxl(path: &std::path::Path) -> eyre::Result<DynamicImage> {
|
||||
let mut image = JxlImage::builder()
|
||||
.open(path)
|
||||
.map_err(|why| eyre!("failed to read image header: {why}"))?;
|
||||
image.request_color_encoding(EnumColourEncoding::srgb(
|
||||
jxl_oxide::RenderingIntent::Relative,
|
||||
));
|
||||
let render = image
|
||||
.render_frame(0)
|
||||
.map_err(|why| eyre!("failed to render image frame: {why}"))?;
|
||||
|
||||
let framebuffer = render.image_all_channels();
|
||||
match image.pixel_format() {
|
||||
PixelFormat::Graya => image::GrayAlphaImage::from_raw(
|
||||
framebuffer.width() as u32,
|
||||
framebuffer.height() as u32,
|
||||
framebuffer
|
||||
.buf()
|
||||
.iter()
|
||||
.map(|x| x * 255. + 0.5)
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map(DynamicImage::ImageLumaA8)
|
||||
.ok_or_eyre("Can't decode gray alpha buffer"),
|
||||
PixelFormat::Gray => image::GrayImage::from_raw(
|
||||
framebuffer.width() as u32,
|
||||
framebuffer.height() as u32,
|
||||
framebuffer
|
||||
.buf()
|
||||
.iter()
|
||||
.map(|x| x * 255. + 0.5)
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map(DynamicImage::ImageLuma8)
|
||||
.ok_or_eyre("Can't decode gray buffer"),
|
||||
PixelFormat::Rgba => image::RgbaImage::from_raw(
|
||||
framebuffer.width() as u32,
|
||||
framebuffer.height() as u32,
|
||||
framebuffer
|
||||
.buf()
|
||||
.iter()
|
||||
.map(|x| x * 255. + 0.5)
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map(DynamicImage::ImageRgba8)
|
||||
.ok_or_eyre("Can't decode rgba buffer"),
|
||||
PixelFormat::Rgb => image::RgbImage::from_raw(
|
||||
framebuffer.width() as u32,
|
||||
framebuffer.height() as u32,
|
||||
framebuffer
|
||||
.buf()
|
||||
.iter()
|
||||
.map(|x| x * 255. + 0.5)
|
||||
.map(|x| x as u8)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.map(DynamicImage::ImageRgb8)
|
||||
.ok_or_eyre("Can't decode rgb buffer"),
|
||||
//TODO: handle this
|
||||
PixelFormat::Cmyk => Err(eyre!("unsupported pixel format: CMYK")),
|
||||
PixelFormat::Cmyka => Err(eyre!("unsupported pixel format: CMYKA")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Use `fast-image-resize` crate for faster thumbnail generation.
|
||||
fn resize_thumbnail(
|
||||
img: &image::DynamicImage,
|
||||
new_width: u32,
|
||||
new_height: u32,
|
||||
) -> image::DynamicImage {
|
||||
let mut resizer = fast_image_resize::Resizer::new();
|
||||
let options = fast_image_resize::ResizeOptions {
|
||||
algorithm: fast_image_resize::ResizeAlg::Convolution(
|
||||
fast_image_resize::FilterType::Lanczos3,
|
||||
),
|
||||
cropping: SrcCropping::FitIntoDestination((new_width as f64, new_height as f64)),
|
||||
..Default::default()
|
||||
};
|
||||
let mut new_image = image::DynamicImage::new(new_width, new_height, img.color());
|
||||
if let Err(err) = resizer.resize(img, &mut new_image, &options) {
|
||||
tracing::warn!(?err, "Failed to use `fast_image_resize`. Falling back.");
|
||||
new_image =
|
||||
image::imageops::resize(img, new_width, new_height, FilterType::Lanczos3).into();
|
||||
}
|
||||
new_image
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue