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)