Initial Vectorail Core library
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#include "vectorail/core/PngTexture.hpp"
|
||||
|
||||
#include "vectorail/core/gl_loader.hpp"
|
||||
|
||||
#include <png.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#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();
|
||||
}
|
||||
|
||||
struct PngMemoryReader {
|
||||
std::span<const uint8_t> bytes;
|
||||
size_t offset = 0;
|
||||
};
|
||||
|
||||
void readPngMemory(png_structp png, png_bytep destination, png_size_t length) {
|
||||
auto* reader = static_cast<PngMemoryReader*>(png_get_io_ptr(png));
|
||||
if (!reader || length > reader->bytes.size() - reader->offset) {
|
||||
png_error(png, "truncated PNG stream");
|
||||
return;
|
||||
}
|
||||
std::memcpy(destination, reader->bytes.data() + reader->offset, length);
|
||||
reader->offset += length;
|
||||
}
|
||||
|
||||
bool decodePng(std::span<const uint8_t> bytes, DdsTexture& out, std::string* error) {
|
||||
out = {};
|
||||
if (bytes.size() < 8 || png_sig_cmp(bytes.data(), 0, 8) != 0) {
|
||||
if (error) *error = "not a PNG stream";
|
||||
return false;
|
||||
}
|
||||
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
png_infop info = png ? png_create_info_struct(png) : nullptr;
|
||||
if (!png || !info || setjmp(png_jmpbuf(png))) {
|
||||
if (error) *error = "could not decode PNG";
|
||||
if (png) png_destroy_read_struct(&png, info ? &info : nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
PngMemoryReader reader{bytes};
|
||||
png_set_read_fn(png, &reader, readPngMemory);
|
||||
png_read_info(png, info);
|
||||
const png_uint_32 width = png_get_image_width(png, info);
|
||||
const png_uint_32 height = png_get_image_height(png, info);
|
||||
const int colorType = png_get_color_type(png, info);
|
||||
const int bitDepth = png_get_bit_depth(png, info);
|
||||
if (bitDepth == 16) png_set_strip_16(png);
|
||||
if (colorType == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 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);
|
||||
if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
|
||||
if ((colorType & PNG_COLOR_MASK_ALPHA) == 0) png_set_add_alpha(png, 0xff, PNG_FILLER_AFTER);
|
||||
png_read_update_info(png, info);
|
||||
|
||||
std::vector<uint8_t> rgba(static_cast<size_t>(width) * height * 4);
|
||||
std::vector<png_bytep> rows(height);
|
||||
for (png_uint_32 y = 0; y < height; ++y) rows[y] = rgba.data() + static_cast<size_t>(y) * width * 4;
|
||||
png_read_image(png, rows.data());
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
|
||||
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, static_cast<GLsizei>(width), static_cast<GLsizei>(height),
|
||||
0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
out.width = static_cast<int>(width);
|
||||
out.height = static_cast<int>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user