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

This commit is contained in:
Vaxry 2025-01-27 13:19:24 +00:00
parent 0d77b4895a
commit 0c11438de4
7 changed files with 105 additions and 6 deletions

View file

@ -17,7 +17,7 @@ jobs:
run: |
sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf
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++ pixman cairo hyprutils libjpeg-turbo libjxl libwebp libspng
- name: Build hyprgraphics with gcc
run: |

View file

@ -47,7 +47,8 @@ pkg_check_modules(
hyprutils
libjpeg
libwebp
libmagic)
libmagic
spng)
pkg_check_modules(
JXL

View file

@ -20,6 +20,7 @@ Dep list:
- libjxl_cms [optional]
- libjxl_threads [optional]
- libmagic
- libspng
## Building

View file

@ -5,6 +5,7 @@
#include "formats/JpegXL.hpp"
#endif
#include "formats/Webp.hpp"
#include "formats/Png.hpp"
#include <magic.h>
#include <format>
@ -15,7 +16,7 @@ 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());
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) {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
@ -47,7 +48,7 @@ Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) {
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());
CAIROSURFACE = PNG::createSurfaceFromPNG(path);
mime = "image/png";
} else if (first_word == "JPEG") {
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);

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

@ -0,0 +1,77 @@
#include "Png.hpp"
#include <spng.h>
#include <vector>
#include <fstream>
#include <filesystem>
#include <cstdint>
#include <hyprutils/utils/ScopeGuard.hpp>
using namespace Hyprutils::Utils;
static std::vector<unsigned char> readBinaryFile(const std::string& filename) {
std::ifstream f(filename, std::ios::binary);
if (!f.good())
return {};
f.unsetf(std::ios::skipws);
return {std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()};
}
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");
spng_ctx* ctx = spng_ctx_new(0);
CScopeGuard x([&] { spng_ctx_free(ctx); });
const auto PNGCONTENT = readBinaryFile(path);
if (PNGCONTENT.empty())
return std::unexpected("loading png: file content was empty (bad file?)");
spng_set_png_buffer(ctx, PNGCONTENT.data(), PNGCONTENT.size());
spng_ihdr ihdr{0};
if (spng_get_ihdr(ctx, &ihdr))
return std::unexpected("loading png: file content was empty (bad file?)");
int fmt = SPNG_FMT_PNG;
if (ihdr.color_type == SPNG_COLOR_TYPE_INDEXED)
fmt = SPNG_FMT_RGB8;
size_t imageLength = 0;
if (spng_decoded_image_size(ctx, fmt, &imageLength))
return std::unexpected("loading png: spng_decoded_image_size failed");
uint8_t* imageData = (uint8_t*)malloc(imageLength);
if (!imageData)
return std::unexpected("loading png: mallocing failed, out of memory?");
// TODO: allow proper decode of high bitrate images
if (spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0)) {
free(imageData);
return std::unexpected("loading png: spng_decode_image failed (invalid image?)");
}
// convert RGBA8888 -> ARGB8888 premult for cairo
for (size_t i = 0; i < imageLength; i += 4) {
uint8_t r, g, b, a;
a = ((*((uint32_t*)(imageData + i))) & 0xFF000000) >> 24;
b = ((*((uint32_t*)(imageData + i))) & 0x00FF0000) >> 16;
g = ((*((uint32_t*)(imageData + i))) & 0x0000FF00) >> 8;
r = (*((uint32_t*)(imageData + i))) & 0x000000FF;
r *= ((float)a / 255.F);
g *= ((float)a / 255.F);
b *= ((float)a / 255.F);
*((uint32_t*)(imageData + i)) = (((uint32_t)a) << 24) | (((uint32_t)r) << 16) | (((uint32_t)g) << 8) | (uint32_t)b;
}
auto CAIROSURFACE = cairo_image_surface_create_for_data(imageData, CAIRO_FORMAT_ARGB32, ihdr.width, ihdr.height, ihdr.width * 4);
if (!CAIROSURFACE)
return std::unexpected("loading png: cairo failed");
return CAIROSURFACE;
}

View file

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

View file

@ -16,7 +16,16 @@ 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());
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::replace(name.begin(), name.end(), '/', '_');
return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS;
}
int main(int argc, char** argv, char** envp) {
@ -27,7 +36,8 @@ int main(int argc, char** argv, char** envp) {
continue;
auto expectation = true;
#ifndef JXL_FOUND
if (file.path().filename() == "hyprland.jxl") expectation = false;
if (file.path().filename() == "hyprland.jxl")
expectation = false;
#endif
EXPECT(tryLoadImage(file.path()), expectation);
}