From 21d69e195d03d9d043247cc0ee4222b22429e2d6 Mon Sep 17 00:00:00 2001 From: erysdren Date: Thu, 15 Oct 2026 11:14:32 -0500 Subject: [PATCH] vtfpp: add DDS file support --- include/vtfpp/DDS.h | 88 +++++++++++++++++++ src/vtfpp/DDS.cpp | 190 +++++++++++++++++++++++++++++++++++++++++ src/vtfpp/_vtfpp.cmake | 110 ++++++++++++------------ 3 files changed, 334 insertions(+), 54 deletions(-) create mode 100644 include/vtfpp/DDS.h create mode 100644 src/vtfpp/DDS.cpp diff --git a/include/vtfpp/DDS.h b/include/vtfpp/DDS.h new file mode 100644 index 00000000..67226cbf --- /dev/null +++ b/include/vtfpp/DDS.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include "ImageConversion.h" + +namespace vtfpp { + +constexpr uint32_t DDS_SIGNATURE = sourcepp::parser::binary::makeFourCC("DDS "); + +class DDS { +public: + struct CreationOptions { + ImageFormat outputFormat = FORMAT_DEFAULT; + float compressedFormatQuality = ImageConversion::DEFAULT_COMPRESSED_QUALITY; + ImageConversion::ResizeMethod widthResizeMethod = ImageConversion::ResizeMethod::POWER_OF_TWO_BIGGER; + ImageConversion::ResizeMethod heightResizeMethod = ImageConversion::ResizeMethod::POWER_OF_TWO_BIGGER; + ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::DEFAULT; + ImageConversion::ResizeBounds resizeBounds; + bool isCubeMap = false; + uint16_t initialDepth = 1; + bool computeMips = true; + }; + + DDS(); + + explicit DDS(std::vector&& ddsData); + + explicit DDS(std::span ddsData); + + explicit DDS(const std::filesystem::path& ddsPath); + + DDS(const DDS& other); + + DDS& operator=(const DDS& other); + + DDS(DDS&&) noexcept = default; + + DDS& operator=(DDS&&) noexcept = default; + + [[nodiscard]] explicit operator bool() const; + + static bool create(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& ddsPath, const CreationOptions& options); + + static bool create(ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& ddsPath, const CreationOptions& options); + + [[nodiscard]] static DDS create(std::span imageData, ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options); + + [[nodiscard]] static DDS create(ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options); + + static bool create(const std::filesystem::path& imagePath, const std::filesystem::path& ddsPath, const CreationOptions& options); + + [[nodiscard]] static DDS create(const std::filesystem::path& imagePath, const CreationOptions& options); + + [[nodiscard]] uint16_t getWidth() const; + + [[nodiscard]] uint16_t getHeight() const; + + [[nodiscard]] bool hasImageData() const; + + [[nodiscard]] ImageFormat getFormat() const; + + [[nodiscard]] uint8_t getMipCount() const; + + [[nodiscard]] std::span getImageDataRaw(uint8_t mip = 0) const; + + [[nodiscard]] std::span getImageDataRaw(uint8_t mip = 0); + + [[nodiscard]] std::vector getImageDataAs(ImageFormat newFormat, uint8_t mip = 0) const; + + [[nodiscard]] std::vector getImageDataAsRGBA8888(uint8_t mip = 0) const; + +protected: + std::vector data; + std::span imageData; + uint16_t width = 0; + uint16_t height = 0; + ImageFormat format = ImageFormat::EMPTY; + uint8_t mipCount = 1; + bool opened = false; +}; + +} // namespace vtfpp diff --git a/src/vtfpp/DDS.cpp b/src/vtfpp/DDS.cpp new file mode 100644 index 00000000..c779bafd --- /dev/null +++ b/src/vtfpp/DDS.cpp @@ -0,0 +1,190 @@ +#include + +#include + +#include +#include + +using namespace sourcepp; +using namespace vtfpp; + +constexpr uint32_t DXT1_SIGNATURE = sourcepp::parser::binary::makeFourCC("DXT1"); +constexpr uint32_t DXT2_SIGNATURE = sourcepp::parser::binary::makeFourCC("DXT2"); +constexpr uint32_t DXT3_SIGNATURE = sourcepp::parser::binary::makeFourCC("DXT3"); +constexpr uint32_t DXT4_SIGNATURE = sourcepp::parser::binary::makeFourCC("DXT4"); +constexpr uint32_t DXT5_SIGNATURE = sourcepp::parser::binary::makeFourCC("DXT5"); +constexpr uint32_t DX10_SIGNATURE = sourcepp::parser::binary::makeFourCC("DX10"); + +DDS::DDS(std::span ddsData) { + BufferStreamReadOnly stream{ddsData}; + + if (stream.read() != DDS_SIGNATURE) { + return; + } + + const auto DDS_PIXELFORMAT_LENGTH = sizeof(uint32_t) * 8; + const auto DDS_HEADER_LENGTH = sizeof(uint32_t) * 23 + sizeof(DDS_PIXELFORMAT_LENGTH); + + // Check header size + uint32_t headerLength = stream.read(); + if (headerLength != DDS_HEADER_LENGTH) { + return; + } + + // Read header fields + uint32_t headerFlags = stream.read(); + uint32_t imageHeight = stream.read(); + uint32_t imageWidth = stream.read(); + uint32_t imagePitch = stream.read(); + uint32_t imageDepth = stream.read(); + uint32_t imageMips = stream.read(); + + // Skip reserved section + stream.skip_u(sizeof(uint32_t) * 11); + + // Check pixel format subheader size + uint32_t pixelHeaderLength = stream.read(); + if (pixelHeaderLength != DDS_PIXELFORMAT_LENGTH) { + return; + } + + // Read pixel format subheader fields + uint32_t pixelHeaderFlags = stream.read(); + uint32_t pixelHeaderFourCC = stream.read(); + uint32_t pixelHeaderBitCount = stream.read(); + uint32_t pixelHeaderRedBitMask = stream.read(); + uint32_t pixelHeaderGreenBitMask = stream.read(); + uint32_t pixelHeaderBlueBitMask = stream.read(); + uint32_t pixelHeaderAlphaBitMask = stream.read(); + + // Read the rest of the main header + uint32_t headerCaps0 = stream.read(); + uint32_t headerCaps1 = stream.read(); + uint32_t headerCaps2 = stream.read(); + uint32_t headerCaps3 = stream.read(); + + // Skip reserved value + stream.skip_u(sizeof(uint32_t)); + + ImageFormat imageFormat = ImageFormat::EMPTY; + + // Check if we need to parse DX10 header + if (pixelHeaderFourCC == DX10_SIGNATURE) { + const auto DX10_HEADER_LENGTH = sizeof(uint32_t) * 5; + + uint32_t dx10HeaderFormat = stream.read(); + uint32_t dx10HeaderResourceDimension = stream.read(); + uint32_t dx10HeaderMiscFlags0 = stream.read(); + uint32_t dx10HeaderArraySize = stream.read(); + uint32_t dx10HeaderMiscFlags1 = stream.read(); + + } else { + // Get image format + switch (pixelHeaderFourCC) { + case DXT1_SIGNATURE: + imageFormat = ImageFormat::DXT1; + break; + case DXT2_SIGNATURE: + imageFormat = ImageFormat::DXT3; + break; + case DXT3_SIGNATURE: + imageFormat = ImageFormat::DXT3; + break; + case DXT4_SIGNATURE: + imageFormat = ImageFormat::DXT5; + break; + case DXT5_SIGNATURE: + imageFormat = ImageFormat::DXT5; + break; + } + } + + // Couldn't determine image format + if (imageFormat == ImageFormat::EMPTY) { + return; + } + + // Setup final fields + this->width = imageWidth; + this->height = imageHeight; + this->format = imageFormat; + this->mipCount = imageMips; + + // Read image data + this->data = stream.read_bytes(stream.size() - stream.tell()); + this->imageData = {this->data.begin(), this->data.end()}; +} + +DDS::DDS() { + this->opened = true; +} + +DDS::DDS(const DDS& other) { + *this = other; +} + +DDS& DDS::operator=(const DDS& other) { + this->opened = other.opened; + this->data = other.data; + this->imageData = {this->data.begin(), this->data.end()}; + this->width = other.width; + this->height = other.height; + this->format = other.format; + this->mipCount = other.mipCount; + + return *this; +} + +DDS::DDS(const std::filesystem::path& ddsPath) + : DDS(fs::readFileBuffer(ddsPath)) {} + +DDS::operator bool() const { + return this->opened; +} + +uint16_t DDS::getWidth() const { + return this->width; +} + +uint16_t DDS::getHeight() const { + return this->height; +} + +bool DDS::hasImageData() const { + return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0; +} + +ImageFormat DDS::getFormat() const { + return this->format; +} + +uint8_t DDS::getMipCount() const { + return this->mipCount; +} + +std::span DDS::getImageDataRaw(uint8_t mip) const { + if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, 0, 1, 0, 1, this->width, this->height, 0, 1)) { + return this->imageData.subspan(offset, length); + } + return {}; +} + +std::span DDS::getImageDataRaw(uint8_t mip) { + if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, 0, 1, 0, 1, this->width, this->height, 0, 1)) { + return this->imageData.subspan(offset, length); + } + return {}; +} + +std::vector DDS::getImageDataAs(ImageFormat newFormat, uint8_t mip) const { + const auto rawImageData = this->getImageDataRaw(mip); + if (rawImageData.empty()) { + return {}; + } + const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height); + return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, mipWidth, mipHeight); +} + +std::vector DDS::getImageDataAsRGBA8888(uint8_t mip) const { + return this->getImageDataAs(ImageFormat::RGBA8888, mip); +} diff --git a/src/vtfpp/_vtfpp.cmake b/src/vtfpp/_vtfpp.cmake index 5d2f4a06..b9c1a63a 100644 --- a/src/vtfpp/_vtfpp.cmake +++ b/src/vtfpp/_vtfpp.cmake @@ -1,54 +1,56 @@ -add_pretty_parser(vtfpp - DEPS bcdec libzstd_static miniz sourcepp_compression sourcepp_parser sourcepp_stb - PRECOMPILED_HEADERS - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/DistanceMapping.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/HOT.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageConversion.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageFormats.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImagePixel.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageQuantize.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/PPL.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/PSFrames.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/SHT.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/TTX.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/VBF.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/VTF.h" - "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/vtfpp.h" - SOURCES - "${CMAKE_CURRENT_LIST_DIR}/DistanceMapping.cpp" - "${CMAKE_CURRENT_LIST_DIR}/HOT.cpp" - "${CMAKE_CURRENT_LIST_DIR}/ImageConversion.cpp" - "${CMAKE_CURRENT_LIST_DIR}/ImageQuantize.cpp" - "${CMAKE_CURRENT_LIST_DIR}/PPL.cpp" - "${CMAKE_CURRENT_LIST_DIR}/PSFrames.cpp" - "${CMAKE_CURRENT_LIST_DIR}/SHT.cpp" - "${CMAKE_CURRENT_LIST_DIR}/TTX.cpp" - "${CMAKE_CURRENT_LIST_DIR}/VBF.cpp" - "${CMAKE_CURRENT_LIST_DIR}/VTF.cpp") - -sourcepp_add_tbb(sourcepp_vtfpp) -sourcepp_add_threads(sourcepp_vtfpp) - -if(SOURCEPP_VTFPP_BUILD_WITH_COMPRESSONATOR) - target_link_libraries(sourcepp_vtfpp PRIVATE CMP_Compressonator) - target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_BUILD_WITH_COMPRESSONATOR) -endif() -if(SOURCEPP_VTFPP_SUPPORT_EXR) - target_link_libraries(sourcepp_vtfpp PRIVATE tinyexr) - target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_EXR) -endif() -if(SOURCEPP_VTFPP_SUPPORT_JXL) - target_link_libraries(sourcepp_vtfpp PRIVATE jxl) - if(SOURCEPP_BUILD_WITH_THREADS) - target_link_libraries(sourcepp_vtfpp PRIVATE jxl_threads) - endif() - target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_JXL) -endif() -if(SOURCEPP_VTFPP_SUPPORT_QOI) - target_link_libraries(sourcepp_vtfpp PRIVATE qoi) - target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_QOI) -endif() -if(SOURCEPP_VTFPP_SUPPORT_WEBP) - target_link_libraries(sourcepp_vtfpp PRIVATE webp) - target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_WEBP) -endif() +add_pretty_parser(vtfpp + DEPS bcdec libzstd_static miniz sourcepp_compression sourcepp_parser sourcepp_stb + PRECOMPILED_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/DDS.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/DistanceMapping.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/HOT.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageConversion.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageFormats.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImagePixel.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/ImageQuantize.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/PPL.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/PSFrames.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/SHT.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/TTX.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/VBF.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/VTF.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/vtfpp/vtfpp.h" + SOURCES + "${CMAKE_CURRENT_LIST_DIR}/DDS.cpp" + "${CMAKE_CURRENT_LIST_DIR}/DistanceMapping.cpp" + "${CMAKE_CURRENT_LIST_DIR}/HOT.cpp" + "${CMAKE_CURRENT_LIST_DIR}/ImageConversion.cpp" + "${CMAKE_CURRENT_LIST_DIR}/ImageQuantize.cpp" + "${CMAKE_CURRENT_LIST_DIR}/PPL.cpp" + "${CMAKE_CURRENT_LIST_DIR}/PSFrames.cpp" + "${CMAKE_CURRENT_LIST_DIR}/SHT.cpp" + "${CMAKE_CURRENT_LIST_DIR}/TTX.cpp" + "${CMAKE_CURRENT_LIST_DIR}/VBF.cpp" + "${CMAKE_CURRENT_LIST_DIR}/VTF.cpp") + +sourcepp_add_tbb(sourcepp_vtfpp) +sourcepp_add_threads(sourcepp_vtfpp) + +if(SOURCEPP_VTFPP_BUILD_WITH_COMPRESSONATOR) + target_link_libraries(sourcepp_vtfpp PRIVATE CMP_Compressonator) + target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_BUILD_WITH_COMPRESSONATOR) +endif() +if(SOURCEPP_VTFPP_SUPPORT_EXR) + target_link_libraries(sourcepp_vtfpp PRIVATE tinyexr) + target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_EXR) +endif() +if(SOURCEPP_VTFPP_SUPPORT_JXL) + target_link_libraries(sourcepp_vtfpp PRIVATE jxl) + if(SOURCEPP_BUILD_WITH_THREADS) + target_link_libraries(sourcepp_vtfpp PRIVATE jxl_threads) + endif() + target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_JXL) +endif() +if(SOURCEPP_VTFPP_SUPPORT_QOI) + target_link_libraries(sourcepp_vtfpp PRIVATE qoi) + target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_QOI) +endif() +if(SOURCEPP_VTFPP_SUPPORT_WEBP) + target_link_libraries(sourcepp_vtfpp PRIVATE webp) + target_compile_definitions(sourcepp_vtfpp PUBLIC VTFPP_SUPPORT_WEBP) +endif()