diff --git a/README.md b/README.md
index b7ee68604..88f73f5eb 100644
--- a/README.md
+++ b/README.md
@@ -207,14 +207,20 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
- vtfpp |
+ vtfpp |
+ PPL v0 |
+ ✅ |
+ ✅ |
+ |
+
+
+
VTF v7.0-7.6
• Strata Source modifications
|
✅ |
✅ |
- |
diff --git a/docs/index.md b/docs/index.md
index d6477eeb5..7affa1c5b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -181,14 +181,19 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
✅ |
- vtfpp |
+ vtfpp |
+ PPL v0 |
+ ✅ |
+ ✅ |
+ |
+
+
VTF v7.0-7.6
• Strata Source modifications
|
✅ |
✅ |
- |
\endhtmlonly
diff --git a/include/vtfpp/ImageConversion.h b/include/vtfpp/ImageConversion.h
index 74b42e194..28365a7ac 100644
--- a/include/vtfpp/ImageConversion.h
+++ b/include/vtfpp/ImageConversion.h
@@ -204,6 +204,8 @@ namespace ImageConversion {
/// Converts image data to a PNG or HDR file. HDR output will be used for floating-point formats.
[[nodiscard]] std::vector convertImageDataToFile(std::span imageData, ImageFormat format, uint16_t width, uint16_t height);
+[[nodiscard]] std::vector convertFileToImageData(std::span fileData, ImageFormat& format, int& width, int& height, int& frameCount);
+
enum class ResizeEdge {
// Matches stbir_edge
CLAMP = 0,
diff --git a/include/vtfpp/PPL.h b/include/vtfpp/PPL.h
new file mode 100644
index 000000000..49b6cf292
--- /dev/null
+++ b/include/vtfpp/PPL.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "ImageConversion.h"
+
+namespace vtfpp {
+
+class PPL {
+public:
+ struct Image {
+ uint32_t width;
+ uint32_t height;
+ std::vector data;
+ };
+
+ explicit PPL(uint32_t checksum_, ImageFormat format_ = ImageFormat::RGB888, uint32_t version_ = 0);
+
+ explicit PPL(std::span pplData);
+
+ explicit PPL(const std::string& pplPath);
+
+ [[nodiscard]] explicit operator bool() const;
+
+ [[nodiscard]] uint32_t getVersion() const;
+
+ void setVersion(uint32_t newVersion);
+
+ [[nodiscard]] uint32_t getChecksum() const;
+
+ void setChecksum(uint32_t newChecksum);
+
+ [[nodiscard]] ImageFormat getFormat() const;
+
+ void setFormat(ImageFormat newFormat);
+
+ [[nodiscard]] bool hasImageForLOD(uint32_t lod) const;
+
+ [[nodiscard]] std::vector getImageLODs() const;
+
+ [[nodiscard]] const Image* getImageRaw(uint32_t lod = 0) const;
+
+ [[nodiscard]] std::optional getImageAs(ImageFormat newFormat, uint32_t lod = 0) const;
+
+ [[nodiscard]] std::optional getImageAsRGB888(uint32_t lod = 0) const;
+
+ bool setImage(std::span imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t lod = 0);
+
+ bool setImage(std::span 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);
+
+ bool setImage(const std::string& imagePath, uint32_t lod);
+
+ bool setImage(const std::string& imagePath, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);
+
+ [[nodiscard]] std::vector saveImageToFile(uint32_t lod = 0) const;
+
+ bool saveImageToFile(const std::string& imagePath, uint32_t lod = 0) const; // NOLINT(*-use-nodiscard)
+
+ [[nodiscard]] std::vector bake();
+
+ bool bake(const std::string& pplPath);
+
+protected:
+ uint32_t version{};
+ uint32_t checksum{};
+ ImageFormat format{};
+
+ std::unordered_map images;
+};
+
+} // namespace vtfpp
diff --git a/include/vtfpp/VTF.h b/include/vtfpp/VTF.h
index 91bf66df9..8add677f3 100644
--- a/include/vtfpp/VTF.h
+++ b/include/vtfpp/VTF.h
@@ -318,9 +318,9 @@ class VTF {
bool setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0);
- [[nodiscard]] std::vector convertAndSaveImageDataToFile(uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;
+ [[nodiscard]] std::vector saveImageToFile(uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;
- void convertAndSaveImageDataToFile(const std::string& imagePath, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;
+ bool saveImageToFile(const std::string& imagePath, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const; // NOLINT(*-use-nodiscard)
[[nodiscard]] bool hasThumbnailData() const;
@@ -334,9 +334,9 @@ class VTF {
void removeThumbnail();
- [[nodiscard]] std::vector convertAndSaveThumbnailDataToFile() const;
+ [[nodiscard]] std::vector saveThumbnailToFile() const;
- void convertAndSaveThumbnailDataToFile(const std::string& imagePath) const;
+ bool saveThumbnailToFile(const std::string& imagePath) const; // NOLINT(*-use-nodiscard)
[[nodiscard]] std::vector bake();
diff --git a/include/vtfpp/vtfpp.h b/include/vtfpp/vtfpp.h
index 772520651..274ab6625 100644
--- a/include/vtfpp/vtfpp.h
+++ b/include/vtfpp/vtfpp.h
@@ -7,4 +7,5 @@
#include "ImageConversion.h"
#include "ImageFormats.h"
+#include "PPL.h"
#include "VTF.h"
diff --git a/src/vtfpp/ImageConversion.cpp b/src/vtfpp/ImageConversion.cpp
index 0f230faae..a9867bc0d 100644
--- a/src/vtfpp/ImageConversion.cpp
+++ b/src/vtfpp/ImageConversion.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#ifdef SOURCEPP_BUILD_WITH_TBB
@@ -11,6 +12,7 @@
#include
#include
+#include
#include
#include
@@ -864,6 +866,87 @@ std::vector ImageConversion::convertImageDataToFile(std::span ImageConversion::convertFileToImageData(std::span fileData, ImageFormat& format, int& width, int& height, int& frameCount) {
+ stbi_convert_iphone_png_to_rgb(true);
+
+ format = ImageFormat::EMPTY;
+ width = 0;
+ height = 0;
+ int channels = 0;
+ frameCount = 1;
+
+ if (stbi_is_hdr_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()))) {
+ std::unique_ptr stbImage{
+ stbi_loadf_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()), &width, &height, &channels, 0),
+ &stbi_image_free,
+ };
+ if (!stbImage) {
+ return {};
+ }
+
+ switch (channels) {
+ case 1: format = ImageFormat::R32F; break;
+ case 3: format = ImageFormat::RGB323232F; break;
+ case 4: format = ImageFormat::RGBA32323232F; break;
+ default: return {};
+ }
+
+ return {reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
+ } else if (stbi_is_16_bit_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()))) {
+ std::unique_ptr stbImage{
+ stbi_load_16_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()), &width, &height, &channels, 0),
+ &stbi_image_free,
+ };
+ if (!stbImage) {
+ return {};
+ }
+
+ if (channels == 4) {
+ format = ImageFormat::RGBA16161616;
+ } else {
+ return {};
+ }
+
+ return {reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
+ } else if (fileData.size() >= 3 && static_cast(fileData[0]) == 'G' && static_cast(fileData[1]) == 'I' && static_cast(fileData[2]) == 'F') {
+ std::unique_ptr stbImage{
+ stbi_load_gif_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()), nullptr, &width, &height, &frameCount, &channels, 0),
+ &stbi_image_free,
+ };
+ if (!stbImage || !frameCount) {
+ return {};
+ }
+
+ switch (channels) {
+ case 1: format = ImageFormat::I8; break;
+ case 2: format = ImageFormat::UV88; break;
+ case 3: format = ImageFormat::RGB888; break;
+ case 4: format = ImageFormat::RGBA8888; break;
+ default: return {};
+ }
+
+ return {reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get() + (ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
+ } else {
+ std::unique_ptr stbImage{
+ stbi_load_from_memory(reinterpret_cast(fileData.data()), static_cast(fileData.size()), &width, &height, &channels, 0),
+ &stbi_image_free,
+ };
+ if (!stbImage) {
+ return {};
+ }
+
+ switch (channels) {
+ case 1: format = ImageFormat::I8; break;
+ case 2: format = ImageFormat::UV88; break;
+ case 3: format = ImageFormat::RGB888; break;
+ case 4: format = ImageFormat::RGBA8888; break;
+ default: return {};
+ }
+
+ return {reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
+ }
+}
+
uint16_t ImageConversion::getResizedDim(uint16_t n, ResizeMethod method) {
switch (method) {
case ResizeMethod::NONE: break;
diff --git a/src/vtfpp/PPL.cpp b/src/vtfpp/PPL.cpp
new file mode 100644
index 000000000..7b4d16372
--- /dev/null
+++ b/src/vtfpp/PPL.cpp
@@ -0,0 +1,219 @@
+#include
+
+#include
+
+#include
+
+using namespace sourcepp;
+using namespace vtfpp;
+
+PPL::PPL(uint32_t checksum_, ImageFormat format_, uint32_t version_)
+ : version(version_)
+ , checksum(checksum_)
+ , format(format_) {}
+
+PPL::PPL(std::span pplData) {
+ BufferStreamReadOnly reader{pplData.data(), pplData.size()};
+ reader >> this->version >> this->checksum >> this->format;
+
+ auto imageCount = reader.read();
+ reader.skip(4);
+ for (uint32_t i = 0; i < imageCount; i++) {
+ auto lod = reader.read();
+ auto offset = reader.read();
+ auto length = reader.read();
+ auto width = reader.read();
+ auto height = reader.read();
+ reader.skip(3);
+
+ this->images[lod] = {
+ .width = width,
+ .height = height,
+ .data = {pplData.data() + offset, pplData.data() + offset + length},
+ };
+ }
+}
+
+PPL::PPL(const std::string& pplPath)
+ : PPL(fs::readFileBuffer(pplPath)) {}
+
+PPL::operator bool() const {
+ return !this->images.empty();
+}
+
+uint32_t PPL::getVersion() const {
+ return this->version;
+}
+
+void PPL::setVersion(uint32_t newVersion) {
+ this->version = newVersion;
+}
+
+uint32_t PPL::getChecksum() const {
+ return this->checksum;
+}
+
+void PPL::setChecksum(uint32_t newChecksum) {
+ this->checksum = newChecksum;
+}
+
+ImageFormat PPL::getFormat() const {
+ return this->format;
+}
+
+void PPL::setFormat(ImageFormat newFormat) {
+ for (auto& [lod, image] : this->images) {
+ image.data = ImageConversion::convertImageDataToFormat(image.data, this->format, newFormat, image.width, image.height);
+ }
+ this->format = newFormat;
+}
+
+bool PPL::hasImageForLOD(uint32_t lod) const {
+ return this->images.contains(lod);
+}
+
+std::vector PPL::getImageLODs() const {
+ auto view = std::views::keys(this->images);
+ return {view.begin(), view.end()};
+}
+
+const PPL::Image* PPL::getImageRaw(uint32_t lod) const {
+ if (!this->hasImageForLOD(lod)) {
+ return nullptr;
+ }
+ return &this->images.at(lod);
+}
+
+std::optional PPL::getImageAs(ImageFormat newFormat, uint32_t lod) const {
+ if (!this->hasImageForLOD(lod)) {
+ return std::nullopt;
+ }
+ const auto& image = this->images.at(lod);
+ return Image{
+ .width = image.width,
+ .height = image.height,
+ .data = ImageConversion::convertImageDataToFormat(image.data, this->format, newFormat, image.width, image.height),
+ };
+}
+
+std::optional PPL::getImageAsRGB888(uint32_t lod) const {
+ return this->getImageAs(ImageFormat::RGB888, lod);
+}
+
+bool PPL::setImage(std::span imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t lod) {
+ if (!width || !height) {
+ return false;
+ }
+ this->images[lod] = {
+ .width = width,
+ .height = height,
+ .data = format_ == this->format ? std::vector{imageData.begin(), imageData.end()} : ImageConversion::convertImageDataToFormat(imageData, format_, this->format, width, height),
+ };
+ return true;
+}
+
+bool PPL::setImage(std::span imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod, ImageConversion::ResizeFilter filter) {
+ if (!width || !height || !resizedWidth || !resizedHeight) {
+ return false;
+ }
+ const auto unresizedData = format_ == this->format ? std::vector{imageData.begin(), imageData.end()} : ImageConversion::convertImageDataToFormat(imageData, format_, this->format, width, height);
+ this->images[lod] = {
+ .width = width,
+ .height = height,
+ .data = ImageConversion::resizeImageData(unresizedData, this->format, width, resizedWidth, height, resizedHeight, false, filter),
+ };
+ return true;
+}
+
+bool PPL::setImage(const std::string& imagePath, uint32_t lod) {
+ ImageFormat inputFormat;
+ int inputWidth, inputHeight, inputFrameCount;
+ auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
+
+ // Unable to decode file
+ if (inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
+ return false;
+ }
+
+ // One frame (normal)
+ if (inputFrameCount == 1) {
+ return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, lod);
+ }
+
+ // Multiple frames (GIF) - discard extra frames
+ return this->setImage({imageData_.data(), ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight)}, inputFormat, inputWidth, inputHeight, lod);
+}
+
+bool PPL::setImage(const std::string& imagePath, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod, ImageConversion::ResizeFilter filter) {
+ ImageFormat inputFormat;
+ int inputWidth, inputHeight, inputFrameCount;
+ auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
+
+ // Unable to decode file
+ if (inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
+ return false;
+ }
+
+ // One frame (normal)
+ if (inputFrameCount == 1) {
+ return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, resizedWidth, resizedHeight, lod, filter);
+ }
+
+ // Multiple frames (GIF) - discard extra frames
+ return this->setImage({imageData_.data(), ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight)}, inputFormat, inputWidth, inputHeight, resizedWidth, resizedHeight, lod, filter);
+}
+
+std::vector PPL::saveImageToFile(uint32_t lod) const {
+ if (auto image = this->getImageRaw(lod)) {
+ return ImageConversion::convertImageDataToFile(image->data, this->format, image->width, image->height);
+ }
+ return {};
+}
+
+bool PPL::saveImageToFile(const std::string& imagePath, uint32_t lod) const {
+ if (auto data = this->saveImageToFile(lod); !data.empty()) {
+ return fs::writeFileBuffer(imagePath, data);
+ }
+ return false;
+}
+
+std::vector PPL::bake() {
+ static constexpr auto ALIGNMENT = 512;
+
+ std::vector out;
+ BufferStream writer{out};
+
+ static constexpr auto headerSize = sizeof(uint32_t) * 8;
+ writer << this->version << this->checksum << this->format;
+ writer.write(this->images.size());
+
+ const auto imageDirectorySize = this->images.size() * sizeof(uint32_t) * 8;
+ while (writer.tell() < ALIGNMENT) {
+ writer.write(0);
+ }
+ writer.seek(headerSize);
+
+ uint32_t currentOffset = ALIGNMENT;
+ for (const auto& [lod, image] : this->images) {
+ writer << lod << currentOffset << static_cast(image.data.size()) << image.width << image.height;
+ for (int i = 0; i < 3; i++) {
+ writer.write(0);
+ }
+ auto seekPoint = writer.tell();
+ writer.seek_u(currentOffset).write(image.data);
+ const auto alignment = math::getPaddingForAlignment(ALIGNMENT, writer.tell());
+ for (int i = 0; i < alignment; i++) {
+ writer.write(0);
+ }
+ writer.seek_u(seekPoint);
+ currentOffset += image.data.size() + alignment;
+ }
+ writer.seek(0, std::ios::end);
+
+ out.resize(writer.size());
+ return out;
+}
+
+bool PPL::bake(const std::string& pplPath) {
+ return fs::writeFileBuffer(pplPath, this->bake());
+}
diff --git a/src/vtfpp/VTF.cpp b/src/vtfpp/VTF.cpp
index f64884eed..2ef802ced 100644
--- a/src/vtfpp/VTF.cpp
+++ b/src/vtfpp/VTF.cpp
@@ -1,12 +1,10 @@
#include
#include
-#include
#include
#include
#include
-#include
#include
@@ -916,106 +914,44 @@ bool VTF::setImage(std::span imageData_, ImageFormat format_, u
}
bool VTF::setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) {
- const auto imageData_ = fs::readFileBuffer(imagePath);
-
- stbi_convert_iphone_png_to_rgb(true);
- int width_, height_, channels;
- if (stbi_is_hdr_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()))) {
- std::unique_ptr stbImage{
- stbi_loadf_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()), &width_, &height_, &channels, 0),
- &stbi_image_free,
- };
- if (!stbImage) {
- return false;
- }
-
- ImageFormat imageFormat;
- switch (channels) {
- case 1: imageFormat = ImageFormat::R32F; break;
- case 3: imageFormat = ImageFormat::RGB323232F; break;
- case 4: imageFormat = ImageFormat::RGBA32323232F; break;
- default: return false;
- }
-
- return this->setImage({reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(imageFormat, width_, height_)}, imageFormat, width_, height_, filter, mip, frame, face, slice);
- } else if (stbi_is_16_bit_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()))) {
- std::unique_ptr stbImage{
- stbi_load_16_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()), &width_, &height_, &channels, 0),
- &stbi_image_free,
- };
- if (!stbImage) {
- return false;
- }
+ ImageFormat inputFormat;
+ int inputWidth, inputHeight, inputFrameCount;
+ auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
- ImageFormat imageFormat;
- if (channels == 4) {
- imageFormat = ImageFormat::RGBA16161616;
- } else {
- return false;
- }
-
- return this->setImage({reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(imageFormat, width_, height_)}, imageFormat, width_, height_, filter, mip, frame, face, slice);
- } else if (imageData_.size() >= 3 && static_cast(imageData_[0]) == 'G' && static_cast(imageData_[1]) == 'I' && static_cast(imageData_[2]) == 'F') {
- int frameCount_ = 0;
- std::unique_ptr stbImage{
- stbi_load_gif_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()), nullptr, &width_, &height_, &frameCount_, &channels, 0),
- &stbi_image_free,
- };
- if (!stbImage || !frameCount_) {
- return false;
- }
+ // Unable to decode file
+ if (inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
+ return false;
+ }
- ImageFormat imageFormat;
- switch (channels) {
- case 1: imageFormat = ImageFormat::I8; break;
- case 2: imageFormat = ImageFormat::UV88; break;
- case 3: imageFormat = ImageFormat::RGB888; break;
- case 4: imageFormat = ImageFormat::RGBA8888; break;
- default: return false;
- }
+ // One frame (normal)
+ if (inputFrameCount == 1) {
+ return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice);
+ }
- bool allSuccess = true;
- const auto frameSize = ImageFormatDetails::getDataLength(imageFormat, width_, height_);
- for (int currentFrame = 0; currentFrame < frameCount_; currentFrame++) {
- if (!this->setImage({reinterpret_cast(stbImage.get() + currentFrame * frameSize), reinterpret_cast(stbImage.get() + currentFrame * frameSize + frameSize)}, imageFormat, width_, height_, filter, mip, frame + currentFrame, face, slice)) {
- allSuccess = false;
- }
- if (currentFrame == 0 && this->frameCount < frame + frameCount_) {
- // Call this after setting the first image, this function is a no-op if no image data is present yet
- this->setFrameCount(frame + frameCount_);
- }
+ // Multiple frames (GIF)
+ bool allSuccess = true;
+ const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
+ for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
+ if (!this->setImage({imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize}, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice)) {
+ allSuccess = false;
}
- return allSuccess;
- } else {
- std::unique_ptr stbImage{
- stbi_load_from_memory(reinterpret_cast(imageData_.data()), static_cast(imageData_.size()), &width_, &height_, &channels, 0),
- &stbi_image_free,
- };
- if (!stbImage) {
- return false;
+ if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
+ // Call this after setting the first image, this function is a no-op if no image data is present yet
+ this->setFrameCount(frame + inputFrameCount);
}
-
- ImageFormat imageFormat;
- switch (channels) {
- case 1: imageFormat = ImageFormat::I8; break;
- case 2: imageFormat = ImageFormat::UV88; break;
- case 3: imageFormat = ImageFormat::RGB888; break;
- case 4: imageFormat = ImageFormat::RGBA8888; break;
- default: return false;
- }
-
- return this->setImage({reinterpret_cast(stbImage.get()), reinterpret_cast(stbImage.get()) + ImageFormatDetails::getDataLength(imageFormat, width_, height_)}, imageFormat, width_, height_, filter, mip, frame, face, slice);
}
+ return allSuccess;
}
-std::vector VTF::convertAndSaveImageDataToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
+std::vector VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height));
}
-void VTF::convertAndSaveImageDataToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
- if (auto data_ = this->convertAndSaveImageDataToFile(mip, frame, face, slice); !data_.empty()) {
- fs::writeFileBuffer(imagePath, data_);
+bool VTF::saveImageToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
+ if (auto data_ = this->saveImageToFile(mip, frame, face, slice); !data_.empty()) {
+ return fs::writeFileBuffer(imagePath, data_);
}
+ return false;
}
bool VTF::hasThumbnailData() const {
@@ -1058,14 +994,15 @@ void VTF::removeThumbnail() {
this->removeResourceInternal(Resource::TYPE_THUMBNAIL_DATA);
}
-std::vector VTF::convertAndSaveThumbnailDataToFile() const {
+std::vector VTF::saveThumbnailToFile() const {
return ImageConversion::convertImageDataToFile(this->getThumbnailDataRaw(), this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight);
}
-void VTF::convertAndSaveThumbnailDataToFile(const std::string& imagePath) const {
- if (auto data_ = this->convertAndSaveThumbnailDataToFile(); !data_.empty()) {
- fs::writeFileBuffer(imagePath, data_);
+bool VTF::saveThumbnailToFile(const std::string& imagePath) const {
+ if (auto data_ = this->saveThumbnailToFile(); !data_.empty()) {
+ return fs::writeFileBuffer(imagePath, data_);
}
+ return false;
}
std::vector VTF::bake() {
diff --git a/src/vtfpp/_vtfpp.cmake b/src/vtfpp/_vtfpp.cmake
index 79a688612..095be2e38 100644
--- a/src/vtfpp/_vtfpp.cmake
+++ b/src/vtfpp/_vtfpp.cmake
@@ -3,10 +3,12 @@ add_pretty_parser(vtfpp
PRECOMPILED_HEADERS
"${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/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}/VTF.cpp")
sourcepp_add_tbb(vtfpp)
diff --git a/test/res/vtfpp/test.ppl b/test/res/vtfpp/test.ppl
new file mode 100644
index 000000000..7cc89c85a
Binary files /dev/null and b/test/res/vtfpp/test.ppl differ
diff --git a/test/vtfpp.cpp b/test/vtfpp.cpp
index f6cc76708..7464f8b74 100644
--- a/test/vtfpp.cpp
+++ b/test/vtfpp.cpp
@@ -6,6 +6,28 @@
using namespace sourcepp;
using namespace vtfpp;
+TEST(vtfpp, read_write_ppl) {
+ auto in = fs::readFileBuffer(ASSET_ROOT "vtfpp/test.ppl");
+
+ PPL reader{in};
+ EXPECT_EQ(reader.getVersion(), 0);
+ EXPECT_EQ(reader.getChecksum(), 0xa9230a52);
+ EXPECT_EQ(reader.getFormat(), ImageFormat::RGB888);
+
+ const auto* image = reader.getImageRaw();
+ ASSERT_TRUE(reader);
+
+ PPL writer{reader.getChecksum(), reader.getFormat(), reader.getVersion()};
+ writer.setImage(image->data, reader.getFormat(), image->width, image->height);
+
+ auto out = writer.bake();
+
+ ASSERT_EQ(in.size(), out.size());
+ for (int i = 0; i < in.size(); i++) {
+ EXPECT_EQ(in[i], out[i]);
+ }
+}
+
#if 0
#define TEST_WRITE_FMT(Format, Flags) \