#include "vectorail/core/PngTexture.hpp" #include "vectorail/core/gl_loader.hpp" #define STBI_ONLY_PNG #define STB_IMAGE_IMPLEMENTATION #include #include #include #include #include #include namespace { uint16_t u16be(std::span bytes, size_t offset) { return static_cast((static_cast(bytes[offset]) << 8) | bytes[offset + 1]); } uint32_t u32be(std::span bytes, size_t offset) { return (static_cast(bytes[offset]) << 24) | (static_cast(bytes[offset + 1]) << 16) | (static_cast(bytes[offset + 2]) << 8) | static_cast(bytes[offset + 3]); } bool readFile(const std::string& path, std::vector& bytes) { std::ifstream file(path, std::ios::binary); if (!file) return false; file.seekg(0, std::ios::end); const std::streamoff size = file.tellg(); if (size < 0) return false; file.seekg(0, std::ios::beg); bytes.assign(static_cast(size), 0); if (!bytes.empty()) file.read(reinterpret_cast(bytes.data()), size); return static_cast(file) || file.eof(); } bool decodePng(std::span bytes, DdsTexture& out, std::string* error) { out = {}; constexpr uint8_t signature[] = {0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a}; if (bytes.size() < sizeof(signature) || !std::equal(std::begin(signature), std::end(signature), bytes.begin())) { if (error) *error = "not a PNG stream"; return false; } if (bytes.size() > static_cast(std::numeric_limits::max())) { if (error) *error = "PNG stream is too large"; return false; } int width = 0; int height = 0; int sourceChannels = 0; stbi_uc* rgba = stbi_load_from_memory( bytes.data(), static_cast(bytes.size()), &width, &height, &sourceChannels, STBI_rgb_alpha); if (!rgba || width <= 0 || height <= 0) { if (error) { const char* reason = stbi_failure_reason(); *error = reason ? std::string("could not decode PNG: ") + reason : "could not decode PNG"; } stbi_image_free(rgba); return false; } glGenTextures(1, &out.id); glBindTexture(GL_TEXTURE_2D, out.id); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); glBindTexture(GL_TEXTURE_2D, 0); stbi_image_free(rgba); out.width = width; out.height = height; return true; } } // namespace bool loadPngTexture(const std::string& path, DdsTexture& out, std::string* error) { std::vector bytes; if (!readFile(path, bytes)) { if (error) *error = "could not open PNG"; return false; } return decodePng(bytes, out, error); } bool loadPngTextureList(const std::string& path, std::vector& out, std::string* error) { out.clear(); std::vector bytes; if (!readFile(path, bytes) || bytes.size() < 10) { if (error) *error = "could not read texture list"; return false; } const std::span view(bytes); const uint16_t count = u16be(view, 4); if (6u + (static_cast(count) + 1u) * 4u > bytes.size()) { if (error) *error = "invalid texture-list offset table"; return false; } out.assign(count, {}); int loaded = 0; for (uint16_t index = 0; index < count; ++index) { const size_t begin = u32be(view, 6 + static_cast(index) * 4); const size_t end = u32be(view, 10 + static_cast(index) * 4); if (begin == end) continue; if (begin > end || end > bytes.size()) { if (error) *error = "invalid embedded PNG range"; return false; } std::string pngError; if (!decodePng(view.subspan(begin, end - begin), out[index], &pngError)) { if (error) *error = "texture " + std::to_string(index) + ": " + pngError; return false; } ++loaded; } if (loaded == 0) { if (error) *error = "texture list has no PNG entries"; return false; } return true; }