Compare commits

...

66 commits
v0.1.0 ... main

Author SHA1 Message Date
68d0644347
CI: use org-wide actions
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2026-04-17 14:46:39 +03:00
Tom Englund
04d7d9f0e5
egl: add XRGB16161616F and ARGB16161616F to formats (#45)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
add two more 16fp formats.
2026-04-08 11:14:25 +01:00
Constantin Piber
cf95d93d17
internal: fix missing include for uint8_t (#44)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2026-04-06 18:35:28 +01:00
482d4b7ec3
version: bump to 0.5.1
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2026-04-06 00:14:16 +01:00
7d63c04b4a
flake.lock: update
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2026-03-02 16:25:23 +02:00
b803731e52
nix: separate overlay with deps 2026-03-02 16:22:21 +02:00
d3d6bf63d8
treewide: alejandra -> nixfmt 2026-03-02 16:20:38 +02:00
Guus Waals
7c75487edd
core: fix missing include for span (#43)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2026-02-08 00:50:07 +00:00
Tom Englund
13c536659d
egl: add egl formats and helpers (#42)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* egl: add egl formats and helpers

add egl formats and helpers in Hyprgraphics::Egl
add lidbrm requirement for drm_fourcc and xf86drm
add gles3 requirement.

* egl: add pixelsPerBlock and minStride

add two helpers, pixelsPerBlock and minStride

* Nix,CI: add opengl

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2026-01-24 19:47:03 +00:00
Sami Ansari
583331e065
feat: add isImageFile() static utility method (#41)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* feat: add isImageFile() static utility method

Adds a static isImageFile() method to CImage that validates if a file
path points to a loadable image by attempting to load it. Returns true
only if the image loads successfully. Memory is freed immediately after
the check via scope block.

* image: optimize isImageFile to check header only

Use formatFromFile() which reads only the file header via libmagic
instead of loading and decoding the entire image.
2026-01-12 18:27:47 +01:00
Hiroki Tagato
f114ea3d97
core: add missing #include <span> (#40)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
Without it, build fails with the following error: error: no template
named 'span' in namespace 'std'
2026-01-05 12:02:58 +01:00
4af02a3925
version: bump to 0.5.0
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-12-28 19:25:35 +01:00
790adab7eb
image: fix missing svg byte stream 2025-12-28 19:09:51 +01:00
6cfc891808
resource/image: add byte stream 2025-12-28 18:53:12 +01:00
28e17fbf49
image/svg: implement embedded
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2025-12-28 15:30:01 +01:00
8a860f2949
image: cleanup format detection 2025-12-28 15:29:58 +01:00
8f1bec691b
version: bump to 0.4.0
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-11-21 14:04:00 +00:00
1fb5bfbd62
resource/AsyncResource: add await() for resources
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-11-18 17:43:36 +00:00
ffc999d980
version: bump to 0.3.0
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-11-06 20:47:32 +00:00
e381b2f1f0
resources/text: fix alignment layout for center/right 2025-11-06 20:42:13 +00:00
Felix Salcher
50fb9f0692
resource/image: add buffer option to image resource (#39)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* add buffer option to image resource

There were also some changes made to the types of the span. The content
is now always a const uint8_t, as if shouldn't be mutable. We can also
omit the reference to the span, as it is very lightweight and can
therefore be copied

* move buffer to StaticImageResource

* minor fixes

* minor fixes

again
2025-10-14 14:37:28 +02:00
9431db625c
version: bump to 0.2.0
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-10-03 12:18:12 +01:00
2503063d75
ci: add librsvg to arch 2025-10-03 12:15:30 +01:00
899ff15787
Nix: add librsvg dep
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-10-01 22:16:34 +03:00
f4995eaa46
image: add svg support
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2025-09-30 20:33:36 +01:00
32e6b8386f
text: recalc layout after setting limits
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-09-22 21:16:20 +01:00
72e6801f08
text: make wrap and ellipsize passed 2025-09-22 21:14:40 +01:00
d839c3f808
text: wrap when limited 2025-09-22 21:12:09 +01:00
ecdbae40ae
text: minor fixups 2025-09-22 21:10:43 +01:00
758678a614
text: add aa and hint to text
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2025-09-22 20:08:59 +01:00
c44e749dd6
version: bump to 0.1.6
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-09-18 11:47:13 +01:00
Vaxry
b86c4d9ed3
asyncResourceGatherer: add new module (#36)
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
Adds a new module based on the hyprlock gatherer

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-09-17 14:42:36 +02:00
Maximilian Seidler
aa9d14963b
png: fix gray pix formats (#35)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-09-11 00:21:04 +02:00
Linux User
621e2e00f1
formats: include vector header (#34)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
Fixes error `no member named 'vector' in namespace 'std'` on llvm/musl
2025-09-03 11:21:59 +02:00
Ramy Kaddouri
157cc52065
formats: add optional AVIF image support with libheif (#32)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
Adds support for AVIF image loading

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
2025-08-20 09:30:02 +01:00
x70b1
4c1d63a0f2
chore: replace libspng with libpng in README.md and CI/Arch (#30)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* replace libspng with libpng in README.md

* replace libspng with libpng in CI/Arch

---------

Co-authored-by: x70b1 <x70b1@users.noreply.github.com>
2025-08-04 12:56:53 +02:00
340494a38b
version: bump to 0.1.5
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-07-10 14:05:40 +02:00
Maximilian Seidler
83885a6edf
formats: fix format selection and some safeguards (#28)
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
* formats: differentiate between JPEG and JXL when using magic

* formats: check magic bytes for jpeg and webp

* jpeg: error handling bs for libjpeg
2025-07-10 10:17:08 +02:00
b841473a0b
png: libpng is straight alpha, but cairo is premult
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
ref #23
2025-07-06 15:22:25 +02:00
Felix Salcher
a71c0529d1
internal: fix style as determined by clang (#27) 2025-07-06 15:14:40 +02:00
Linux User
5f9c68e3f8
image: include span header (#26)
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
Fixes error `no template named 'span' in namespace 'std'` on llvm/musl
2025-07-05 22:05:59 +02:00
Felix Salcher
13375fa03f
core: Add Image Embedding Support (#24)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
Images can now be loaded from an std::span<> of bytes. For now, png only.
2025-07-03 21:07:39 +02:00
b3d628d016
version: bump to 0.1.4
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-06-22 21:42:57 +02:00
15c6f8f3a5
CI/Nix: add cache-nix-action
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
Use nixbuild/nix-quick-install-action which pairs well with
nix-community/cache-nix-action.

Should help with build times by reducing the number of packages needing
to be re-downloaded on each run.

Parameters are taken from https://github.com/nix-community/cache-nix-action
and may be tweaked later.
2025-06-20 01:21:57 +03:00
c7225d7375 images/png: mark surface dirty after copy
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
fixes #22
2025-06-06 21:34:12 +02:00
Friday
80b754e38e
nix: update to gcc15 (#21)
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
also updated dependencies in flake.lock
2025-06-05 18:46:40 +01:00
Vaxry
df811098c1
formats: move to libpng for png support (#19)
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
closes #18
2025-06-05 14:10:27 +01:00
Honkazel
6075491094
core: clang-tidy & comp changes (#15)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* some clang-tidyfy

* designated init in Color header

* some linkage changes

* just doin some casts explicit

* oeao

* bruh

* explicitly cast to size_t, not ptrdiff_t
2025-04-19 00:31:30 +02:00
9d7f2687c8 version: bump to 0.1.3
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-04-06 16:28:42 +01:00
UjinT34
760d67a2a8
color: CM structs, constants & math (#14) 2025-04-06 17:27:46 +02:00
175c6b29b6
CI: remove deprecated magic-nix-cache-action
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-02-08 23:11:11 +02:00
Jan Beich
575ae47b78
cmakelists: pass all libjxl CFLAGS/LDFLAGS after 52202272d8 (#13)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
JXL_LIBRARIES contains `pkg-config --libs-only-l libjxl` but FreeBSD
also needs JXL_LIBRARY_DIRS aka `pkg-config --libs-only-L libjxl`.
2025-02-06 11:23:12 +00:00
e19ee9031a version: bump to 0.1.2
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-02-04 16:55:19 +00:00
davc0n
5ac80e3686
tests: add a symlink test (#10)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* Add symlink test

* Add tests output dir to .gitignore
2025-02-01 20:10:59 +01:00
12cd7034e4 png: handle invalid buffer size returned by libspng
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
sometimes (no clue why) spng_decoded_image_size is just plain wrong. In those cases, just guess what the size should be with 32bpp.

fixes #9
2025-01-27 23:00:29 +00:00
23783b9603
CI/Arch/Clang: add libspng
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2025-01-27 15:43:56 +02:00
6355b72d9c
Nix: add libspng dep 2025-01-27 15:42:22 +02:00
0c11438de4 core: move to libspng for png
Some checks are pending
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Waiting to run
Build & Test (Arch) / Arch: Build and Test (clang) (push) Waiting to run
Build & Test / nix (hyprgraphics) (push) Waiting to run
Build & Test / nix (hyprgraphics-with-tests) (push) Waiting to run
2025-01-27 13:39:39 +00:00
0d77b4895a
flake.lock: update
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2025-01-23 14:21:29 +02:00
Zach DeCook
52202272d8
core: Allow compiling without JXL support (#6)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
* Allow compiling without JXL support

Remember to link the libraries and add the compile definitions

* tests: when compiled without JXL support, expect that to fail
2025-01-05 22:14:50 +00:00
mcwindy
b09980755d
README: Add Dependencies (#4)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2024-12-31 16:07:26 +00:00
6dea3fba08
flake.lock: update
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
2024-12-23 00:23:56 +02:00
Jan Beich
0f9b8ca692
color: add missing header for libc++ (#3)
Some checks failed
Build & Test (Arch) / Arch: Build and Test (gcc) (push) Has been cancelled
Build & Test (Arch) / Arch: Build and Test (clang) (push) Has been cancelled
Build & Test / nix (hyprgraphics) (push) Has been cancelled
Build & Test / nix (hyprgraphics-with-tests) (push) Has been cancelled
src/color/Color.cpp:71:66: error: no member named 'min' in namespace 'std'; did you mean 'fmin'?
   71 |     const double vmax = std::max(std::max(r, g), b), vmin = std::min(std::min(r, g), b);
      |                                                             ~~~~~^~~
      |                                                                  fmin
/usr/include/c++/v1/cmath:447:9: note: 'fmin' declared here
  447 | using ::fmin _LIBCPP_USING_IF_EXISTS;
      |         ^
src/color/Color.cpp:71:75: error: no member named 'min' in namespace 'std'; did you mean 'fmin'?
   71 |     const double vmax = std::max(std::max(r, g), b), vmin = std::min(std::min(r, g), b);
      |                                                                      ~~~~~^~~
      |                                                                           fmin
/usr/include/c++/v1/cmath:447:9: note: 'fmin' declared here
  447 | using ::fmin _LIBCPP_USING_IF_EXISTS;
      |         ^
2024-12-18 17:59:51 +01:00
qxb3
fb2c026864
image: add symlink support (#1) 2024-12-08 19:53:39 +01:00
cc95e5babc version: bump to 0.1.1 2024-12-03 17:52:51 +00:00
7ba28704d3 color: Add color 2024-12-03 17:52:39 +00:00
44 changed files with 2225 additions and 158 deletions

101
.clang-tidy Normal file
View file

@ -0,0 +1,101 @@
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: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu 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++ pango pixman cairo hyprutils libdrm libglvnd libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg
- name: Build hyprgraphics with gcc - name: Build hyprgraphics with gcc
run: | run: |
@ -44,7 +44,7 @@ jobs:
run: | run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
pacman --noconfirm --noprogressbar -Syyu 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++ pango pixman cairo hyprutils libdrm libglvnd libjpeg-turbo libjxl libwebp libpng ttf-dejavu librsvg
- name: Build hyprgraphics with clang - name: Build hyprgraphics with clang
run: | run: |

View file

@ -1,6 +1,7 @@
name: Build & Test name: Build & Test
on: [push, pull_request, workflow_dispatch] on: [push, pull_request, workflow_dispatch]
jobs: jobs:
nix: nix:
strategy: strategy:
@ -9,19 +10,8 @@ jobs:
- hyprgraphics - hyprgraphics
- hyprgraphics-with-tests - hyprgraphics-with-tests
runs-on: ubuntu-latest if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork)
steps: uses: hyprwm/actions/.github/workflows/nix.yml@main
- uses: actions/checkout@v3 secrets: inherit
with:
- uses: cachix/install-nix-action@v26 command: nix build .#${{ matrix.package }} --print-build-logs
- 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,3 +44,5 @@ Makefile
cmake_install.cmake cmake_install.cmake
compile_commands.json compile_commands.json
hyprutils.pc hyprutils.pc
tests/test_output/

View file

@ -21,6 +21,16 @@ set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprgraphics.pc.in hyprgraphics.pc @ONLY) configure_file(hyprgraphics.pc.in hyprgraphics.pc @ONLY)
set(CMAKE_CXX_STANDARD 26) 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) if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprgraphics in Debug") message(STATUS "Configuring hyprgraphics in Debug")
@ -37,20 +47,51 @@ endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "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) find_package(PkgConfig REQUIRED)
pkg_check_modules( pkg_check_modules(
deps deps
REQUIRED REQUIRED
IMPORTED_TARGET IMPORTED_TARGET
libdrm
pixman-1 pixman-1
cairo cairo
pangocairo
hyprutils hyprutils
libjpeg libjpeg
libwebp libwebp
libmagic
libpng
librsvg-2.0)
pkg_check_modules(
JXL
IMPORTED_TARGET
libjxl libjxl
libjxl_cms libjxl_cms
libjxl_threads 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()
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()
add_library(hyprgraphics SHARED ${SRCFILES}) add_library(hyprgraphics SHARED ${SRCFILES})
target_include_directories( target_include_directories(
@ -58,8 +99,15 @@ target_include_directories(
PUBLIC "./include" PUBLIC "./include"
PRIVATE "./src") PRIVATE "./src")
set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION} set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION}
SOVERSION 0) SOVERSION 4)
target_link_libraries(hyprgraphics PkgConfig::deps) 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 # tests
add_custom_target(tests) add_custom_target(tests)
@ -71,6 +119,13 @@ add_test(
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprgraphics_image "image") COMMAND hyprgraphics_image "image")
add_dependencies(tests hyprgraphics_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 # Installation
install(TARGETS hyprgraphics) install(TARGETS hyprgraphics)

View file

@ -6,6 +6,23 @@ 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. 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 ## Building
```sh ```sh

View file

@ -1 +1 @@
0.1.0 0.5.1

12
flake.lock generated
View file

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

View file

@ -12,46 +12,32 @@
}; };
}; };
outputs = { outputs =
self, {
nixpkgs, self,
systems, nixpkgs,
... systems,
} @ inputs: let ...
inherit (nixpkgs) lib; }@inputs:
eachSystem = lib.genAttrs (import systems); let
pkgsFor = eachSystem (system: inherit (nixpkgs) lib;
import nixpkgs { eachSystem = lib.genAttrs (import systems);
localSystem.system = system; pkgsFor = eachSystem (
overlays = with self.overlays; [hyprgraphics]; 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;
}); });
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); formatter = eachSystem (system: pkgsFor.${system}.nixfmt-tree);
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

@ -0,0 +1,94 @@
#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

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

View file

@ -0,0 +1,41 @@
#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

@ -0,0 +1,37 @@
#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

@ -0,0 +1,33 @@
#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

@ -0,0 +1,30 @@
#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

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

39
nix/overlays.nix Normal file
View file

@ -0,0 +1,39 @@
{
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; };
};
}

252
src/color/Color.cpp Normal file
View file

@ -0,0 +1,252 @@
#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)),
};
}

282
src/egl/Egl.cpp Normal file
View file

@ -0,0 +1,282 @@
#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,57 +1,89 @@
#include <hyprgraphics/image/Image.hpp> #include <hyprgraphics/image/Image.hpp>
#include "utils/Format.hpp"
#include "formats/Bmp.hpp" #include "formats/Bmp.hpp"
#include "formats/Jpeg.hpp" #include "formats/Jpeg.hpp"
#ifdef JXL_FOUND
#include "formats/JpegXL.hpp" #include "formats/JpegXL.hpp"
#endif
#ifdef HEIF_FOUND
#include "formats/Avif.hpp"
#endif
#include "formats/Webp.hpp" #include "formats/Webp.hpp"
#include "formats/Png.hpp"
#include "formats/Svg.hpp"
#include <magic.h> #include <magic.h>
#include <format> #include <format>
using namespace Hyprgraphics; using namespace Hyprgraphics;
using namespace Hyprutils::Memory; 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; 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);
const auto type_str = std::string(magic_file(handle, path.c_str())); switch (FORMAT) {
const auto first_word = type_str.substr(0, type_str.find(" ")); 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;
}
if (first_word == "PNG") { if (!CAIROSURFACE) {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str()); lastError = CAIROSURFACE.error();
mime = "image/png"; return;
} else if (first_word == "JPEG") { }
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
imageHasAlpha = false; if (const auto STATUS = cairo_surface_status(*CAIROSURFACE); STATUS != CAIRO_STATUS_SUCCESS) {
mime = "image/jpeg"; lastError = std::format("Could not create surface: {}", cairo_status_to_string(STATUS));
} else if (first_word == "BMP") { return;
CAIROSURFACE = BMP::createSurfaceFromBMP(path); }
imageHasAlpha = false;
mime = "image/bmp"; loadSuccess = true;
} else { pCairoSurface = makeShared<CCairoSurface>(CAIROSURFACE.value());
lastError = "unrecognized image"; }
return;
} 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 (!CAIROSURFACE) { if (!CAIROSURFACE) {
@ -91,3 +123,7 @@ Hyprutils::Memory::CSharedPointer<CCairoSurface> Hyprgraphics::CImage::cairoSurf
std::string Hyprgraphics::CImage::getMime() { std::string Hyprgraphics::CImage::getMime() {
return mime; return mime;
} }
bool Hyprgraphics::CImage::isImageFile(const std::string& path) {
return formatFromFile(path) != IMAGE_FORMAT_ERROR;
}

View file

@ -0,0 +1,92 @@
#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

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

View file

@ -1,8 +1,16 @@
#include "Jpeg.hpp" #include "Jpeg.hpp"
#include <cstddef>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <vector> #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) { std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const std::string& path) {
@ -18,12 +26,20 @@ std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const s
file.seekg(0); file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size()); 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 // now the JPEG is in the memory
jpeg_decompress_struct decompressStruct = {}; jpeg_decompress_struct decompressStruct = {};
jpeg_error_mgr errorManager = {}; jpeg_error_mgr errorManager = {};
decompressStruct.err = jpeg_std_error(&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_create_decompress(&decompressStruct);
jpeg_mem_src(&decompressStruct, bytes.data(), bytes.size()); jpeg_mem_src(&decompressStruct, bytes.data(), bytes.size());
jpeg_read_header(&decompressStruct, true); jpeg_read_header(&decompressStruct, true);
@ -47,7 +63,7 @@ std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const s
JSAMPROW rowRead; JSAMPROW rowRead;
while (decompressStruct.output_scanline < decompressStruct.output_height) { while (decompressStruct.output_scanline < decompressStruct.output_height) {
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE); const auto PROW = CAIRODATA + (static_cast<size_t>(decompressStruct.output_scanline * CAIROSTRIDE));
rowRead = PROW; rowRead = PROW;
jpeg_read_scanlines(&decompressStruct, &rowRead, 1); jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
} }

130
src/image/formats/Png.cpp Normal file
View file

@ -0,0 +1,130 @@
#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;
}

13
src/image/formats/Png.hpp Normal file
View file

@ -0,0 +1,13 @@
#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>);
};

96
src/image/formats/Svg.cpp Normal file
View file

@ -0,0 +1,96 @@
#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;
}

14
src/image/formats/Svg.hpp Normal file
View file

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

View file

@ -0,0 +1,88 @@
#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

@ -0,0 +1,9 @@
#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

@ -0,0 +1,75 @@
#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

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

View file

@ -0,0 +1,11 @@
#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

@ -0,0 +1,29 @@
#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

@ -0,0 +1,20 @@
#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

@ -0,0 +1,109 @@
#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);
}

139
tests/arg.cpp Normal file
View file

@ -0,0 +1,139 @@
#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,13 +1,16 @@
#include <algorithm>
#include <print> #include <print>
#include <format> #include <format>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <vector>
#include <hyprgraphics/image/Image.hpp> #include <hyprgraphics/image/Image.hpp>
#include "shared.hpp" #include "shared.hpp"
using namespace Hyprgraphics; using namespace Hyprgraphics;
bool tryLoadImage(const std::string& path) { static bool tryLoadImageFromFile(const std::string& path) {
auto image = CImage(path); auto image = path.ends_with("svg") ? CImage(path, {512, 512}) : CImage(path);
if (!image.success()) { if (!image.success()) {
std::println("Failed to load {}: {}", path, image.getError()); std::println("Failed to load {}: {}", path, image.getError());
@ -16,7 +19,54 @@ bool tryLoadImage(const std::string& path) {
std::println("Loaded {} successfully: Image is {}x{} of type {}", path, image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime()); 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::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;
} }
int main(int argc, char** argv, char** envp) { int main(int argc, char** argv, char** envp) {
@ -25,9 +75,28 @@ int main(int argc, char** argv, char** envp) {
for (auto& file : std::filesystem::directory_iterator("./resource/images/")) { for (auto& file : std::filesystem::directory_iterator("./resource/images/")) {
if (!file.is_regular_file()) if (!file.is_regular_file())
continue; continue;
auto expectation = true;
EXPECT(tryLoadImage(file.path()), 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);
} }
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; return ret;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1,15 @@
<?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>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

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