Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package hyprgraphics for openSUSE:Factory checked in at 2025-06-05 20:34:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/hyprgraphics (Old) and /work/SRC/openSUSE:Factory/.hyprgraphics.new.19631 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "hyprgraphics" Thu Jun 5 20:34:47 2025 rev:2 rq:1283006 version:0.1.3 Changes: -------- --- /work/SRC/openSUSE:Factory/hyprgraphics/hyprgraphics.changes 2024-12-18 20:11:34.620892570 +0100 +++ /work/SRC/openSUSE:Factory/.hyprgraphics.new.19631/hyprgraphics.changes 2025-06-05 20:37:25.132976445 +0200 @@ -1,0 +2,17 @@ +Fri May 9 09:59:41 UTC 2025 - Florian "sp1rit" <[email protected]> + +- Update to version 0.1.3: + + A minor update with some fixes and new CM-related math + + Additions: + - CM structs, constants & math + +- Changes from version 0.1.2: + + This release adds a few small fixes, and moves to a more robust + and fast png parser. + + Changes: + - move to libspng for png parsing + - Allow compiling without JXL support + + Fixes: + - add symlink support for image + +------------------------------------------------------------------- Old: ---- hyprgraphics-0.1.1.tar.xz New: ---- hyprgraphics-0.1.3.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ hyprgraphics.spec ++++++ --- /var/tmp/diff_new_pack.RAFIks/_old 2025-06-05 20:37:25.628997069 +0200 +++ /var/tmp/diff_new_pack.RAFIks/_new 2025-06-05 20:37:25.628997069 +0200 @@ -19,7 +19,7 @@ %define sover 0 Name: hyprgraphics -Version: 0.1.1 +Version: 0.1.3 Release: 0 Summary: Hyprland graphics / resource utilities License: BSD-3-Clause @@ -37,6 +37,7 @@ BuildRequires: pkgconfig(libmagic) BuildRequires: pkgconfig(libwebp) BuildRequires: pkgconfig(pixman-1) +BuildRequires: pkgconfig(spng) %define _description %{expand: Hyprgraphics is a small C++ library with graphics / resource related ++++++ hyprgraphics-0.1.1.tar.xz -> hyprgraphics-0.1.3.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/.github/workflows/arch.yml new/hyprgraphics-0.1.3/.github/workflows/arch.yml --- old/hyprgraphics-0.1.1/.github/workflows/arch.yml 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/.github/workflows/arch.yml 2025-04-06 17:28:42.000000000 +0200 @@ -17,7 +17,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp libspng - name: Build hyprgraphics with gcc run: | @@ -44,7 +44,7 @@ run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp + pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp libspng - name: Build hyprgraphics with clang run: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/.github/workflows/nix.yml new/hyprgraphics-0.1.3/.github/workflows/nix.yml --- old/hyprgraphics-0.1.1/.github/workflows/nix.yml 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/.github/workflows/nix.yml 2025-04-06 17:28:42.000000000 +0200 @@ -14,7 +14,6 @@ - uses: actions/checkout@v3 - uses: cachix/install-nix-action@v26 - - uses: DeterminateSystems/magic-nix-cache-action@main # not needed (yet) # - uses: cachix/cachix-action@v12 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/.gitignore new/hyprgraphics-0.1.3/.gitignore --- old/hyprgraphics-0.1.1/.gitignore 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/.gitignore 2025-04-06 17:28:42.000000000 +0200 @@ -44,3 +44,5 @@ cmake_install.cmake compile_commands.json hyprutils.pc + +tests/test_output/ \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/CMakeLists.txt new/hyprgraphics-0.1.3/CMakeLists.txt --- old/hyprgraphics-0.1.1/CMakeLists.txt 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/CMakeLists.txt 2025-04-06 17:28:42.000000000 +0200 @@ -47,10 +47,22 @@ hyprutils libjpeg libwebp + libmagic + spng) + +pkg_check_modules( + JXL + IMPORTED_TARGET libjxl libjxl_cms libjxl_threads - libmagic) +) +if(NOT JXL_FOUND) + file(GLOB_RECURSE JPEGXLFILES CONFIGURE_DEPENDS "src/*JpegXL.cpp") + list(REMOVE_ITEM SRCFILES ${JPEGXLFILES}) +else() + add_compile_definitions(JXL_FOUND) +endif() add_library(hyprgraphics SHARED ${SRCFILES}) target_include_directories( @@ -60,6 +72,9 @@ set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION} SOVERSION 0) target_link_libraries(hyprgraphics PkgConfig::deps) +if(JXL_FOUND) + target_link_libraries(hyprgraphics PkgConfig::JXL) +endif() # tests add_custom_target(tests) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/README.md new/hyprgraphics-0.1.3/README.md --- old/hyprgraphics-0.1.1/README.md 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/README.md 2025-04-06 17:28:42.000000000 +0200 @@ -6,6 +6,22 @@ Hyprgraphics depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for hyprgraphics ABI breaks, not stdlib. +## Dependencies + +Requires a compiler with C++26 support. + +Dep list: + - pixman-1 + - cairo + - hyprutils + - libjpeg + - libwebp + - libjxl [optional] + - libjxl_cms [optional] + - libjxl_threads [optional] + - libmagic + - libspng + ## Building ```sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/VERSION new/hyprgraphics-0.1.3/VERSION --- old/hyprgraphics-0.1.1/VERSION 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/VERSION 2025-04-06 17:28:42.000000000 +0200 @@ -1 +1 @@ -0.1.1 +0.1.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/flake.lock new/hyprgraphics-0.1.3/flake.lock --- old/hyprgraphics-0.1.1/flake.lock 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/flake.lock 2025-04-06 17:28:42.000000000 +0200 @@ -10,11 +10,11 @@ ] }, "locked": { - "lastModified": 1732288281, - "narHash": "sha256-XTU9B53IjGeJiJ7LstOhuxcRjCOFkQFl01H78sT9Lg4=", + "lastModified": 1737632363, + "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "b26f33cc1c8a7fd5076e19e2cce3f062dca6351c", + "rev": "006620eb29d54ea9086538891404c78563d1bae1", "type": "github" }, "original": { @@ -25,11 +25,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1732014248, - "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=", + "lastModified": 1737469691, + "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367", + "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", "type": "github" }, "original": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/include/hyprgraphics/color/Color.hpp new/hyprgraphics-0.1.3/include/hyprgraphics/color/Color.hpp --- old/hyprgraphics-0.1.1/include/hyprgraphics/color/Color.hpp 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/include/hyprgraphics/color/Color.hpp 2025-04-06 17:28:42.000000000 +0200 @@ -1,5 +1,6 @@ #pragma once +#include <array> namespace Hyprgraphics { class CColor { public: @@ -18,6 +19,25 @@ double l = 0, a = 0, b = 0; }; + // xy 0.0 - 1.0 + struct xy { + double x = 0, y = 0; + + bool operator==(const xy& p2) const { + return x == p2.x && y == p2.y; + } + }; + + // XYZ 0.0 - 1.0 + struct XYZ { + double x = 0, y = 0, z = 0; + + // per-component division + XYZ operator/(const XYZ& other) const { + return {x / other.x, y / other.y, z / other.z}; + } + }; + CColor(); // black CColor(const SSRGB& rgb); CColor(const SHSL& hsl); @@ -27,7 +47,7 @@ SHSL asHSL() const; SOkLab asOkLab() const; - bool operator==(const CColor& other) const { + bool operator==(const CColor& other) const { return other.r == r && other.g == g && other.b == b; } @@ -35,4 +55,40 @@ // SRGB space for internal color storage double r = 0, g = 0, b = 0; }; + + // 3x3 matrix for CM transformations + class CMatrix3 { + public: + CMatrix3() = default; + CMatrix3(const std::array<std::array<double, 3>, 3>& values); + + CMatrix3 invert() const; + CColor::XYZ operator*(const CColor::XYZ& xyz) const; + CMatrix3 operator*(const CMatrix3& other) const; + + const std::array<std::array<double, 3>, 3>& mat(); + + static const CMatrix3& identity(); + + private: + std::array<std::array<double, 3>, 3> m = { + 0, 0, 0, // + 0, 0, 0, // + 0, 0, 0, // + }; + }; + + CColor::XYZ xy2xyz(const CColor::xy& xy); + CMatrix3 adaptWhite(const CColor::xy& src, const CColor::xy& dst); + + struct SPCPRimaries { + CColor::xy red, green, blue, white; + + bool operator==(const SPCPRimaries& p2) const { + return red == p2.red && green == p2.green && blue == p2.blue && white == p2.white; + } + + CMatrix3 toXYZ() const; // toXYZ() * rgb -> xyz + CMatrix3 convertMatrix(const SPCPRimaries& dst) const; // convertMatrix(dst) * rgb with "this" primaries -> rgb with dst primaries + }; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/nix/default.nix new/hyprgraphics-0.1.3/nix/default.nix --- old/hyprgraphics-0.1.1/nix/default.nix 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/nix/default.nix 2025-04-06 17:28:42.000000000 +0200 @@ -9,6 +9,7 @@ hyprutils, libjpeg, libjxl, + libspng, libwebp, pixman, version ? "git", @@ -50,6 +51,7 @@ hyprutils libjpeg libjxl + libspng libwebp pixman ]; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/src/color/Color.cpp new/hyprgraphics-0.1.3/src/color/Color.cpp --- old/hyprgraphics-0.1.1/src/color/Color.cpp 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/src/color/Color.cpp 2025-04-06 17:28:42.000000000 +0200 @@ -1,4 +1,5 @@ #include <hyprgraphics/color/Color.hpp> +#include <algorithm> #include <cmath> using namespace Hyprgraphics; @@ -25,6 +26,114 @@ return p; } +Hyprgraphics::CMatrix3::CMatrix3(const std::array<std::array<double, 3>, 3>& values) : m(values) {} + +CMatrix3 Hyprgraphics::CMatrix3::invert() const { + double invDet = 1 / + (0 // + + m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2]) // + - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) // + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]) // + ); + + return CMatrix3(std::array<std::array<double, 3>, 3>{ + (m[1][1] * m[2][2] - m[2][1] * m[1][2]) * invDet, (m[0][2] * m[2][1] - m[0][1] * m[2][2]) * invDet, (m[0][1] * m[1][2] - m[0][2] * m[1][1]) * invDet, // + (m[1][2] * m[2][0] - m[1][0] * m[2][2]) * invDet, (m[0][0] * m[2][2] - m[0][2] * m[2][0]) * invDet, (m[1][0] * m[0][2] - m[0][0] * m[1][2]) * invDet, // + (m[1][0] * m[2][1] - m[2][0] * m[1][1]) * invDet, (m[2][0] * m[0][1] - m[0][0] * m[2][1]) * invDet, (m[0][0] * m[1][1] - m[1][0] * m[0][1]) * invDet, // + }); +} + +CColor::XYZ Hyprgraphics::CMatrix3::operator*(const CColor::XYZ& value) const { + return CColor::XYZ{ + m[0][0] * value.x + m[0][1] * value.y + m[0][2] * value.z, // + m[1][0] * value.x + m[1][1] * value.y + m[1][2] * value.z, // + m[2][0] * value.x + m[2][1] * value.y + m[2][2] * value.z, // + }; +} + +CMatrix3 Hyprgraphics::CMatrix3::operator*(const CMatrix3& other) const { + std::array<std::array<double, 3>, 3> res = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + res[i][j] += m[i][k] * other.m[k][j]; + } + } + } + return CMatrix3(res); +} + +const std::array<std::array<double, 3>, 3>& Hyprgraphics::CMatrix3::mat() { + return m; +}; + +const CMatrix3& CMatrix3::identity() { + static const CMatrix3 Identity3 = CMatrix3(std::array<std::array<double, 3>, 3>{ + 1, 0, 0, // + 0, 1, 0, // + 0, 0, 1, // + }); + return Identity3; +} + +CColor::XYZ Hyprgraphics::xy2xyz(const CColor::xy& xy) { + if (xy.y == 0.0) + return {0.0, 0.0, 0.0}; + + return {xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y}; +} + +CMatrix3 Bradford = CMatrix3(std::array<std::array<double, 3>, 3>{ + 0.8951, 0.2664, -0.1614, // + -0.7502, 1.7135, 0.0367, // + 0.0389, -0.0685, 1.0296, // +}); + +CMatrix3 BradfordInv = Bradford.invert(); + +CMatrix3 Hyprgraphics::adaptWhite(const CColor::xy& src, const CColor::xy& dst) { + if (src == dst) + return CMatrix3::identity(); + + const auto srcXYZ = xy2xyz(src); + const auto dstXYZ = xy2xyz(dst); + const auto factors = (Bradford * dstXYZ) / (Bradford * srcXYZ); + + return BradfordInv * + CMatrix3(std::array<std::array<double, 3>, 3>{ + factors.x, 0.0, 0.0, // + 0.0, factors.y, 0.0, // + 0.0, 0.0, factors.z, // + }) * + Bradford; +} + +CMatrix3 Hyprgraphics::SPCPRimaries::toXYZ() const { + const auto r = xy2xyz(red); + const auto g = xy2xyz(green); + const auto b = xy2xyz(blue); + const auto w = xy2xyz(white); + + const auto invMat = CMatrix3(std::array<std::array<double, 3>, 3>{ + r.x, g.x, b.x, // + r.y, g.y, b.y, // + r.z, g.z, b.z, // + }) + .invert(); + + const auto s = invMat * w; + + return std::array<std::array<double, 3>, 3>{ + s.x * r.x, s.y * g.x, s.z * b.x, // + s.x * r.y, s.y * g.y, s.z * b.y, // + s.x * r.z, s.y * g.z, s.z * b.z, // + }; +} + +CMatrix3 Hyprgraphics::SPCPRimaries::convertMatrix(const SPCPRimaries& dst) const { + return dst.toXYZ().invert() * adaptWhite(white, dst.white) * toXYZ(); +} + Hyprgraphics::CColor::CColor() { ; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/src/image/Image.cpp new/hyprgraphics-0.1.3/src/image/Image.cpp --- old/hyprgraphics-0.1.1/src/image/Image.cpp 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/src/image/Image.cpp 2025-04-06 17:28:42.000000000 +0200 @@ -1,8 +1,11 @@ #include <hyprgraphics/image/Image.hpp> #include "formats/Bmp.hpp" #include "formats/Jpeg.hpp" +#ifdef JXL_FOUND #include "formats/JpegXL.hpp" +#endif #include "formats/Webp.hpp" +#include "formats/Png.hpp" #include <magic.h> #include <format> @@ -13,41 +16,48 @@ std::expected<cairo_surface_t*, std::string> CAIROSURFACE; const auto len = path.length(); if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) { - CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str()); - mime = "image/png"; + CAIROSURFACE = PNG::createSurfaceFromPNG(path); + mime = "image/png"; } else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) { CAIROSURFACE = JPEG::createSurfaceFromJPEG(path); imageHasAlpha = false; - mime = "image/jpeg"; + mime = "image/jpeg"; } else if (path.find(".bmp") == len - 4 || path.find(".BMP") == len - 4) { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; - mime = "image/bmp"; + mime = "image/bmp"; } else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) { CAIROSURFACE = WEBP::createSurfaceFromWEBP(path); - mime = "image/webp"; + mime = "image/webp"; } else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) { + +#ifdef JXL_FOUND CAIROSURFACE = JXL::createSurfaceFromJXL(path); - mime = "image/jxl"; + mime = "image/jxl"; +#else + lastError = "hyprgraphics compiled without JXL support"; + return; +#endif + } else { // magic is slow, so only use it when no recognized extension is found - auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS); + auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS | MAGIC_SYMLINK); 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(" ")); if (first_word == "PNG") { - CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str()); - mime = "image/png"; + CAIROSURFACE = PNG::createSurfaceFromPNG(path); + mime = "image/png"; } else if (first_word == "JPEG") { CAIROSURFACE = JPEG::createSurfaceFromJPEG(path); imageHasAlpha = false; - mime = "image/jpeg"; + mime = "image/jpeg"; } else if (first_word == "BMP") { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; - mime = "image/bmp"; + mime = "image/bmp"; } else { lastError = "unrecognized image"; return; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/src/image/formats/Bmp.cpp new/hyprgraphics-0.1.3/src/image/formats/Bmp.cpp --- old/hyprgraphics-0.1.1/src/image/formats/Bmp.cpp 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/src/image/formats/Bmp.cpp 2025-04-06 17:28:42.000000000 +0200 @@ -102,8 +102,8 @@ if (!std::filesystem::exists(path)) return std::unexpected("loading bmp: file doesn't exist"); - std::ifstream bitmapImageStream(path); - BmpHeader bitmapHeader; + std::ifstream bitmapImageStream(path); + BmpHeader bitmapHeader; if (const auto RET = bitmapHeader.load(bitmapImageStream); RET.has_value()) return std::unexpected("loading bmp: " + *RET); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/src/image/formats/Png.cpp new/hyprgraphics-0.1.3/src/image/formats/Png.cpp --- old/hyprgraphics-0.1.1/src/image/formats/Png.cpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.1.3/src/image/formats/Png.cpp 2025-04-06 17:28:42.000000000 +0200 @@ -0,0 +1,93 @@ +#include "Png.hpp" +#include <spng.h> +#include <vector> +#include <fstream> +#include <filesystem> +#include <cstdint> +#include <hyprutils/utils/ScopeGuard.hpp> +using namespace Hyprutils::Utils; + +static std::vector<unsigned char> readBinaryFile(const std::string& filename) { + std::ifstream f(filename, std::ios::binary); + if (!f.good()) + return {}; + f.unsetf(std::ios::skipws); + return {std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()}; +} + +std::expected<cairo_surface_t*, std::string> PNG::createSurfaceFromPNG(const std::string& path) { + if (!std::filesystem::exists(path)) + return std::unexpected("loading png: file doesn't exist"); + + spng_ctx* ctx = spng_ctx_new(0); + + CScopeGuard x([&] { spng_ctx_free(ctx); }); + + const auto PNGCONTENT = readBinaryFile(path); + + if (PNGCONTENT.empty()) + return std::unexpected("loading png: file content was empty (bad file?)"); + + spng_set_png_buffer(ctx, PNGCONTENT.data(), PNGCONTENT.size()); + + spng_ihdr ihdr{0}; + if (int ret = spng_get_ihdr(ctx, &ihdr); ret) + return std::unexpected(std::string{"loading png: spng_get_ihdr failed: "} + spng_strerror(ret)); + + int fmt = SPNG_FMT_PNG; + if (ihdr.color_type == SPNG_COLOR_TYPE_INDEXED) + fmt = SPNG_FMT_RGB8; + + size_t imageLength = 0; + if (int ret = spng_decoded_image_size(ctx, fmt, &imageLength); ret) + return std::unexpected(std::string{"loading png: spng_decoded_image_size failed: "} + spng_strerror(ret)); + + uint8_t* imageData = (uint8_t*)malloc(imageLength); + + if (!imageData) + return std::unexpected("loading png: mallocing failed, out of memory?"); + + // TODO: allow proper decode of high bitrate images + bool succeededDecode = false; + int ret = spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0); + if (!ret) + succeededDecode = true; + + if (!succeededDecode && ret == SPNG_EBUFSIZ) { + // hack, but I don't know why decoded_image_size is sometimes wrong + imageLength = ihdr.height * ihdr.width * 4 /* FIXME: this is wrong if we doing >32bpp!!!! */; + imageData = (uint8_t*)realloc(imageData, imageLength); + + ret = spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0); + } + + if (!ret) + succeededDecode = true; + + if (!succeededDecode) { + free(imageData); + return std::unexpected(std::string{"loading png: spng_decode_image failed: "} + spng_strerror(ret) + " (bad image?)"); + } + + // convert RGBA8888 -> ARGB8888 premult for cairo + for (size_t i = 0; i < imageLength; i += 4) { + uint8_t r, g, b, a; + a = ((*((uint32_t*)(imageData + i))) & 0xFF000000) >> 24; + b = ((*((uint32_t*)(imageData + i))) & 0x00FF0000) >> 16; + g = ((*((uint32_t*)(imageData + i))) & 0x0000FF00) >> 8; + r = (*((uint32_t*)(imageData + i))) & 0x000000FF; + + r *= ((float)a / 255.F); + g *= ((float)a / 255.F); + b *= ((float)a / 255.F); + + *((uint32_t*)(imageData + i)) = (((uint32_t)a) << 24) | (((uint32_t)r) << 16) | (((uint32_t)g) << 8) | (uint32_t)b; + } + + auto CAIROSURFACE = cairo_image_surface_create_for_data(imageData, CAIRO_FORMAT_ARGB32, ihdr.width, ihdr.height, ihdr.width * 4); + + if (!CAIROSURFACE) + return std::unexpected("loading png: cairo failed"); + + return CAIROSURFACE; +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/src/image/formats/Png.hpp new/hyprgraphics-0.1.3/src/image/formats/Png.hpp --- old/hyprgraphics-0.1.1/src/image/formats/Png.hpp 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.1.3/src/image/formats/Png.hpp 2025-04-06 17:28:42.000000000 +0200 @@ -0,0 +1,9 @@ +#pragma once + +#include <cairo/cairo.h> +#include <string> +#include <expected> + +namespace PNG { + std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::string&); +}; \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/tests/image.cpp new/hyprgraphics-0.1.3/tests/image.cpp --- old/hyprgraphics-0.1.1/tests/image.cpp 2024-12-03 18:52:51.000000000 +0100 +++ new/hyprgraphics-0.1.3/tests/image.cpp 2025-04-06 17:28:42.000000000 +0200 @@ -16,7 +16,16 @@ std::println("Loaded {} successfully: Image is {}x{} of type {}", path, image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime()); - return true; + const auto TEST_DIR = std::filesystem::current_path().string() + "/test_output"; + + // try to write it for inspection + if (!std::filesystem::exists(TEST_DIR)) + std::filesystem::create_directory(TEST_DIR); + + std::string name = image.getMime(); + std::replace(name.begin(), name.end(), '/', '_'); + + return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS; } int main(int argc, char** argv, char** envp) { @@ -25,8 +34,12 @@ for (auto& file : std::filesystem::directory_iterator("./resource/images/")) { if (!file.is_regular_file()) continue; - - EXPECT(tryLoadImage(file.path()), true); + auto expectation = true; +#ifndef JXL_FOUND + if (file.path().filename() == "hyprland.jxl") + expectation = false; +#endif + EXPECT(tryLoadImage(file.path()), expectation); } return ret; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/hyprgraphics-0.1.1/tests/resource/images/hyprland.symlink new/hyprgraphics-0.1.3/tests/resource/images/hyprland.symlink --- old/hyprgraphics-0.1.1/tests/resource/images/hyprland.symlink 1970-01-01 01:00:00.000000000 +0100 +++ new/hyprgraphics-0.1.3/tests/resource/images/hyprland.symlink 2025-06-05 20:37:25.785003556 +0200 @@ -0,0 +1 @@ +symbolic link to hyprland.png
