core: Add image and cairo

This commit is contained in:
Vaxry 2024-11-22 15:02:12 +00:00
parent 8b0000af17
commit 292597d55a
23 changed files with 724 additions and 1 deletions

14
.gitignore vendored
View file

@ -30,3 +30,17 @@
*.exe
*.out
*.app
build/
.vscode/
.cache/
.cmake/
CMakeCache.txt
CMakeFiles/
CTestTestfile.cmake
DartConfiguration.tcl
Makefile
cmake_install.cmake
compile_commands.json
hyprutils.pc

61
CMakeLists.txt Normal file
View file

@ -0,0 +1,61 @@
cmake_minimum_required(VERSION 3.19)
file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW)
string(STRIP ${VER_RAW} HYPRGRAPHICS_VERSION)
add_compile_definitions(HYPRGRAPHICS_VERSION="${HYPRGRAPHICS_VERSION}")
project(
hyprgraphics
VERSION ${HYPRGRAPHICS_VERSION}
DESCRIPTION "Small C++ library for utilities used across the Hypr* ecosystem")
include(CTest)
include(GNUInstallDirs)
set(PREFIX ${CMAKE_INSTALL_PREFIX})
set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
configure_file(hyprgraphics.pc.in hyprgraphics.pc @ONLY)
set(CMAKE_CXX_STANDARD 23)
if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
message(STATUS "Configuring hyprgraphics in Debug")
add_compile_definitions(HYPRGRAPHICS_DEBUG)
else()
add_compile_options(-O3)
message(STATUS "Configuring hyprgraphics in Release")
endif()
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "include/*.hpp")
file(GLOB_RECURSE PUBLIC_HEADERS CONFIGURE_DEPENDS "include/*.hpp")
find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET pixman-1 cairo hyprutils libjpeg libwebp libjxl libjxl_cms libjxl_threads libmagic)
add_library(hyprgraphics SHARED ${SRCFILES})
target_include_directories(
hyprgraphics
PUBLIC "./include"
PRIVATE "./src")
set_target_properties(hyprgraphics PROPERTIES VERSION ${HYPRGRAPHICS_VERSION}
SOVERSION 0)
target_link_libraries(hyprgraphics PkgConfig::deps)
# tests
add_custom_target(tests)
add_executable(hyprgraphics_image "tests/image.cpp")
target_link_libraries(hyprgraphics_image PRIVATE hyprgraphics PkgConfig::deps)
add_test(
NAME "Image"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
COMMAND hyprgraphics_image "image")
add_dependencies(tests hyprgraphics_image)
# Installation
install(TARGETS hyprgraphics)
install(DIRECTORY "include/hyprgraphics" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${CMAKE_BINARY_DIR}/hyprgraphics.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

View file

@ -1,2 +1,17 @@
# hyprgraphics
Hyprland graphics / resource utilities
Hyprgraphics is a small C++ library with graphics / resource related utilities used across the hypr* ecosystem.
## Stability
Hyprutils depends on the ABI stability of the stdlib implementation of your compiler. Sover bumps will be done only for hyprutils ABI breaks, not stdlib.
## Building
```sh
git clone https://github.com/hyprwm/hyprgraphics
cd hyprgraphics/
cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build
cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf _NPROCESSORS_CONF`
sudo cmake --install build
```

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.0

10
hyprgraphics.pc.in Normal file
View file

@ -0,0 +1,10 @@
prefix=@PREFIX@
includedir=@INCLUDE@
libdir=@LIBDIR@
Name: hyprgraphics
URL: https://github.com/hyprwm/hyprgraphics
Description: Hyprland graphics utilities library used across the ecosystem
Version: @HYPRGRAPHICS_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -lhyprgraphics

View file

@ -0,0 +1,29 @@
#pragma once
#include <cstdint>
#include <cairo/cairo.h>
#include <hyprutils/math/Vector2D.hpp>
namespace Hyprgraphics {
// A simple cairo surface wrapper. Will destroy the surface in the ~dtor.
class CCairoSurface {
public:
CCairoSurface(cairo_surface_t* surf);
~CCairoSurface();
CCairoSurface(CCairoSurface&&) = delete;
CCairoSurface(const CCairoSurface&) = delete;
CCairoSurface& operator=(const CCairoSurface&) = delete;
CCairoSurface& operator=(CCairoSurface&&) = delete;
cairo_surface_t* cairo();
Hyprutils::Math::Vector2D size();
int status();
uint8_t* data();
int stride();
private:
cairo_surface_t* pSurface = nullptr;
};
};

View file

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <cairo/cairo.h>
#include "../cairo/CairoSurface.hpp"
#include <hyprutils/memory/SharedPtr.hpp>
namespace Hyprgraphics {
class CImage {
public:
// create an image from a provided path.
CImage(const std::string& path);
~CImage();
bool success();
bool hasAlpha();
std::string getError();
std::string getMime();
Hyprutils::Memory::CSharedPointer<CCairoSurface> cairoSurface();
private:
std::string lastError, filepath, mime;
Hyprutils::Memory::CSharedPointer<CCairoSurface> pCairoSurface;
bool imageHasAlpha = true, loadSuccess = false;
};
};

View file

@ -0,0 +1,32 @@
#include <hyprgraphics/cairo/CairoSurface.hpp>
using namespace Hyprgraphics;
Hyprgraphics::CCairoSurface::CCairoSurface(cairo_surface_t* surf) : pSurface(surf) {
;
}
Hyprgraphics::CCairoSurface::~CCairoSurface() {
if (pSurface)
cairo_surface_destroy(pSurface);
}
cairo_surface_t* Hyprgraphics::CCairoSurface::cairo() {
return pSurface;
}
Hyprutils::Math::Vector2D Hyprgraphics::CCairoSurface::size() {
return {cairo_image_surface_get_width(pSurface), cairo_image_surface_get_height(pSurface)};
}
int Hyprgraphics::CCairoSurface::status() {
return cairo_surface_status(pSurface);
}
uint8_t* Hyprgraphics::CCairoSurface::data() {
return (uint8_t*)cairo_image_surface_get_data(pSurface);
}
int Hyprgraphics::CCairoSurface::stride() {
return cairo_image_surface_get_stride(pSurface);
}

93
src/image/Image.cpp Normal file
View file

@ -0,0 +1,93 @@
#include <hyprgraphics/image/Image.hpp>
#include "formats/Bmp.hpp"
#include "formats/Jpeg.hpp"
#include "formats/JpegXL.hpp"
#include "formats/Webp.hpp"
#include <magic.h>
#include <format>
using namespace Hyprgraphics;
using namespace Hyprutils::Memory;
Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) {
std::expected<cairo_surface_t*, std::string> CAIROSURFACE;
const auto len = path.length();
if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
mime = "image/png";
} else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
imageHasAlpha = false;
mime = "image/jpeg";
} else if (path.find(".bmp") == len - 4 || path.find(".BMP") == len - 4) {
CAIROSURFACE = BMP::createSurfaceFromBMP(path);
imageHasAlpha = false;
mime = "image/bmp";
} else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) {
CAIROSURFACE = WEBP::createSurfaceFromWEBP(path);
mime = "image/webp";
} else if (path.find(".jxl") == len - 4 || path.find(".JXL") == len - 4) {
CAIROSURFACE = JXL::createSurfaceFromJXL(path);
mime = "image/jxl";
} else {
// magic is slow, so only use it when no recognized extension is found
auto handle = magic_open(MAGIC_NONE | MAGIC_COMPRESS);
magic_load(handle, nullptr);
const auto type_str = std::string(magic_file(handle, path.c_str()));
const auto first_word = type_str.substr(0, type_str.find(" "));
if (first_word == "PNG") {
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
mime = "image/png";
} else if (first_word == "JPEG") {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
imageHasAlpha = false;
mime = "image/jpeg";
} else if (first_word == "BMP") {
CAIROSURFACE = BMP::createSurfaceFromBMP(path);
imageHasAlpha = false;
mime = "image/bmp";
} else {
lastError = "unrecognized image";
return;
}
}
if (!CAIROSURFACE) {
lastError = CAIROSURFACE.error();
return;
}
if (const auto STATUS = cairo_surface_status(*CAIROSURFACE); STATUS != CAIRO_STATUS_SUCCESS) {
lastError = std::format("Could not create surface: {}", cairo_status_to_string(STATUS));
return;
}
loadSuccess = true;
pCairoSurface = makeShared<CCairoSurface>(CAIROSURFACE.value());
}
Hyprgraphics::CImage::~CImage() {
;
}
bool Hyprgraphics::CImage::success() {
return loadSuccess;
}
bool Hyprgraphics::CImage::hasAlpha() {
return imageHasAlpha;
}
std::string Hyprgraphics::CImage::getError() {
return lastError;
}
Hyprutils::Memory::CSharedPointer<CCairoSurface> Hyprgraphics::CImage::cairoSurface() {
return pCairoSurface;
}
std::string Hyprgraphics::CImage::getMime() {
return mime;
}

123
src/image/formats/Bmp.cpp Normal file
View file

@ -0,0 +1,123 @@
#include "Bmp.hpp"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <filesystem>
#include <optional>
#include <fstream>
#include <vector>
#include <string.h>
class BmpHeader {
public:
unsigned char format[2];
uint32_t sizeOfFile;
uint16_t reserved1;
uint16_t reserved2;
uint32_t dataOffset;
uint32_t sizeOfBitmapHeader;
uint32_t width;
uint32_t height;
uint16_t numberOfColors;
uint16_t numberOfBitPerPixel;
uint32_t compressionMethod;
uint32_t imageSize;
uint32_t horizontalResolutionPPM;
uint32_t verticalResolutionPPM;
uint32_t numberOfCollors;
uint32_t numberOfImportantCollors;
std::optional<std::string> load(std::ifstream& file) {
file.seekg(0, std::ios::end);
uint32_t streamLength = file.tellg();
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(&format), sizeof(format));
if (!(format[0] == 66 && format[1] == 77))
return "Unable to parse bitmap header: wrong bmp file type";
file.read(reinterpret_cast<char*>(&sizeOfFile), sizeof(sizeOfFile));
if (sizeOfFile != streamLength)
return "Unable to parse bitmap header: wrong value of file size header";
file.read(reinterpret_cast<char*>(&reserved1), sizeof(reserved1));
file.read(reinterpret_cast<char*>(&reserved2), sizeof(reserved2));
file.read(reinterpret_cast<char*>(&dataOffset), sizeof(dataOffset));
file.read(reinterpret_cast<char*>(&sizeOfBitmapHeader), sizeof(sizeOfBitmapHeader));
file.read(reinterpret_cast<char*>(&width), sizeof(width));
file.read(reinterpret_cast<char*>(&height), sizeof(height));
file.read(reinterpret_cast<char*>(&numberOfColors), sizeof(numberOfColors));
file.read(reinterpret_cast<char*>(&numberOfBitPerPixel), sizeof(numberOfBitPerPixel));
file.read(reinterpret_cast<char*>(&compressionMethod), sizeof(compressionMethod));
file.read(reinterpret_cast<char*>(&imageSize), sizeof(imageSize));
file.read(reinterpret_cast<char*>(&horizontalResolutionPPM), sizeof(horizontalResolutionPPM));
file.read(reinterpret_cast<char*>(&verticalResolutionPPM), sizeof(verticalResolutionPPM));
file.read(reinterpret_cast<char*>(&numberOfCollors), sizeof(numberOfCollors));
file.read(reinterpret_cast<char*>(&numberOfImportantCollors), sizeof(numberOfImportantCollors));
if (!imageSize)
imageSize = sizeOfFile - dataOffset;
if (imageSize != (width * height * numberOfBitPerPixel / 8))
return "Unable to parse bitmap header: wrong image size";
file.seekg(dataOffset);
};
};
static void reflectImage(unsigned char* image, uint32_t numberOfRows, int stride) {
int rowStart = 0;
int rowEnd = numberOfRows - 1;
std::vector<unsigned char> temp;
temp.resize(stride);
while (rowStart < rowEnd) {
memcpy(&temp[0], &image[rowStart * stride], stride);
memcpy(&image[rowStart * stride], &image[rowEnd * stride], stride);
memcpy(&image[rowEnd * stride], &temp[0], stride);
rowStart++;
rowEnd--;
}
};
static void convertRgbToArgb(std::ifstream& imageStream, unsigned char* outputImage, uint32_t newImageSize) {
uint8_t forthBitCounter = 0;
unsigned long imgCursor = 0;
while (imgCursor < newImageSize) {
imageStream.read(reinterpret_cast<char*>(&outputImage[imgCursor]), 1);
imgCursor++;
forthBitCounter++;
if (forthBitCounter == 3) {
outputImage[imgCursor] = 0;
imgCursor++;
forthBitCounter = 0;
}
}
};
std::expected<cairo_surface_t*, std::string> BMP::createSurfaceFromBMP(const std::string& path) {
if (!std::filesystem::exists(path))
return std::unexpected("loading bmp: file doesn't exist");
std::ifstream bitmapImageStream(path);
BmpHeader bitmapHeader;
if (const auto RET = bitmapHeader.load(bitmapImageStream); RET.has_value())
return std::unexpected("loading bmp: " + *RET);
cairo_format_t format = CAIRO_FORMAT_ARGB32;
int stride = cairo_format_stride_for_width(format, bitmapHeader.width);
unsigned char* imageData = (unsigned char*)malloc(bitmapHeader.height * stride);
if (bitmapHeader.numberOfBitPerPixel == 24)
convertRgbToArgb(bitmapImageStream, imageData, bitmapHeader.height * stride);
else if (bitmapHeader.numberOfBitPerPixel == 32)
bitmapImageStream.read(reinterpret_cast<char*>(&imageData), bitmapHeader.imageSize);
else {
bitmapImageStream.close();
return std::unexpected("loading bmp: unsupported bmp stream");
}
bitmapImageStream.close();
reflectImage(imageData, bitmapHeader.height, stride);
return cairo_image_surface_create_for_data(imageData, format, bitmapHeader.width, bitmapHeader.height, stride);
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <expected>
namespace BMP {
std::expected<cairo_surface_t*, std::string> createSurfaceFromBMP(const std::string&);
};

View file

@ -0,0 +1,61 @@
#include "Jpeg.hpp"
#include <filesystem>
#include <fstream>
#include <vector>
std::expected<cairo_surface_t*, std::string> JPEG::createSurfaceFromJPEG(const std::string& path) {
if (!std::filesystem::exists(path))
return std::unexpected("loading jpeg: file doesn't exist");
if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__)
return std::unexpected("loading jpeg: cannot load on big endian");
std::ifstream file(path, std::ios::binary | std::ios::ate);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::vector<uint8_t> bytes(file.tellg());
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());
// now the JPEG is in the memory
jpeg_decompress_struct decompressStruct = {};
jpeg_error_mgr errorManager = {};
decompressStruct.err = jpeg_std_error(&errorManager);
jpeg_create_decompress(&decompressStruct);
jpeg_mem_src(&decompressStruct, bytes.data(), bytes.size());
jpeg_read_header(&decompressStruct, true);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
decompressStruct.out_color_space = JCS_EXT_BGRA;
#else
decompressStruct.out_color_space = JCS_EXT_ARGB;
#endif
// decompress
jpeg_start_decompress(&decompressStruct);
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS)
return std::unexpected("loading jpeg: cairo failed to create an image surface");
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
JSAMPROW rowRead;
while (decompressStruct.output_scanline < decompressStruct.output_height) {
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
rowRead = PROW;
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
}
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, bytes.data(), bytes.size(), nullptr, nullptr);
jpeg_finish_decompress(&decompressStruct);
jpeg_destroy_decompress(&decompressStruct);
return cairoSurface;
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <jpeglib.h>
#include <expected>
namespace JPEG {
std::expected<cairo_surface_t*, std::string> createSurfaceFromJPEG(const std::string&);
};

View file

@ -0,0 +1,92 @@
#include "JpegXL.hpp"
#include <filesystem>
#include <fstream>
#include <jxl/decode_cxx.h>
#include <jxl/resizable_parallel_runner_cxx.h>
#include <vector>
std::expected<cairo_surface_t*, std::string> JXL::createSurfaceFromJXL(const std::string& path) {
if (!std::filesystem::exists(path))
return std::unexpected("loading jxl: file doesn't exist");
std::ifstream file(path, std::ios::binary | std::ios::ate);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::vector<uint8_t> bytes(file.tellg());
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());
JxlSignature signature = JxlSignatureCheck(bytes.data(), bytes.size());
if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
return std::unexpected("loading jxl: not jxl");
auto dec = JxlDecoderMake(nullptr);
auto runner = JxlResizableParallelRunnerMake(nullptr);
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get()))
return std::unexpected("loading jxl: JxlResizableParallelRunner failed");
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE))
return std::unexpected("loading jxl: JxlDecoderSubscribeEvents failed");
JxlDecoderSetInput(dec.get(), bytes.data(), bytes.size());
JxlDecoderCloseInput(dec.get());
if (JXL_DEC_BASIC_INFO != JxlDecoderProcessInput(dec.get()))
return std::unexpected("loading jxl: JxlDecoderProcessInput failed");
JxlBasicInfo basicInfo;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &basicInfo))
return std::unexpected("loading jxl: JxlDecoderGetBasicInfo failed");
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, basicInfo.xsize, basicInfo.ysize);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: cairo failed");
}
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
JxlPixelFormat format = {
.num_channels = 4,
.data_type = JXL_TYPE_UINT8,
.endianness = JXL_LITTLE_ENDIAN,
.align = cairo_image_surface_get_stride(cairoSurface),
};
const auto OUTPUTSIZE = basicInfo.xsize * basicInfo.ysize * format.num_channels;
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
if (status == JXL_DEC_ERROR) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: JxlDecoderProcessInput failed");
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: JxlDecoderProcessInput expected more input");
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
JxlResizableParallelRunnerSetThreads(runner.get(), JxlResizableParallelRunnerSuggestThreads(basicInfo.xsize, basicInfo.ysize));
size_t bufferSize;
if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &bufferSize)) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: JxlDecoderImageOutBufferSize failed");
}
if (bufferSize != OUTPUTSIZE) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: invalid output size");
}
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, CAIRODATA, bufferSize)) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading jxl: JxlDecoderSetImageOutBuffer failed");
}
} else if (status == JXL_DEC_FULL_IMAGE) {
for (size_t i = 0; i < OUTPUTSIZE - 2; i += format.num_channels) {
std::swap(CAIRODATA[i + 0], CAIRODATA[i + 2]);
}
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, "image/jxl", bytes.data(), bytes.size(), nullptr, nullptr);
return cairoSurface;
}
}
return nullptr;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <expected>
namespace JXL {
std::expected<cairo_surface_t*, std::string> createSurfaceFromJXL(const std::string&);
};

View file

@ -0,0 +1,63 @@
#include "Webp.hpp"
#include <filesystem>
#include <fstream>
#include <webp/decode.h>
#include <vector>
std::expected<cairo_surface_t*, std::string> WEBP::createSurfaceFromWEBP(const std::string& path) {
if (!std::filesystem::exists(path))
return std::unexpected("loading webp: file doesn't exist");
std::ifstream file(path, std::ios::binary | std::ios::ate);
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
std::vector<uint8_t> bytes(file.tellg());
file.seekg(0);
file.read(reinterpret_cast<char*>(bytes.data()), bytes.size());
// now the WebP is in the memory
WebPDecoderConfig config;
if (!WebPInitDecoderConfig(&config))
return std::unexpected("loading webp: WebPInitDecoderConfig failed");
if (WebPGetFeatures(bytes.data(), bytes.size(), &config.input) != VP8_STATUS_OK)
return std::unexpected("loading webp: file is not valid webp");
const auto HEIGHT = config.input.height;
const auto WIDTH = config.input.width;
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT);
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(cairoSurface);
return std::unexpected("loading webp: cairo failed");
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
config.output.colorspace = MODE_bgrA;
#else
config.output.colorspace = MODE_Argb;
#endif
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
config.options.no_fancy_upsampling = 1;
config.output.u.RGBA.rgba = CAIRODATA;
config.output.u.RGBA.stride = CAIROSTRIDE;
config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT;
config.output.is_external_memory = 1;
config.output.width = WIDTH;
config.output.height = HEIGHT;
if (WebPDecode(bytes.data(), bytes.size(), &config) != VP8_STATUS_OK)
return std::unexpected("loading webp: webp decode failed");
cairo_surface_mark_dirty(cairoSurface);
cairo_surface_set_mime_data(cairoSurface, "image/webp", bytes.data(), bytes.size(), nullptr, nullptr);
WebPFreeDecBuffer(&config.output);
return cairoSurface;
}

View file

@ -0,0 +1,9 @@
#pragma once
#include <cairo/cairo.h>
#include <string>
#include <expected>
namespace WEBP {
std::expected<cairo_surface_t*, std::string> createSurfaceFromWEBP(const std::string&);
};

33
tests/image.cpp Normal file
View file

@ -0,0 +1,33 @@
#include <print>
#include <format>
#include <filesystem>
#include <hyprgraphics/image/Image.hpp>
#include "shared.hpp"
using namespace Hyprgraphics;
bool tryLoadImage(const std::string& path) {
auto image = CImage(path);
if (!image.success()) {
std::println("Failed to load {}: {}", path, image.getError());
return false;
}
std::println("Loaded {} successfully: Image is {}x{} of type {}", path, image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime());
return true;
}
int main(int argc, char** argv, char** envp) {
int ret = 0;
for (auto& file : std::filesystem::directory_iterator("./resource/images/")) {
if (!file.is_regular_file())
continue;
EXPECT(tryLoadImage(file.path()), true);
}
return ret;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

32
tests/shared.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <iostream>
namespace Colors {
constexpr const char* RED = "\x1b[31m";
constexpr const char* GREEN = "\x1b[32m";
constexpr const char* YELLOW = "\x1b[33m";
constexpr const char* BLUE = "\x1b[34m";
constexpr const char* MAGENTA = "\x1b[35m";
constexpr const char* CYAN = "\x1b[36m";
constexpr const char* RESET = "\x1b[0m";
};
#define EXPECT(expr, val) \
if (const auto RESULT = expr; RESULT != (val)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << val << " but got " << RESULT << "\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \
}
#define EXPECT_VECTOR2D(expr, val) \
do { \
const auto& RESULT = expr; \
const auto& EXPECTED = val; \
if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \
std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \
<< RESULT.y << ")\n"; \
ret = 1; \
} else { \
std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \
} \
} while (0)