Skip to content

Commit 6e26408

Browse files
committed
core: move to libspng for png
1 parent 0d77b48 commit 6e26408

File tree

6 files changed

+104
-5
lines changed

6 files changed

+104
-5
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ pkg_check_modules(
4747
hyprutils
4848
libjpeg
4949
libwebp
50-
libmagic)
50+
libmagic
51+
spng)
5152

5253
pkg_check_modules(
5354
JXL

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Dep list:
2020
- libjxl_cms [optional]
2121
- libjxl_threads [optional]
2222
- libmagic
23+
- libspng
2324

2425
## Building
2526

src/image/Image.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "formats/JpegXL.hpp"
66
#endif
77
#include "formats/Webp.hpp"
8+
#include "formats/Png.hpp"
89
#include <magic.h>
910
#include <format>
1011

@@ -15,7 +16,7 @@ Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) {
1516
std::expected<cairo_surface_t*, std::string> CAIROSURFACE;
1617
const auto len = path.length();
1718
if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) {
18-
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
19+
CAIROSURFACE = PNG::createSurfaceFromPNG(path);
1920
mime = "image/png";
2021
} else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) {
2122
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
@@ -47,7 +48,7 @@ Hyprgraphics::CImage::CImage(const std::string& path) : filepath(path) {
4748
const auto first_word = type_str.substr(0, type_str.find(" "));
4849

4950
if (first_word == "PNG") {
50-
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
51+
CAIROSURFACE = PNG::createSurfaceFromPNG(path);
5152
mime = "image/png";
5253
} else if (first_word == "JPEG") {
5354
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);

src/image/formats/Png.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include "Png.hpp"
2+
#include <spng.h>
3+
#include <vector>
4+
#include <fstream>
5+
#include <filesystem>
6+
#include <cstdint>
7+
#include <hyprutils/utils/ScopeGuard.hpp>
8+
using namespace Hyprutils::Utils;
9+
10+
static std::vector<unsigned char> readBinaryFile(const std::string& filename) {
11+
std::ifstream f(filename, std::ios::binary);
12+
if (!f.good())
13+
return {};
14+
f.unsetf(std::ios::skipws);
15+
return {std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>()};
16+
}
17+
18+
std::expected<cairo_surface_t*, std::string> PNG::createSurfaceFromPNG(const std::string& path) {
19+
if (!std::filesystem::exists(path))
20+
return std::unexpected("loading png: file doesn't exist");
21+
22+
spng_ctx* ctx = spng_ctx_new(0);
23+
24+
CScopeGuard x([&] { spng_ctx_free(ctx); });
25+
26+
const auto PNGCONTENT = readBinaryFile(path);
27+
28+
if (PNGCONTENT.empty())
29+
return std::unexpected("loading png: file content was empty (bad file?)");
30+
31+
spng_set_png_buffer(ctx, PNGCONTENT.data(), PNGCONTENT.size());
32+
33+
spng_ihdr ihdr{0};
34+
if (spng_get_ihdr(ctx, &ihdr))
35+
return std::unexpected("loading png: file content was empty (bad file?)");
36+
37+
int fmt = SPNG_FMT_PNG;
38+
if (ihdr.color_type == SPNG_COLOR_TYPE_INDEXED)
39+
fmt = SPNG_FMT_RGB8;
40+
41+
size_t imageLength = 0;
42+
if (spng_decoded_image_size(ctx, fmt, &imageLength))
43+
return std::unexpected("loading png: spng_decoded_image_size failed");
44+
45+
uint8_t* imageData = (uint8_t*)malloc(imageLength);
46+
47+
if (!imageData)
48+
return std::unexpected("loading png: mallocing failed, out of memory?");
49+
50+
// TODO: allow proper decode of high bitrate images
51+
if (spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0)) {
52+
free(imageData);
53+
return std::unexpected("loading png: spng_decode_image failed (invalid image?)");
54+
}
55+
56+
// convert RGBA8888 -> ARGB8888 premult for cairo
57+
for (size_t i = 0; i < imageLength; i += 4) {
58+
uint8_t r, g, b, a;
59+
a = ((*((uint32_t*)(imageData + i))) & 0xFF000000) >> 24;
60+
b = ((*((uint32_t*)(imageData + i))) & 0x00FF0000) >> 16;
61+
g = ((*((uint32_t*)(imageData + i))) & 0x0000FF00) >> 8;
62+
r = (*((uint32_t*)(imageData + i))) & 0x000000FF;
63+
64+
r *= ((float)a / 255.F);
65+
g *= ((float)a / 255.F);
66+
b *= ((float)a / 255.F);
67+
68+
*((uint32_t*)(imageData + i)) = (((uint32_t)a) << 24) | (((uint32_t)r) << 16) | (((uint32_t)g) << 8) | (uint32_t)b;
69+
}
70+
71+
auto CAIROSURFACE = cairo_image_surface_create_for_data(imageData, CAIRO_FORMAT_ARGB32, ihdr.width, ihdr.height, ihdr.width * 4);
72+
73+
if (!CAIROSURFACE)
74+
return std::unexpected("loading png: cairo failed");
75+
76+
return CAIROSURFACE;
77+
}

src/image/formats/Png.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include <cairo/cairo.h>
4+
#include <string>
5+
#include <expected>
6+
7+
namespace PNG {
8+
std::expected<cairo_surface_t*, std::string> createSurfaceFromPNG(const std::string&);
9+
};

tests/image.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ bool tryLoadImage(const std::string& path) {
1616

1717
std::println("Loaded {} successfully: Image is {}x{} of type {}", path, image.cairoSurface()->size().x, image.cairoSurface()->size().y, image.getMime());
1818

19-
return true;
19+
const auto TEST_DIR = std::filesystem::current_path().string() + "/test_output";
20+
21+
// try to write it for inspection
22+
if (!std::filesystem::exists(TEST_DIR))
23+
std::filesystem::create_directory(TEST_DIR);
24+
25+
std::string name = image.getMime();
26+
std::replace(name.begin(), name.end(), '/', '_');
27+
28+
return cairo_surface_write_to_png(image.cairoSurface()->cairo(), (TEST_DIR + "/" + name + ".png").c_str()) == CAIRO_STATUS_SUCCESS;
2029
}
2130

2231
int main(int argc, char** argv, char** envp) {
@@ -27,7 +36,8 @@ int main(int argc, char** argv, char** envp) {
2736
continue;
2837
auto expectation = true;
2938
#ifndef JXL_FOUND
30-
if (file.path().filename() == "hyprland.jxl") expectation = false;
39+
if (file.path().filename() == "hyprland.jxl")
40+
expectation = false;
3141
#endif
3242
EXPECT(tryLoadImage(file.path()), expectation);
3343
}

0 commit comments

Comments
 (0)