Skip to content

Commit

Permalink
Add support for JPEG-XL
Browse files Browse the repository at this point in the history
  • Loading branch information
arrufat authored and fufexan committed Nov 21, 2024
1 parent 3f8cc92 commit bdb7b3b
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 3 deletions.
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
129 changes: 129 additions & 0 deletions src/helpers/JpegXL.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "JpegXL.hpp"

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <filesystem>
#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);
}

void* imageRawData;

struct stat fileInfo = {};

const auto FD = open(path.c_str(), O_RDONLY);

fstat(FD, &fileInfo);

imageRawData = malloc(fileInfo.st_size);

read(FD, imageRawData, fileInfo.st_size);

close(FD);

JxlSignature signature = JxlSignatureCheck(reinterpret_cast<const uint8_t*>(imageRawData), fileInfo.st_size);
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER) {
Debug::log(ERR, "createSurfaceFromJXL: file is not JXL format");
free(imageRawData);
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");
free(imageRawData);
exit(1);
}

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

JxlDecoderSetInput(dec.get(), reinterpret_cast<const uint8_t*>(imageRawData), fileInfo.st_size);
JxlDecoderCloseInput(dec.get());
if (JXL_DEC_BASIC_INFO != JxlDecoderProcessInput(dec.get())) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput failed");
free(imageRawData);
exit(1);
}

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

auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, basic_info.xsize, basic_info.ysize);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
Debug::log(ERR, "createSurfaceFromJXL: Cairo Failed (?)");
free(imageRawData);
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_BIG_ENDIAN,
.align = cairo_image_surface_get_stride(cairoSurface),
};

const auto output_size = basic_info.xsize * basic_info.ysize * format.num_channels;

for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput failed");
free(imageRawData);
cairo_surface_destroy(cairoSurface);
exit(1);
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderProcessInput expected more input");
free(imageRawData);
cairo_surface_destroy(cairoSurface);
exit(1);
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(basic_info.xsize, basic_info.ysize));
size_t buffer_size;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderImageOutBufferSize failed");
free(imageRawData);
cairo_surface_destroy(cairoSurface);
exit(1);
}
if (buffer_size != output_size) {
Debug::log(ERR, "createSurfaceFromJXL: invalid output buffer size");
free(imageRawData);
cairo_surface_destroy(cairoSurface);
exit(1);
}
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, CAIRODATA, buffer_size)) {
Debug::log(ERR, "createSurfaceFromJXL: JxlDecoderSetImageOutBuffer failed");
free(imageRawData);
cairo_surface_destroy(cairoSurface);
exit(1);
}
} else if (status == JXL_DEC_FULL_IMAGE) {
for (size_t i = 0; i < output_size - 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", (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
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

0 comments on commit bdb7b3b

Please sign in to comment.