From a1df22903aa32c6f880acdcf746cfa9e74b61cc2 Mon Sep 17 00:00:00 2001 From: Vaxry Date: Thu, 7 Mar 2024 14:50:48 +0000 Subject: [PATCH] lib: add resampling --- hyprcursor-util/src/main.cpp | 4 +- include/hyprcursor.hpp | 23 +++--- libhyprcursor/hyprcursor.cpp | 123 ++++++++++++++++++++++++++++-- libhyprcursor/internalDefines.hpp | 8 ++ tests/test.cpp | 10 ++- 5 files changed, 149 insertions(+), 19 deletions(-) diff --git a/hyprcursor-util/src/main.cpp b/hyprcursor-util/src/main.cpp index afc2a48..cdc2b6d 100644 --- a/hyprcursor-util/src/main.cpp +++ b/hyprcursor-util/src/main.cpp @@ -262,8 +262,8 @@ static std::optional extractXTheme(const std::string& xpath, const std::cout << "Found xcursor " << xcursor.path().stem().string() << "\n"; // decompile xcursor - const auto OUT = spawnSync( - std::format("rm /tmp/hyprcursor-util/* && cd /tmp/hyprcursor-util && xcur2png {} -d /tmp/hyprcursor-util 2>&1", std::filesystem::canonical(xcursor.path()).string())); + const auto OUT = spawnSync(std::format("rm -f /tmp/hyprcursor-util/* && cd /tmp/hyprcursor-util && xcur2png {} -d /tmp/hyprcursor-util 2>&1", + std::filesystem::canonical(xcursor.path()).string())); // read the config std::vector entries; diff --git a/include/hyprcursor.hpp b/include/hyprcursor.hpp index 281070a..b3652e4 100644 --- a/include/hyprcursor.hpp +++ b/include/hyprcursor.hpp @@ -7,11 +7,13 @@ class CHyprcursorImplementation; namespace Hyprcursor { /*! - Simple struct for some info about shape requests + Simple struct for styles */ - struct SCursorSurfaceInfo { + struct SCursorStyleInfo { /* - Shape size + Shape size. + + 0 means "any" or "unspecified". */ unsigned int size = 0; }; @@ -39,20 +41,23 @@ namespace Hyprcursor { */ bool valid(); + /*! + Loads this theme at a given style, synchronously. + */ + bool loadThemeStyle(const SCursorStyleInfo& info); + /*! Returns a cairo_surface_t for a given cursor shape and size. - Once done, call cursorSurfaceDone() + Once done with a size, call cursorSurfaceDone() */ - cairo_surface_t* getSurfaceFor(const char* shape, const SCursorSurfaceInfo& info); + cairo_surface_t* getSurfaceFor(const char* shape, const SCursorStyleInfo& info); /*! - Marks a surface as done, meaning ready to be freed. - - Always call after using a surface. + Marks a certain style as done, allowing it to be potentially freed */ - void cursorSurfaceDone(cairo_surface_t* surface); + void cursorSurfaceStyleDone(const SCursorStyleInfo&); private: CHyprcursorImplementation* impl = nullptr; diff --git a/libhyprcursor/hyprcursor.cpp b/libhyprcursor/hyprcursor.cpp index 17e119b..9d9db53 100644 --- a/libhyprcursor/hyprcursor.cpp +++ b/libhyprcursor/hyprcursor.cpp @@ -162,7 +162,7 @@ bool CHyprcursorManager::valid() { return finalizedAndValid; } -cairo_surface_t* CHyprcursorManager::getSurfaceFor(const char* shape_, const SCursorSurfaceInfo& info) { +cairo_surface_t* CHyprcursorManager::getSurfaceFor(const char* shape_, const SCursorStyleInfo& info) { std::string REQUESTEDSHAPE = shape_; for (auto& shape : impl->theme.shapes) { @@ -174,18 +174,129 @@ cairo_surface_t* CHyprcursorManager::getSurfaceFor(const char* shape_, const SCu if (image->side != info.size) continue; - // found pixel-perfect size + // found size return image->cairoSurface; } - // TODO: resampling + // if we get here, means loadThemeStyle wasn't called most likely. If resize algo is specified, this is an error. + if (shape.resizeAlgo != RESIZE_NONE) { + Debug::log(ERR, "getSurfaceFor didn't match a size?"); + return nullptr; + } + + // find nearest + int leader = 1337; + SLoadedCursorImage* leaderImg = nullptr; + for (auto& image : impl->loadedShapes[&shape].images) { + if (std::abs((int)(image->side - info.size)) > leader) + continue; + + leaderImg = image.get(); + leader = image->side; + } + + if (!leaderImg) { // ??? + Debug::log(ERR, "getSurfaceFor didn't match any nearest size?"); + return nullptr; + } + + return leaderImg->cairoSurface; } + return nullptr; } -void CHyprcursorManager::cursorSurfaceDone(cairo_surface_t* surface) { - ; - // TODO: when resampling. +bool CHyprcursorManager::loadThemeStyle(const SCursorStyleInfo& info) { + for (auto& shape : impl->theme.shapes) { + if (shape.resizeAlgo == RESIZE_NONE) + continue; // don't resample NONE style cursors + + bool sizeFound = false; + + for (auto& image : impl->loadedShapes[&shape].images) { + if (image->side != info.size) + continue; + + sizeFound = true; + break; + } + + if (sizeFound) + continue; + + // size wasn't found, let's resample. + SLoadedCursorImage* leader = nullptr; + int leaderVal = 1000000; + for (auto& image : impl->loadedShapes[&shape].images) { + if (image->side < info.size) + continue; + + if (image->side > leaderVal) + continue; + + leaderVal = image->side; + leader = image.get(); + } + + if (!leader) { + for (auto& image : impl->loadedShapes[&shape].images) { + if (image->side < info.size) + continue; + + if (std::abs((int)(image->side - info.size)) > leaderVal) + continue; + + leaderVal = image->side; + leader = image.get(); + } + } + + if (!leader) { + Debug::log(ERR, "Resampling failed to find a candidate???"); + return false; + } + + auto& newImage = impl->loadedShapes[&shape].images.emplace_back(std::make_unique()); + newImage->artificial = true; + newImage->side = info.size; + newImage->artificialData = new char[info.size * info.size * 4]; + newImage->cairoSurface = cairo_image_surface_create_for_data((unsigned char*)newImage->artificialData, CAIRO_FORMAT_ARGB32, info.size, info.size, info.size * 4); + + const auto PCAIRO = cairo_create(newImage->cairoSurface); + + cairo_set_antialias(PCAIRO, shape.resizeAlgo == RESIZE_BILINEAR ? CAIRO_ANTIALIAS_GOOD : CAIRO_ANTIALIAS_NONE); + + cairo_save(PCAIRO); + cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR); + cairo_paint(PCAIRO); + cairo_restore(PCAIRO); + + const auto PTN = cairo_pattern_create_for_surface(leader->cairoSurface); + cairo_pattern_set_extend(PTN, CAIRO_EXTEND_NONE); + const float scale = info.size / (float)leader->side; + cairo_scale(PCAIRO, scale, scale); + cairo_pattern_set_filter(PTN, shape.resizeAlgo == RESIZE_BILINEAR ? CAIRO_FILTER_GOOD : CAIRO_FILTER_NEAREST); + cairo_set_source(PCAIRO, PTN); + + cairo_rectangle(PCAIRO, 0, 0, info.size, info.size); + + cairo_fill(PCAIRO); + cairo_surface_flush(newImage->cairoSurface); + + cairo_pattern_destroy(PTN); + cairo_destroy(PCAIRO); + } + + return true; +} + +void CHyprcursorManager::cursorSurfaceStyleDone(const SCursorStyleInfo& info) { + for (auto& shape : impl->theme.shapes) { + if (shape.resizeAlgo == RESIZE_NONE) + continue; + + std::erase_if(impl->loadedShapes[&shape].images, [info](const auto& e) { return !e->artificial && (info.size == 0 || e->side == info.size); }); + } } /* diff --git a/libhyprcursor/internalDefines.hpp b/libhyprcursor/internalDefines.hpp index 6124688..1f2e747 100644 --- a/libhyprcursor/internalDefines.hpp +++ b/libhyprcursor/internalDefines.hpp @@ -10,6 +10,10 @@ struct SLoadedCursorImage { ~SLoadedCursorImage() { if (data) delete[] (char*)data; + if (artificialData) + delete[] (char*)artificialData; + if (cairoSurface) + cairo_surface_destroy(cairoSurface); } // read stuff @@ -19,6 +23,10 @@ struct SLoadedCursorImage { cairo_surface_t* cairoSurface = nullptr; int side = 0; + + // means this was created by resampling + void* artificialData = nullptr; + bool artificial = false; }; struct SLoadedCursorShape { diff --git a/tests/test.cpp b/tests/test.cpp index 7479b9e..3d1372d 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -5,8 +5,14 @@ int main(int argc, char** argv) { Hyprcursor::CHyprcursorManager mgr(nullptr); - // get cursor for arrow - const auto ARROW = mgr.getSurfaceFor("arrow", Hyprcursor::SCursorSurfaceInfo{.size = 64}); + // preload size 48 for testing + if (!mgr.loadThemeStyle(Hyprcursor::SCursorStyleInfo{.size = 48})) { + std::cout << "failed loading style\n"; + return 1; + } + + // get cursor for left_ptr + const auto ARROW = mgr.getSurfaceFor("left_ptr", Hyprcursor::SCursorStyleInfo{.size = 48}); // save to disk const auto RET = cairo_surface_write_to_png(ARROW, "/tmp/arrow.png");