From 415262065fff0a04b229cd00165f346a86a0a73a Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 24 Apr 2024 19:06:14 +0200 Subject: [PATCH] core: Add support for JPEG and WEBP background images (#286) * Add KDevelop files to the .gitignore * Add support for JPEG and WEBP background images Most of the code is copy-pasted from hyprpaper * Try doing nix stuff * Do not use brackets for short ifs --- .gitignore | 1 + CMakeLists.txt | 2 +- nix/default.nix | 6 ++ src/helpers/Jpeg.cpp | 75 +++++++++++++++++++++++ src/helpers/Jpeg.hpp | 8 +++ src/helpers/Webp.cpp | 85 ++++++++++++++++++++++++++ src/helpers/Webp.hpp | 8 +++ src/renderer/AsyncResourceGatherer.cpp | 56 ++++++++++++++++- 8 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 src/helpers/Jpeg.cpp create mode 100644 src/helpers/Jpeg.hpp create mode 100644 src/helpers/Webp.cpp create mode 100644 src/helpers/Webp.hpp diff --git a/.gitignore b/.gitignore index 03bade0..068ac23 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build/ compile_commands.json protocols/*.c protocols/*.h +*.kdev4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cc75f1..9246428 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ message(STATUS "Checking deps...") find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) find_package(OpenGL REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon cairo pangocairo libdrm gbm) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols wayland-egl hyprlang>=0.4.0 egl opengl xkbcommon libjpeg libwebp libmagic cairo pangocairo libdrm gbm) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") add_executable(hyprlock ${SRCFILES}) diff --git a/nix/default.nix b/nix/default.nix index deb0634..028c039 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,8 +4,11 @@ cmake, pkg-config, cairo, + file, libdrm, libGL, + libjpeg, + libwebp, libxkbcommon, mesa, hyprlang, @@ -28,8 +31,11 @@ stdenv.mkDerivation { buildInputs = [ cairo + file libdrm libGL + libjpeg + libwebp libxkbcommon mesa hyprlang diff --git a/src/helpers/Jpeg.cpp b/src/helpers/Jpeg.cpp new file mode 100644 index 0000000..c9ad08d --- /dev/null +++ b/src/helpers/Jpeg.cpp @@ -0,0 +1,75 @@ +#include "Jpeg.hpp" +#include "Log.hpp" + +#include +#include +#include +#include +#include + +cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::filesystem::path& path) { + + if (!std::filesystem::exists(path)) { + Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??"); + return nullptr; + } + + if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) { + Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy."); + return nullptr; + } + + 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); + + // now the JPEG is in the memory + + jpeg_decompress_struct decompressStruct = {}; + jpeg_error_mgr errorManager = {}; + + decompressStruct.err = jpeg_std_error(&errorManager); + jpeg_create_decompress(&decompressStruct); + jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size); + jpeg_read_header(&decompressStruct, true); + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + decompressStruct.out_color_space = JCS_EXT_BGRA; +#else + decompressStruct.out_color_space = JCS_EXT_ARGB; +#endif + + // decompress + jpeg_start_decompress(&decompressStruct); + + auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height); + + if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) { + Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)"); + return nullptr; + } + + const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface); + const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface); + JSAMPROW rowRead; + + while (decompressStruct.output_scanline < decompressStruct.output_height) { + const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE); + rowRead = PROW; + jpeg_read_scanlines(&decompressStruct, &rowRead, 1); + } + + cairo_surface_mark_dirty(cairoSurface); + cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData); + jpeg_finish_decompress(&decompressStruct); + jpeg_destroy_decompress(&decompressStruct); + + return cairoSurface; +} diff --git a/src/helpers/Jpeg.hpp b/src/helpers/Jpeg.hpp new file mode 100644 index 0000000..58a6e8b --- /dev/null +++ b/src/helpers/Jpeg.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace JPEG { + cairo_surface_t* createSurfaceFromJPEG(const std::filesystem::path&); +}; diff --git a/src/helpers/Webp.cpp b/src/helpers/Webp.cpp new file mode 100644 index 0000000..08d1ada --- /dev/null +++ b/src/helpers/Webp.cpp @@ -0,0 +1,85 @@ +#include "Webp.hpp" +#include "Log.hpp" + +#include +#include +#include +#include + +#include + +cairo_surface_t* WEBP::createSurfaceFromWEBP(const std::filesystem::path& path) { + + if (!std::filesystem::exists(path)) { + Debug::log(ERR, "createSurfaceFromWEBP: file doesn't exist??"); + return nullptr; + } + + 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); + + // now the WebP is in the memory + + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) { + Debug::log(CRIT, "WebPInitDecoderConfig Failed"); + return nullptr; + } + + if (WebPGetFeatures((const unsigned char*)imageRawData, fileInfo.st_size, &config.input) != VP8_STATUS_OK) { + Debug::log(ERR, "createSurfaceFromWEBP: file is not webp format"); + free(imageRawData); + return nullptr; + } + + const auto HEIGHT = config.input.height; + const auto WIDTH = config.input.width; + + auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT); + if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) { + Debug::log(CRIT, "createSurfaceFromWEBP: Cairo Failed (?)"); + cairo_surface_destroy(cairoSurface); + return nullptr; + } + + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + config.output.colorspace = MODE_bgrA; +#else + config.output.colorspace = MODE_Argb; +#endif + + const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface); + const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface); + + config.options.no_fancy_upsampling = 1; + config.output.u.RGBA.rgba = CAIRODATA; + config.output.u.RGBA.stride = CAIROSTRIDE; + config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT; + config.output.is_external_memory = 1; + config.output.width = WIDTH; + config.output.height = HEIGHT; + + if (WebPDecode((const unsigned char*)imageRawData, fileInfo.st_size, &config) != VP8_STATUS_OK) { + Debug::log(CRIT, "createSurfaceFromWEBP: WebP Decode Failed (?)"); + return nullptr; + } + + cairo_surface_mark_dirty(cairoSurface); + cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_PNG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData); + + WebPFreeDecBuffer(&config.output); + + return cairoSurface; + +} diff --git a/src/helpers/Webp.hpp b/src/helpers/Webp.hpp new file mode 100644 index 0000000..3fc4790 --- /dev/null +++ b/src/helpers/Webp.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace WEBP { + cairo_surface_t* createSurfaceFromWEBP(const std::filesystem::path&); +}; diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index 914292e..1859774 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -2,10 +2,14 @@ #include "../config/ConfigManager.hpp" #include "../core/Egl.hpp" #include +#include #include #include +#include #include "../core/hyprlock.hpp" #include "../helpers/MiscFunctions.hpp" +#include "../helpers/Jpeg.hpp" +#include "../helpers/Webp.hpp" std::mutex cvmtx; @@ -105,6 +109,13 @@ SPreloadedAsset* CAsyncResourceGatherer::getAssetByID(const std::string& id) { return nullptr; } +enum class FileType { + PNG, + JPEG, + WEBP, + UNKNOWN, +}; + void CAsyncResourceGatherer::gather() { const auto CWIDGETS = g_pConfigManager->getWidgetConfigs(); @@ -131,11 +142,50 @@ void CAsyncResourceGatherer::gather() { if (path.empty() || path == "screenshot") continue; - std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; - const auto ABSOLUTEPATH = absolutePath(path, ""); + std::string id = (c.type == "background" ? std::string{"background:"} : std::string{"image:"}) + path; + std::filesystem::path ABSOLUTEPATH(absolutePath(path, "")); + + // determine the file type + std::string ext = ABSOLUTEPATH.extension().string(); + // convert the extension to lower case + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { return c <= 'Z' && c >= 'A' ? c - ('Z' - 'z') : c; }); + + FileType ft = FileType::UNKNOWN; + Debug::log(WARN, "Extension: {}", ext); + if (ext == ".png") + ft = FileType::PNG; + else if (ext == ".jpg" || ext == ".jpeg") + ft = FileType::JPEG; + else if (ext == ".webp") + ft = FileType::WEBP; + else { + // magic is slow, so only use it when no recognized extension is found + auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS); + magic_load(handle, nullptr); + + const auto type_str = std::string(magic_file(handle, path.c_str())); + const auto first_word = type_str.substr(0, type_str.find(" ")); + magic_close(handle); + + if (first_word == "PNG") + ft = FileType::PNG; + else if (first_word == "JPEG") + ft = FileType::JPEG; + else if (first_word == "RIFF" && type_str.find("Web/P image") != std::string::npos) + ft = FileType::WEBP; + } // preload bg img - const auto CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str()); + cairo_surface_t* CAIROISURFACE = nullptr; + switch (ft) { + case FileType::PNG: CAIROISURFACE = cairo_image_surface_create_from_png(ABSOLUTEPATH.c_str()); break; + case FileType::JPEG: CAIROISURFACE = JPEG::createSurfaceFromJPEG(ABSOLUTEPATH); break; + case FileType::WEBP: CAIROISURFACE = WEBP::createSurfaceFromWEBP(ABSOLUTEPATH); break; + default: Debug::log(ERR, "unrecognized image format of {}", path.c_str()); continue; + } + + if (CAIROISURFACE == nullptr) + continue; const auto CAIRO = cairo_create(CAIROISURFACE); cairo_scale(CAIRO, 1, 1);