diff --git a/README.md b/README.md index a12ff47d8..8e51e8666 100644 --- a/README.md +++ b/README.md @@ -219,11 +219,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one - vtfpp + vtfpp APNG ✅ ❌ - Python + Python @@ -286,6 +286,12 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ❌ + + SHT v0-1 + ✅ + ✅ + + TGA ✅ diff --git a/docs/index.md b/docs/index.md index b65905b75..08f1a924b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -191,11 +191,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ - vtfpp + vtfpp APNG ✅ ❌ - Python + Python BMP @@ -247,6 +247,11 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one ✅ ❌ + + SHT v0-1 + ✅ + ✅ + TGA ✅ diff --git a/include/vtfpp/ImageConversion.h b/include/vtfpp/ImageConversion.h index 2ce55881f..9c1be6f31 100644 --- a/include/vtfpp/ImageConversion.h +++ b/include/vtfpp/ImageConversion.h @@ -387,6 +387,9 @@ void setResizedDims(uint16_t& width, ResizeMethod widthResize, uint16_t& height, /// Resize given image data to the new dimensions, where the new width and height are governed by the resize methods [[nodiscard]] std::vector resizeImageDataStrict(std::span imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t& widthOut, ResizeMethod widthResize, uint16_t height, uint16_t newHeight, uint16_t& heightOut, ResizeMethod heightResize, bool srgb, ResizeFilter filter, ResizeEdge edge = ResizeEdge::CLAMP); +/// Crops the given image to the new dimensions. If the image format is compressed it will be converted to its container format before the crop, and converted back before returning +[[nodiscard]] std::vector cropImageData(std::span imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset); + /// Extracts a single channel from the given image data. /// May have unexpected behavior if called on formats that use bitfields like BGRA5551! /// Data is packed according to pixel channel C++ type size diff --git a/include/vtfpp/SHT.h b/include/vtfpp/SHT.h new file mode 100644 index 000000000..7155f727f --- /dev/null +++ b/include/vtfpp/SHT.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace vtfpp { + +class SHT { +public: + struct Sequence { + struct Frame { + struct Bounds { + float x1; + float y1; + float x2; + float y2; + }; + + float duration; + // A sprite frame can reference 1 or 4 images, depending on the version + std::array bounds; + + void setAllBounds(Bounds newBounds) { + this->bounds[0] = newBounds; + this->bounds[1] = newBounds; + this->bounds[2] = newBounds; + this->bounds[3] = newBounds; + } + }; + + uint32_t id; + bool loop; + std::vector frames; + float durationTotal; + }; + + SHT(); + + explicit SHT(std::span shtData); + + explicit SHT(const std::string& shtPath); + + [[nodiscard]] explicit operator bool() const; + + [[nodiscard]] uint32_t getVersion() const; + + void setVersion(uint32_t v); + + [[nodiscard]] const std::vector& getSequences() const; + + [[nodiscard]] std::vector& getSequences(); + + [[nodiscard]] const Sequence* getSequenceFromID(uint32_t id) const; + + [[nodiscard]] Sequence* getSequenceFromID(uint32_t id); + + [[nodiscard]] uint8_t getFrameBoundsCount() const; + + [[nodiscard]] std::vector bake() const; + + bool bake(const std::string& shtPath) const; // NOLINT(*-use-nodiscard) + +protected: + bool opened; + + uint32_t version; + std::vector sequences; +}; + +} // namespace vtfpp diff --git a/include/vtfpp/VTF.h b/include/vtfpp/VTF.h index 7ea7132f7..5deb09ef7 100644 --- a/include/vtfpp/VTF.h +++ b/include/vtfpp/VTF.h @@ -13,6 +13,7 @@ #include #include "ImageConversion.h" +#include "SHT.h" namespace vtfpp { @@ -48,6 +49,7 @@ struct Resource { using ConvertedData = std::variant< std::monostate, // Anything that would be equivalent to just returning data directly, or used as an error + SHT, // Particle Sheet uint32_t, // CRC, TSO std::pair, // LOD std::string, // KVD @@ -55,6 +57,10 @@ struct Resource { >; [[nodiscard]] ConvertedData convertData() const; + [[nodiscard]] SHT getDataAsParticleSheet() const { + return std::get(this->convertData()); + } + [[nodiscard]] uint32_t getDataAsCRC() const { return std::get(this->convertData()); } @@ -296,7 +302,16 @@ class VTF { [[nodiscard]] const Resource* getResource(Resource::Type type) const; - void setParticleSheetResource(std::span value); + /// This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV there if trying to render a particle + [[nodiscard]] std::vector getParticleSheetFrameDataRaw(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const; + + /// This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV there if trying to render a particle + [[nodiscard]] std::vector getParticleSheetFrameDataAs(ImageFormat newFormat, uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const; + + /// This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV there if trying to render a particle + [[nodiscard]] std::vector getParticleSheetFrameDataAsRGBA8888(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const; + + void setParticleSheetResource(const SHT& value); void removeParticleSheetResource(); diff --git a/include/vtfpp/vtfpp.h b/include/vtfpp/vtfpp.h index 274ab6625..8c4dacada 100644 --- a/include/vtfpp/vtfpp.h +++ b/include/vtfpp/vtfpp.h @@ -8,4 +8,5 @@ #include "ImageConversion.h" #include "ImageFormats.h" #include "PPL.h" +#include "SHT.h" #include "VTF.h" diff --git a/lang/python/src/gamepp.h b/lang/python/src/gamepp.h index 5353c4134..a3b6f587e 100644 --- a/lang/python/src/gamepp.h +++ b/lang/python/src/gamepp.h @@ -16,16 +16,16 @@ inline void register_python(py::module_& m) { using namespace gamepp; py::class_(gamepp, "GameInstance") - .def_static("find", &GameInstance::find, py::arg("window_name_override") = "") - .def_prop_ro("window_title", &GameInstance::getWindowTitle) - .def_prop_ro("window_pos", &GameInstance::getWindowPos) - .def_prop_ro("window_size", &GameInstance::getWindowSize) - .def("command", &GameInstance::command, py::arg("command"), py::rv_policy::reference) - .def("input_begin", &GameInstance::inputBegin, py::arg("input"), py::rv_policy::reference) - .def("input_end", &GameInstance::inputEnd, py::arg("input"), py::rv_policy::reference) - .def("input_once", &GameInstance::inputOnce, py::arg("input"), py::rv_policy::reference) - .def("input_hold", &GameInstance::inputHold, py::arg("input"), py::arg("sec"), py::rv_policy::reference) - .def("wait", &GameInstance::wait, py::arg("sec"), py::rv_policy::reference); + .def_static("find", &GameInstance::find, py::arg("window_name_override") = "") + .def_prop_ro("window_title", &GameInstance::getWindowTitle) + .def_prop_ro("window_pos", &GameInstance::getWindowPos) + .def_prop_ro("window_size", &GameInstance::getWindowSize) + .def("command", &GameInstance::command, py::arg("command"), py::rv_policy::reference) + .def("input_begin", &GameInstance::inputBegin, py::arg("input"), py::rv_policy::reference) + .def("input_end", &GameInstance::inputEnd, py::arg("input"), py::rv_policy::reference) + .def("input_once", &GameInstance::inputOnce, py::arg("input"), py::rv_policy::reference) + .def("input_hold", &GameInstance::inputHold, py::arg("input"), py::arg("sec"), py::rv_policy::reference) + .def("wait", &GameInstance::wait, py::arg("sec"), py::rv_policy::reference); } } // namespace gamepp diff --git a/lang/python/src/sourcepp.h b/lang/python/src/sourcepp.h index 079c37ae0..8178ee745 100644 --- a/lang/python/src/sourcepp.h +++ b/lang/python/src/sourcepp.h @@ -21,11 +21,11 @@ inline void register_python(py::module_& m) { const auto registerVecType = [&math](std::string_view name) { py::class_(math, name.data()) - .def("__len__", &V::size) - .def("__setitem__", [](V& self, uint8_t index, typename V::value_type val) { self[index] = val; }) - .def("__getitem__", [](V& self, uint8_t index) { return self[index]; }) - .def_static("zero", &V::zero) - .def("is_zero", &V::isZero); + .def("__len__", &V::size) + .def("__setitem__", [](V& self, uint8_t index, typename V::value_type val) { self[index] = val; }) + .def("__getitem__", [](V& self, uint8_t index) { return self[index]; }) + .def_static("zero", &V::zero) + .def("is_zero", &V::isZero); }; registerVecType.operator()("Vec2i8"); diff --git a/lang/python/src/steampp.h b/lang/python/src/steampp.h index bd44ebfd3..e6dbe4186 100644 --- a/lang/python/src/steampp.h +++ b/lang/python/src/steampp.h @@ -16,21 +16,21 @@ inline void register_python(py::module_& m) { using namespace steampp; py::class_(steampp, "Steam") - .def(py::init<>()) - .def_prop_ro("install_dir", &Steam::getInstallDir) - .def_prop_ro("library_dirs", &Steam::getLibraryDirs) - .def_prop_ro("sourcemod_dir", &Steam::getSourceModDir) - .def_prop_ro("installed_apps", &Steam::getInstalledApps) - .def("is_app_installed", &Steam::isAppInstalled, py::arg("appID")) - .def("get_app_name", &Steam::getAppName, py::arg("appID")) - .def("get_app_install_dir", &Steam::getAppInstallDir, py::arg("appID")) - .def("get_app_icon_path", &Steam::getAppIconPath, py::arg("appID")) - .def("get_app_logo_path", &Steam::getAppLogoPath, py::arg("appID")) - .def("get_app_box_art_path", &Steam::getAppBoxArtPath, py::arg("appID")) - .def("get_app_store_art_path", &Steam::getAppStoreArtPath, py::arg("appID")) - .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine, py::arg("appID")) - .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine, py::arg("appID")) - .def("__bool__", &Steam::operator bool, py::is_operator()); + .def(py::init<>()) + .def_prop_ro("install_dir", &Steam::getInstallDir) + .def_prop_ro("library_dirs", &Steam::getLibraryDirs) + .def_prop_ro("sourcemod_dir", &Steam::getSourceModDir) + .def_prop_ro("installed_apps", &Steam::getInstalledApps) + .def("is_app_installed", &Steam::isAppInstalled, py::arg("appID")) + .def("get_app_name", &Steam::getAppName, py::arg("appID")) + .def("get_app_install_dir", &Steam::getAppInstallDir, py::arg("appID")) + .def("get_app_icon_path", &Steam::getAppIconPath, py::arg("appID")) + .def("get_app_logo_path", &Steam::getAppLogoPath, py::arg("appID")) + .def("get_app_box_art_path", &Steam::getAppBoxArtPath, py::arg("appID")) + .def("get_app_store_art_path", &Steam::getAppStoreArtPath, py::arg("appID")) + .def("is_app_using_source_engine", &Steam::isAppUsingSourceEngine, py::arg("appID")) + .def("is_app_using_source_2_engine", &Steam::isAppUsingSource2Engine, py::arg("appID")) + .def("__bool__", &Steam::operator bool, py::is_operator()); } } // namespace steampp diff --git a/lang/python/src/toolpp.h b/lang/python/src/toolpp.h index a81a303cb..22bbfc228 100644 --- a/lang/python/src/toolpp.h +++ b/lang/python/src/toolpp.h @@ -16,154 +16,168 @@ inline void register_python(py::module_& m) { auto toolpp = m.def_submodule("toolpp"); using namespace toolpp; - py::enum_(toolpp, "CmdSeqCommandSpecial") - .value("NONE", CmdSeq::Command::Special::NONE) - .value("CHANGE_DIRECTORY", CmdSeq::Command::Special::CHANGE_DIRECTORY) - .value("COPY_FILE", CmdSeq::Command::Special::COPY_FILE) - .value("DELETE_FILE", CmdSeq::Command::Special::DELETE_FILE) - .value("RENAME_FILE", CmdSeq::Command::Special::RENAME_FILE) - .value("COPY_FILE_IF_EXISTS_ALIAS", CmdSeq::Command::SPECIAL_COPY_FILE_IF_EXISTS_ALIAS) - .value("COPY_FILE_IF_EXISTS", CmdSeq::Command::Special::COPY_FILE_IF_EXISTS) - .export_values(); - - py::class_(toolpp, "CmdSeqCommand") - .def_rw("enabled", &CmdSeq::Command::enabled) - .def_rw("executable", &CmdSeq::Command::executable) - .def_rw("arguments", &CmdSeq::Command::arguments) - .def_rw("ensure_file_exists", &CmdSeq::Command::ensureFileExists) - .def_rw("path_to_theoretically_existing_file", &CmdSeq::Command::pathToTheoreticallyExistingFile) - .def_rw("use_process_window", &CmdSeq::Command::useProcessWindow) - .def_rw("wait_for_keypress", &CmdSeq::Command::waitForKeypress) - .def_static("get_special_display_name_for", &CmdSeq::Command::getSpecialDisplayNameFor, py::arg("special")) - .def("get_executable_display_name", &CmdSeq::Command::getExecutableDisplayName); - - py::class_(toolpp, "CmdSeqSequence") - .def_rw("name", &CmdSeq::Sequence::name) - .def_rw("commands", &CmdSeq::Sequence::commands); - - py::enum_(toolpp, "CmdSeqType") - .value("INVALID", CmdSeq::Type::INVALID) - .value("BINARY", CmdSeq::Type::BINARY) - .value("KEYVALUES_STRATA", CmdSeq::Type::KEYVALUES_STRATA) - .export_values(); - - py::class_(toolpp, "CmdSeq") - .def(py::init(), py::arg("path")) - .def(py::init(), py::arg("type")) - .def("__bool__", &CmdSeq::operator bool, py::is_operator()) - .def_prop_rw("type", &CmdSeq::getType, &CmdSeq::setType) - .def_prop_ro("version", &CmdSeq::getVersion) - .def("set_version", &CmdSeq::setVersion, py::arg("is_v02")) - .def("sequences", py::overload_cast<>(&CmdSeq::getSequences), py::rv_policy::reference_internal) - .def("bake", [](const CmdSeq& self) { - const auto d = self.bake(); - return py::bytes{d.data(), d.size()}; - }) - .def("bake_to_file", py::overload_cast(&CmdSeq::bake, py::const_), py::arg("path")); - - py::class_(toolpp, "FGDEntityClassProperty") - .def_ro("name", &FGD::Entity::ClassProperty::name) - .def_ro("arguments", &FGD::Entity::ClassProperty::arguments); - - py::class_(toolpp, "FGDEntityField") - .def_ro("name", &FGD::Entity::Field::name) - .def_ro("value_type", &FGD::Entity::Field::valueType) - .def_ro("readonly", &FGD::Entity::Field::readonly) - .def_ro("reportable", &FGD::Entity::Field::reportable) - .def_ro("display_name", &FGD::Entity::Field::displayName) - .def_ro("value_default", &FGD::Entity::Field::valueDefault) - .def_ro("description", &FGD::Entity::Field::description); - - py::class_(toolpp, "FGDEntityFieldChoicesChoice") - .def_ro("value", &FGD::Entity::FieldChoices::Choice::value) - .def_ro("display_name", &FGD::Entity::FieldChoices::Choice::displayName); - - py::class_(toolpp, "FGDEntityFieldChoices") - .def_ro("name", &FGD::Entity::FieldChoices::name) - .def_ro("readonly", &FGD::Entity::FieldChoices::readonly) - .def_ro("reportable", &FGD::Entity::FieldChoices::reportable) - .def_ro("display_name", &FGD::Entity::FieldChoices::displayName) - .def_ro("value_default", &FGD::Entity::FieldChoices::valueDefault) - .def_ro("description", &FGD::Entity::FieldChoices::description) - .def_ro("choices", &FGD::Entity::FieldChoices::choices); - - py::class_(toolpp, "FGDEntityFieldFlagsFlag") - .def_ro("value", &FGD::Entity::FieldFlags::Flag::value) - .def_ro("display_name", &FGD::Entity::FieldFlags::Flag::displayName) - .def_ro("enabled_by_default", &FGD::Entity::FieldFlags::Flag::enabledByDefault) - .def_ro("description", &FGD::Entity::FieldFlags::Flag::description); - - py::class_(toolpp, "FGDEntityFieldFlags") - .def_ro("name", &FGD::Entity::FieldFlags::name) - .def_ro("readonly", &FGD::Entity::FieldFlags::readonly) - .def_ro("reportable", &FGD::Entity::FieldFlags::reportable) - .def_ro("display_name", &FGD::Entity::FieldFlags::displayName) - .def_ro("description", &FGD::Entity::FieldFlags::description) - .def_ro("flags", &FGD::Entity::FieldFlags::flags); - - py::class_(toolpp, "FGDEntityIO") - .def_ro("name", &FGD::Entity::IO::name) - .def_ro("value_type", &FGD::Entity::IO::valueType) - .def_ro("description", &FGD::Entity::IO::description); - - py::class_(toolpp, "FGDEntity") - .def_ro("class_type", &FGD::Entity::classType) - .def_ro("class_properties", &FGD::Entity::classProperties) - .def_ro("description", &FGD::Entity::description) - .def_ro("fields", &FGD::Entity::fields) - .def_ro("fields_with_choices", &FGD::Entity::fieldsWithChoices) - .def_ro("fields_with_flags", &FGD::Entity::fieldsWithFlags) - .def_ro("inputs", &FGD::Entity::inputs) - .def_ro("outputs", &FGD::Entity::outputs); - - py::class_(toolpp, "FGDAutoVisGroup") - .def_ro("parent_name", &FGD::AutoVisGroup::parentName) - .def_ro("name", &FGD::AutoVisGroup::name) - .def_ro("entities", &FGD::AutoVisGroup::entities); - - py::class_(toolpp, "FGD") - .def(py::init<>()) - .def(py::init(), py::arg("fgd_path")) - .def("load", &FGD::load, py::arg("fgd_path")) - .def_prop_ro("version", &FGD::getVersion) - .def_prop_ro("map_size", &FGD::getMapSize) - .def_prop_ro("entities", &FGD::getEntities) - .def_prop_ro("material_exclusion_dirs", &FGD::getMaterialExclusionDirs) - .def_prop_ro("auto_visgroups", &FGD::getAutoVisGroups); - - py::class_(toolpp, "FGDWriterAutoVisGroupWriter") - .def("visgroup", &FGDWriter::AutoVisGroupWriter::visGroup, py::arg("name"), py::arg("entities"), py::rv_policy::reference) - .def("end_auto_visgroup", &FGDWriter::AutoVisGroupWriter::endAutoVisGroup, py::rv_policy::reference); - - py::class_(toolpp, "FGDWriterEntityWriterKeyValueChoicesWriter") - .def("choice", &FGDWriter::EntityWriter::KeyValueChoicesWriter::choice, py::arg("value"), py::arg("display_name"), py::rv_policy::reference) - .def("end_key_value_choices", &FGDWriter::EntityWriter::KeyValueChoicesWriter::endKeyValueChoices, py::rv_policy::reference); - - py::class_(toolpp, "FGDWriterEntityWriterKeyValueFlagsWriter") - .def("flag", &FGDWriter::EntityWriter::KeyValueFlagsWriter::flag, py::arg("value"), py::arg("display_name"), py::arg("enabled_by_default"), py::arg("description") = "", py::rv_policy::reference) - .def("end_key_value_flags", &FGDWriter::EntityWriter::KeyValueFlagsWriter::endKeyValueFlags, py::rv_policy::reference); - - py::class_(toolpp, "FGDWriterEntityWriter") - .def("key_value", &FGDWriter::EntityWriter::keyValue, py::arg("name"), py::arg("value_type"), py::arg("display_name") = "", py::arg("value_default") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false, py::rv_policy::reference) - .def("begin_key_value_choices", &FGDWriter::EntityWriter::beginKeyValueChoices, py::arg("name"), py::arg("display_name") = "", py::arg("value_default") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false) - .def("begin_key_value_flags", &FGDWriter::EntityWriter::beginKeyValueFlags, py::arg("name"), py::arg("display_name") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false) - .def("input", &FGDWriter::EntityWriter::input, py::arg("name"), py::arg("value_type"), py::arg("description") = "", py::rv_policy::reference) - .def("output", &FGDWriter::EntityWriter::output, py::arg("name"), py::arg("value_type"), py::arg("description") = "", py::rv_policy::reference) - .def("end_entity", &FGDWriter::EntityWriter::endEntity, py::rv_policy::reference); - - py::class_(toolpp, "FGDWriter") - .def_static("begin", &FGDWriter::begin) - .def("include", &FGDWriter::include, py::arg("fgd_path"), py::rv_policy::reference) - .def("version", &FGDWriter::version, py::arg("version"), py::rv_policy::reference) - .def("map_size", &FGDWriter::mapSize, py::arg("map_size"), py::rv_policy::reference) - .def("material_exclusion_dirs", &FGDWriter::materialExclusionDirs, py::arg("material_exclusion_dirs"), py::rv_policy::reference) - .def("begin_auto_visgroup", &FGDWriter::beginAutoVisGroup, py::arg("parent_name")) - .def("begin_entity", &FGDWriter::beginEntity, py::arg("class_type"), py::arg("class_properties"), py::arg("name"), py::arg("description") = "") - .def("bake", [](const FGDWriter& self) { - const auto d = self.bake(); - return py::bytes{d.data(), d.size()}; - }) - .def("bake_to_file", py::overload_cast(&FGDWriter::bake, py::const_), py::arg("path")); + auto cCmdSeq = py::class_(toolpp, "CmdSeq"); + auto cCmdSeqCommand = py::class_(cCmdSeq, "Command"); + + py::enum_(cCmdSeqCommand, "Special") + .value("NONE", CmdSeq::Command::Special::NONE) + .value("CHANGE_DIRECTORY", CmdSeq::Command::Special::CHANGE_DIRECTORY) + .value("COPY_FILE", CmdSeq::Command::Special::COPY_FILE) + .value("DELETE_FILE", CmdSeq::Command::Special::DELETE_FILE) + .value("RENAME_FILE", CmdSeq::Command::Special::RENAME_FILE) + .value("COPY_FILE_IF_EXISTS_ALIAS", CmdSeq::Command::SPECIAL_COPY_FILE_IF_EXISTS_ALIAS) + .value("COPY_FILE_IF_EXISTS", CmdSeq::Command::Special::COPY_FILE_IF_EXISTS) + .export_values(); + + cCmdSeqCommand + .def_rw("enabled", &CmdSeq::Command::enabled) + .def_rw("executable", &CmdSeq::Command::executable) + .def_rw("arguments", &CmdSeq::Command::arguments) + .def_rw("ensure_file_exists", &CmdSeq::Command::ensureFileExists) + .def_rw("path_to_theoretically_existing_file", &CmdSeq::Command::pathToTheoreticallyExistingFile) + .def_rw("use_process_window", &CmdSeq::Command::useProcessWindow) + .def_rw("wait_for_keypress", &CmdSeq::Command::waitForKeypress) + .def_static("get_special_display_name_for", &CmdSeq::Command::getSpecialDisplayNameFor, py::arg("special")) + .def("get_executable_display_name", &CmdSeq::Command::getExecutableDisplayName); + + py::class_(cCmdSeq, "Sequence") + .def_rw("name", &CmdSeq::Sequence::name) + .def_rw("commands", &CmdSeq::Sequence::commands); + + py::enum_(cCmdSeq, "Type") + .value("INVALID", CmdSeq::Type::INVALID) + .value("BINARY", CmdSeq::Type::BINARY) + .value("KEYVALUES_STRATA", CmdSeq::Type::KEYVALUES_STRATA) + .export_values(); + + cCmdSeq + .def(py::init(), py::arg("path")) + .def(py::init(), py::arg("type")) + .def("__bool__", &CmdSeq::operator bool, py::is_operator()) + .def_prop_rw("type", &CmdSeq::getType, &CmdSeq::setType) + .def_prop_ro("version", &CmdSeq::getVersion) + .def("set_version", &CmdSeq::setVersion, py::arg("is_v02")) + .def("sequences", py::overload_cast<>(&CmdSeq::getSequences), py::rv_policy::reference_internal) + .def("bake", [](const CmdSeq& self) { + const auto d = self.bake(); + return py::bytes{d.data(), d.size()}; + }) + .def("bake_to_file", py::overload_cast(&CmdSeq::bake, py::const_), py::arg("path")); + + auto cFGD = py::class_(toolpp, "FGD"); + auto cFGDEntity = py::class_(cFGD, "Entity"); + + py::class_(cFGDEntity, "ClassProperty") + .def_ro("name", &FGD::Entity::ClassProperty::name) + .def_ro("arguments", &FGD::Entity::ClassProperty::arguments); + + py::class_(cFGDEntity, "Field") + .def_ro("name", &FGD::Entity::Field::name) + .def_ro("value_type", &FGD::Entity::Field::valueType) + .def_ro("readonly", &FGD::Entity::Field::readonly) + .def_ro("reportable", &FGD::Entity::Field::reportable) + .def_ro("display_name", &FGD::Entity::Field::displayName) + .def_ro("value_default", &FGD::Entity::Field::valueDefault) + .def_ro("description", &FGD::Entity::Field::description); + + auto cFGDEntityFieldChoices = py::class_(cFGDEntity, "FieldChoices"); + + py::class_(cFGDEntityFieldChoices, "Choice") + .def_ro("value", &FGD::Entity::FieldChoices::Choice::value) + .def_ro("display_name", &FGD::Entity::FieldChoices::Choice::displayName); + + cFGDEntityFieldChoices + .def_ro("name", &FGD::Entity::FieldChoices::name) + .def_ro("readonly", &FGD::Entity::FieldChoices::readonly) + .def_ro("reportable", &FGD::Entity::FieldChoices::reportable) + .def_ro("display_name", &FGD::Entity::FieldChoices::displayName) + .def_ro("value_default", &FGD::Entity::FieldChoices::valueDefault) + .def_ro("description", &FGD::Entity::FieldChoices::description) + .def_ro("choices", &FGD::Entity::FieldChoices::choices); + + auto cFGDEntityFieldFlags = py::class_(toolpp, "FGDEntityFieldFlags"); + + py::class_(cFGDEntityFieldFlags, "Flag") + .def_ro("value", &FGD::Entity::FieldFlags::Flag::value) + .def_ro("display_name", &FGD::Entity::FieldFlags::Flag::displayName) + .def_ro("enabled_by_default", &FGD::Entity::FieldFlags::Flag::enabledByDefault) + .def_ro("description", &FGD::Entity::FieldFlags::Flag::description); + + cFGDEntityFieldFlags + .def_ro("name", &FGD::Entity::FieldFlags::name) + .def_ro("readonly", &FGD::Entity::FieldFlags::readonly) + .def_ro("reportable", &FGD::Entity::FieldFlags::reportable) + .def_ro("display_name", &FGD::Entity::FieldFlags::displayName) + .def_ro("description", &FGD::Entity::FieldFlags::description) + .def_ro("flags", &FGD::Entity::FieldFlags::flags); + + py::class_(cFGDEntity, "IO") + .def_ro("name", &FGD::Entity::IO::name) + .def_ro("value_type", &FGD::Entity::IO::valueType) + .def_ro("description", &FGD::Entity::IO::description); + + cFGDEntity + .def_ro("class_type", &FGD::Entity::classType) + .def_ro("class_properties", &FGD::Entity::classProperties) + .def_ro("description", &FGD::Entity::description) + .def_ro("fields", &FGD::Entity::fields) + .def_ro("fields_with_choices", &FGD::Entity::fieldsWithChoices) + .def_ro("fields_with_flags", &FGD::Entity::fieldsWithFlags) + .def_ro("inputs", &FGD::Entity::inputs) + .def_ro("outputs", &FGD::Entity::outputs); + + py::class_(cFGD, "AutoVisGroup") + .def_ro("parent_name", &FGD::AutoVisGroup::parentName) + .def_ro("name", &FGD::AutoVisGroup::name) + .def_ro("entities", &FGD::AutoVisGroup::entities); + + cFGD + .def(py::init<>()) + .def(py::init(), py::arg("fgd_path")) + .def("load", &FGD::load, py::arg("fgd_path")) + .def_prop_ro("version", &FGD::getVersion) + .def_prop_ro("map_size", &FGD::getMapSize) + .def_prop_ro("entities", &FGD::getEntities) + .def_prop_ro("material_exclusion_dirs", &FGD::getMaterialExclusionDirs) + .def_prop_ro("auto_visgroups", &FGD::getAutoVisGroups); + + auto cFGDWriter = py::class_(toolpp, "FGDWriter"); + + py::class_(cFGDWriter, "AutoVisGroupWriter") + .def("visgroup", &FGDWriter::AutoVisGroupWriter::visGroup, py::arg("name"), py::arg("entities"), py::rv_policy::reference) + .def("end_auto_visgroup", &FGDWriter::AutoVisGroupWriter::endAutoVisGroup, py::rv_policy::reference); + + auto cFGDWriterEntityWriter = py::class_(cFGDWriter, "EntityWriter"); + + py::class_(cFGDWriterEntityWriter, "KeyValueChoicesWriter") + .def("choice", &FGDWriter::EntityWriter::KeyValueChoicesWriter::choice, py::arg("value"), py::arg("display_name"), py::rv_policy::reference) + .def("end_key_value_choices", &FGDWriter::EntityWriter::KeyValueChoicesWriter::endKeyValueChoices, py::rv_policy::reference); + + py::class_(cFGDWriterEntityWriter, "KeyValueFlagsWriter") + .def("flag", &FGDWriter::EntityWriter::KeyValueFlagsWriter::flag, py::arg("value"), py::arg("display_name"), py::arg("enabled_by_default"), py::arg("description") = "", py::rv_policy::reference) + .def("end_key_value_flags", &FGDWriter::EntityWriter::KeyValueFlagsWriter::endKeyValueFlags, py::rv_policy::reference); + + cFGDWriterEntityWriter + .def("key_value", &FGDWriter::EntityWriter::keyValue, py::arg("name"), py::arg("value_type"), py::arg("display_name") = "", py::arg("value_default") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false, py::rv_policy::reference) + .def("begin_key_value_choices", &FGDWriter::EntityWriter::beginKeyValueChoices, py::arg("name"), py::arg("display_name") = "", py::arg("value_default") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false) + .def("begin_key_value_flags", &FGDWriter::EntityWriter::beginKeyValueFlags, py::arg("name"), py::arg("display_name") = "", py::arg("description") = "", py::arg("readonly") = false, py::arg("report") = false) + .def("input", &FGDWriter::EntityWriter::input, py::arg("name"), py::arg("value_type"), py::arg("description") = "", py::rv_policy::reference) + .def("output", &FGDWriter::EntityWriter::output, py::arg("name"), py::arg("value_type"), py::arg("description") = "", py::rv_policy::reference) + .def("end_entity", &FGDWriter::EntityWriter::endEntity, py::rv_policy::reference); + + cFGDWriter + .def_static("begin", &FGDWriter::begin) + .def("include", &FGDWriter::include, py::arg("fgd_path"), py::rv_policy::reference) + .def("version", &FGDWriter::version, py::arg("version"), py::rv_policy::reference) + .def("map_size", &FGDWriter::mapSize, py::arg("map_size"), py::rv_policy::reference) + .def("material_exclusion_dirs", &FGDWriter::materialExclusionDirs, py::arg("material_exclusion_dirs"), py::rv_policy::reference) + .def("begin_auto_visgroup", &FGDWriter::beginAutoVisGroup, py::arg("parent_name")) + .def("begin_entity", &FGDWriter::beginEntity, py::arg("class_type"), py::arg("class_properties"), py::arg("name"), py::arg("description") = "") + .def("bake", [](const FGDWriter& self) { + const auto d = self.bake(); + return py::bytes{d.data(), d.size()}; + }) + .def("bake_to_file", py::overload_cast(&FGDWriter::bake, py::const_), py::arg("path")); } } // namespace vcryptpp diff --git a/lang/python/src/vtfpp.h b/lang/python/src/vtfpp.h index 3fb9838c3..62fa54739 100644 --- a/lang/python/src/vtfpp.h +++ b/lang/python/src/vtfpp.h @@ -3,10 +3,12 @@ #include #include +#include #include #include #include #include +#include namespace py = nanobind; @@ -19,72 +21,75 @@ void register_python(py::module_& m) { auto vtfpp = m.def_submodule("vtfpp"); py::enum_(vtfpp, "ImageFormat") - .value("RGBA8888", ImageFormat::RGBA8888) - .value("ABGR8888", ImageFormat::ABGR8888) - .value("RGB888", ImageFormat::RGB888) - .value("BGR888", ImageFormat::BGR888) - .value("RGB565", ImageFormat::RGB565) - .value("I8", ImageFormat::I8) - .value("IA88", ImageFormat::IA88) - .value("P8", ImageFormat::P8) - .value("A8", ImageFormat::A8) - .value("RGB888_BLUESCREEN", ImageFormat::RGB888_BLUESCREEN) - .value("BGR888_BLUESCREEN", ImageFormat::BGR888_BLUESCREEN) - .value("ARGB8888", ImageFormat::ARGB8888) - .value("BGRA8888", ImageFormat::BGRA8888) - .value("DXT1", ImageFormat::DXT1) - .value("DXT3", ImageFormat::DXT3) - .value("DXT5", ImageFormat::DXT5) - .value("BGRX8888", ImageFormat::BGRX8888) - .value("BGR565", ImageFormat::BGR565) - .value("BGRX5551", ImageFormat::BGRX5551) - .value("BGRA4444", ImageFormat::BGRA4444) - .value("DXT1_ONE_BIT_ALPHA", ImageFormat::DXT1_ONE_BIT_ALPHA) - .value("BGRA5551", ImageFormat::BGRA5551) - .value("UV88", ImageFormat::UV88) - .value("UVWQ8888", ImageFormat::UVWQ8888) - .value("RGBA16161616F", ImageFormat::RGBA16161616F) - .value("RGBA16161616", ImageFormat::RGBA16161616) - .value("UVLX8888", ImageFormat::UVLX8888) - .value("R32F", ImageFormat::R32F) - .value("RGB323232F", ImageFormat::RGB323232F) - .value("RGBA32323232F", ImageFormat::RGBA32323232F) - .value("RG1616F", ImageFormat::RG1616F) - .value("RG3232F", ImageFormat::RG3232F) - .value("RGBX8888", ImageFormat::RGBX8888) - .value("EMPTY", ImageFormat::EMPTY) - .value("ATI2N", ImageFormat::ATI2N) - .value("ATI1N", ImageFormat::ATI1N) - .value("RGBA1010102", ImageFormat::RGBA1010102) - .value("BGRA1010102", ImageFormat::BGRA1010102) - .value("R16F", ImageFormat::R16F) - .value("R8", ImageFormat::R8) - .value("BC7", ImageFormat::BC7) - .value("BC6H", ImageFormat::BC6H) - .export_values(); + .value("RGBA8888", ImageFormat::RGBA8888) + .value("ABGR8888", ImageFormat::ABGR8888) + .value("RGB888", ImageFormat::RGB888) + .value("BGR888", ImageFormat::BGR888) + .value("RGB565", ImageFormat::RGB565) + .value("I8", ImageFormat::I8) + .value("IA88", ImageFormat::IA88) + .value("P8", ImageFormat::P8) + .value("A8", ImageFormat::A8) + .value("RGB888_BLUESCREEN", ImageFormat::RGB888_BLUESCREEN) + .value("BGR888_BLUESCREEN", ImageFormat::BGR888_BLUESCREEN) + .value("ARGB8888", ImageFormat::ARGB8888) + .value("BGRA8888", ImageFormat::BGRA8888) + .value("DXT1", ImageFormat::DXT1) + .value("DXT3", ImageFormat::DXT3) + .value("DXT5", ImageFormat::DXT5) + .value("BGRX8888", ImageFormat::BGRX8888) + .value("BGR565", ImageFormat::BGR565) + .value("BGRX5551", ImageFormat::BGRX5551) + .value("BGRA4444", ImageFormat::BGRA4444) + .value("DXT1_ONE_BIT_ALPHA", ImageFormat::DXT1_ONE_BIT_ALPHA) + .value("BGRA5551", ImageFormat::BGRA5551) + .value("UV88", ImageFormat::UV88) + .value("UVWQ8888", ImageFormat::UVWQ8888) + .value("RGBA16161616F", ImageFormat::RGBA16161616F) + .value("RGBA16161616", ImageFormat::RGBA16161616) + .value("UVLX8888", ImageFormat::UVLX8888) + .value("R32F", ImageFormat::R32F) + .value("RGB323232F", ImageFormat::RGB323232F) + .value("RGBA32323232F", ImageFormat::RGBA32323232F) + .value("RG1616F", ImageFormat::RG1616F) + .value("RG3232F", ImageFormat::RG3232F) + .value("RGBX8888", ImageFormat::RGBX8888) + .value("EMPTY", ImageFormat::EMPTY) + .value("ATI2N", ImageFormat::ATI2N) + .value("ATI1N", ImageFormat::ATI1N) + .value("RGBA1010102", ImageFormat::RGBA1010102) + .value("BGRA1010102", ImageFormat::BGRA1010102) + .value("R16F", ImageFormat::R16F) + .value("R8", ImageFormat::R8) + .value("BC7", ImageFormat::BC7) + .value("BC6H", ImageFormat::BC6H) + .export_values(); { using namespace ImageFormatDetails; auto ImageFormatDetails = vtfpp.def_submodule("ImageFormatDetails"); - ImageFormatDetails.def("red", &red, py::arg("format")); - ImageFormatDetails.def("decompressedRed", &decompressedRed, py::arg("format")); - ImageFormatDetails.def("green", &green, py::arg("format")); - ImageFormatDetails.def("decompressedGreen", &decompressedGreen, py::arg("format")); - ImageFormatDetails.def("blue", &blue, py::arg("format")); - ImageFormatDetails.def("decompressedBlue", &decompressedBlue, py::arg("format")); - ImageFormatDetails.def("alpha", &alpha, py::arg("format")); - ImageFormatDetails.def("decompressedAlpha", &decompressedAlpha, py::arg("format")); - ImageFormatDetails.def("bpp", &bpp, py::arg("format")); - ImageFormatDetails.def("containerFormat", &containerFormat, py::arg("format")); - ImageFormatDetails.def("large", &large, py::arg("format")); - ImageFormatDetails.def("decimal", &decimal, py::arg("format")); - ImageFormatDetails.def("compressed", &compressed, py::arg("format")); - ImageFormatDetails.def("transparent", &transparent, py::arg("format")); - ImageFormatDetails.def("opaque", &opaque, py::arg("format")); - - ImageFormatDetails.def("get_data_length", py::overload_cast(&getDataLength), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("slice_count") = 1); - ImageFormatDetails.def("get_data_length_extended", py::overload_cast(&getDataLength), py::arg("format"), py::arg("mip_count"), py::arg("frame_count"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice_count")); + ImageFormatDetails + .def("red", &red, py::arg("format")) + .def("decompressedRed", &decompressedRed, py::arg("format")) + .def("green", &green, py::arg("format")) + .def("decompressedGreen", &decompressedGreen, py::arg("format")) + .def("blue", &blue, py::arg("format")) + .def("decompressedBlue", &decompressedBlue, py::arg("format")) + .def("alpha", &alpha, py::arg("format")) + .def("decompressedAlpha", &decompressedAlpha, py::arg("format")) + .def("bpp", &bpp, py::arg("format")) + .def("containerFormat", &containerFormat, py::arg("format")) + .def("large", &large, py::arg("format")) + .def("decimal", &decimal, py::arg("format")) + .def("compressed", &compressed, py::arg("format")) + .def("transparent", &transparent, py::arg("format")) + .def("opaque", &opaque, py::arg("format")); + + ImageFormatDetails + .def("get_data_length", py::overload_cast(&getDataLength), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("slice_count") = 1) + .def("get_data_length_extended", py::overload_cast(&getDataLength), py::arg("format"), py::arg("mip_count"), py::arg("frame_count"), py::arg("face_count"), py::arg("width"), py::arg("height"), py::arg("slice_count")); + ImageFormatDetails.def("get_data_position", [](ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice = 0, uint16_t sliceCount = 1) -> std::pair { uint32_t offset, length; if (getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height, slice, sliceCount)) { @@ -98,11 +103,12 @@ void register_python(py::module_& m) { using namespace ImageDimensions; auto ImageDimensions = vtfpp.def_submodule("ImageDimensions"); - ImageDimensions.def("get_mip_dim", &getMipDim, py::arg("mip"), py::arg("dim")); - ImageDimensions.def("get_recommended_mip_count_for_dims", &getRecommendedMipCountForDims, py::arg("format"), py::arg("width"), py::arg("height")); + ImageDimensions + .def("get_mip_dim", &getMipDim, py::arg("mip"), py::arg("dim")) + .def("get_recommended_mip_count_for_dims", &getRecommendedMipCountForDims, py::arg("format"), py::arg("width"), py::arg("height")); } - // Skip ImagePixel, difficult to bind + // Skip ImagePixel, difficult and pointless to bind { using namespace ImageConversion; @@ -124,13 +130,14 @@ void register_python(py::module_& m) { }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("resolution") = 0, py::arg("bilinear") = true); py::enum_(ImageConversion, "FileFormat") - .value("DEFAULT", FileFormat::DEFAULT) - .value("PNG", FileFormat::PNG) - .value("JPEG", FileFormat::JPEG) - .value("BMP", FileFormat::BMP) - .value("TGA", FileFormat::TGA) - .value("HDR", FileFormat::HDR) - .export_values(); + .value("DEFAULT", FileFormat::DEFAULT) + .value("PNG", FileFormat::PNG) + .value("JPEG", FileFormat::JPEG) + .value("BMP", FileFormat::BMP) + .value("TGA", FileFormat::TGA) + .value("HDR", FileFormat::HDR) + .value("EXR", FileFormat::EXR) + .export_values(); ImageConversion.def("get_default_file_format_for_image_format", &getDefaultFileFormatForImageFormat, py::arg("format")); @@ -147,27 +154,27 @@ void register_python(py::module_& m) { }, py::arg("file_data")); py::enum_(ImageConversion, "ResizeEdge") - .value("CLAMP", ResizeEdge::CLAMP) - .value("REFLECT", ResizeEdge::REFLECT) - .value("WRAP", ResizeEdge::WRAP) - .value("ZERO", ResizeEdge::ZERO) - .export_values(); + .value("CLAMP", ResizeEdge::CLAMP) + .value("REFLECT", ResizeEdge::REFLECT) + .value("WRAP", ResizeEdge::WRAP) + .value("ZERO", ResizeEdge::ZERO) + .export_values(); py::enum_(ImageConversion, "ResizeFilter") - .value("DEFAULT", ResizeFilter::DEFAULT) - .value("BOX", ResizeFilter::BOX) - .value("BILINEAR", ResizeFilter::BILINEAR) - .value("CUBIC_BSPLINE", ResizeFilter::CUBIC_BSPLINE) - .value("CATMULLROM", ResizeFilter::CATMULLROM) - .value("MITCHELL", ResizeFilter::MITCHELL) - .export_values(); + .value("DEFAULT", ResizeFilter::DEFAULT) + .value("BOX", ResizeFilter::BOX) + .value("BILINEAR", ResizeFilter::BILINEAR) + .value("CUBIC_BSPLINE", ResizeFilter::CUBIC_BSPLINE) + .value("CATMULLROM", ResizeFilter::CATMULLROM) + .value("MITCHELL", ResizeFilter::MITCHELL) + .export_values(); py::enum_(ImageConversion, "ResizeMethod") - .value("NONE", ResizeMethod::NONE) - .value("POWER_OF_TWO_BIGGER", ResizeMethod::POWER_OF_TWO_BIGGER) - .value("POWER_OF_TWO_SMALLER", ResizeMethod::POWER_OF_TWO_SMALLER) - .value("POWER_OF_TWO_NEAREST", ResizeMethod::POWER_OF_TWO_NEAREST) - .export_values(); + .value("NONE", ResizeMethod::NONE) + .value("POWER_OF_TWO_BIGGER", ResizeMethod::POWER_OF_TWO_BIGGER) + .value("POWER_OF_TWO_SMALLER", ResizeMethod::POWER_OF_TWO_SMALLER) + .value("POWER_OF_TWO_NEAREST", ResizeMethod::POWER_OF_TWO_NEAREST) + .export_values(); ImageConversion.def("get_resized_dim", &getResizedDim, py::arg("n"), py::arg("resize_method")); ImageConversion.def("get_resized_dims", [](uint16_t width, ResizeMethod widthResize, uint16_t height, ResizeMethod heightResize) -> std::pair { @@ -186,256 +193,331 @@ void register_python(py::module_& m) { return {py::bytes{d.data(), d.size()}, widthOut, heightOut}; }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("width_resize"), py::arg("height"), py::arg("new_height"), py::arg("height_resize"), py::arg("srgb"), py::arg("filter"), py::arg("edge") = ResizeEdge::CLAMP); + ImageConversion.def("crop_image_data", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset) { + const auto d = cropImageData({reinterpret_cast(imageData.data()), imageData.size()}, format, width, newWidth, xOffset, height, newHeight, yOffset); + return py::bytes{d.data(), d.size()}; + }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("new_width"), py::arg("x_offset"), py::arg("height"), py::arg("new_height"), py::arg("y_offset")); + // Skip extractChannelFromImageData, difficult to bind // Skip applyChannelToImageData, difficult to bind } - py::class_(vtfpp, "PPLImage") - .def_ro("width", &PPL::Image::width) - .def_ro("height", &PPL::Image::height) - .def_prop_ro("data", [](const PPL::Image& self) { - return py::bytes{self.data.data(), self.data.size()}; - }); - - py::class_(vtfpp, "PPL") - .def(py::init(), py::arg("checksum"), py::arg("format") = ImageFormat::RGB888, py::arg("version") = 0) - .def("__init__", [](PPL* self, const py::bytes& pplData) { - return new(self) PPL{{reinterpret_cast(pplData.data()), pplData.size()}}; - }, py::arg("ppl_data")) - .def(py::init(), py::arg("path")) - .def("__bool__", &PPL::operator bool, py::is_operator()) - .def_prop_rw("version", &PPL::getVersion, &PPL::setVersion) - .def_prop_rw("checksum", &PPL::getChecksum, &PPL::setChecksum) - .def_prop_rw("format", &PPL::getFormat, &PPL::setFormat) - .def("has_image_for_lod", &PPL::hasImageForLOD, py::arg("lod")) - .def_prop_ro("image_lods", &PPL::getImageLODs) - .def("get_image_raw", [](const PPL& self, uint32_t lod = 0) -> std::optional { - const auto* image = self.getImageRaw(lod); - if (!image) { - return std::nullopt; - } - return *image; - }, py::arg("lod")) - .def("get_image_as", &PPL::getImageAs, py::arg("new_format"), py::arg("lod")) - .def("get_image_as_rgb888", &PPL::getImageAsRGB888, py::arg("lod")) - .def("set_image", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t lod = 0) { - self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, lod); - }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("lod") = 0) - .def("set_image_resized", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR) { - self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, resizedWidth, resizedHeight, lod, filter); - }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def("set_image_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("lod") = 0) - .def("set_image_resized_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def("save_image", [](const PPL& self, uint32_t lod = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { - const auto d = self.saveImageToFile(lod, fileFormat); - return py::bytes{d.data(), d.size()}; - }, py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("save_image_to_file", py::overload_cast(&PPL::saveImageToFile, py::const_), py::arg("image_path"), py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("bake", [](PPL& self) { - const auto d = self.bake(); - return py::bytes{d.data(), d.size()}; - }) - .def("bake_to_file", py::overload_cast(&PPL::bake), py::arg("ppl_path")); + auto cPPL = py::class_(vtfpp, "PPL"); + + py::class_(cPPL, "Image") + .def_ro("width", &PPL::Image::width) + .def_ro("height", &PPL::Image::height) + .def_prop_ro("data", [](const PPL::Image& self) { + return py::bytes{self.data.data(), self.data.size()}; + }); + + cPPL + .def(py::init(), py::arg("checksum"), py::arg("format") = ImageFormat::RGB888, py::arg("version") = 0) + .def("__init__", [](PPL* self, const py::bytes& pplData) { + return new(self) PPL{{reinterpret_cast(pplData.data()), pplData.size()}}; + }, py::arg("ppl_data")) + .def(py::init(), py::arg("path")) + .def("__bool__", &PPL::operator bool, py::is_operator()) + .def_prop_rw("version", &PPL::getVersion, &PPL::setVersion) + .def_prop_rw("checksum", &PPL::getChecksum, &PPL::setChecksum) + .def_prop_rw("format", &PPL::getFormat, &PPL::setFormat) + .def("has_image_for_lod", &PPL::hasImageForLOD, py::arg("lod")) + .def_prop_ro("image_lods", &PPL::getImageLODs) + .def("get_image_raw", [](const PPL& self, uint32_t lod = 0) -> std::optional { + const auto* image = self.getImageRaw(lod); + if (!image) { + return std::nullopt; + } + return *image; + }, py::arg("lod")) + .def("get_image_as", &PPL::getImageAs, py::arg("new_format"), py::arg("lod")) + .def("get_image_as_rgb888", &PPL::getImageAsRGB888, py::arg("lod")) + .def("set_image", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t lod = 0) { + self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, lod); + }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("lod") = 0) + .def("set_image_resized", [](PPL& self, const py::bytes& imageData, ImageFormat format, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR) { + self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, resizedWidth, resizedHeight, lod, filter); + }, py::arg("imageData"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) + .def("set_image_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("lod") = 0) + .def("set_image_resized_from_file", py::overload_cast(&PPL::setImage), py::arg("image_path"), py::arg("resized_width"), py::arg("resized_height"), py::arg("lod") = 0, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) + .def("save_image", [](const PPL& self, uint32_t lod = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { + const auto d = self.saveImageToFile(lod, fileFormat); + return py::bytes{d.data(), d.size()}; + }, py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("save_image_to_file", py::overload_cast(&PPL::saveImageToFile, py::const_), py::arg("image_path"), py::arg("lod") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("bake", [](PPL& self) { + const auto d = self.bake(); + return py::bytes{d.data(), d.size()}; + }) + .def("bake_to_file", py::overload_cast(&PPL::bake), py::arg("ppl_path")); + + auto cSHT = py::class_(vtfpp, "SHT"); + auto cSHTSequence = py::class_(cSHT, "Sequence"); + auto cSHTSequenceFrame = py::class_(cSHTSequence, "Frame"); + + py::class_(cSHTSequenceFrame, "Bounds") + .def_rw("x1", &SHT::Sequence::Frame::Bounds::x1) + .def_rw("y1", &SHT::Sequence::Frame::Bounds::y1) + .def_rw("x2", &SHT::Sequence::Frame::Bounds::x2) + .def_rw("y2", &SHT::Sequence::Frame::Bounds::y2); + + cSHTSequenceFrame + .def_rw("duration", &SHT::Sequence::Frame::duration) + .def_rw("bounds", &SHT::Sequence::Frame::bounds) + .def("set_all_bounds", &SHT::Sequence::Frame::setAllBounds, py::arg("newBounds")); + + cSHTSequence + .def_rw("id", &SHT::Sequence::id) + .def_rw("loop", &SHT::Sequence::loop) + .def_rw("frames", &SHT::Sequence::frames) + .def_rw("duration_total", &SHT::Sequence::durationTotal); + + cSHT + .def(py::init<>()) + .def("__init__", [](SHT* self, const py::bytes& shtData) { + return new(self) SHT{{reinterpret_cast(shtData.data()), shtData.size()}}; + }, py::arg("sht_data")) + .def(py::init(), py::arg("sht_path")) + .def("__bool__", &SHT::operator bool, py::is_operator()) + .def_prop_rw("version", &SHT::getVersion, &SHT::setVersion) + .def_prop_rw("sequences", [](const SHT& self) -> std::vector { return self.getSequences(); }, [](SHT& self, const std::vector& sequences) { self.getSequences() = sequences; }) + .def("get_sequence_from_id", [](const SHT& self, uint32_t id) -> const SHT::Sequence* { return self.getSequenceFromID(id); }, py::arg("id"), py::rv_policy::reference_internal) + .def("get_frame_bounds_count", &SHT::getFrameBoundsCount) + .def("bake", [](const SHT& self) { + const auto d = self.bake(); + return py::bytes{d.data(), d.size()}; + }) + .def("bake_to_file", py::overload_cast(&SHT::bake, py::const_), py::arg("sht_path")); vtfpp.attr("VTF_SIGNATURE") = VTF_SIGNATURE; - py::enum_(vtfpp, "CompressionMethod") - .value("DEFLATE", CompressionMethod::DEFLATE) - .value("ZSTD", CompressionMethod::ZSTD) - .export_values(); - - py::enum_(vtfpp, "ResourceType") - .value("UNKNOWN", Resource::TYPE_UNKNOWN) - .value("THUMBNAIL_DATA", Resource::TYPE_THUMBNAIL_DATA) - .value("IMAGE_DATA", Resource::TYPE_IMAGE_DATA) - .value("PARTICLE_SHEET_DATA", Resource::TYPE_PARTICLE_SHEET_DATA) - .value("CRC", Resource::TYPE_CRC) - .value("LOD_CONTROL_INFO", Resource::TYPE_LOD_CONTROL_INFO) - .value("EXTENDED_FLAGS", Resource::TYPE_EXTENDED_FLAGS) - .value("KEYVALUES_DATA", Resource::TYPE_KEYVALUES_DATA) - .value("AUX_COMPRESSION", Resource::TYPE_AUX_COMPRESSION) - .export_values(); - - py::enum_(vtfpp, "ResourceFlags") - .value("NONE", Resource::FLAG_NONE) - .value("LOCAL_DATA", Resource::FLAG_LOCAL_DATA) - .export_values(); - - // Skip Resource, mostly useless outside C++ - - py::enum_(vtfpp, "VTFFlags") - .value("NONE", VTF::FLAG_NONE) - .value("POINT_SAMPLE", VTF::FLAG_POINT_SAMPLE) - .value("TRILINEAR", VTF::FLAG_TRILINEAR) - .value("CLAMP_S", VTF::FLAG_CLAMP_S) - .value("CLAMP_T", VTF::FLAG_CLAMP_T) - .value("ANISOTROPIC", VTF::FLAG_ANISOTROPIC) - .value("HINT_DXT5", VTF::FLAG_HINT_DXT5) - .value("SRGB", VTF::FLAG_SRGB) - .value("NO_COMPRESS", VTF::FLAG_NO_COMPRESS) - .value("NORMAL", VTF::FLAG_NORMAL) - .value("NO_MIP", VTF::FLAG_NO_MIP) - .value("NO_LOD", VTF::FLAG_NO_LOD) - .value("LOAD_LOWEST_MIPS", VTF::FLAG_LOAD_LOWEST_MIPS) - .value("PROCEDURAL", VTF::FLAG_PROCEDURAL) - .value("ONE_BIT_ALPHA", VTF::FLAG_ONE_BIT_ALPHA) - .value("MULTI_BIT_ALPHA", VTF::FLAG_MULTI_BIT_ALPHA) - .value("ENVMAP", VTF::FLAG_ENVMAP) - .value("RENDERTARGET", VTF::FLAG_RENDERTARGET) - .value("DEPTH_RENDERTARGET", VTF::FLAG_DEPTH_RENDERTARGET) - .value("NO_DEBUG_OVERRIDE", VTF::FLAG_NO_DEBUG_OVERRIDE) - .value("SINGLE_COPY", VTF::FLAG_SINGLE_COPY) - .value("ONE_OVER_MIP_LEVEL_IN_ALPHA", VTF::FLAG_ONE_OVER_MIP_LEVEL_IN_ALPHA) - .value("PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL", VTF::FLAG_PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL) - .value("NORMAL_TO_DUDV", VTF::FLAG_NORMAL_TO_DUDV) - .value("ALPHA_TEST_MIP_GENERATION", VTF::FLAG_ALPHA_TEST_MIP_GENERATION) - .value("NO_DEPTH_BUFFER", VTF::FLAG_NO_DEPTH_BUFFER) - .value("NICE_FILTERED", VTF::FLAG_NICE_FILTERED) - .value("CLAMP_U", VTF::FLAG_CLAMP_U) - .value("VERTEX_TEXTURE", VTF::FLAG_VERTEX_TEXTURE) - .value("SSBUMP", VTF::FLAG_SSBUMP) - .value("UNFILTERABLE_OK", VTF::FLAG_UNFILTERABLE_OK) - .value("BORDER", VTF::FLAG_BORDER) - .value("SPECVAR_RED", VTF::FLAG_SPECVAR_RED) - .value("SPECVAR_ALPHA", VTF::FLAG_SPECVAR_ALPHA) - .export_values(); - - py::class_(vtfpp, "VTFCreationOptions") - .def(py::init<>()) - .def_rw("major_version", &VTF::CreationOptions::majorVersion) - .def_rw("minor_version", &VTF::CreationOptions::minorVersion) - .def_rw("output_format", &VTF::CreationOptions::outputFormat) - .def_rw("width_resize_method", &VTF::CreationOptions::widthResizeMethod) - .def_rw("height_resize_method", &VTF::CreationOptions::heightResizeMethod) - .def_rw("filter", &VTF::CreationOptions::filter) - .def_rw("flags", &VTF::CreationOptions::flags) - .def_rw("initial_frame_count", &VTF::CreationOptions::initialFrameCount) - .def_rw("start_frame", &VTF::CreationOptions::startFrame) - .def_rw("is_cubemap", &VTF::CreationOptions::isCubeMap) - .def_rw("has_spheremap", &VTF::CreationOptions::hasSphereMap) - .def_rw("initial_slice_count", &VTF::CreationOptions::initialSliceCount) - .def_rw("create_mips", &VTF::CreationOptions::createMips) - .def_rw("create_thumbnail", &VTF::CreationOptions::createThumbnail) - .def_rw("create_reflectivity", &VTF::CreationOptions::createReflectivity) - .def_rw("compression_level", &VTF::CreationOptions::compressionLevel) - .def_rw("compression_method", &VTF::CreationOptions::compressionMethod) - .def_rw("bumpmap_scale", &VTF::CreationOptions::bumpMapScale); - - py::class_(vtfpp, "VTF") - .def_ro_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED) - .def_ro_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED) - .def_ro_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT) - .def_ro_static("MAX_RESOURCES", &VTF::MAX_RESOURCES) - .def(py::init<>()) - .def("__init__", [](VTF* self, const py::bytes& vtfData, bool parseHeaderOnly = false) { - return new(self) VTF{std::span{reinterpret_cast(vtfData.data()), vtfData.size()}, parseHeaderOnly}; - }, py::arg("vtf_data"), py::arg("parse_header_only") = false) - .def(py::init(), py::arg("vtf_path"), py::arg("parse_header_only") = false) - .def("__bool__", &VTF::operator bool, py::is_operator()) - .def_static("create_and_bake", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, VTF::CreationOptions options) { - VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, vtfPath, options); - }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_static("create_blank_and_bake", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_static("create", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::CreationOptions options) { - return VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, options); - }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_static("create_blank", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_static("create_from_file_and_bake", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_static("create_from_file", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("creation_options") = VTF::CreationOptions{}) - .def_prop_rw("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion) - .def_prop_rw("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion) - .def_prop_rw("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod) - .def_prop_rw("image_height_resize_method", &VTF::getImageHeightResizeMethod, &VTF::setImageHeightResizeMethod) - .def_prop_ro("width", &VTF::getWidth) - .def("width_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getWidth(mip); }, py::arg("mip") = 0) - .def_prop_ro("height", &VTF::getHeight) - .def("height_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getHeight(mip); }, py::arg("mip") = 0) - .def("set_size", &VTF::setSize, py::arg("width"), py::arg("height"), py::arg("filter")) - .def_prop_rw("flags", &VTF::getFlags, &VTF::setFlags) - .def("add_flags", &VTF::addFlags, py::arg("flags")) - .def("remove_flags", &VTF::removeFlags, py::arg("flags")) - .def_prop_ro("format", &VTF::getFormat) - .def("set_format", &VTF::setFormat, py::arg("new_format"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def_prop_rw("mip_count", &VTF::getMipCount, &VTF::setMipCount) - .def("set_recommended_mip_count", &VTF::setRecommendedMipCount) - .def("compute_mips", &VTF::computeMips, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def_prop_rw("frame_count", &VTF::getFrameCount, &VTF::setFrameCount) - .def_prop_ro("face_count", &VTF::getFaceCount) - .def("set_face_count", &VTF::setFaceCount, py::arg("is_cubemap"), py::arg("has_spheremap") = false) - .def_prop_rw("slice_count", &VTF::getSliceCount, &VTF::setSliceCount) - .def("set_frame_face_and_slice_count", &VTF::setFrameFaceAndSliceCount, py::arg("new_frame_count"), py::arg("is_cubemap"), py::arg("has_spheremap") = false, py::arg("new_slice_count") = 1) - .def_prop_rw("start_frame", &VTF::getStartFrame, &VTF::setStartFrame) - .def_prop_rw("reflectivity", &VTF::getReflectivity, &VTF::setReflectivity) - .def("compute_reflectivity", &VTF::computeReflectivity) - .def_prop_rw("bumpmap_scale", &VTF::getBumpMapScale, &VTF::setBumpMapScale) - .def_prop_ro("thumbnail_format", &VTF::getThumbnailFormat) - .def_prop_ro("thumbnail_width", &VTF::getThumbnailWidth) - .def_prop_ro("thumbnail_height", &VTF::getThumbnailHeight) - // Skip getResources - // Skip getResource - .def("set_particle_sheet_resource", [](VTF& self, const py::bytes& value) { return self.setParticleSheetResource({reinterpret_cast(value.data()), value.size()}); }, py::arg("value")) - .def("remove_particle_sheet_resource", &VTF::removeParticleSheetResource) - .def("set_crc_resource", &VTF::setCRCResource, py::arg("value")) - .def("remove_crc_resource", &VTF::removeCRCResource) - .def("set_lod_resource", &VTF::setLODResource, py::arg("u"), py::arg("v")) - .def("remove_lod_resource", &VTF::removeLODResource) - .def("set_extended_flags_resource", &VTF::setExtendedFlagsResource, py::arg("value")) - .def("remove_extended_flags_resource", &VTF::removeExtendedFlagsResource) - .def("set_keyvalues_data_resource", &VTF::setKeyValuesDataResource, py::arg("value")) - .def("remove_keyvalues_data_resource", &VTF::removeKeyValuesDataResource) - .def_prop_rw("compression_level", &VTF::getCompressionLevel, &VTF::setCompressionLevel) - .def_prop_rw("compression_method", &VTF::getCompressionMethod, &VTF::setCompressionMethod) - .def("has_image_data", &VTF::hasImageData) - .def("image_data_is_srgb", &VTF::imageDataIsSRGB) - .def("get_image_data_raw", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { - const auto d = self.getImageDataRaw(mip, frame, face, slice); - return py::bytes{d.data(), d.size()}; - }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("get_image_data_as", [](const VTF& self, ImageFormat newFormat, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { - const auto d = self.getImageDataAs(newFormat, mip, frame, face, slice); - return py::bytes{d.data(), d.size()}; - }, py::arg("new_format"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("get_image_data_as_rgba8888", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { - const auto d = self.getImageDataAsRGBA8888(mip, frame, face, slice); - return py::bytes{d.data(), d.size()}; - }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("set_image", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { - return self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, filter, mip, frame, face, slice); - }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("filter"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("set_image_from_file", py::overload_cast(&VTF::setImage), py::arg("image_path"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) - .def("save_image", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { - const auto d = self.saveImageToFile(mip, frame, face, slice, fileFormat); - return py::bytes{d.data(), d.size()}; - }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("save_image_to_file", py::overload_cast(&VTF::saveImageToFile, py::const_), py::arg("image_path"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("has_thumbnail_data", &VTF::hasThumbnailData) - .def("get_thumbnail_data_raw", [](const VTF& self) { - const auto d = self.getThumbnailDataRaw(); - return py::bytes{d.data(), d.size()}; - }) - .def("get_thumbnail_data_as", [](const VTF& self, ImageFormat newFormat) { - const auto d = self.getThumbnailDataAs(newFormat); - return py::bytes{d.data(), d.size()}; - }, py::arg("new_format")) - .def("get_thumbnail_data_as_rgba8888", [](const VTF& self) { - const auto d = self.getThumbnailDataAsRGBA8888(); - return py::bytes{d.data(), d.size()}; - }) - .def("set_thumbnail", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height) { - return self.setThumbnail({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height); - }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height")) - .def("compute_thumbnail", &VTF::computeThumbnail, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) - .def("remove_thumbnail", &VTF::removeThumbnail) - .def("save_thumbnail", [](const VTF& self, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { - const auto d = self.saveThumbnailToFile(fileFormat); - return py::bytes{d.data(), d.size()}; - }, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("save_thumbnail_to_file", py::overload_cast(&VTF::saveThumbnailToFile, py::const_), py::arg("image_path"), py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) - .def("bake", [](const VTF& self) { - const auto d = self.bake(); - return py::bytes{d.data(), d.size()}; - }) - .def("bake_to_file", py::overload_cast(&VTF::bake, py::const_), py::arg("vtf_path")); + py::enum_(vtfpp, "CompressionMethod", py::is_arithmetic()) + .value("DEFLATE", CompressionMethod::DEFLATE) + .value("ZSTD", CompressionMethod::ZSTD) + .export_values(); + + auto cResource = py::class_(vtfpp, "Resource"); + + py::enum_(cResource, "Type") + .value("UNKNOWN", Resource::TYPE_UNKNOWN) + .value("THUMBNAIL_DATA", Resource::TYPE_THUMBNAIL_DATA) + .value("IMAGE_DATA", Resource::TYPE_IMAGE_DATA) + .value("PARTICLE_SHEET_DATA", Resource::TYPE_PARTICLE_SHEET_DATA) + .value("CRC", Resource::TYPE_CRC) + .value("LOD_CONTROL_INFO", Resource::TYPE_LOD_CONTROL_INFO) + .value("EXTENDED_FLAGS", Resource::TYPE_EXTENDED_FLAGS) + .value("KEYVALUES_DATA", Resource::TYPE_KEYVALUES_DATA) + .value("AUX_COMPRESSION", Resource::TYPE_AUX_COMPRESSION) + .export_values(); + + py::enum_(cResource, "Flags", py::is_flag()) + .value("NONE", Resource::FLAG_NONE) + .value("LOCAL_DATA", Resource::FLAG_LOCAL_DATA) + .export_values(); + + cResource + .def_static("get_order", &Resource::getOrder) + .def_ro("type", &Resource::type) + .def_ro("flags", &Resource::flags) + .def("get_data_as_particle_sheet", &Resource::getDataAsParticleSheet) + .def("get_data_as_crc", &Resource::getDataAsCRC) + .def("get_data_as_extended_flags", &Resource::getDataAsExtendedFlags) + .def("get_data_as_lod_control_info", &Resource::getDataAsLODControlInfo) + .def("get_data_as_keyvalues_data", &Resource::getDataAsKeyValuesData) + .def("get_data_as_aux_compression_level", &Resource::getDataAsAuxCompressionLevel) + .def("get_data_as_aux_compression_method", &Resource::getDataAsAuxCompressionMethod) + .def("get_data_as_aux_compression_length", &Resource::getDataAsAuxCompressionLength, py::arg("mip"), py::arg("mip_count"), py::arg("frame"), py::arg("frame_count"), py::arg("face"), py::arg("face_count")); + + auto cVTF = py::class_(vtfpp, "VTF"); + + py::enum_(cVTF, "Flags", py::is_flag()) + .value("NONE", VTF::FLAG_NONE) + .value("POINT_SAMPLE", VTF::FLAG_POINT_SAMPLE) + .value("TRILINEAR", VTF::FLAG_TRILINEAR) + .value("CLAMP_S", VTF::FLAG_CLAMP_S) + .value("CLAMP_T", VTF::FLAG_CLAMP_T) + .value("ANISOTROPIC", VTF::FLAG_ANISOTROPIC) + .value("HINT_DXT5", VTF::FLAG_HINT_DXT5) + .value("SRGB", VTF::FLAG_SRGB) + .value("NO_COMPRESS", VTF::FLAG_NO_COMPRESS) + .value("NORMAL", VTF::FLAG_NORMAL) + .value("NO_MIP", VTF::FLAG_NO_MIP) + .value("NO_LOD", VTF::FLAG_NO_LOD) + .value("LOAD_LOWEST_MIPS", VTF::FLAG_LOAD_LOWEST_MIPS) + .value("PROCEDURAL", VTF::FLAG_PROCEDURAL) + .value("ONE_BIT_ALPHA", VTF::FLAG_ONE_BIT_ALPHA) + .value("MULTI_BIT_ALPHA", VTF::FLAG_MULTI_BIT_ALPHA) + .value("ENVMAP", VTF::FLAG_ENVMAP) + .value("RENDERTARGET", VTF::FLAG_RENDERTARGET) + .value("DEPTH_RENDERTARGET", VTF::FLAG_DEPTH_RENDERTARGET) + .value("NO_DEBUG_OVERRIDE", VTF::FLAG_NO_DEBUG_OVERRIDE) + .value("SINGLE_COPY", VTF::FLAG_SINGLE_COPY) + .value("ONE_OVER_MIP_LEVEL_IN_ALPHA", VTF::FLAG_ONE_OVER_MIP_LEVEL_IN_ALPHA) + .value("PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL", VTF::FLAG_PREMULTIPLY_COLOR_BY_ONE_OVER_MIP_LEVEL) + .value("NORMAL_TO_DUDV", VTF::FLAG_NORMAL_TO_DUDV) + .value("ALPHA_TEST_MIP_GENERATION", VTF::FLAG_ALPHA_TEST_MIP_GENERATION) + .value("NO_DEPTH_BUFFER", VTF::FLAG_NO_DEPTH_BUFFER) + .value("NICE_FILTERED", VTF::FLAG_NICE_FILTERED) + .value("CLAMP_U", VTF::FLAG_CLAMP_U) + .value("VERTEX_TEXTURE", VTF::FLAG_VERTEX_TEXTURE) + .value("SSBUMP", VTF::FLAG_SSBUMP) + .value("UNFILTERABLE_OK", VTF::FLAG_UNFILTERABLE_OK) + .value("BORDER", VTF::FLAG_BORDER) + .value("SPECVAR_RED", VTF::FLAG_SPECVAR_RED) + .value("SPECVAR_ALPHA", VTF::FLAG_SPECVAR_ALPHA) + .export_values(); + + py::class_(cVTF, "CreationOptions") + .def(py::init<>()) + .def_rw("major_version", &VTF::CreationOptions::majorVersion) + .def_rw("minor_version", &VTF::CreationOptions::minorVersion) + .def_rw("output_format", &VTF::CreationOptions::outputFormat) + .def_rw("width_resize_method", &VTF::CreationOptions::widthResizeMethod) + .def_rw("height_resize_method", &VTF::CreationOptions::heightResizeMethod) + .def_rw("filter", &VTF::CreationOptions::filter) + .def_rw("flags", &VTF::CreationOptions::flags) + .def_rw("initial_frame_count", &VTF::CreationOptions::initialFrameCount) + .def_rw("start_frame", &VTF::CreationOptions::startFrame) + .def_rw("is_cubemap", &VTF::CreationOptions::isCubeMap) + .def_rw("has_spheremap", &VTF::CreationOptions::hasSphereMap) + .def_rw("initial_slice_count", &VTF::CreationOptions::initialSliceCount) + .def_rw("create_mips", &VTF::CreationOptions::createMips) + .def_rw("create_thumbnail", &VTF::CreationOptions::createThumbnail) + .def_rw("create_reflectivity", &VTF::CreationOptions::createReflectivity) + .def_rw("compression_level", &VTF::CreationOptions::compressionLevel) + .def_rw("compression_method", &VTF::CreationOptions::compressionMethod) + .def_rw("bumpmap_scale", &VTF::CreationOptions::bumpMapScale); + + cVTF + .def_ro_static("FLAG_MASK_GENERATED", &VTF::FLAG_MASK_GENERATED) + .def_ro_static("FORMAT_UNCHANGED", &VTF::FORMAT_UNCHANGED) + .def_ro_static("FORMAT_DEFAULT", &VTF::FORMAT_DEFAULT) + .def_ro_static("MAX_RESOURCES", &VTF::MAX_RESOURCES) + .def(py::init<>()) + .def("__init__", [](VTF* self, const py::bytes& vtfData, bool parseHeaderOnly = false) { + return new(self) VTF{std::span{reinterpret_cast(vtfData.data()), vtfData.size()}, parseHeaderOnly}; + }, py::arg("vtf_data"), py::arg("parse_header_only") = false) + .def(py::init(), py::arg("vtf_path"), py::arg("parse_header_only") = false) + .def("__bool__", &VTF::operator bool, py::is_operator()) + .def_static("create_and_bake", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, VTF::CreationOptions options) { + VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, vtfPath, options); + }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_static("create_blank_and_bake", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_static("create", [](const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, VTF::CreationOptions options) { + return VTF::create({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, options); + }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_static("create_blank", py::overload_cast(&VTF::create), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_static("create_from_file_and_bake", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("vtf_path"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_static("create_from_file", py::overload_cast(&VTF::create), py::arg("image_path"), py::arg("creation_options") = VTF::CreationOptions{}) + .def_prop_rw("version_major", &VTF::getMajorVersion, &VTF::setMajorVersion) + .def_prop_rw("version_minor", &VTF::getMinorVersion, &VTF::setMinorVersion) + .def_prop_rw("image_width_resize_method", &VTF::getImageWidthResizeMethod, &VTF::setImageWidthResizeMethod) + .def_prop_rw("image_height_resize_method", &VTF::getImageHeightResizeMethod, &VTF::setImageHeightResizeMethod) + .def_prop_ro("width", &VTF::getWidth) + .def("width_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getWidth(mip); }, py::arg("mip") = 0) + .def_prop_ro("height", &VTF::getHeight) + .def("height_for_mip", [](const VTF& self, uint8_t mip = 0) { return self.getHeight(mip); }, py::arg("mip") = 0) + .def("set_size", &VTF::setSize, py::arg("width"), py::arg("height"), py::arg("filter")) + .def_prop_rw("flags", &VTF::getFlags, &VTF::setFlags) + .def("add_flags", &VTF::addFlags, py::arg("flags")) + .def("remove_flags", &VTF::removeFlags, py::arg("flags")) + .def_prop_ro("format", &VTF::getFormat) + .def("set_format", &VTF::setFormat, py::arg("new_format"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) + .def_prop_rw("mip_count", &VTF::getMipCount, &VTF::setMipCount) + .def("set_recommended_mip_count", &VTF::setRecommendedMipCount) + .def("compute_mips", &VTF::computeMips, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) + .def_prop_rw("frame_count", &VTF::getFrameCount, &VTF::setFrameCount) + .def_prop_ro("face_count", &VTF::getFaceCount) + .def("set_face_count", &VTF::setFaceCount, py::arg("is_cubemap"), py::arg("has_spheremap") = false) + .def_prop_rw("slice_count", &VTF::getSliceCount, &VTF::setSliceCount) + .def("set_frame_face_and_slice_count", &VTF::setFrameFaceAndSliceCount, py::arg("new_frame_count"), py::arg("is_cubemap"), py::arg("has_spheremap") = false, py::arg("new_slice_count") = 1) + .def_prop_rw("start_frame", &VTF::getStartFrame, &VTF::setStartFrame) + .def_prop_rw("reflectivity", &VTF::getReflectivity, &VTF::setReflectivity) + .def("compute_reflectivity", &VTF::computeReflectivity) + .def_prop_rw("bumpmap_scale", &VTF::getBumpMapScale, &VTF::setBumpMapScale) + .def_prop_ro("thumbnail_format", &VTF::getThumbnailFormat) + .def_prop_ro("thumbnail_width", &VTF::getThumbnailWidth) + .def_prop_ro("thumbnail_height", &VTF::getThumbnailHeight) + // Skipping getResources, don't want to do the same hack as in SHT here, its way more expensive + .def("get_resource", &VTF::getResource, py::arg("type"), py::rv_policy::reference_internal) + .def("get_particle_sheet_frame_data_raw", [](const VTF& self, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> std::tuple { + uint16_t spriteWidth, spriteHeight; + const auto d = self.getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice); + return {spriteWidth, spriteHeight, py::bytes{d.data(), d.size()}}; + }, py::arg("sht_sequence_id"), py::arg("sht_frame"), py::arg("sht_bounds") = 0, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("get_particle_sheet_frame_data_as", [](const VTF& self, ImageFormat format, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> std::tuple { + uint16_t spriteWidth, spriteHeight; + const auto d = self.getParticleSheetFrameDataAs(format, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice); + return {spriteWidth, spriteHeight, py::bytes{d.data(), d.size()}}; + }, py::arg("format"), py::arg("sht_sequence_id"), py::arg("sht_frame"), py::arg("sht_bounds") = 0, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("get_particle_sheet_frame_data_as_rgba8888", [](const VTF& self, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds = 0, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) -> std::tuple { + uint16_t spriteWidth, spriteHeight; + const auto d = self.getParticleSheetFrameDataAsRGBA8888(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice); + return {spriteWidth, spriteHeight, py::bytes{d.data(), d.size()}}; + }, py::arg("sht_sequence_id"), py::arg("sht_frame"), py::arg("sht_bounds") = 0, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("set_particle_sheet_resource", &VTF::setParticleSheetResource, py::arg("value")) + .def("remove_particle_sheet_resource", &VTF::removeParticleSheetResource) + .def("set_crc_resource", &VTF::setCRCResource, py::arg("value")) + .def("remove_crc_resource", &VTF::removeCRCResource) + .def("set_lod_resource", &VTF::setLODResource, py::arg("u"), py::arg("v")) + .def("remove_lod_resource", &VTF::removeLODResource) + .def("set_extended_flags_resource", &VTF::setExtendedFlagsResource, py::arg("value")) + .def("remove_extended_flags_resource", &VTF::removeExtendedFlagsResource) + .def("set_keyvalues_data_resource", &VTF::setKeyValuesDataResource, py::arg("value")) + .def("remove_keyvalues_data_resource", &VTF::removeKeyValuesDataResource) + .def_prop_rw("compression_level", &VTF::getCompressionLevel, &VTF::setCompressionLevel) + .def_prop_rw("compression_method", &VTF::getCompressionMethod, &VTF::setCompressionMethod) + .def("has_image_data", &VTF::hasImageData) + .def("image_data_is_srgb", &VTF::imageDataIsSRGB) + .def("get_image_data_raw", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { + const auto d = self.getImageDataRaw(mip, frame, face, slice); + return py::bytes{d.data(), d.size()}; + }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("get_image_data_as", [](const VTF& self, ImageFormat newFormat, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { + const auto d = self.getImageDataAs(newFormat, mip, frame, face, slice); + return py::bytes{d.data(), d.size()}; + }, py::arg("new_format"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("get_image_data_as_rgba8888", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { + const auto d = self.getImageDataAsRGBA8888(mip, frame, face, slice); + return py::bytes{d.data(), d.size()}; + }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("set_image", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) { + return self.setImage({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height, filter, mip, frame, face, slice); + }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height"), py::arg("filter"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("set_image_from_file", py::overload_cast(&VTF::setImage), py::arg("image_path"), py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0) + .def("save_image", [](const VTF& self, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { + const auto d = self.saveImageToFile(mip, frame, face, slice, fileFormat); + return py::bytes{d.data(), d.size()}; + }, py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("save_image_to_file", py::overload_cast(&VTF::saveImageToFile, py::const_), py::arg("image_path"), py::arg("mip") = 0, py::arg("frame") = 0, py::arg("face") = 0, py::arg("slice") = 0, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("has_thumbnail_data", &VTF::hasThumbnailData) + .def("get_thumbnail_data_raw", [](const VTF& self) { + const auto d = self.getThumbnailDataRaw(); + return py::bytes{d.data(), d.size()}; + }) + .def("get_thumbnail_data_as", [](const VTF& self, ImageFormat newFormat) { + const auto d = self.getThumbnailDataAs(newFormat); + return py::bytes{d.data(), d.size()}; + }, py::arg("new_format")) + .def("get_thumbnail_data_as_rgba8888", [](const VTF& self) { + const auto d = self.getThumbnailDataAsRGBA8888(); + return py::bytes{d.data(), d.size()}; + }) + .def("set_thumbnail", [](VTF& self, const py::bytes& imageData, ImageFormat format, uint16_t width, uint16_t height) { + return self.setThumbnail({reinterpret_cast(imageData.data()), imageData.size()}, format, width, height); + }, py::arg("image_data"), py::arg("format"), py::arg("width"), py::arg("height")) + .def("compute_thumbnail", &VTF::computeThumbnail, py::arg("filter") = ImageConversion::ResizeFilter::BILINEAR) + .def("remove_thumbnail", &VTF::removeThumbnail) + .def("save_thumbnail", [](const VTF& self, ImageConversion::FileFormat fileFormat = ImageConversion::FileFormat::DEFAULT) { + const auto d = self.saveThumbnailToFile(fileFormat); + return py::bytes{d.data(), d.size()}; + }, py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("save_thumbnail_to_file", py::overload_cast(&VTF::saveThumbnailToFile, py::const_), py::arg("image_path"), py::arg("file_format") = ImageConversion::FileFormat::DEFAULT) + .def("bake", [](const VTF& self) { + const auto d = self.bake(); + return py::bytes{d.data(), d.size()}; + }) + .def("bake_to_file", py::overload_cast(&VTF::bake, py::const_), py::arg("vtf_path")); } } // namespace vtfpp diff --git a/src/vtfpp/ImageConversion.cpp b/src/vtfpp/ImageConversion.cpp index 729e638f7..ce60fb62d 100644 --- a/src/vtfpp/ImageConversion.cpp +++ b/src/vtfpp/ImageConversion.cpp @@ -1804,3 +1804,21 @@ std::vector ImageConversion::resizeImageDataStrict(std::span ImageConversion::cropImageData(std::span imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset) { + if (imageData.empty() || format == ImageFormat::EMPTY || xOffset + newWidth >= width || yOffset + newHeight >= height) { + return {}; + } + if (ImageFormatDetails::compressed(format)) { + // This is horrible but what can you do? Don't crop compressed formats! Don't do it! + const auto container = ImageFormatDetails::containerFormat(format); + return convertImageDataToFormat(cropImageData(convertImageDataToFormat(imageData, format, container, width, height), container, width, newWidth, xOffset, height, newHeight, yOffset), container, format, newWidth, newHeight); + } + + const auto pixelSize = ImageFormatDetails::bpp(format) / 8; + std::vector out(pixelSize * newWidth * newHeight); + for (uint16_t y = yOffset; y < yOffset + newHeight; y++) { + std::memcpy(out.data() + (((y - yOffset) * newWidth) * pixelSize), imageData.data() + (((y * width) + xOffset) * pixelSize), newWidth * pixelSize); + } + return out; +} diff --git a/src/vtfpp/SHT.cpp b/src/vtfpp/SHT.cpp new file mode 100644 index 000000000..f94148c3a --- /dev/null +++ b/src/vtfpp/SHT.cpp @@ -0,0 +1,119 @@ +#include + +#include + +using namespace sourcepp; +using namespace vtfpp; + +SHT::SHT() + : opened(true) + , version(0) {} + +SHT::SHT(std::span shtData) { + BufferStreamReadOnly stream{shtData.data(), shtData.size()}; + + stream >> this->version; + + this->sequences.resize(stream.read()); + for (auto& sequence : this->sequences) { + stream >> sequence.id; + sequence.loop = stream.read(); + sequence.frames.resize(stream.read()); + stream >> sequence.durationTotal; + + for (auto& frame : sequence.frames) { + frame.duration = stream.read(); + for (uint8_t i = 0; i < this->getFrameBoundsCount(); i++) { + auto& bounds = frame.bounds[i]; + stream >> bounds.x1 >> bounds.y1 >> bounds.x2 >> bounds.y2; + } + } + } + + this->opened = true; +} + +SHT::SHT(const std::string& shtPath) + : SHT(fs::readFileBuffer(shtPath)) {} + +SHT::operator bool() const { + return this->opened; +} + +uint32_t SHT::getVersion() const { + return this->version; +} + +void SHT::setVersion(uint32_t v) { + if (v != 0 && v != 1) { + return; + } + this->version = v; +} + +const std::vector& SHT::getSequences() const { + return this->sequences; +} + +std::vector& SHT::getSequences() { + return this->sequences; +} + +const SHT::Sequence* SHT::getSequenceFromID(uint32_t id) const { + if (auto pos = std::find_if(this->sequences.begin(), this->sequences.end(), [id](const Sequence& sequence) { + return sequence.id == id; + }); pos != this->sequences.end()) { + return &*pos; + } + return nullptr; +} + +SHT::Sequence* SHT::getSequenceFromID(uint32_t id) { + if (auto pos = std::find_if(this->sequences.begin(), this->sequences.end(), [id](const Sequence& sequence) { + return sequence.id == id; + }); pos != this->sequences.end()) { + return &*pos; + } + return nullptr; +} + +uint8_t SHT::getFrameBoundsCount() const { + return (this->version > 0) ? 4 : 1; +} + +std::vector SHT::bake() const { + if (!this->opened) { + return {}; + } + + std::vector sheetData; + BufferStream stream{sheetData}; + + stream + .write(this->version) + .write(this->sequences.size()); + + for (const auto& sequence : this->sequences) { + stream + .write(sequence.id) + .write(sequence.loop) + .write(sequence.frames.size()) + .write(sequence.durationTotal); + + for (const auto& frame : sequence.frames) { + stream.write(frame.duration); + + for (uint8_t i = 0; i < this->getFrameBoundsCount(); i++) { + auto& bounds = frame.bounds[i]; + stream << bounds.x1 << bounds.y1 << bounds.x2 << bounds.y2; + } + } + } + + sheetData.resize(stream.tell()); + return sheetData; +} + +bool SHT::bake(const std::string& path) const { + return fs::writeFileBuffer(path, this->bake()); +} diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp index 4c29d30dc..d63ab26ab 100644 --- a/src/vtfpp/VTF.cpp +++ b/src/vtfpp/VTF.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef SOURCEPP_BUILD_WITH_THREADS #include @@ -82,6 +83,11 @@ const std::array& Resource::getOrder() { Resource::ConvertedData Resource::convertData() const { switch (this->type) { + case TYPE_PARTICLE_SHEET_DATA: + if (this->data.size() <= sizeof(uint32_t)) { + return {}; + } + return SHT{{reinterpret_cast(this->data.data()) + sizeof(uint32_t), *reinterpret_cast(this->data.data())}}; case TYPE_CRC: case TYPE_EXTENDED_FLAGS: if (this->data.size() != sizeof(uint32_t)) { @@ -944,8 +950,66 @@ void VTF::regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t this->setResourceInternal(Resource::TYPE_IMAGE_DATA, newImageData); } -void VTF::setParticleSheetResource(std::span value) { - this->setResourceInternal(Resource::TYPE_PARTICLE_SHEET_DATA, value); +std::vector VTF::getParticleSheetFrameDataRaw(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const { + spriteWidth = 0; + spriteHeight = 0; + + auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA); + if (!shtResource) { + return {}; + } + + auto sht = shtResource->getDataAsParticleSheet(); + const auto* sequence = sht.getSequenceFromID(shtSequenceID); + if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) { + return {}; + } + + // These values are likely slightly too large thanks to float magic, use a + // shader that scales UVs instead of this function if precision is a concern + // This will also break if any of the bounds are above 1 or below 0, but that + // hasn't been observed in official textures + const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds]; + uint16_t x1 = std::clamp(std::floor(bounds.x1 * static_cast(this->getWidth(mip))), 0, this->getWidth(mip)); + uint16_t y1 = std::clamp(std::ceil( bounds.y1 * static_cast(this->getHeight(mip))), 0, this->getHeight(mip)); + uint16_t x2 = std::clamp(std::ceil( bounds.x2 * static_cast(this->getWidth(mip))), 0, this->getHeight(mip)); + uint16_t y2 = std::clamp(std::floor(bounds.y2 * static_cast(this->getHeight(mip))), 0, this->getWidth(mip)); + + if (x1 > x2) [[unlikely]] { + std::swap(x1, x2); + } + if (y1 > y2) [[unlikely]] { + std::swap(y1, y2); + } + spriteWidth = x2 - x1; + spriteWidth = y2 - y1; + + const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1); + if (out.empty()) { + spriteWidth = 0; + spriteHeight = 0; + } + return out; +} + +std::vector VTF::getParticleSheetFrameDataAs(ImageFormat newFormat, uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const { + return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight); +} + +std::vector VTF::getParticleSheetFrameDataAsRGBA8888(uint16_t& spriteWidth, uint16_t& spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const { + return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice); +} + +void VTF::setParticleSheetResource(const SHT& value) { + std::vector particleSheetData; + BufferStream writer{particleSheetData}; + + auto bakedSheet = value.bake(); + writer.write(bakedSheet.size()); + writer.write(bakedSheet); + particleSheetData.resize(writer.size()); + + this->setResourceInternal(Resource::TYPE_PARTICLE_SHEET_DATA, particleSheetData); } void VTF::removeParticleSheetResource() { diff --git a/src/vtfpp/_vtfpp.cmake b/src/vtfpp/_vtfpp.cmake index fc6fec5f3..b19f2af41 100644 --- a/src/vtfpp/_vtfpp.cmake +++ b/src/vtfpp/_vtfpp.cmake @@ -4,11 +4,13 @@ add_pretty_parser(vtfpp "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageConversion.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageFormats.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/PPL.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/SHT.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/VTF.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/vtfpp.h" SOURCES "${CMAKE_CURRENT_LIST_DIR}/ImageConversion.cpp" "${CMAKE_CURRENT_LIST_DIR}/PPL.cpp" + "${CMAKE_CURRENT_LIST_DIR}/SHT.cpp" "${CMAKE_CURRENT_LIST_DIR}/VTF.cpp") sourcepp_add_opencl(vtfpp)