Skip to content

Commit

Permalink
feat: add JPEG XL support via jxl-oxide
Browse files Browse the repository at this point in the history
Co-authored-by: Mitchel Stewart <[email protected]>
  • Loading branch information
mmstick and Quackdoc authored Oct 9, 2024
1 parent 584f6b3 commit fd44edf
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 24 deletions.
154 changes: 148 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dirs = "5.0.1"
eyre = "0.6.12"
fast_image_resize = { version = "4.2.1", features = ["image"] }
image = { workspace = true, features = ["hdr", "jpeg", "png", "rayon", "webp"] }
jxl-oxide = "0.9.0"
notify = "6.1.1"
rand = "0.8"
ron = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ Any contribution intentionally submitted for inclusion in the work by you shall

```
// SPDX-License-Identifier: MPL-2.0
```
```
122 changes: 105 additions & 17 deletions src/wallpaper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use std::{

use cosmic_bg_config::{state::State, Color, Entry, SamplingMethod, ScalingMode, Source};
use cosmic_config::CosmicConfigEntry;
use image::{DynamicImage, ImageReader};
use eyre::{eyre, OptionExt};
use image::{DynamicImage, GrayAlphaImage, GrayImage, ImageReader, RgbImage, RgbaImage};
use jxl_oxide::{EnumColourEncoding, JxlImage, PixelFormat};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use rand::{seq::SliceRandom, thread_rng};
use sctk::reexports::{
Expand Down Expand Up @@ -123,27 +125,42 @@ impl Wallpaper {
tracing::info!("No source for wallpaper");
continue;
};

cur_resized_img = match source {
Source::Path(ref path) => {
if self.current_image.is_none() {
self.current_image = Some(match ImageReader::open(&path) {
Ok(img) => {
match img
.with_guessed_format()
.ok()
.and_then(|f| f.decode().ok())
{
Some(img) => img,
None => {
tracing::warn!(
"Could not decode image: {}",
path.display()
);
continue;
self.current_image = Some(match path.extension() {
Some(ext) if ext == "jxl" => match decode_jpegxl(&path) {
Ok(image) => image,
Err(why) => {
tracing::warn!(
?why,
"jpegl-xl image decode failed: {}",
path.display()
);
continue;
}
},

_ => match ImageReader::open(&path) {
Ok(img) => {
match img
.with_guessed_format()
.ok()
.and_then(|f| f.decode().ok())
{
Some(img) => img,
None => {
tracing::warn!(
"could not decode image: {}",
path.display()
);
continue;
}
}
}
}
Err(_) => continue,
Err(_) => continue,
},
});
}
let img = self.current_image.as_ref().unwrap();
Expand Down Expand Up @@ -380,3 +397,74 @@ fn current_image(output: &str) -> Option<Source> {

wallpaper.map(|(_name, path)| path)
}

/// Decodes JPEG XL image files into `image::DynamicImage` via `jxl-oxide`.
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 => 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 => 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 => 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 => 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")),
}
}

0 comments on commit fd44edf

Please sign in to comment.