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

This commit is contained in:
Vaxry 2025-09-30 19:31:50 +01:00
parent 32e6b8386f
commit 510efd0a36
Signed by: vaxry
GPG key ID: 665806380871D640
10 changed files with 119 additions and 13 deletions

View file

@ -59,7 +59,8 @@ pkg_check_modules(
libjpeg
libwebp
libmagic
libpng)
libpng
librsvg-2.0)
pkg_check_modules(
JXL

View file

@ -21,6 +21,7 @@ Dep list:
- libjxl_threads [optional]
- libmagic
- libpng
- librsvg2
## Building

View file

@ -7,14 +7,14 @@
#include <hyprutils/memory/SharedPtr.hpp>
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<uint8_t>&, eImageFormat);
~CImage();
@ -31,6 +31,7 @@ namespace Hyprgraphics {
private:
std::string lastError, filepath, mime;
Hyprutils::Math::Vector2D m_svgSize;
Hyprutils::Memory::CSharedPointer<CCairoSurface> pCairoSurface;
bool imageHasAlpha = true, loadSuccess = false;
};

View file

@ -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;
};
};

View file

@ -9,11 +9,13 @@
#endif
#include "formats/Webp.hpp"
#include "formats/Png.hpp"
#include "formats/Svg.hpp"
#include <magic.h>
#include <format>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Math;
Hyprgraphics::CImage::CImage(const std::span<uint8_t>& data, eImageFormat format) {
std::expected<cairo_surface_t*, std::string> CAIROSURFACE;
@ -47,24 +49,26 @@ Hyprgraphics::CImage::CImage(const std::span<uint8_t>& data, eImageFormat format
pCairoSurface = makeShared<CCairoSurface>(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<cairo_surface_t*, std::string> 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;

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

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

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

@ -0,0 +1,11 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <expected>
#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);
};

View file

@ -13,6 +13,10 @@ 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);

View file

@ -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());

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