Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

44 changed files with 158 additions and 2225 deletions

View file

@ -1,101 +0,0 @@
WarningsAsErrors: '*'
HeaderFilterRegex: '.*\.hpp'
FormatStyle: 'file'
Checks: >
-*,
bugprone-*,
-bugprone-easily-swappable-parameters,
-bugprone-forward-declaration-namespace,
-bugprone-forward-declaration-namespace,
-bugprone-macro-parentheses,
-bugprone-narrowing-conversions,
-bugprone-branch-clone,
-bugprone-assignment-in-if-condition,
concurrency-*,
-concurrency-mt-unsafe,
cppcoreguidelines-*,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-explicit-virtual-functions,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-member-init,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-pro-type-cstyle-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-reinterpret-cast,
google-global-names-in-headers,
-google-readability-casting,
google-runtime-operator,
misc-*,
-misc-unused-parameters,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-include-cleaner,
-misc-use-anonymous-namespace,
-misc-const-correctness,
modernize-*,
-modernize-return-braced-init-list,
-modernize-use-trailing-return-type,
-modernize-use-using,
-modernize-use-override,
-modernize-avoid-c-arrays,
-modernize-macro-to-enum,
-modernize-loop-convert,
-modernize-use-nodiscard,
-modernize-pass-by-value,
-modernize-use-auto,
performance-*,
-performance-avoid-endl,
-performance-unnecessary-value-param,
portability-std-allocator-const,
readability-*,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-magic-numbers,
-readability-uppercase-literal-suffix,
-readability-braces-around-statements,
-readability-redundant-access-specifiers,
-readability-else-after-return,
-readability-container-data-pointer,
-readability-implicit-bool-conversion,
-readability-avoid-nested-conditional-operator,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-convert-member-functions-to-static,
-readability-qualified-auto,
-readability-make-member-function-const,
-readability-isolate-declaration,
-readability-inconsistent-declaration-parameter-name,
-clang-diagnostic-error,
CheckOptions:
performance-for-range-copy.WarnOnAllAutoCopies: true
performance-inefficient-string-concatenation.StrictMode: true
readability-braces-around-statements.ShortStatementLines: 0
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassIgnoredRegexp: I.*
readability-identifier-naming.ClassPrefix: C # We can't use regex here?!?!?!?
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.EnumPrefix: e
readability-identifier-naming.EnumConstantCase: UPPER_CASE
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.NamespacePrefix: N
readability-identifier-naming.StructPrefix: S
readability-identifier-naming.StructCase: CamelCase

View file

@ -17,7 +17,7 @@ jobs:
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++ pango pixman cairo hyprutils libdrm libglvnd libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp
- name: Build hyprgraphics with gcc
run: |
@ -44,7 +44,7 @@ jobs:
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++ pango pixman cairo hyprutils libdrm libglvnd libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg
pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp
- name: Build hyprgraphics with clang
run: |

View file

@ -1,7 +1,6 @@
name: Build & Test
on: [push, pull_request, workflow_dispatch]
jobs:
nix:
strategy:
@ -10,8 +9,19 @@ jobs:
- hyprgraphics
- hyprgraphics-with-tests
if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
uses: hyprwm/actions/.github/workflows/nix.yml@main
secrets: inherit
with:
command: nix build .#${{ matrix.package }} --print-build-logs
runs-on: ubuntu-latest
steps:
- 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
# with:
# name: hyprland
# authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- name: Build & Test
run: nix build .#${{ matrix.package }} --print-build-logs

2
.gitignore vendored
View file

@ -44,5 +44,3 @@ Makefile
cmake_install.cmake
compile_commands.json
hyprutils.pc
tests/test_output/

View file

@ -21,16 +21,6 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprgraphics.pc.in hyprgraphics.pc @ONLY)
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
# -Wno-missing-braces for clang
add_compile_options(
-Wall
-Wextra
-Wpedantic
-Wno-unused-parameter
-Wno-unused-value
-Wno-missing-field-initializers
-Wno-missing-braces)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprgraphics in Debug")
@ -47,51 +37,20 @@ endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp")
set(GLES_VERSION "GLES3")
find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
find_package(PkgConfig REQUIRED)
pkg_check_modules(
deps
REQUIRED
IMPORTED_TARGET
libdrm
pixman-1
cairo
pangocairo
hyprutils
libjpeg
libwebp
libmagic
libpng
librsvg-2.0)
pkg_check_modules(
JXL
IMPORTED_TARGET
libjxl
libjxl_cms
libjxl_threads
)
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()
pkg_check_modules(
HEIF
IMPORTED_TARGET
libheif
)
if(NOT HEIF_FOUND)
file(GLOB_RECURSE HEIFFILES CONFIGURE_DEPENDS "src/*Avif.cpp")
list(REMOVE_ITEM SRCFILES ${HEIFFILES})
else()
add_compile_definitions(HEIF_FOUND)
endif()
libmagic)
add_library(hyprgraphics SHARED ${SRCFILES})
target_include_directories(
@ -99,15 +58,8 @@ target_include_directories(
PUBLIC "./include"
PRIVATE "./src")
set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION}
SOVERSION 4)
SOVERSION 0)
target_link_libraries(hyprgraphics PkgConfig::deps)
if(JXL_FOUND)
target_link_libraries(hyprgraphics PkgConfig::JXL)
endif()
if(HEIF_FOUND)
target_link_libraries(hyprgraphics PkgConfig::HEIF)
endif()
# tests
add_custom_target(tests)
@ -119,13 +71,6 @@ add_test(
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprgraphics_image "image")
add_dependencies(tests hyprgraphics_image)
add_executable(hyprgraphics_arg "tests/arg.cpp")
target_link_libraries(hyprgraphics_arg PRIVATE hyprgraphics PkgConfig::deps)
add_test(
NAME "ARG"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprgraphics_arg "image")
add_dependencies(tests hyprgraphics_arg)
# Installation
install(TARGETS hyprgraphics)

View file

