132 lines
4.5 KiB
C++
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;
|
|
}
|