diff --git a/CMakeLists.txt b/CMakeLists.txt index 9657410..67c6c9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ pkg_check_modules( libjpeg libwebp libmagic - spng) + libpng) pkg_check_modules( JXL diff --git a/src/image/formats/Png.cpp b/src/image/formats/Png.cpp index ee2d18b..e1e88c1 100644 --- a/src/image/formats/Png.cpp +++ b/src/image/formats/Png.cpp @@ -1,94 +1,84 @@ #include "Png.hpp" -#include #include #include #include #include #include +#include +#include +#include #include using namespace Hyprutils::Utils; -static std::vector 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(f), std::istreambuf_iterator()}; -} - std::expected 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); + FILE* fp = fopen(path.c_str(), "rb"); + if (!fp) + return std::unexpected("loading png: couldn't open file"); - CScopeGuard x([&] { spng_ctx_free(ctx); }); + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_infop info = png_create_info_struct(png); + if (!png || !info) + return std::unexpected("loading png: couldn't init libpng"); - const auto PNGCONTENT = readBinaryFile(path); + CScopeGuard x([&png, &info, fp] { + png_destroy_read_struct(&png, &info, nullptr); + fclose(fp); + }); - if (PNGCONTENT.empty()) - return std::unexpected("loading png: file content was empty (bad file?)"); + if (setjmp(png_jmpbuf(png))) + return std::unexpected("loading png: couldn't setjmp"); - spng_set_png_buffer(ctx, PNGCONTENT.data(), PNGCONTENT.size()); + png_init_io(png, fp); + png_read_info(png, info); - spng_ihdr ihdr{.width = 0}; - if (int ret = spng_get_ihdr(ctx, &ihdr); ret) - return std::unexpected(std::string{"loading png: spng_get_ihdr failed: "} + spng_strerror(ret)); + const size_t W = png_get_image_width(png, info); + const size_t H = png_get_image_height(png, info); + const auto COLOR_TYPE = png_get_color_type(png, info); + const auto BPP = png_get_bit_depth(png, info); - int fmt = SPNG_FMT_PNG; - if (ihdr.color_type == SPNG_COLOR_TYPE_INDEXED) - fmt = SPNG_FMT_RGB8; + if (BPP == 16) + png_set_strip_16(png); + if (COLOR_TYPE == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY && BPP < 8) + png_set_expand_gray_1_2_4_to_8(png); + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png); - size_t imageLength = 0; - if (int ret = spng_decoded_image_size(ctx, fmt, &imageLength); ret) - return std::unexpected(std::string{"loading png: spng_decoded_image_size failed: "} + spng_strerror(ret)); + if (COLOR_TYPE == PNG_COLOR_TYPE_RGB || COLOR_TYPE == PNG_COLOR_TYPE_GRAY || COLOR_TYPE == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + else if (COLOR_TYPE == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); - uint8_t* imageData = (uint8_t*)malloc(imageLength); + png_read_update_info(png, info); - if (!imageData) - return std::unexpected("loading png: mallocing failed, out of memory?"); - - // TODO: allow proper decode of high bitrate images - bool succeededDecode = false; - int ret = spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0); - if (!ret) - succeededDecode = true; - - if (!succeededDecode && ret == SPNG_EBUFSIZ) { - // hack, but I don't know why decoded_image_size is sometimes wrong - imageLength = static_cast(ihdr.height * ihdr.width * 4) /* FIXME: this is wrong if we doing >32bpp!!!! */; - imageData = (uint8_t*)realloc(imageData, imageLength); - - ret = spng_decode_image(ctx, imageData, imageLength, SPNG_FMT_RGBA8, 0); + std::vector rowPointers; + rowPointers.resize(H); + std::vector rawData; + rawData.resize(W * H * 4); + for (size_t y = 0; y < H; y++) { + rowPointers[y] = rawData.data() + (y * W * 4); } - if (!ret) - succeededDecode = true; + png_read_image(png, rowPointers.data()); - if (!succeededDecode) { - free(imageData); - return std::unexpected(std::string{"loading png: spng_decode_image failed: "} + spng_strerror(ret) + " (bad image?)"); + for (size_t i = 0; i < W * H * 4; i += 4) { + uint8_t r = rawData[i + 0]; + uint8_t g = rawData[i + 1]; + uint8_t b = rawData[i + 2]; + uint8_t a = rawData[i + 3]; + *(uint32_t*)&rawData[i] = (a << 24) | (r << 16) | (g << 8) | b; } - // 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); + auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, W, H); if (!CAIROSURFACE) return std::unexpected("loading png: cairo failed"); + memcpy(cairo_image_surface_get_data(CAIROSURFACE), rawData.data(), rawData.size()); + return CAIROSURFACE; }