@ -6,23 +6,6 @@ Hyprgraphics is a small C++ library with graphics / resource related utilities u
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
- libpng
- librsvg2
## Building
```sh

View file

@ -1 +1 @@
0.5.1
0.1.0

12
flake.lock generated
View file

@ -10,11 +10,11 @@
]
},
"locked": {
"lastModified": 1772459870,
"narHash": "sha256-xxkK2Cvqxpf/4UGcJ/TyCwrvmiNWsKsJfFzHMp2bxis=",
"lastModified": 1732288281,
"narHash": "sha256-XTU9B53IjGeJiJ7LstOhuxcRjCOFkQFl01H78sT9Lg4=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "e63f3a79334dec49f8eb1691f66f18115df04085",
"rev": "b26f33cc1c8a7fd5076e19e2cce3f062dca6351c",
"type": "github"
},
"original": {
@ -25,11 +25,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1772198003,
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
"lastModified": 1732014248,
"narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
"rev": "23e89b7da85c3640bbc2173fe04f4bd114342367",
"type": "github"
},
"original": {

View file

@ -12,32 +12,46 @@
};
};
outputs =
{
self,
nixpkgs,
systems,
...
}@inputs:
let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (
system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [ hyprgraphics-with-deps ];
}
);
in
{
overlays = import ./nix/overlays.nix { inherit inputs lib self; };
packages = eachSystem (system: {
default = self.packages.${system}.hyprgraphics;
inherit (pkgsFor.${system}) hyprgraphics hyprgraphics-with-tests;
outputs = {
self,
nixpkgs,
systems,
...
} @ inputs: let
inherit (nixpkgs) lib;
eachSystem = lib.genAttrs (import systems);
pkgsFor = eachSystem (system:
import nixpkgs {
localSystem.system = system;
overlays = with self.overlays; [hyprgraphics];
});
mkDate = longDate: (lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
formatter = eachSystem (system: pkgsFor.${system}.nixfmt-tree);
version = lib.removeSuffix "\n" (builtins.readFile ./VERSION);
in {
overlays = {
default = self.overlays.hyprgraphics;
hyprgraphics = lib.composeManyExtensions [
inputs.hyprutils.overlays.default
(final: prev: {
hyprgraphics = final.callPackage ./nix/default.nix {
stdenv = final.gcc14Stdenv;
version = version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
};
hyprgraphics-with-tests = final.hyprgraphics.override {doCheck = true;};
})
];
};
packages = eachSystem (system: {
default = self.packages.${system}.hyprgraphics;
inherit (pkgsFor.${system}) hyprgraphics hyprgraphics-with-tests;
});
formatter = eachSystem (system: pkgsFor.${system}.alejandra);
};
}

View file

@ -1,94 +0,0 @@
#pragma once
#include <array>
namespace Hyprgraphics {
class CColor {
public:
// SRGB (NOT linear!!) 0.0 - 1.0
struct SSRGB {
double r = 0, g = 0, b = 0;
};
// HSL 0.0 - 1.0
struct SHSL {
double h = 0, s = 0, l = 0;
};
// OkLab 0.0 - 1.0
struct SOkLab {
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 = x / other.x, .y = y / other.y, .z = z / other.z};
}
};
CColor(); // black
CColor(const SSRGB& rgb);
CColor(const SHSL& hsl);
CColor(const SOkLab& lab);
SSRGB asRgb() const;
SHSL asHSL() const;
SOkLab asOkLab() const;
bool operator==(const CColor& other) const {
return other.r == r && other.g == g && other.b == b;
}
private:
// 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
};
};

View file

@ -1,47 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <GLES3/gl32.h>
#include <optional>
#include <hyprutils/math/Vector2D.hpp>
namespace Hyprgraphics::Egl {
inline constexpr std::array<GLint, 4> SWIZZLE_A1GB{GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE};
inline constexpr std::array<GLint, 4> SWIZZLE_ABG1{GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_ABGR{GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED};
inline constexpr std::array<GLint, 4> SWIZZLE_ARGB{GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE};
inline constexpr std::array<GLint, 4> SWIZZLE_B1RG{GL_BLUE, GL_ONE, GL_RED, GL_GREEN};
inline constexpr std::array<GLint, 4> SWIZZLE_BARG{GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN};
inline constexpr std::array<GLint, 4> SWIZZLE_BGR1{GL_BLUE, GL_GREEN, GL_RED, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_BGRA{GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA};
inline constexpr std::array<GLint, 4> SWIZZLE_G1AB{GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE};
inline constexpr std::array<GLint, 4> SWIZZLE_GBA1{GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_GBAR{GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED};
inline constexpr std::array<GLint, 4> SWIZZLE_GRAB{GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE};
inline constexpr std::array<GLint, 4> SWIZZLE_R001{GL_RED, GL_ZERO, GL_ZERO, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_R1BG{GL_RED, GL_ONE, GL_BLUE, GL_GREEN};
inline constexpr std::array<GLint, 4> SWIZZLE_RABG{GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN};
inline constexpr std::array<GLint, 4> SWIZZLE_RG01{GL_RED, GL_GREEN, GL_ZERO, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_GR01{GL_GREEN, GL_RED, GL_ZERO, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_RGB1{GL_RED, GL_GREEN, GL_BLUE, GL_ONE};
inline constexpr std::array<GLint, 4> SWIZZLE_RGBA{GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
struct SPixelFormat {
uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */
int glInternalFormat = 0;
int glFormat = 0;
int glType = 0;
bool withAlpha = true;
uint32_t alphaStripped = 0; /* DRM_FORMAT_INVALID */
uint32_t bytesPerBlock = 0;
Hyprutils::Math::Vector2D blockSize;
std::optional<std::array<GLint, 4>> swizzle = std::nullopt;
};
const SPixelFormat* getPixelFormatFromDRM(uint32_t drmFormat);
const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha);
bool isDrmFormatOpaque(uint32_t drmFormat);
int pixelsPerBlock(const SPixelFormat* const fmt);
int minStride(const SPixelFormat* const fmt, int32_t width);
}

View file

@ -1,29 +1,15 @@
#pragma once
#include <string>
#include <span>
#include <cairo/cairo.h>
#include "../cairo/CairoSurface.hpp"
#include <hyprutils/memory/SharedPtr.hpp>
namespace Hyprgraphics {
enum eImageFormat : uint8_t {
IMAGE_FORMAT_PNG,
IMAGE_FORMAT_AVIF,
IMAGE_FORMAT_JPEG,
IMAGE_FORMAT_JXL,
IMAGE_FORMAT_BMP,
IMAGE_FORMAT_SVG,
IMAGE_FORMAT_WEBP,
IMAGE_FORMAT_ERROR,
IMAGE_FORMAT_AUTO, // take an educated guess
};
class CImage {
public:
CImage(const std::string& path, const Hyprutils::Math::Vector2D& size = {} /* Ignored if not svg */);
CImage(std::span<const uint8_t>, eImageFormat format = IMAGE_FORMAT_AUTO, const Hyprutils::Math::Vector2D& size = {} /* Ignored if not svg */);
// create an image from a provided path.
CImage(const std::string& path);
~CImage();
CImage(const CImage&) = delete;
@ -37,11 +23,8 @@ namespace Hyprgraphics {
Hyprutils::Memory::CSharedPointer<CCairoSurface> cairoSurface();
static bool isImageFile(const std::string& path);
private:
std::string lastError, filepath, mime;
Hyprutils::Math::Vector2D m_svgSize;
Hyprutils::Memory::CSharedPointer<CCairoSurface> pCairoSurface;
bool imageHasAlpha = true, loadSuccess = false;
};

View file

@ -1,41 +0,0 @@
#pragma once
#include <thread>
#include <atomic>
#include <vector>
#include <unordered_map>
#include <condition_variable>
#include "../cairo/CairoSurface.hpp"
#include "./resources/AsyncResource.hpp"
#include <hyprutils/memory/Atomic.hpp>
namespace Hyprgraphics {
class CAsyncResourceGatherer {
public:
CAsyncResourceGatherer();
~CAsyncResourceGatherer();
void enqueue(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource);
// Synchronously await the resource being available
void await(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource);
private:
std::thread m_gatherThread;
struct {
std::mutex requestMutex;
std::condition_variable requestsCV;
bool exit = false;
bool needsToProcess = false;
} m_asyncLoopState;
std::vector<Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource>> m_targetsToLoad;
std::mutex m_targetsToLoadMutex;
//
void asyncAssetSpinLock();
void wakeUpMainThread();
};
}

View file

@ -1,37 +0,0 @@
#pragma once
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/signal/Signal.hpp>
#include "../../cairo/CairoSurface.hpp"
#include <atomic>
namespace Hyprgraphics {
struct SAsyncResourceImpl;
class IAsyncResource {
public:
IAsyncResource();
virtual ~IAsyncResource() = default;
virtual void render() = 0;
struct {
// this signal fires on the worker thread. **Really** consider making this signal handler call something to wake your
// main event loop up and do things there.
Hyprutils::Signal::CSignalT<> finished;
} m_events;
// you probably shouldn't use this but it's here just in case.
std::atomic<bool> m_ready = false;
struct {
// This pointer can be made not thread safe as after .finished the worker thread will not touch it anymore
// and before that you shouldnt touch it either
Hyprutils::Memory::CSharedPointer<CCairoSurface> cairoSurface;
Hyprutils::Math::Vector2D pixelSize;
} m_asset;
Hyprutils::Memory::CUniquePointer<SAsyncResourceImpl> m_impl;
};
}

View file

@ -1,33 +0,0 @@
#pragma once
#include <string>
#include <span>
#include <hyprutils/math/Vector2D.hpp>
#include "./AsyncResource.hpp"
#include "../../color/Color.hpp"
#include <optional>
#include <span>
namespace Hyprgraphics {
class CImageResource : public IAsyncResource {
public:
enum eTextAlignmentMode : uint8_t {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_RIGHT,
};
CImageResource(const std::string& path);
CImageResource(const std::string& svg, const Hyprutils::Math::Vector2D& size);
CImageResource(const std::span<const uint8_t>& data, const Hyprutils::Math::Vector2D& size = {} /* unused if not svg */);
virtual ~CImageResource() = default;
virtual void render();
private:
std::string m_path;
Hyprutils::Math::Vector2D m_svgSize;
std::span<const uint8_t> m_data;
};
};

View file

@ -1,30 +0,0 @@
#pragma once
#include "./AsyncResource.hpp"
#include "../../color/Color.hpp"
#include "hyprgraphics/image/Image.hpp"
#include <optional>
#include <span>
#include <hyprutils/math/Vector2D.hpp>
namespace Hyprgraphics {
class CStaticImageResource : public IAsyncResource {
public:
enum eTextAlignmentMode : uint8_t {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_RIGHT,
};
CStaticImageResource(const std::span<const uint8_t> data, eImageFormat format);
virtual ~CStaticImageResource() = default;
virtual void render();
private:
const std::span<const uint8_t> m_data;
const eImageFormat m_format = eImageFormat::IMAGE_FORMAT_PNG;
};
};

View file

@ -1,42 +0,0 @@
#pragma once
#include "AsyncResource.hpp"
#include "../../color/Color.hpp"
#include <cairo/cairo.h>
#include <optional>
#include <hyprutils/math/Vector2D.hpp>
namespace Hyprgraphics {
class CTextResource : public IAsyncResource {
public:
enum eTextAlignmentMode : uint8_t {
TEXT_ALIGN_LEFT = 0,
TEXT_ALIGN_CENTER,
TEXT_ALIGN_RIGHT,
};
struct STextResourceData {
std::string text = "Sample Text";
std::string font = "Sans Serif";
size_t fontSize = 16;
CColor color = CColor{CColor::SSRGB{.r = 1.F, .g = 1.F, .b = 1.F}};
eTextAlignmentMode align = TEXT_ALIGN_LEFT;
std::optional<Hyprutils::Math::Vector2D> maxSize = std::nullopt;
cairo_antialias_t antialias = CAIRO_ANTIALIAS_GOOD;
cairo_hint_style_t hintStyle = CAIRO_HINT_STYLE_SLIGHT;
bool ellipsize = false;
bool wrap = true;
};
CTextResource(STextResourceData&& data);
virtual ~CTextResource() = default;
virtual void render();
private:
STextResourceData m_data;
};
};

View file

@ -7,21 +7,14 @@
cairo,
file,
hyprutils,
libGL,
libdrm,
libheif,
libjpeg,
libjxl,
librsvg,
libspng,
libwebp,
pango,
pixman,
version ? "git",
doCheck ? false,
debug ? false,
}:
let
}: let
inherit (builtins) foldl';
inherit (lib.lists) flatten;
inherit (lib.sources) cleanSource cleanSourceWith;
@ -34,52 +27,44 @@ let
customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters;
in
customStdenv.mkDerivation {
pname = "hyprgraphics";
inherit version doCheck;
customStdenv.mkDerivation {
pname = "hyprgraphics";
inherit version doCheck;
src = cleanSourceWith {
filter =
name: _type:
let
src = cleanSourceWith {
filter = name: _type: let
baseName = baseNameOf (toString name);
in
!(hasSuffix ".nix" baseName);
src = cleanSource ../.;
};
! (hasSuffix ".nix" baseName);
src = cleanSource ../.;
};
nativeBuildInputs = [
cmake
pkg-config
];
nativeBuildInputs = [
cmake
pkg-config
];
buildInputs = [
cairo
file
hyprutils
libGL
libdrm
libheif
libjpeg
libjxl
librsvg
libspng
libwebp
pango
pixman
];
buildInputs = [
cairo
file
hyprutils
libjpeg
libjxl
libwebp
pixman
];
outputs = [
"out"
"dev"
];
outputs = ["out" "dev"];
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
cmakeBuildType =
if debug
then "Debug"
else "RelWithDebInfo";
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprgraphics";
description = "Small C++ library with graphics / resource related utilities used across the hypr* ecosystem";
license = licenses.bsd3;
platforms = platforms.linux;
};
}
meta = with lib; {
homepage = "https://github.com/hyprwm/hyprgraphics";
description = "Small C++ library with graphics / resource related utilities used across the hypr* ecosystem";
license = licenses.bsd3;
platforms = platforms.linux;
};
}

View file

@ -1,39 +0,0 @@
{
lib,
self,
inputs,
...
}:
let
mkDate =
longDate:
(lib.concatStringsSep "-" [
(builtins.substring 0 4 longDate)
(builtins.substring 4 2 longDate)
(builtins.substring 6 2 longDate)
]);
version = lib.removeSuffix "\n" (builtins.readFile ../VERSION);
in
{
default = self.overlays.hyprgraphics;
hyprgraphics-with-deps = lib.composeManyExtensions [
inputs.hyprutils.overlays.default
self.overlays.hyprgraphics
];
hyprgraphics = final: prev: {
hyprgraphics = final.callPackage ./default.nix {
stdenv = final.gcc15Stdenv;
version =
version
+ "+date="
+ (mkDate (self.lastModifiedDate or "19700101"))
+ "_"
+ (self.shortRev or "dirty");
};
hyprgraphics-with-tests = final.hyprgraphics.override { doCheck = true; };
};
}

View file

@ -1,252 +0,0 @@
#include <hyprgraphics/color/Color.hpp>
#include <algorithm>
#include <cmath>
using namespace Hyprgraphics;
static double gammaToLinear(const double in) {
return in >= 0.04045 ? std::pow((in + 0.055) / 1.055, 2.4) : in / 12.92;
}
static double linearToGamma(const double in) {
return in >= 0.0031308 ? (1.055 * std::pow(in, 0.41666666666)) - 0.055 : 12.92 * in;
}
static double hueToRgb(double p, double q, double t) {
if (t < 0)
t += 1;
if (t > 1)
t -= 1;
if (t < 1.0 / 6.0)
return p + ((q - p) * 6.0 * t);
if (t < 1.0 / 2.0)
return q;
if (t < 2.0 / 3.0)
return p + ((q - p) * (2.0 / 3.0 - t) * 6.0);
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{
.x = (m[0][0] * value.x) + (m[0][1] * value.y) + (m[0][2] * value.z), //
.y = (m[1][0] * value.x) + (m[1][1] * value.y) + (m[1][2] * value.z), //
.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 {.x = 0.0, .y = 0.0, .z = 0.0};
return {.x = xy.x / xy.y, .y = 1.0, .z = (1.0 - xy.x - xy.y) / xy.y};
}
static 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, //
});
static 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() {
;
}
Hyprgraphics::CColor::CColor(const SSRGB& rgb) : r(rgb.r), g(rgb.g), b(rgb.b) {
;
}
Hyprgraphics::CColor::CColor(const SHSL& hsl) {
if (hsl.s <= 0) {
r = hsl.l;
g = hsl.l;
b = hsl.l;
} else {
const double q = hsl.l < 0.5 ? hsl.l * (1.0 + hsl.s) : hsl.l + hsl.s - (hsl.l * hsl.s);
const double p = (2.0 * hsl.l) - q;
r = hueToRgb(p, q, hsl.h + (1.0 / 3.0));
g = hueToRgb(p, q, hsl.h);
b = hueToRgb(p, q, hsl.h - (1.0 / 3.0));
}
}
Hyprgraphics::CColor::CColor(const SOkLab& lab) {
const double l = std::pow(lab.l + (lab.a * 0.3963377774) + (lab.b * 0.2158037573), 3);
const double m = std::pow(lab.l + (lab.a * (-0.1055613458)) + (lab.b * (-0.0638541728)), 3);
const double s = std::pow(lab.l + (lab.a * (-0.0894841775)) + (lab.b * (-1.2914855480)), 3);
r = linearToGamma((l * 4.0767416621) + (m * -3.3077115913) + (s * 0.2309699292));
g = linearToGamma((l * (-1.2684380046)) + (m * 2.6097574011) + (s * (-0.3413193965)));
b = linearToGamma((l * (-0.0041960863)) + (m * (-0.7034186147)) + (s * 1.7076147010));
}
Hyprgraphics::CColor::SSRGB Hyprgraphics::CColor::asRgb() const {
return Hyprgraphics::CColor::SSRGB{
.r = r,
.g = g,
.b = b,
};
}
Hyprgraphics::CColor::SHSL Hyprgraphics::CColor::asHSL() const {
const double vmax = std::max({r, g, b}), vmin = std::min({r, g, b});
double h = 0, s = 0, l = (vmax + vmin) / 2.0;
if (vmax == vmin) {
return Hyprgraphics::CColor::SHSL{
.h = 0,
.s = 0,
.l = l,
};
}
const double d = vmax - vmin;
s = l > 0.5 ? d / (2.0 - vmax - vmin) : d / (vmax + vmin);
if (vmax == r)
h = (g - b) / d + (g < b ? 6.0 : 0.0);
if (vmax == g)
h = (b - r) / d + 2;
if (vmax == b)
h = (r - g) / d + 4;
h /= 6.0;
return Hyprgraphics::CColor::SHSL{
.h = h,
.s = s,
.l = l,
};
}
Hyprgraphics::CColor::SOkLab Hyprgraphics::CColor::asOkLab() const {
const double linR = gammaToLinear(r);
const double linG = gammaToLinear(g);
const double linB = gammaToLinear(b);
const double l = std::cbrtf((0.4122214708 * linR) + (0.5363325363 * linG) + (0.0514459929 * linB));
const double m = std::cbrtf((0.2119034982 * linR) + (0.6806995451 * linG) + (0.1073969566 * linB));
const double s = std::cbrtf((0.0883024619 * linR) + (0.2817188376 * linG) + (0.6299787005 * linB));
return Hyprgraphics::CColor::SOkLab{
.l = (l * 0.2104542553) + (m * 0.7936177850) + (s * (-0.0040720468)),
.a = (l * 1.9779984951) + (m * (-2.4285922050)) + (s * 0.4505937099),
.b = (l * 0.0259040371) + (m * 0.7827717662) + (s * (-0.8086757660)),
};
}

View file

@ -1,282 +0,0 @@
#include <cmath>
#include <hyprgraphics/egl/Egl.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <vector>
#include <GLES3/gl32.h>
#include <xf86drm.h>
#include <drm_fourcc.h>
using namespace Hyprutils::Memory;
namespace Hyprgraphics::Egl {
static inline const std::vector<SPixelFormat> GLES3_FORMATS = {
{
.drmFormat = DRM_FORMAT_ARGB8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
},
{
.drmFormat = DRM_FORMAT_XRGB8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
},
{
.drmFormat = DRM_FORMAT_XBGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_ABGR8888,
.glInternalFormat = GL_RGBA8,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR8888,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_BGR888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_BGR888,
.bytesPerBlock = 3,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_RGBX4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_RGBA4444,
.glInternalFormat = GL_RGBA4,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_4_4_4_4,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX4444,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_RGBX5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_RGBA5551,
.glInternalFormat = GL_RGB5_A1,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_SHORT_5_5_5_1,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_RGBX5551,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_RGB565,
.glInternalFormat = GL_RGB565,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_SHORT_5_6_5,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_RGB565,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_XBGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_ABGR2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_XRGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGR1},
},
{
.drmFormat = DRM_FORMAT_ARGB2101010,
.glInternalFormat = GL_RGB10_A2,
.glFormat = GL_RGBA,
.glType = GL_UNSIGNED_INT_2_10_10_10_REV,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB2101010,
.bytesPerBlock = 4,
.swizzle = {SWIZZLE_BGRA},
},
{
.drmFormat = DRM_FORMAT_XRGB16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XRGB16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_BGR1},
},
{
.drmFormat = DRM_FORMAT_ARGB16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XRGB16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_BGRA},
},
{
.drmFormat = DRM_FORMAT_XBGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGB1},
},
{
.drmFormat = DRM_FORMAT_ABGR16161616F,
.glInternalFormat = GL_RGBA16F,
.glFormat = GL_RGBA,
.glType = GL_HALF_FLOAT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616F,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_XBGR16161616,
.glInternalFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = false,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_ABGR16161616,
.glInternalFormat = GL_RGBA16UI,
.glFormat = GL_RGBA_INTEGER,
.glType = GL_UNSIGNED_SHORT,
.withAlpha = true,
.alphaStripped = DRM_FORMAT_XBGR16161616,
.bytesPerBlock = 8,
.swizzle = {SWIZZLE_RGBA},
},
{
.drmFormat = DRM_FORMAT_YVYU,
.bytesPerBlock = 4,
.blockSize = {2, 1},
},
{
.drmFormat = DRM_FORMAT_VYUY,
.bytesPerBlock = 4,
.blockSize = {2, 1},
},
{
.drmFormat = DRM_FORMAT_R8,
.glInternalFormat = GL_R8,
.glFormat = GL_RED,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 1,
.swizzle = {SWIZZLE_R001},
},
{
.drmFormat = DRM_FORMAT_GR88,
.glInternalFormat = GL_RG8,
.glFormat = GL_RG,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 2,
.swizzle = {SWIZZLE_RG01},
},
{
.drmFormat = DRM_FORMAT_RGB888,
.glInternalFormat = GL_RGB8,
.glFormat = GL_RGB,
.glType = GL_UNSIGNED_BYTE,
.bytesPerBlock = 3,
.swizzle = {SWIZZLE_BGR1},
},
};
const SPixelFormat* getPixelFormatFromDRM(uint32_t drmFormat) {
for (auto const& fmt : GLES3_FORMATS) {
if (fmt.drmFormat == drmFormat)
return &fmt;
}
return nullptr;
}
const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha) {
for (auto const& fmt : GLES3_FORMATS) {
if (fmt.glFormat == sc<int>(glFormat) && fmt.glType == sc<int>(glType) && fmt.withAlpha == alpha)
return &fmt;
}
return nullptr;
}
bool isDrmFormatOpaque(uint32_t drmFormat) {
const auto FMT = getPixelFormatFromDRM(drmFormat);
if (!FMT)
return false;
return !FMT->withAlpha;
}
int pixelsPerBlock(const SPixelFormat* const fmt) {
return fmt->blockSize.x * fmt->blockSize.y > 0 ? fmt->blockSize.x * fmt->blockSize.y : 1;
}
int minStride(const SPixelFormat* const fmt, int32_t width) {
return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt));
}
}

View file

@ -1,89 +1,57 @@
#include <hyprgraphics/image/Image.hpp>
#include "utils/Format.hpp"
#include "formats/Bmp.hpp"
#include "formats/Jpeg.hpp"
#ifdef JXL_FOUND
#include "formats/JpegXL.hpp"
#endif
#ifdef HEIF_FOUND
#include "formats/Avif.hpp"
#endif
#include "formats/Webp.hpp"
#include "formats/Png.hpp"
#include "formats/Svg.hpp"
#include <magic.h>
#include <format>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Math;
Hyprgraphics::CImage::CImage(std::span<const uint8_t> data, eImageFormat format, const Vector2D& size) {
const auto FORMAT = format == IMAGE_FORMAT_AUTO ? formatFromStream(data) : format;
if (FORMAT == IMAGE_FORMAT_ERROR) {
lastError = "invalid file";
return;
}
Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) {
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";
} 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";
} else if (path.find(".bmp") == len - 4 || path.find(".BMP") == len - 4) {
CAIROSURFACE = BMP::createSurfaceFromBMP(path);
imageHasAlpha = false;
mime = "image/bmp";
} else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) {
CAIROSURFACE = WEBP::createSurfaceFromWEBP(path);
mime = "image/webp";
} else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) {
CAIROSURFACE = JXL::createSurfaceFromJXL(path);
mime = "image/jxl";
} 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);
switch (FORMAT) {
case IMAGE_FORMAT_PNG: CAIROSURFACE = PNG::createSurfaceFromPNG(data); break;
#ifdef HEIF_FOUND
case IMAGE_FORMAT_AVIF: CAIROSURFACE = AVIF::createSurfaceFromAvif(data); break;
#else
case IMAGE_FORMAT_AVIF: lastError = "hyprgraphics compiled without HEIF support"; return;
#endif
case IMAGE_FORMAT_SVG: CAIROSURFACE = SVG::createSurfaceFromData(data, size); break;
default: lastError = "Currently only PNG and AVIF images are supported for embedding"; return;
}
const auto type_str = std::string(magic_file(handle, path.c_str()));
const auto first_word = type_str.substr(0, type_str.find(" "));
if (!CAIROSURFACE) {
lastError = CAIROSURFACE.error();
return;
}
if (const auto STATUS = cairo_surface_status(*CAIROSURFACE); STATUS != CAIRO_STATUS_SUCCESS) {
lastError = std::format("Could not create surface: {}", cairo_status_to_string(STATUS));
return;
}
loadSuccess = true;
pCairoSurface = makeShared<CCairoSurface>(CAIROSURFACE.value());
}
Hyprgraphics::CImage::CImage(const std::string& path, const Vector2D& size) : filepath(path), m_svgSize(size) {
const auto FORMAT = formatFromFile(path);
if (FORMAT == IMAGE_FORMAT_ERROR) {
lastError = "invalid file";
return;
}
std::expected<cairo_surface_t*, std::string> CAIROSURFACE;
mime = mimeOf(FORMAT);
switch (FORMAT) {
case IMAGE_FORMAT_PNG: CAIROSURFACE = PNG::createSurfaceFromPNG(path); break;
case IMAGE_FORMAT_BMP: CAIROSURFACE = BMP::createSurfaceFromBMP(path); break;
#ifdef HEIF_FOUND
case IMAGE_FORMAT_AVIF: CAIROSURFACE = AVIF::createSurfaceFromAvif(path); break;
#else
case IMAGE_FORMAT_AVIF: lastError = "hyprgraphics compiled without HEIF support"; return;
#endif
#ifdef JXL_FOUND
case IMAGE_FORMAT_JXL: CAIROSURFACE = JXL::createSurfaceFromJXL(path); break;
#else
case IMAGE_FORMAT_JXL: lastError = "hyprgraphics compiled without JXL support"; return;
#endif
case IMAGE_FORMAT_JPEG: CAIROSURFACE = JPEG::createSurfaceFromJPEG(path); break;
case IMAGE_FORMAT_SVG: CAIROSURFACE = SVG::createSurfaceFromSVG(path, size); break;
case IMAGE_FORMAT_WEBP: CAIROSURFACE = WEBP::createSurfaceFromWEBP(path); break;
default: lastError = "internal error"; return;
if (first_word == "PNG") {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
mime = "image/png";
} else if (first_word == "JPEG") {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
imageHasAlpha = false;
mime = "image/jpeg";
} else if (first_word == "BMP") {
CAIROSURFACE = BMP::createSurfaceFromBMP(path);
imageHasAlpha = false;
mime = "image/bmp";
} else {
lastError = "unrecognized image";
return;
}
}
if (!CAIROSURFACE) {
@ -123,7 +91,3 @@ Hyprutils::Memory::CSharedPointer<CCairoSurface> Hyprgraphics::CImage::cairoSurf
std::string Hyprgraphics::CImage::getMime() {
return mime;
}
bool Hyprgraphics::CImage::isImageFile(const std::string& path) {
return formatFromFile(path) != IMAGE_FORMAT_ERROR;
}

View file

@ -1,92 +0,0 @@
#include "Avif.hpp"
#include <cairo.h>
#include <cstdint>
#include <cstring>
#include <expected>
#include <filesystem>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <libheif/heif.h>
#include <vector>
using namespace Hyprutils::Utils;
static std::expected<cairo_surface_t*, std::string> loadFromContext(heif_context* ctx) {
heif_image_handle* handle;
heif_context_get_primary_image_handle(ctx, &handle);
heif_image* img;
struct heif_error err = heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, nullptr);
if (err.code != heif_error_Ok)
return std::unexpected("loading avif: failed to decode image");
size_t width = heif_image_get_width(img, heif_channel_interleaved);
size_t height = heif_image_get_height(img, heif_channel_interleaved);
if (width == static_cast<size_t>(-1) || height == static_cast<size_t>(-1))
return std::unexpected("loading avif: failed to get width or height");
int stride;
const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride);
if (!data)
return std::unexpected("loading avif: get_plane_readonly failed");
std::vector<uint8_t> rawData;
rawData.resize(width * height * 4);
for (size_t y = 0; y < height; y++) {
const uint8_t* src = data + (y * stride);
uint32_t* dst = (uint32_t*)(rawData.data() + (y * width * 4));
for (size_t x = 0; x < width; x++) {
uint8_t r = src[(4 * x) + 0];
uint8_t g = src[(4 * x) + 1];
uint8_t b = src[(4 * x) + 2];
uint8_t a = src[(4 * x) + 3];
r = (r * a) / 255.F;
g = (g * a) / 255.F;
b = (b * a) / 255.F;
dst[x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
if (!CAIROSURFACE)
return std::unexpected("loading avif: cairo failed");
memcpy(cairo_image_surface_get_data(CAIROSURFACE), rawData.data(), rawData.size());
cairo_surface_mark_dirty(CAIROSURFACE);
heif_image_release(img);
heif_image_handle_release(handle);
return CAIROSURFACE;
}
std::expected<cairo_surface_t*, std::string> AVIF::createSurfaceFromAvif(const std::string& path) {
if (!std::filesystem::exists(path))
return std::unexpected("loading avif: file doesn't exist");
heif_context* ctx = heif_context_alloc();
struct heif_error err = heif_context_read_from_file(ctx, path.c_str(), nullptr);
if (err.code != heif_error_Ok)
return std::unexpected("loading avif: failed to load from file");
auto result = loadFromContext(ctx);
heif_context_free(ctx);
return result;
}
std::expected<cairo_surface_t*, std::string> AVIF::createSurfaceFromAvif(const std::span<const uint8_t> buf) {
heif_context* ctx = heif_context_alloc();
struct heif_error err = heif_context_read_from_memory(ctx, buf.data(), buf.size(), nullptr);
if (err.code != heif_error_Ok)
return std::unexpected("loading avif: failed to load from memory");
auto result = loadFromContext(ctx);
heif_context_free(ctx);
return result;
}

View file

@ -1,12 +0,0 @@
#pragma once
#include <cairo/cairo.h>
#include <cstdint>
#include <span>
#include <string>
#include <expected>
namespace AVIF {
std::expected<cairo_surface_t*, std::string> createSurfaceFromAvif(const std::string&);
std::expected<cairo_surface_t*, std::string> createSurfaceFromAvif(const std::span<const uint8_t>);
};

View file

@ -3,12 +3,11 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstddef>
#include <filesystem>
#include <optional>
#include <fstream>
#include <vector>
#include <cstring>
#include <string.h>
class BmpHeader {
public:
@ -35,7 +34,7 @@ class BmpHeader {
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(&format), sizeof(format));
if (format[0] != 66 || format[1] != 77)
if (!(format[0] == 66 && format[1] == 77))
return "Unable to parse bitmap header: wrong bmp file type";
file.read(reinterpret_cast<char*>(&sizeOfFile), sizeof(sizeOfFile));
@ -76,9 +75,9 @@ static void reflectImage(unsigned char* image, uint32_t numberOfRows, int stride
std::vector<unsigned char> temp;
temp.resize(stride);
while (rowStart < rowEnd) {
memcpy(&temp[0], &image[static_cast<size_t>(rowStart * stride)], stride);
memcpy(&image[static_cast<size_t>(rowStart * stride)], &image[static_cast<size_t>(rowEnd * stride)], stride);
memcpy(&image[static_cast<size_t>(rowEnd * stride)], &temp[0], stride);
memcpy(&temp[0], &image[rowStart * stride], stride);
memcpy(&image[rowStart * stride], &image[rowEnd * stride], stride);
memcpy(&image[rowEnd * stride], &temp[0], stride);
rowStart++;
rowEnd--;
}
@ -103,14 +102,14 @@ std::expected<cairo_surface_t*, std::string> BMP::createSurfaceFromBMP(const std
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);
cairo_format_t format = CAIRO_FORMAT_ARGB32;
int stride = cairo_format_stride_for_width(format, bitmapHeader.width);
unsigned char* imageData = (unsigned char*)malloc(static_cast<size_t>(bitmapHeader.height * stride));
unsigned char* imageData = (unsigned char*)malloc(bitmapHeader.height * stride);
if (bitmapHeader.numberOfBitPerPixel == 24)
convertRgbToArgb(bitmapImageStream, imageData, bitmapHeader.height * stride);
@ -123,4 +122,4 @@ std::expected<cairo_surface_t*, std::string> BMP::createSurfaceFromBMP(const std
bitmapImageStream.close();
reflectImage(imageData, bitmapHeader.height, stride);
return cairo_image_surface_create_for_data(imageData, format, bitmapHeader.width, bitmapHeader.height, stride);
}
}

View file

@ -1,16 +1,8 @@
#include "Jpeg.hpp"
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <vector>
#include <csetjmp>
// TODO: TurboJPEG C API and get rid of this
jmp_buf bailoutBuf = {};
static void bailout(j_common_ptr _) {
longjmp(bailoutBuf, 1);
}
std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const std::string& path) {
@ -26,20 +18,12 @@ std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const s
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());
if (bytes[0] != 0xFF || bytes[1] != 0xD8)
return std::unexpected("loading jpeg: invalid magic bytes");
// now the JPEG is in the memory
jpeg_decompress_struct decompressStruct = {};
jpeg_error_mgr errorManager = {};
decompressStruct.err = jpeg_std_error(&errorManager);
errorManager.error_exit = bailout;
if (setjmp(bailoutBuf))
return std::unexpected("loading jpeg: libjpeg encountered a fatal error");
jpeg_create_decompress(&decompressStruct);
jpeg_mem_src(&decompressStruct, bytes.data(), bytes.size());
jpeg_read_header(&decompressStruct, true);
@ -63,7 +47,7 @@ std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const s
JSAMPROW rowRead;
while (decompressStruct.output_scanline < decompressStruct.output_height) {
const auto PROW = CAIRODATA + (static_cast<size_t>(decompressStruct.output_scanline * CAIROSTRIDE));
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
rowRead = PROW;
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
}

View file

@ -1,130 +0,0 @@
#include "Png.hpp"
#include <cstddef>
#include <vector>
#include <filesystem>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <png.h>
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
static std::expected<cairo_surface_t*, std::string> loadPNG(png_structp, png_infop);
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");
FILE* fp = fopen(path.c_str(), "rb");
if (!fp)
return std::unexpected("loading png: couldn't open file");
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info = png_create_info_struct(png);
if (!png || !info)
return std::unexpected("loading png: couldn't init libpng");
CScopeGuard x([&png, &info, fp] {
png_destroy_read_struct(&png, &info, nullptr);
fclose(fp);
});
if (setjmp(png_jmpbuf(png)))
return std::unexpected("loading png: couldn't setjmp");
png_init_io(png, fp);
return loadPNG(png, info);
}
struct SReadState {
const std::span<const uint8_t> data;
size_t offset;
};
static void customReadFunction(png_structp png, png_bytep data, png_size_t length) {
SReadState* state = static_cast<SReadState*>(png_get_io_ptr(png));
if (state->offset + length > state->data.size()) {
png_error(png, "read error");
return;
}
memcpy(data, state->data.data() + state->offset, length);
state->offset += length;
}
std::expected<cairo_surface_t*, std::string> PNG::createSurfaceFromPNG(const std::span<const uint8_t> data) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info = png_create_info_struct(png);
if (!png || !info)
return std::unexpected("loading png: couldn't init libpng");
CScopeGuard x([&png, &info] { png_destroy_read_struct(&png, &info, nullptr); });
if (setjmp(png_jmpbuf(png)))
return std::unexpected("loading png: couldn't setjmp");
SReadState readState = {.data = data, .offset = 0};
png_set_read_fn(png, &readState, customReadFunction);
png_set_sig_bytes(png, 0);
return loadPNG(png, info);
}
static std::expected<cairo_surface_t*, std::string> loadPNG(png_structp png, png_infop info) {
png_read_info(png, info);
const size_t W = png_get_image_width(png, info);
const size_t H = png_get_image_height(png, info);
const auto COLOR_TYPE = png_get_color_type(png, info);
const auto BPP = png_get_bit_depth(png, info);
if (BPP == 16)
png_set_strip_16(png);
if (COLOR_TYPE == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY && BPP < 8)
png_set_expand_gray_1_2_4_to_8(png);
if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY || COLOR_TYPE == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
if (COLOR_TYPE == PNG_COLOR_TYPE_RGB || COLOR_TYPE == PNG_COLOR_TYPE_GRAY || COLOR_TYPE == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
png_read_update_info(png, info);
std::vector<uint8_t*> rowPointers;
rowPointers.resize(H);
std::vector<uint8_t> rawData;
rawData.resize(W * H * 4);
for (size_t y = 0; y < H; y++) {
rowPointers[y] = rawData.data() + (y * W * 4);
}
png_read_image(png, rowPointers.data());
for (size_t i = 0; i < W * H * 4; i += 4) {
uint8_t r = rawData[i + 0];
uint8_t g = rawData[i + 1];
uint8_t b = rawData[i + 2];
uint8_t a = rawData[i + 3];
r *= ((float)a) / 255.F;
g *= ((float)a) / 255.F;
b *= ((float)a) / 255.F;
*(uint32_t*)&rawData[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, W, H);
if (!CAIROSURFACE)
return std::unexpected("loading png: cairo failed");
memcpy(cairo_image_surface_get_data(CAIROSURFACE), rawData.data(), rawData.size());
cairo_surface_mark_dirty(CAIROSURFACE);
return CAIROSURFACE;
}

View file

@ -1,13 +0,0 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <expected>
#include <png.h>
#include <span>
#include <cstdint>
namespace PNG {
std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::string&);
std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::span<const uint8_t>);
};

View file

@ -1,96 +0,0 @@
#include "Svg.hpp"
#include <filesystem>
#include <fstream>
#include <librsvg/rsvg.h>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::Utils;
using namespace Hyprutils::Math;
using namespace Hyprutils::String;
static std::optional<std::string> readFileAsString(const std::string& path) {
std::error_code ec;
if (!std::filesystem::exists(path, ec) || ec)
return std::nullopt;
std::ifstream file(path);
if (!file.good())
return std::nullopt;
return trim(std::string((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())));
}
std::expected<cairo_surface_t*, std::string> SVG::createSurfaceFromSVG(const std::string& path, const Vector2D& size) {
if (!std::filesystem::exists(path))
return std::unexpected("loading svg: file doesn't exist");
if (size.x < 1 || size.y < 1)
return std::unexpected("loading svg: invalid size");
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.x, size.y);
const auto PCAIRO = cairo_create(cairoSurface);
cairo_save(PCAIRO);
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(PCAIRO);
cairo_restore(PCAIRO);
GError* error = nullptr;
auto file = readFileAsString(path);
if (!file)
return std::unexpected("loading png: file doesn't exist / inaccessible");
RsvgHandle* handle = rsvg_handle_new_from_data((unsigned char*)file->data(), file->size(), &error);
if (!handle)
return std::unexpected("loading svg: rsvg failed to read data");
RsvgRectangle rect = {0, 0, (double)size.x, (double)size.y};
if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error))
return std::unexpected("loading svg: rsvg failed to render");
// done
cairo_surface_flush(cairoSurface);
cairo_destroy(PCAIRO);
g_object_unref(handle);
return cairoSurface;
}
std::expected<cairo_surface_t*, std::string> SVG::createSurfaceFromData(const std::span<const uint8_t>& data, const Vector2D& size) {
if (size.x < 1 || size.y < 1)
return std::unexpected("loading svg: invalid size");
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size.x, size.y);
const auto PCAIRO = cairo_create(cairoSurface);
cairo_save(PCAIRO);
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(PCAIRO);
cairo_restore(PCAIRO);
GError* error = nullptr;
RsvgHandle* handle = rsvg_handle_new_from_data((unsigned char*)data.data(), data.size(), &error);
if (!handle)
return std::unexpected("loading svg: rsvg failed to read data");
RsvgRectangle rect = {0, 0, (double)size.x, (double)size.y};
if (!rsvg_handle_render_document(handle, PCAIRO, &rect, &error))
return std::unexpected("loading svg: rsvg failed to render");
// done
cairo_surface_flush(cairoSurface);
cairo_destroy(PCAIRO);
g_object_unref(handle);
return cairoSurface;
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <cstdint>
#include <cairo/cairo.h>
#include <string>
#include <expected>
#include <span>
#include <png.h>
#include <hyprutils/math/Vector2D.hpp>
namespace SVG {
std::expected<cairo_surface_t*, std::string> createSurfaceFromSVG(const std::string&, const Hyprutils::Math::Vector2D& size);
std::expected<cairo_surface_t*, std::string> createSurfaceFromData(const std::span<const uint8_t>&, const Hyprutils::Math::Vector2D& size);
};

View file

@ -1,6 +1,5 @@
#include "Webp.hpp"
#include <cstddef>
#include <filesystem>
#include <fstream>
#include <webp/decode.h>
@ -17,9 +16,6 @@ std::expected<cairo_surface_t*, std::string> WEBP::createSurfaceFromWEBP(const s
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());
if (bytes[0] != 'R' || bytes[1] != 'I' || bytes[2] != 'F' || bytes[3] != 'F')
return std::unexpected("loading webp: invalid magic bytes");
// now the WebP is in the memory
WebPDecoderConfig config;
@ -50,7 +46,7 @@ std::expected<cairo_surface_t*, std::string> WEBP::createSurfaceFromWEBP(const s
config.options.no_fancy_upsampling = 1;
config.output.u.RGBA.rgba = CAIRODATA;
config.output.u.RGBA.stride = CAIROSTRIDE;
config.output.u.RGBA.size = static_cast<size_t>(CAIROSTRIDE * HEIGHT);
config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT;
config.output.is_external_memory = 1;
config.output.width = WIDTH;
config.output.height = HEIGHT;

View file

@ -1,88 +0,0 @@
#include "Format.hpp"
#include <magic.h>
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprgraphics;
using namespace Hyprutils::Utils;
static eImageFormat formatFromStr(const std::string& r) {
if (r == "image/png")
return IMAGE_FORMAT_PNG;
if (r == "image/jpeg" || r == "image/jpg")
return IMAGE_FORMAT_JPEG;
if (r == "image/bmp")
return IMAGE_FORMAT_BMP;
if (r == "image/webp")
return IMAGE_FORMAT_WEBP;
if (r == "image/svg" || r.starts_with("image/svg") /* +xml */)
return IMAGE_FORMAT_SVG;
if (r == "image/jxl")
return IMAGE_FORMAT_JXL;
if (r == "image/avif")
return IMAGE_FORMAT_AVIF;
return IMAGE_FORMAT_ERROR;
}
static eImageFormat formatOf(const std::span<const uint8_t>& data) {
magic_t m = magic_open(MAGIC_MIME_TYPE);
if (!m)
return IMAGE_FORMAT_ERROR;
CScopeGuard x([&] {
magic_close(m); //
});
if (magic_load(m, nullptr) != 0)
return IMAGE_FORMAT_ERROR;
const char* result = magic_buffer(m, data.data(), data.size());
if (!result)
return IMAGE_FORMAT_ERROR;
auto r = std::string{result};
return formatFromStr(r);
}
static eImageFormat formatOf(const std::string& path) {
magic_t m = magic_open(MAGIC_MIME_TYPE | MAGIC_SYMLINK);
if (!m)
return IMAGE_FORMAT_ERROR;
CScopeGuard x([&] {
magic_close(m); //
});
if (magic_load(m, nullptr) != 0)
return IMAGE_FORMAT_ERROR;
const char* result = magic_file(m, path.c_str());
if (!result)
return IMAGE_FORMAT_ERROR;
auto r = std::string{result};
return formatFromStr(r);
}
eImageFormat Hyprgraphics::formatFromStream(const std::span<const uint8_t>& data) {
return formatOf(data);
}
eImageFormat Hyprgraphics::formatFromFile(const std::string& path) {
return formatOf(path);
}
const char* Hyprgraphics::mimeOf(eImageFormat f) {
switch (f) {
case IMAGE_FORMAT_PNG: return "image/png";
case IMAGE_FORMAT_AVIF: return "image/avif";
case IMAGE_FORMAT_BMP: return "image/bmp";
case IMAGE_FORMAT_JPEG: return "image/jpeg";
case IMAGE_FORMAT_JXL: return "image/jxl";
case IMAGE_FORMAT_SVG: return "image/svg";
case IMAGE_FORMAT_WEBP: return "image/webp";
default: return "error";
}
}

View file

@ -1,9 +0,0 @@
#pragma once
#include <hyprgraphics/image/Image.hpp>
namespace Hyprgraphics {
eImageFormat formatFromStream(const std::span<const uint8_t>& data);
eImageFormat formatFromFile(const std::string& path);
const char* mimeOf(eImageFormat);
};

View file

@ -1,75 +0,0 @@
#include <hyprgraphics/resource/AsyncResourceGatherer.hpp>
#include "resources/AsyncResource.hpp"
using namespace Hyprgraphics;
CAsyncResourceGatherer::CAsyncResourceGatherer() {
m_gatherThread = std::thread([this]() { asyncAssetSpinLock(); });
}
CAsyncResourceGatherer::~CAsyncResourceGatherer() {
m_asyncLoopState.exit = true;
wakeUpMainThread();
if (m_gatherThread.joinable())
m_gatherThread.join();
}
void CAsyncResourceGatherer::wakeUpMainThread() {
m_asyncLoopState.needsToProcess = true;
m_asyncLoopState.requestsCV.notify_all();
}
void CAsyncResourceGatherer::enqueue(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource) {
{
std::lock_guard<std::mutex> lg(m_targetsToLoadMutex);
m_targetsToLoad.emplace_back(resource);
}
wakeUpMainThread();
}
void CAsyncResourceGatherer::await(Hyprutils::Memory::CAtomicSharedPointer<IAsyncResource> resource) {
resource->m_impl->awaitingCv = Hyprutils::Memory::makeUnique<std::condition_variable>();
std::unique_lock<std::mutex> lk(resource->m_impl->awaitingMtx);
resource->m_impl->awaitingCv->wait(lk, [&resource] { return resource->m_impl->awaitingEvent; });
resource->m_impl->awaitingCv.reset();
}
void CAsyncResourceGatherer::asyncAssetSpinLock() {
while (!m_asyncLoopState.exit) {
std::unique_lock lk(m_asyncLoopState.requestMutex);
if (!m_asyncLoopState.needsToProcess) // avoid a lock if a thread managed to request something already since we .unlock()ed
m_asyncLoopState.requestsCV.wait_for(lk, std::chrono::seconds(5), [this] { return m_asyncLoopState.needsToProcess; }); // wait for events
if (m_asyncLoopState.exit)
break;
m_asyncLoopState.needsToProcess = false;
lk.unlock();
m_targetsToLoadMutex.lock();
if (m_targetsToLoad.empty()) {
m_targetsToLoadMutex.unlock();
continue;
}
auto requests = m_targetsToLoad;
m_targetsToLoad.clear();
m_targetsToLoadMutex.unlock();
// process requests
for (auto& r : requests) {
r->render();
if (r->m_impl->awaitingCv) {
r->m_impl->awaitingEvent = true;
r->m_impl->awaitingCv->notify_all();
}
r->m_ready = true;
r->m_events.finished.emit();
}
}
}

View file

@ -1,8 +0,0 @@
#include "AsyncResource.hpp"
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
IAsyncResource::IAsyncResource() : m_impl(makeUnique<SAsyncResourceImpl>()) {
;
}

View file

@ -1,11 +0,0 @@
#include <hyprgraphics/resource/resources/AsyncResource.hpp>
#include <condition_variable>
namespace Hyprgraphics {
struct SAsyncResourceImpl {
Hyprutils::Memory::CUniquePointer<std::condition_variable> awaitingCv;
std::mutex awaitingMtx;
bool awaitingEvent = false;
};
}

View file

@ -1,29 +0,0 @@
#include <hyprgraphics/image/Image.hpp>
#include <hyprgraphics/resource/resources/ImageResource.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
CImageResource::CImageResource(const std::string& path) : m_path(path) {
;
}
CImageResource::CImageResource(const std::string& svg, const Hyprutils::Math::Vector2D& size) : m_path(svg), m_svgSize(size) {
;
}
CImageResource::CImageResource(const std::span<const uint8_t>& data, const Hyprutils::Math::Vector2D& size) : m_svgSize(size), m_data(data) {
;
}
void CImageResource::render() {
auto image = !m_data.empty() ? CImage(m_data, IMAGE_FORMAT_AUTO, m_svgSize) : CImage(m_path, m_svgSize);
m_asset.cairoSurface = image.cairoSurface();
m_asset.pixelSize = m_asset.cairoSurface && m_asset.cairoSurface->cairo() ? m_asset.cairoSurface->size() : Hyprutils::Math::Vector2D{};
}

View file

@ -1,20 +0,0 @@
#include <hyprgraphics/resource/resources/StaticImageResource.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
CStaticImageResource::CStaticImageResource(const std::span<const uint8_t> data, eImageFormat format) : m_data(data), m_format(format) {
;
}
void CStaticImageResource::render() {
auto image = CImage(m_data, m_format);
m_asset.cairoSurface = image.cairoSurface();
m_asset.pixelSize = m_asset.cairoSurface && m_asset.cairoSurface->cairo() ? m_asset.cairoSurface->size() : Hyprutils::Math::Vector2D{};
}

View file

@ -1,109 +0,0 @@
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/Casts.hpp>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
CTextResource::CTextResource(CTextResource::STextResourceData&& data) : m_data(std::move(data)) {
;
}
void CTextResource::render() {
auto CAIROSURFACE = makeUnique<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1 /* dummy value */));
auto CAIRO = cairo_create(CAIROSURFACE->cairo());
PangoLayout* layout = pango_cairo_create_layout(CAIRO);
PangoFontDescription* fontDesc = pango_font_description_from_string(m_data.font.c_str());
pango_font_description_set_size(fontDesc, m_data.fontSize * PANGO_SCALE);
pango_layout_set_font_description(layout, fontDesc);
pango_font_description_free(fontDesc);
cairo_font_options_t* options = cairo_font_options_create();
cairo_font_options_set_antialias(options, m_data.antialias);
cairo_font_options_set_hint_style(options, m_data.hintStyle);
pango_cairo_context_set_font_options(pango_layout_get_context(layout), options);
cairo_font_options_destroy(options);
PangoAlignment pangoAlign = PANGO_ALIGN_LEFT;
switch (m_data.align) {
case TEXT_ALIGN_LEFT: break;
case TEXT_ALIGN_CENTER: pangoAlign = PANGO_ALIGN_CENTER; break;
case TEXT_ALIGN_RIGHT: pangoAlign = PANGO_ALIGN_RIGHT; break;
default: break;
}
pango_layout_set_alignment(layout, pangoAlign);
PangoAttrList* attrList = nullptr;
GError* gError = nullptr;
char* buf = nullptr;
if (pango_parse_markup(m_data.text.c_str(), -1, 0, &attrList, &buf, nullptr, &gError))
pango_layout_set_text(layout, buf, -1);
else {
g_error_free(gError);
pango_layout_set_text(layout, m_data.text.c_str(), -1);
}
if (!attrList)
attrList = pango_attr_list_new();
if (buf)
free(buf);
pango_attr_list_insert(attrList, pango_attr_scale_new(1));
pango_layout_set_attributes(layout, attrList);
pango_attr_list_unref(attrList);
PangoRectangle ink, logical;
pango_layout_get_pixel_extents(layout, &ink, &logical);
if (m_data.maxSize) {
if (m_data.ellipsize)
pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
if (m_data.maxSize->x >= 0)
pango_layout_set_width(layout, std::min(logical.width * PANGO_SCALE, sc<int>(m_data.maxSize->x * PANGO_SCALE)));
if (m_data.maxSize->y >= 0)
pango_layout_set_height(layout, std::min(logical.height * PANGO_SCALE, sc<int>(m_data.maxSize->y * PANGO_SCALE)));
if (m_data.wrap)
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
pango_layout_get_pixel_extents(layout, &ink, &logical);
}
pango_layout_get_pixel_extents(layout, &ink, &logical);
// TODO: avoid this?
cairo_destroy(CAIRO);
CAIROSURFACE.reset();
m_asset.cairoSurface = makeShared<CCairoSurface>(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, logical.width, logical.height));
CAIRO = cairo_create(m_asset.cairoSurface->cairo());
// clear the pixmap
cairo_save(CAIRO);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_CLEAR);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
// render the thing
const auto RGB = m_data.color.asRgb();
cairo_set_source_rgba(CAIRO, RGB.r, RGB.g, RGB.b, 1.F);
cairo_move_to(CAIRO, -logical.x, -logical.y);
pango_cairo_show_layout(CAIRO, layout);
g_object_unref(layout);
cairo_surface_flush(m_asset.cairoSurface->cairo());
m_asset.pixelSize = {logical.width, logical.height};
cairo_destroy(CAIRO);
}

View file

@ -1,139 +0,0 @@
#include <algorithm>
#include <print>
#include <format>
#include <filesystem>
#include <fstream>
#include <vector>
#include <hyprgraphics/resource/AsyncResourceGatherer.hpp>
#include <hyprgraphics/resource/resources/TextResource.hpp>
#include <hyprgraphics/resource/resources/ImageResource.hpp>
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include "shared.hpp"
using namespace Hyprutils::Memory;
using namespace Hyprutils::Math;
using namespace Hyprgraphics;
#define UP CUniquePointer
static UP<CAsyncResourceGatherer> g_asyncResourceGatherer;
static struct {
std::mutex wakeupMutex;
std::condition_variable wakeup;
bool exit = false;
bool needsToProcess = false;
int loadedAssets = 0;
std::mutex resourcesMutex;
std::vector<CAtomicSharedPointer<IAsyncResource>> resources;
} state;
//
static bool renderText(const std::string& text, Vector2D max = {}) {
// this stinks a bit but it's due to our ASP impl.
auto resource =
makeAtomicShared<CTextResource>(CTextResource::STextResourceData{.text = text, .fontSize = 72, .maxSize = max.x == 0 ? std::nullopt : std::optional<Vector2D>(max)});
CAtomicSharedPointer<IAsyncResource> resourceGeneric(resource);
g_asyncResourceGatherer->enqueue(resourceGeneric);
state.resourcesMutex.lock();
state.resources.emplace_back(std::move(resourceGeneric));
state.resourcesMutex.unlock();
resource->m_events.finished.listenStatic([]() {
state.needsToProcess = true;
state.wakeup.notify_all();
});
std::println("Enqueued \"{}\" successfully.", text);
return true;
}
static bool renderImage(const std::string& path) {
// this stinks a bit but it's due to our ASP impl.
auto resource = makeAtomicShared<CImageResource>(path);
CAtomicSharedPointer<IAsyncResource> resourceGeneric(resource);
g_asyncResourceGatherer->enqueue(resourceGeneric);
state.resourcesMutex.lock();
state.resources.emplace_back(std::move(resourceGeneric));
state.resourcesMutex.unlock();
resource->m_events.finished.listenStatic([]() {
state.needsToProcess = true;
state.wakeup.notify_all();
});
std::println("Enqueued \"{}\" successfully.", path);
return true;
}
int main(int argc, char** argv, char** envp) {
int ret = 0;
g_asyncResourceGatherer = makeUnique<CAsyncResourceGatherer>();
EXPECT(renderText("Hello World"), true);
EXPECT(renderText("<b><i>Test markup</i></b>"), true);
EXPECT(renderText("Test ellipsis!!!!!", {512, 190}),
true);
EXPECT(renderImage("./resource/images/hyprland.png"), true);
while (!state.exit) {
std::unique_lock lk(state.wakeupMutex);
if (!state.needsToProcess) // avoid a lock if a thread managed to request something already since we .unlock()ed
state.wakeup.wait_for(lk, std::chrono::seconds(5), [] { return state.needsToProcess; }); // wait for events
if (state.exit)
break;
state.needsToProcess = false;
state.resourcesMutex.lock();
const bool SHOULD_EXIT = std::ranges::all_of(state.resources, [](const auto& e) { return !!e->m_ready; });
state.resourcesMutex.unlock();
if (SHOULD_EXIT)
break;
lk.unlock();
}
// all assets should be done, let's render them
size_t idx = 0;
for (const auto& r : state.resources) {
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 = std::format("render-arg-{}", idx);
EXPECT(!!r->m_asset.cairoSurface->cairo(), true);
//NOLINTNEXTLINE
if (!r->m_asset.cairoSurface->cairo())
continue;
EXPECT(cairo_surface_write_to_png(r->m_asset.cairoSurface->cairo(), (TEST_DIR + "/" + name + ".png").c_str()), CAIRO_STATUS_SUCCESS);
idx++;
}
g_asyncResourceGatherer.reset();
return ret;
}

View file

@ -1,16 +1,13 @@
#include <algorithm>
#include <print>
#include <format>
#include <filesystem>
#include <fstream>
#include <vector>
#include <hyprgraphics/image/Image.hpp>
#include "shared.hpp"
using namespace Hyprgraphics;
static bool tryLoadImageFromFile(const std::string& path) {
auto image = path.ends_with("svg") ? CImage(path, {512, 512}) : CImage(path);
bool tryLoadImage(const std::string& path) {
auto image = CImage(path);
if (!image.success()) {
std::println("Failed to load {}: {}", path, image.getError());
@ -19,54 +16,7 @@ static bool tryLoadImageFromFile(const std::string& path) {
std::println("Loaded {} successfully: Image is {}x{} of type {}", path, image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime());
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::ranges::replace(name, '/', '_');
//NOLINTNEXTLINE
return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS;
}
static bool tryLoadImageFromBuffer(const std::span<uint8_t>& data, eImageFormat format) {
auto image = CImage(data, format);
if (!image.success()) {
std::println("Failed to load embedded image: {}", image.getError());
return false;
}
std::println("Loaded embedded Image successfully: Image is {}x{} of type {}", image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime());
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() + "_embedded";
std::ranges::replace(name, '/', '_');
//NOLINTNEXTLINE
return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS;
}
static std::vector<uint8_t> getImageBuffer(const std::string& path) {
std::vector<uint8_t> buffer;
std::ifstream file(path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
buffer.resize(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
return buffer;
return true;
}
int main(int argc, char** argv, char** envp) {
@ -75,28 +25,9 @@ int main(int argc, char** argv, char** envp) {
for (auto& file : std::filesystem::directory_iterator("./resource/images/")) {
if (!file.is_regular_file())
continue;
auto expectation = true;
#ifndef JXL_FOUND
if (file.path().filename() == "hyprland.jxl")
expectation = false;
#endif
#ifndef HEIF_FOUND
if (file.path().filename() == "hyprland.avif")
expectation = false;
#endif
EXPECT(tryLoadImageFromFile(file.path()), expectation);
EXPECT(tryLoadImage(file.path()), true);
}
auto pngBuffer = getImageBuffer("./resource/images/hyprland.png");
EXPECT(tryLoadImageFromBuffer(pngBuffer, Hyprgraphics::IMAGE_FORMAT_AUTO), true);
#ifdef HEIF_FOUND
auto avifBuffer = getImageBuffer("./resource/images/hyprland.avif");
EXPECT(tryLoadImageFromBuffer(avifBuffer, Hyprgraphics::IMAGE_FORMAT_AVIF), true);
#endif
auto svgBuffer = getImageBuffer("./resource/images/hyprland.svg");
EXPECT(tryLoadImageFromBuffer(pngBuffer, Hyprgraphics::IMAGE_FORMAT_AUTO), true);
return ret;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 1006.49 1006.49">
<defs>
<style>
.st0 {
fill: url(#a);
}
</style>
<linearGradient id="a" x1="493.93" y1="901.88" x2="493.93" y2="104.6" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00a8f4"/>
<stop offset="1" stop-color="#00e5d0"/>
</linearGradient>
</defs>
<path class="st0" d="M681.59,325.09c-6.06-8.7-11.78-16.91-17.43-25.14-8.03-11.73-18.9-26.74-31.49-44.09-28.76-39.72-73.68-100.94-104.06-151.25v125.92c31.29,44.88,61.67,85.34,77.88,108.99,35.52,51.8,76.66,107,101.06,162.21,73.34,165.94-29.82,328.29-209.07,330.23h-3.21c-.45,0-.89-.02-1.35-.02-.45,0-.89.02-1.35.02h-3.21c-179.25-1.94-282.41-164.29-209.07-330.23,24.41-55.21,65.54-110.41,101.06-162.21,16.21-23.65,46.59-64.11,77.88-108.99v-125.92c-30.38,50.32-75.3,111.54-104.06,151.25-12.59,17.36-23.45,32.37-31.49,44.09-5.65,8.23-11.37,16.44-17.43,25.14-32.82,47.18-66.78,95.97-89.93,148.35-22.49,50.89-32.35,102.89-29.28,154.54,2.96,50.07,18.54,98.28,45.04,139.46,26.2,40.69,63.03,74.42,106.51,97.55,44.94,23.88,95.4,36.31,150,36.89,1.33.02,2.64.02,3.96.02.45,0,.9-.02,1.35-.02.45,0,.89.02,1.35.02,1.33,0,2.64,0,3.96-.02,54.6-.57,105.06-13,150-36.89,43.48-23.13,80.32-56.86,106.51-97.55,26.5-41.17,42.09-89.39,45.04-139.46,3.07-51.64-6.8-103.65-29.28-154.54-23.15-52.38-57.11-101.17-89.93-148.35Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1 +0,0 @@
hyprland.png