diff --git a/CMakeLists.txt b/CMakeLists.txt index f3a7084..7d55651 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,8 @@ pkg_check_modules( libjpeg libwebp libmagic - libpng) + libpng + librsvg-2.0) pkg_check_modules( JXL diff --git a/README.md b/README.md index 4efb695..2c9e62f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Dep list: - libjxl_threads [optional] - libmagic - libpng + - librsvg2 ## Building diff --git a/include/hyprgraphics/image/Image.hpp b/include/hyprgraphics/image/Image.hpp index d453a4a..1d5df90 100644 --- a/include/hyprgraphics/image/Image.hpp +++ b/include/hyprgraphics/image/Image.hpp @@ -7,14 +7,14 @@ #include namespace Hyprgraphics { - enum eImageFormat { + enum eImageFormat : uint8_t { IMAGE_FORMAT_PNG, IMAGE_FORMAT_AVIF, }; class CImage { public: - CImage(const std::string& path); + CImage(const std::string& path, const Hyprutils::Math::Vector2D& size = {} /* for SVG */); CImage(const std::span&, eImageFormat); ~CImage(); @@ -31,6 +31,7 @@ namespace Hyprgraphics { private: std::string lastError, filepath, mime; + Hyprutils::Math::Vector2D m_svgSize; Hyprutils::Memory::CSharedPointer pCairoSurface; bool imageHasAlpha = true, loadSuccess = false; }; diff --git a/include/hyprgraphics/resource/resources/ImageResource.hpp b/include/hyprgraphics/resource/resources/ImageResource.hpp index cf9260f..0ac1d06 100644 --- a/include/hyprgraphics/resource/resources/ImageResource.hpp +++ b/include/hyprgraphics/resource/resources/ImageResource.hpp @@ -17,11 +17,13 @@ namespace Hyprgraphics { }; CImageResource(const std::string& path); + CImageResource(const std::string& svg, const Hyprutils::Math::Vector2D& size); virtual ~CImageResource() = default; virtual void render(); private: - std::string m_path; + std::string m_path; + Hyprutils::Math::Vector2D m_svgSize; }; }; diff --git a/src/image/Image.cpp b/src/image/Image.cpp index 1b9f0dc..58c2098 100644 --- a/src/image/Image.cpp +++ b/src/image/Image.cpp @@ -9,11 +9,13 @@ #endif #include "formats/Webp.hpp" #include "formats/Png.hpp" +#include "formats/Svg.hpp" #include #include using namespace Hyprgraphics; using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; Hyprgraphics::CImage::CImage(const std::span& data, eImageFormat format) { std::expected CAIROSURFACE; @@ -47,24 +49,26 @@ Hyprgraphics::CImage::CImage(const std::span& data, eImageFormat format pCairoSurface = makeShared(CAIROSURFACE.value()); } -Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) { +Hyprgraphics::CImage::CImage(const std::string& path, const Vector2D& size) : filepath(path), m_svgSize(size) { std::expected CAIROSURFACE; - const auto len = path.length(); - if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) { + if (path.ends_with(".png") || path.ends_with(".PNG")) { CAIROSURFACE = PNG::createSurfaceFromPNG(path); mime = "image/png"; - } else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) { + } else if (path.ends_with(".jpg") || path.ends_with(".JPG") || path.ends_with(".jpeg") || path.ends_with(".JPEG")) { CAIROSURFACE = JPEG::createSurfaceFromJPEG(path); imageHasAlpha = false; mime = "image/jpeg"; - } else if (path.find(".bmp") == len - 4 || path.find(".BMP") == len - 4) { + } else if (path.ends_with(".bmp") || path.ends_with(".BMP")) { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; mime = "image/bmp"; - } else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) { + } else if (path.ends_with(".webp") || path.ends_with(".WEBP")) { CAIROSURFACE = WEBP::createSurfaceFromWEBP(path); mime = "image/webp"; - } else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) { + } else if (path.ends_with(".svg") || path.ends_with(".SVG")) { + CAIROSURFACE = SVG::createSurfaceFromSVG(path, m_svgSize); + mime = "image/svg"; + } else if (path.ends_with(".jxl") || path.ends_with(".JXL")) { #ifdef JXL_FOUND CAIROSURFACE = JXL::createSurfaceFromJXL(path); @@ -74,7 +78,7 @@ Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) { return; #endif - } else if (path.find(".avif") == len - 5 || path.find(".AVIF") == len - 5) { + } else if (path.ends_with(".avif") || path.ends_with(".AVIF")) { #ifdef HEIF_FOUND CAIROSURFACE = AVIF::createSurfaceFromAvif(path); @@ -122,6 +126,10 @@ Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) { CAIROSURFACE = BMP::createSurfaceFromBMP(path); imageHasAlpha = false; mime = "image/bmp"; + } else if (first_word == "SVG") { + CAIROSURFACE = SVG::createSurfaceFromSVG(path, m_svgSize); + imageHasAlpha = false; + mime = "image/svg"; } else { lastError = "unrecognized image"; return; diff --git a/src/image/formats/Svg.cpp b/src/image/formats/Svg.cpp new file mode 100644 index 0000000..c48a1fa --- /dev/null +++ b/src/image/formats/Svg.cpp @@ -0,0 +1,63 @@ +#include "Svg.hpp" +#include +#include +#include +#include +#include + +using namespace Hyprutils::Utils; +using namespace Hyprutils::Math; +using namespace Hyprutils::String; + +static std::optional 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(file)), (std::istreambuf_iterator()))); +} + +std::expected 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; +} \ No newline at end of file diff --git a/src/image/formats/Svg.hpp b/src/image/formats/Svg.hpp new file mode 100644 index 0000000..93bb759 --- /dev/null +++ b/src/image/formats/Svg.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace SVG { + std::expected createSurfaceFromSVG(const std::string&, const Hyprutils::Math::Vector2D& size); +}; diff --git a/src/resource/resources/ImageResource.cpp b/src/resource/resources/ImageResource.cpp index 2b82c5c..65dca11 100644 --- a/src/resource/resources/ImageResource.cpp +++ b/src/resource/resources/ImageResource.cpp @@ -13,8 +13,12 @@ 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) { + ; +} + void CImageResource::render() { - auto image = CImage(m_path); + auto image = 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{}; diff --git a/tests/image.cpp b/tests/image.cpp index 64832c6..887e5af 100644 --- a/tests/image.cpp +++ b/tests/image.cpp @@ -10,7 +10,7 @@ using namespace Hyprgraphics; 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()) { std::println("Failed to load {}: {}", path, image.getError()); diff --git a/tests/resource/images/hyprland.svg b/tests/resource/images/hyprland.svg new file mode 100644 index 0000000..fefe957 --- /dev/null +++ b/tests/resource/images/hyprland.svg @@ -0,0 +1,15 @@ + + + + + + + + + + +