Files
vectorail-core/src/PngTexture.cpp
T

132 lines
4.5 KiB
C++

#include "vectorail/core/PngTexture.hpp"
#include "vectorail/core/gl_loader.hpp"
#define STBI_ONLY_PNG
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <algorithm>
#include <fstream>
#include <limits>
#include <span>
#include <vector>
namespace {
uint16_t u16be(std::span<const uint8_t> bytes, size_t offset) {
return static_cast<uint16_t>((static_cast<uint16_t>(bytes[offset]) << 8) | bytes[offset + 1]);
}
uint32_t u32be(std::span<const uint8_t> bytes, size_t offset) {
return (static_cast<uint32_t>(bytes[offset]) << 24) |
(static_cast<uint32_t>(bytes[offset + 1]) << 16) |
(static_cast<uint32_t>(bytes[offset + 2]) << 8) |
static_cast<uint32_t>(bytes[offset + 3]);
}
bool readFile(const std::string& path, std::vector<uint8_t>& 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_t>(size), 0);
if (!bytes.empty()) file.read(reinterpret_cast<char*>(bytes.data()), size);
return static_cast<bool>(file) || file.eof();
}
bool decodePng(std::span<const uint8_t> 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<size_t>(std::numeric_limits<int>::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<int>(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<uint8_t> 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<DdsTexture>& out, std::string* error) {
out.clear();
std::vector<uint8_t> bytes;
if (!readFile(path, bytes) || bytes.size() < 10) {
if (error) *error = "could not read texture list";
return false;
}
const std::span<const uint8_t> view(bytes);
const uint16_t count = u16be(view, 4);
if (6u + (static_cast<size_t>(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<size_t>(index) * 4);
const size_t end = u32be(view, 10 + static_cast<size_t>(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;
}