Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for JPEG-XL #212

Merged
merged 6 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ pkg_check_modules(
pangocairo
libjpeg
libwebp
libjxl
libjxl_cms
libjxl_threads
hyprlang>=0.2.0
hyprutils>=0.2.0)

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@ The development files of these packages need to be installed on the system for `
- libglvnd-core
- libjpeg-turbo
- libwebp
- libjxl
- hyprlang
- hyprutils
- hyprwayland-scanner

To install all of these in Fedora, run this command:
```
sudo dnf install wayland-devel wayland-protocols-devel hyprlang-devel pango-devel cairo-devel file-devel libglvnd-devel libglvnd-core-devel libjpeg-turbo-devel libwebp-devel gcc-c++ hyprutils-devel hyprwayland-scanner
sudo dnf install wayland-devel wayland-protocols-devel hyprlang-devel pango-devel cairo-devel file-devel libglvnd-devel libglvnd-core-devel libjpeg-turbo-devel libwebp-devel libjxl-devel gcc-c++ hyprutils-devel hyprwayland-scanner
```

On Arch:
```
sudo pacman -S ninja gcc wayland-protocols libjpeg-turbo libwebp pango cairo pkgconf cmake libglvnd wayland hyprutils hyprwayland-scanner hyprlang
sudo pacman -S ninja gcc wayland-protocols libjpeg-turbo libwebp libjxl pango cairo pkgconf cmake libglvnd wayland hyprutils hyprwayland-scanner hyprlang
```

On OpenSUSE:
Expand Down Expand Up @@ -89,7 +90,7 @@ splash = true

```

Preload will tell Hyprland to load a particular image (supported formats: png, jpg, jpeg, webp). Wallpaper will apply the wallpaper to the selected output (`monitor` is the monitor's name, easily can be retrieved with `hyprctl monitors`. You can leave it empty to set all monitors without an active wallpaper. You can also use `desc:` followed by the monitor's description without the (PORT) at the end)
Preload will tell Hyprland to load a particular image (supported formats: png, jpg, jpeg, jpeg xl, webp). Wallpaper will apply the wallpaper to the selected output (`monitor` is the monitor's name, easily can be retrieved with `hyprctl monitors`. You can leave it empty to set all monitors without an active wallpaper. You can also use `desc:` followed by the monitor's description without the (PORT) at the end)

You may add `contain:` or `tile:` before the file path in `wallpaper=` to set the mode to either contain or tile, respectively, instead of cover:

Expand Down
2 changes: 2 additions & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
libdatrie,
libGL,
libjpeg,
libjxl,
libselinux,
libsepol,
libthai,
Expand Down Expand Up @@ -66,6 +67,7 @@ stdenv.mkDerivation {
libdatrie
libGL
libjpeg
libjxl
libselinux
libsepol
libthai
Expand Down
107 changes: 107 additions & 0 deletions src/helpers/JpegXL.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#include "JpegXL.hpp"

#include <filesystem>
#include <fstream>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner_cxx.h>

cairo_surface_t* JXL::createSurfaceFromJXL(const std::string& path) {

if (!std::filesystem::exists(path)) {
Debug::log(ERR, "createSurfaceFromJXL: file doesn't exist??");
exit(1);
}

std::ifstream file(path, std::ios::binary | std::ios::ate);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::vector<uint8_t> bytes(file.tellg());
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());

JxlSignature signature = JxlSignatureCheck(bytes.data(), bytes.size());
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
Debug::log(ERR, "createSurfaceFromJXL: file is not JXL format");
exit(1);
}

auto dec = JxlDecoderMake(nullptr);
auto runner = JxlResizableParallelRunnerMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderSetParallelRunner failed");
exit(1);
}

if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderSubscribeEvents failed");
exit(1);
}

JxlDecoderSetInput(dec.get(), bytes.data(), bytes.size());
JxlDecoderCloseInput(dec.get());
if (JXL_DEC_BASIC_INFO != JxlDecoderProcessInput(dec.get())) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput failed");
exit(1);
}

JxlBasicInfo basicInfo;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &basicInfo)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderGetBasicInfo failed");
exit(1);
}

auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, basicInfo.xsize, basicInfo.ysize);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "createSurfaceFromJXL: Cairo Failed (?)");
cairo_surface_destroy(cairoSurface);
exit(1);
}

const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);

JxlPixelFormat format = {
.num_channels = 4,
.data_type = JXL_TYPE_UINT8,
.endianness = JXL_LITTLE_ENDIAN,
.align = cairo_image_surface_get_stride(cairoSurface),
};

const auto OUTPUTSIZE = basicInfo.xsize * basicInfo.ysize * format.num_channels;

for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput failed");
cairo_surface_destroy(cairoSurface);
exit(1);
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput expected more input");
cairo_surface_destroy(cairoSurface);
exit(1);
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(basicInfo.xsize, basicInfo.ysize));
size_t bufferSize;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &bufferSize)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderImageOutBufferSize failed");
cairo_surface_destroy(cairoSurface);
exit(1);
}
if (bufferSize != OUTPUTSIZE) {
Debug::log(ERR, "createSurfaceFromJXL: invalid output buffer size");
cairo_surface_destroy(cairoSurface);
exit(1);
}
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, CAIRODATA, bufferSize)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderSetImageOutBuffer failed");
cairo_surface_destroy(cairoSurface);
exit(1);
}
} else if (status == JXL_DEC_FULL_IMAGE) {
for (size_t i = 0; i < OUTPUTSIZE - 2; i += format.num_channels) {
std::swap(CAIRODATA[i + 0], CAIRODATA[i + 2]);
}
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, "image/jxl", bytes.data(), bytes.size(), nullptr, nullptr);
return cairoSurface;
}
}
}
7 changes: 7 additions & 0 deletions src/helpers/JpegXL.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once

#include "../defines.hpp"

namespace JXL {
cairo_surface_t* createSurfaceFromJXL(const std::string&);
};
2 changes: 2 additions & 0 deletions src/render/WallpaperTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ void CWallpaperTarget::create(const std::string& path) {
m_bHasAlpha = false;
} else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) {
CAIROSURFACE = WEBP::createSurfaceFromWEBP(path);
} else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) {
CAIROSURFACE = JXL::createSurfaceFromJXL(path);
} else {
// magic is slow, so only use it when no recognized extension is found
auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
Expand Down
1 change: 1 addition & 0 deletions src/render/WallpaperTarget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "../helpers/Jpeg.hpp"
#include "../helpers/Bmp.hpp"
#include "../helpers/Webp.hpp"
#include "../helpers/JpegXL.hpp"

class CWallpaperTarget {
public:
Expand Down