Merge pull request #384 from MihailRis/in-memory-png-loading

In-memory png loading
This commit is contained in:
MihailRis 2024-11-27 23:37:02 +03:00 committed by GitHub
commit 30eed09224
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 132 additions and 107 deletions

View File

@ -282,7 +282,7 @@ bool AssetsLoader::loadExternalTexture(
for (auto& path : alternatives) { for (auto& path : alternatives) {
if (fs::exists(path)) { if (fs::exists(path)) {
try { try {
auto image = imageio::read(path.string()); auto image = imageio::read(path);
assets->store(Texture::from(image.get()), name); assets->store(Texture::from(image.get()), name);
return true; return true;
} catch (const std::exception& err) { } catch (const std::exception& err) {

View File

@ -50,7 +50,9 @@ assetload::postfunc assetload::texture(
) { ) {
auto actualFile = paths->find(filename + ".png").u8string(); auto actualFile = paths->find(filename + ".png").u8string();
try { try {
std::shared_ptr<ImageData> image(imageio::read(actualFile).release()); std::shared_ptr<ImageData> image(
imageio::read(fs::u8path(actualFile)).release()
);
return [name, image, actualFile](auto assets) { return [name, image, actualFile](auto assets) {
assets->store(Texture::from(image.get()), name); assets->store(Texture::from(image.get()), name);
}; };
@ -96,7 +98,7 @@ static bool append_atlas(AtlasBuilder& atlas, const fs::path& file) {
if (atlas.has(name)) { if (atlas.has(name)) {
return false; return false;
} }
auto image = imageio::read(file.string()); auto image = imageio::read(file);
image->fixAlphaColor(); image->fixAlphaColor();
atlas.add(name, std::move(image)); atlas.add(name, std::move(image));
return true; return true;
@ -150,7 +152,7 @@ assetload::postfunc assetload::font(
std::string pagefile = filename + "_" + std::to_string(i) + ".png"; std::string pagefile = filename + "_" + std::to_string(i) + ".png";
auto file = paths->find(pagefile); auto file = paths->find(pagefile);
if (fs::exists(file)) { if (fs::exists(file)) {
pages->push_back(imageio::read(file.u8string())); pages->push_back(imageio::read(file));
} else if (i == 0) { } else if (i == 0) {
throw std::runtime_error("font must have page 0"); throw std::runtime_error("font must have page 0");
} else { } else {

View File

@ -5,12 +5,13 @@
#include <unordered_map> #include <unordered_map>
#include "graphics/core/ImageData.hpp" #include "graphics/core/ImageData.hpp"
#include "files/files.hpp"
#include "png.hpp" #include "png.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;
using image_reader = using image_reader =
std::function<std::unique_ptr<ImageData>(const std::string&)>; std::function<std::unique_ptr<ImageData>(const ubyte*, size_t)>;
using image_writer = std::function<void(const std::string&, const ImageData*)>; using image_writer = std::function<void(const std::string&, const ImageData*)>;
static std::unordered_map<std::string, image_reader> readers { static std::unordered_map<std::string, image_reader> readers {
@ -33,14 +34,21 @@ inline std::string extensionOf(const std::string& filename) {
return fs::u8path(filename).extension().u8string(); return fs::u8path(filename).extension().u8string();
} }
std::unique_ptr<ImageData> imageio::read(const std::string& filename) { std::unique_ptr<ImageData> imageio::read(const fs::path& filename) {
auto found = readers.find(extensionOf(filename)); auto found = readers.find(extensionOf(filename.u8string()));
if (found == readers.end()) { if (found == readers.end()) {
throw std::runtime_error( throw std::runtime_error(
"file format is not supported (read): " + filename "file format is not supported (read): " + filename.u8string()
);
}
auto bytes = files::read_bytes_buffer(filename);
try {
return std::unique_ptr<ImageData>(found->second(bytes.data(), bytes.size()));
} catch (const std::runtime_error& err) {
throw std::runtime_error(
"could not to load image " + filename.u8string() + ": " + err.what()
); );
} }
return std::unique_ptr<ImageData>(found->second(filename));
} }
void imageio::write(const std::string& filename, const ImageData* image) { void imageio::write(const std::string& filename, const ImageData* image) {

View File

@ -2,6 +2,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <filesystem>
class ImageData; class ImageData;
@ -11,6 +12,6 @@ namespace imageio {
bool is_read_supported(const std::string& extension); bool is_read_supported(const std::string& extension);
bool is_write_supported(const std::string& extension); bool is_write_supported(const std::string& extension);
std::unique_ptr<ImageData> read(const std::string& filename); std::unique_ptr<ImageData> read(const std::filesystem::path& file);
void write(const std::string& filename, const ImageData* image); void write(const std::string& filename, const ImageData* image);
} }

View File

@ -92,82 +92,99 @@ int _png_write(
return 0; return 0;
} }
std::unique_ptr<ImageData> _png_load(const char* file) { struct InMemoryReader {
FILE* fp = nullptr; const ubyte* bytes;
if ((fp = fopen(file, "rb")) == nullptr) { size_t size;
return nullptr; size_t offset;
};
static void read_in_memory(png_structp pngPtr, png_bytep dst, png_size_t toread) {
png_voidp ioPtr = png_get_io_ptr(pngPtr);
if (ioPtr == nullptr) {
throw std::runtime_error("png_get_io_ptr(...) -> NULL");
} }
png_struct* png = png_create_read_struct( auto& reader = *reinterpret_cast<InMemoryReader*>(ioPtr);
PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr if (reader.offset + toread > reader.size) {
throw std::runtime_error("buffer underflow");
}
std::memcpy(dst, reader.bytes + reader.offset, toread);
reader.offset += toread;
}
std::unique_ptr<ImageData> png::load_image(const ubyte* bytes, size_t size) {
if (!png_check_sig(bytes, size)) {
throw std::runtime_error("invalid png signature");
}
png_structp pngPtr = nullptr;
pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (pngPtr == nullptr) {
throw std::runtime_error("failed png_create_read_struct");
}
png_infop infoPtr = nullptr;
infoPtr = png_create_info_struct(pngPtr);
if(infoPtr == nullptr) {
png_destroy_read_struct(&pngPtr, nullptr, nullptr);
throw std::runtime_error("failed png_create_info_struct");
}
InMemoryReader reader {bytes, size, 0};
png_set_read_fn(pngPtr, &reader, read_in_memory);
png_read_info(pngPtr, infoPtr);
png_uint_32 width = 0;
png_uint_32 height = 0;
int bitDepth = 0;
int colorType = -1;
png_uint_32 retval = png_get_IHDR(pngPtr, infoPtr,
&width,
&height,
&bitDepth,
&colorType,
nullptr, nullptr, nullptr
); );
if (png == nullptr) { if (retval != 1) {
fclose(fp); png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
return nullptr; throw std::runtime_error("failed png_get_IHDR");
} }
png_info* info = png_create_info_struct(png); if (bitDepth == 16) png_set_strip_16(pngPtr);
if (info == nullptr) {
png_destroy_read_struct(&png, (png_info**)nullptr, (png_info**)nullptr); if (colorType == PNG_COLOR_TYPE_PALETTE) {
fclose(fp); png_set_palette_to_rgb(pngPtr);
return nullptr;
} }
png_info* end_info = png_create_info_struct(png); if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
if (end_info == nullptr) { png_set_expand_gray_1_2_4_to_8(pngPtr);
png_destroy_read_struct(&png, (png_info**)nullptr, (png_info**)nullptr);
fclose(fp);
return nullptr;
} }
if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
if (setjmp(png_jmpbuf(png))) { png_set_tRNS_to_alpha(pngPtr);
png_destroy_read_struct(&png, &info, &end_info);
fclose(fp);
return nullptr;
} }
png_init_io(png, fp);
png_read_info(png, info);
int width = png_get_image_width(png, info);
int height = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
int bit_depth = png_get_bit_depth(png, info);
if (bit_depth == 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 && bit_depth < 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);
// These color_type don't have an alpha channel then fill it with 0xff. // These color_type don't have an alpha channel then fill it with 0xff.
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE) colorType == PNG_COLOR_TYPE_PALETTE) {
png_set_filler(png, 0xFF, PNG_FILLER_AFTER); png_set_filler(pngPtr, 0xFF, PNG_FILLER_AFTER);
}
if (colorType == PNG_COLOR_TYPE_GRAY ||
colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
png_set_gray_to_rgb(pngPtr);
}
png_read_update_info(pngPtr, infoPtr);
if (color_type == PNG_COLOR_TYPE_GRAY || int rowBytes = png_get_rowbytes(pngPtr, infoPtr);
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
int row_bytes = png_get_rowbytes(png, info);
// color_type = png_get_color_type(png, info);
// png_get_color_type returns 2 (RGB) but raster always have alpha channel // png_get_color_type returns 2 (RGB) but raster always have alpha channel
// due to PNG_FILLER_AFTER // due to PNG_FILLER_AFTER
colorType = 6;
bitDepth = png_get_bit_depth(pngPtr, infoPtr);
color_type = 6; auto imageData = std::make_unique<png_byte[]>(rowBytes * height);
bit_depth = png_get_bit_depth(png, info); auto rowPointers = std::make_unique<png_byte*[]>(height);
auto image_data = std::make_unique<png_byte[]>(row_bytes * height);
auto row_pointers = std::make_unique<png_byte*[]>(height);
for (int i = 0; i < height; ++i) { for (int i = 0; i < height; ++i) {
row_pointers[height - 1 - i] = image_data.get() + i * row_bytes; rowPointers[height - 1 - i] = imageData.get() + i * rowBytes;
} }
png_read_image(png, row_pointers.get()); png_read_image(pngPtr, rowPointers.get());
ImageFormat format = ImageFormat::rgba8888; ImageFormat format = ImageFormat::rgba8888;
switch (color_type) { switch (colorType) {
case PNG_COLOR_TYPE_RGBA: case PNG_COLOR_TYPE_RGBA:
format = ImageFormat::rgba8888; format = ImageFormat::rgba8888;
break; break;
@ -175,35 +192,36 @@ std::unique_ptr<ImageData> _png_load(const char* file) {
format = ImageFormat::rgb888; format = ImageFormat::rgb888;
break; break;
default: default:
logger.error() << "color type " << color_type png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
<< " is not supported!"; throw std::runtime_error(
png_destroy_read_struct(&png, &info, &end_info); "color type " + std::to_string(colorType) + " is not supported!"
fclose(fp); );
return nullptr;
} }
auto image = std::make_unique<ImageData>( auto image = std::make_unique<ImageData>(
format, width, height, std::move(image_data) format, width, height, std::move(imageData)
); );
png_destroy_read_struct(&png, &info, &end_info); png_destroy_read_struct(&pngPtr, &infoPtr, nullptr);
fclose(fp);
return image; return image;
} }
std::unique_ptr<ImageData> png::load_image(const std::string& filename) { std::unique_ptr<Texture> png::load_texture(const ubyte* bytes, size_t size) {
auto image = _png_load(filename.c_str()); auto image = load_image(bytes, size);
if (image == nullptr) {
throw std::runtime_error("could not load image " + filename);
}
return image;
}
std::unique_ptr<Texture> png::load_texture(const std::string& filename) {
auto image = load_image(filename);
auto texture = GLTexture::from(image.get()); auto texture = GLTexture::from(image.get());
texture->setNearestFilter(); texture->setNearestFilter();
return texture; return texture;
} }
std::unique_ptr<Texture> png::load_texture(const std::string& filename) {
auto bytes = files::read_bytes_buffer(fs::u8path(filename));
try {
return load_texture(bytes.data(), bytes.size());
} catch (const std::runtime_error& err) {
throw std::runtime_error(
"could not to load " + filename + ": " + err.what()
);
}
}
void png::write_image(const std::string& filename, const ImageData* image) { void png::write_image(const std::string& filename, const ImageData* image) {
_png_write( _png_write(
filename.c_str(), filename.c_str(),

View File

@ -3,11 +3,14 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include "typedefs.hpp"
class Texture; class Texture;
class ImageData; class ImageData;
namespace png { namespace png {
std::unique_ptr<ImageData> load_image(const std::string& filename); std::unique_ptr<ImageData> load_image(const ubyte* bytes, size_t size);
void write_image(const std::string& filename, const ImageData* image); void write_image(const std::string& filename, const ImageData* image);
std::unique_ptr<Texture> load_texture(const ubyte* bytes, size_t size);
std::unique_ptr<Texture> load_texture(const std::string& filename); std::unique_ptr<Texture> load_texture(const std::string& filename);
} }

View File

@ -62,7 +62,7 @@ static std::unique_ptr<ImageData> load_icon(const fs::path& resdir) {
try { try {
auto file = resdir / fs::u8path("textures/misc/icon.png"); auto file = resdir / fs::u8path("textures/misc/icon.png");
if (fs::exists(file)) { if (fs::exists(file)) {
return imageio::read(file.u8string()); return imageio::read(file);
} }
} catch (const std::exception& err) { } catch (const std::exception& err) {
logger.error() << "could not load window icon: " << err.what(); logger.error() << "could not load window icon: " << err.what();

View File

@ -1,22 +1,25 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "api_lua.hpp"
#include "coders/png.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "engine.hpp"
#include "content/Content.hpp" #include "content/Content.hpp"
#include "debug/Logger.hpp"
#include "engine.hpp"
#include "files/engine_paths.hpp" #include "files/engine_paths.hpp"
#include "files/files.hpp"
#include "files/settings_io.hpp" #include "files/settings_io.hpp"
#include "frontend/menu.hpp" #include "frontend/menu.hpp"
#include "frontend/screens/MenuScreen.hpp" #include "frontend/screens/MenuScreen.hpp"
#include "graphics/core/Texture.hpp"
#include "logic/EngineController.hpp" #include "logic/EngineController.hpp"
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "util/listutil.hpp"
#include "window/Events.hpp" #include "window/Events.hpp"
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/generator/WorldGenerator.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "util/listutil.hpp" #include "world/generator/WorldGenerator.hpp"
#include "api_lua.hpp"
using namespace scripting; using namespace scripting;
@ -187,24 +190,14 @@ static int l_get_setting_info(lua::State* L) {
throw std::runtime_error("unsupported setting type"); throw std::runtime_error("unsupported setting type");
} }
#include "coders/png.hpp"
#include "debug/Logger.hpp"
#include "files/files.hpp"
#include "graphics/core/Texture.hpp"
/// FIXME: replace with in-memory implementation
static void load_texture( static void load_texture(
const ubyte* bytes, size_t size, const std::string& destname const ubyte* bytes, size_t size, const std::string& destname
) { ) {
auto path = engine->getPaths()->resolve("export:.__vc_imagedata");
try { try {
files::write_bytes(path, bytes, size); engine->getAssets()->store(png::load_texture(bytes, size), destname);
engine->getAssets()->store(png::load_texture(path.u8string()), destname);
std::filesystem::remove(path);
} catch (const std::runtime_error& err) { } catch (const std::runtime_error& err) {
debug::Logger logger("lua.corelib"); debug::Logger logger("lua.corelib");
logger.error() << "could not to decode image: " << err.what(); logger.error() << err.what();
} }
} }