11#include <unordered_map>
14#ifdef SOURCEPP_BUILD_WITH_TBB
18#ifdef SOURCEPP_BUILD_WITH_THREADS
23#include <BufferStream.h>
37[[nodiscard]] std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level,
CompressionMethod method) {
41 mz_ulong compressedSize = mz_compressBound(data.size());
42 std::vector<std::byte> out(compressedSize);
45 while ((status = mz_compress2(
reinterpret_cast<unsigned char*
>(out.data()), &compressedSize,
reinterpret_cast<const unsigned char*
>(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
47 out.resize(compressedSize);
50 if (status != MZ_OK) {
53 out.resize(compressedSize);
61 const auto expectedSize = ZSTD_compressBound(data.size());
62 std::vector<std::byte> out(expectedSize);
64 const auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
65 if (ZSTD_isError(compressedSize)) {
69 out.resize(compressedSize);
82template<std::
unsigned_
integral T,
bool ExistingDataIsSwizzled>
83constexpr void swizzleUncompressedImageData(std::span<std::byte> inputData, std::span<std::byte> outputData,
ImageFormat format, uint16_t width, uint16_t height, uint16_t depth) {
90 ](uint32_t x, uint32_t y, uint32_t z) {
91 auto widthL2m = widthL2;
92 auto heightL2m = heightL2;
93 auto depthL2m = depthL2;
95 uint32_t shiftCount = 0;
98 offset |= (z & 1) << shiftCount++;
101 if (heightL2m --> 0) {
102 offset |= (y & 1) << shiftCount++;
105 if (widthL2m --> 0) {
106 offset |= (x & 1) << shiftCount++;
109 }
while (x || y || z);
113 const auto* inputPtr =
reinterpret_cast<const T*
>(inputData.data());
114 auto* outputPtr =
reinterpret_cast<T*
>(outputData.data());
116 for (uint16_t x = 0; x < width; x++) {
117 for (uint16_t y = 0; y < height; y++) {
118 for (uint16_t z = 0; z < depth; z++) {
119 if constexpr (ExistingDataIsSwizzled) {
120 *outputPtr++ =
reinterpret_cast<const T*
>(inputData.data())[zIndex(x, y, z)];
122 reinterpret_cast<T*
>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
129template<
bool ExistingDataIsSwizzled>
130void swizzleUncompressedImageDataXBOX(std::span<std::byte> inputData, std::span<std::byte> outputData,
ImageFormat format, uint16_t width, uint16_t height, uint16_t depth) {
131 const auto zIndex = [
135 ](int32_t x, int32_t y, int32_t z) {
136 int widthL2m = widthL2;
137 int heightL2m = heightL2;
138 int depthL2m = depthL2;
140 uint32_t shiftCount = 0;
142 while (widthL2m > 0 || heightL2m > 0 || depthL2m > 0) {
144 offset |= (x & 1) << shiftCount++;
149 offset |= (y & 1) << shiftCount++;
154 offset |= (z & 1) << shiftCount++;
164 uint32_t linearIndex = 0;
165 for (uint16_t z = 0; z < depth; z++) {
166 for (uint16_t y = 0; y < height; y++) {
167 for (uint16_t x = 0; x < width; x++) {
168 const auto codedIndex = zIndex(x, y, z);
169 for (uint32_t b = 0; b < stride; b++) {
170 if constexpr (ExistingDataIsSwizzled) {
171 outputData[linearIndex * stride + b] = inputData[codedIndex * stride + b];
173 outputData[codedIndex * stride + b] = inputData[linearIndex * stride + b];
182template<
bool ConvertingFromSource>
183void swapImageDataEndianForConsole(std::span<std::byte> imageData,
ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth,
VTF::Platform platform) {
196 std::ranges::copy(newData, imageData.begin());
204 std::span dxtData{
reinterpret_cast<uint16_t*
>(imageData.data()), imageData.size() /
sizeof(uint16_t)};
206#ifdef SOURCEPP_BUILD_WITH_TBB
207 std::execution::par_unseq,
209 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
210 BufferStream::swap_endian(&value);
222 std::vector<std::byte> out(imageData.size());
223 for(
int mip = mipCount - 1; mip >= 0; mip--) {
225 for (
int frame = 0; frame < frameCount; frame++) {
226 for (
int face = 0; face < faceCount; face++) {
227 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height)) {
228 std::span imageDataSpan{imageData.data() + offset, length * mipDepth};
229 std::span outSpan{out.data() + offset, length * mipDepth};
231 ::swizzleUncompressedImageDataXBOX<ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
233 ::swizzleUncompressedImageData<uint32_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
235 ::swizzleUncompressedImageData<uint16_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
237 ::swizzleUncompressedImageData<uint8_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
243 std::memcpy(imageData.data(), out.data(), out.size());
247template<
bool ConvertingFromDDS>
248[[nodiscard]] std::vector<std::byte> convertBetweenDDSAndVTFMipOrderForXBOX(
bool padded, std::span<const std::byte> imageData,
ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth,
bool& ok) {
249 std::vector<std::byte> reorderedImageData;
251 BufferStream reorderedStream{reorderedImageData};
253 if constexpr (ConvertingFromDDS) {
254 for (
int i = mipCount - 1; i >= 0; i--) {
256 for (
int j = 0; j < frameCount; j++) {
257 for (
int k = 0; k < faceCount; k++) {
258 for (
int l = 0; l < mipDepth; l++) {
259 uint32_t oldOffset, length;
260 if (!
ImageFormatDetails::getDataPositionXBOX(oldOffset, length, padded, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
264 reorderedStream << imageData.subspan(oldOffset, length);
270 for (
int j = 0; j < frameCount; j++) {
271 for (
int k = 0; k < faceCount; k++) {
272 for (
int i = 0; i < mipCount; i++) {
274 for (
int l = 0; l < mipDepth; l++) {
275 uint32_t oldOffset, length;
276 if (!
ImageFormatDetails::getDataPosition(oldOffset, length, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
280 reorderedStream << imageData.subspan(oldOffset, length);
284 if (padded && j + 1 != frameCount && reorderedStream.tell() > 512) {
291 return reorderedImageData;
297 switch (this->
type) {
299 if (this->data.size() <=
sizeof(uint32_t)) {
302 return SHT{{
reinterpret_cast<const std::byte*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data())}};
306 if (this->data.size() !=
sizeof(uint32_t)) {
309 return *
reinterpret_cast<const uint32_t*
>(this->data.data());
311 if (this->data.size() !=
sizeof(uint32_t)) {
314 return std::make_tuple(
315 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 0),
316 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 1),
317 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 2),
318 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 3));
321 if (this->data.size() <=
sizeof(uint32_t)) {
324 return std::string(
reinterpret_cast<const char*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data()));
326 if (this->data.size() <=
sizeof(uint32_t)) {
329 return HOT{{
reinterpret_cast<const std::byte*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data())}};
331 if (this->data.size() !=
sizeof(uint32_t) +
sizeof(
PCC)) {
334 BufferStreamReadOnly stream{this->data};
335 if (stream.read<uint32_t>() !=
sizeof(
PCC)) {
354 static constexpr auto PALETTE_FRAME_SIZE = 256 *
sizeof(ImagePixel::BGRA8888);
355 if (this->data.size() % PALETTE_FRAME_SIZE != 0 || PALETTE_FRAME_SIZE * frame > this->data.size()) {
358 return {this->data.data() + PALETTE_FRAME_SIZE * frame, this->data.data() + PALETTE_FRAME_SIZE * (frame + 1)};
378 return std::get<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>>(this->
convertData());
394 if (this->data.size() <
sizeof(uint32_t) * 2) {
397 return static_cast<int16_t
>(BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff);
401 if (this->data.size() <
sizeof(uint32_t) * 2) {
404 const auto method =
static_cast<int16_t
>((BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff0000) >> 16);
412 if (this->data.size() < ((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2) *
sizeof(uint32_t)) {
415 return BufferStream{this->data}.skip<uint32_t>((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2).read<uint32_t>();
422VTF::VTF(std::vector<std::byte>&& vtfData,
bool parseHeaderOnly,
bool hdr)
423 :
data(std::move(vtfData)) {
424 BufferStreamReadOnly stream{this->data};
426 if (
const auto signature = stream.read<uint32_t>(); signature ==
VTF_SIGNATURE) {
427 stream >> this->platform;
428 if (this->platform != PLATFORM_PC) {
431 stream >> this->version;
436 stream.set_big_endian(
true);
437 stream >> this->platform;
438 if (this->platform != PLATFORM_X360 && this->platform != PLATFORM_PS3_ORANGEBOX && this->platform != PLATFORM_PS3_PORTAL2) {
441 stream >> this->version;
442 if (this->version != 8) {
447 this->platform = PLATFORM_PS3_PORTAL2;
453 stream >> this->platform;
454 if (this->platform != PLATFORM_XBOX) {
457 stream >> this->version;
458 if (this->version != 0) {
467 const auto headerSize = stream.read<uint32_t>();
469 const auto readResources = [
this, &stream](uint32_t resourceCount) {
471 this->resources.reserve(resourceCount);
472 for (
int i = 0; i < resourceCount; i++) {
473 auto& [type, flags_, data_] = this->resources.emplace_back();
475 auto typeAndFlags = stream.read<uint32_t>();
476 if (stream.is_big_endian()) {
478 BufferStream::swap_endian(&typeAndFlags);
482 data_ = stream.read_span<std::byte>(4);
485 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(data_.data()));
494 std::ranges::sort(this->resources, [](
const Resource& lhs,
const Resource& rhs) {
504 return *
reinterpret_cast<uint32_t*
>(lhs.
data.data()) < *
reinterpret_cast<uint32_t*
>(rhs.
data.data());
509 for (
auto& resource : this->resources) {
512 const auto lastOffset = *
reinterpret_cast<uint32_t*
>(lastResource->data.data());
513 const auto currentOffset = *
reinterpret_cast<uint32_t*
>(resource.data.data());
514 const auto curPos = stream.tell();
515 stream.seek(lastOffset);
516 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
517 stream.seek(
static_cast<int64_t
>(curPos));
519 lastResource = &resource;
523 const auto offset = *
reinterpret_cast<uint32_t*
>(lastResource->data.data());
524 const auto curPos = stream.tell();
526 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
527 stream.seek(
static_cast<int64_t
>(curPos));
532 const auto postHeaderReadTransform = [
this, hdr] {
536 if (this->format ==
ImageFormat::DXT1 && this->flags & (FLAG_V0_ONE_BIT_ALPHA | FLAG_V0_MULTI_BIT_ALPHA)) {
542 this->format =
static_cast<ImageFormat>(
static_cast<int32_t
>(this->format) - 3);
549 if (this->platform == PLATFORM_PC) {
558 if (this->flags & FLAG_V0_NO_MIP && this->mipCount > 1) {
559 this->removeFlags(FLAG_V0_NO_MIP);
563 switch (this->platform) {
564 case PLATFORM_UNKNOWN:
571 .read(this->frameCount)
572 .read(this->startFrame)
574 .read(this->reflectivity[0])
575 .read(this->reflectivity[1])
576 .read(this->reflectivity[2])
578 .read(this->bumpMapScale)
580 .read(this->mipCount);
582 postHeaderReadTransform();
586 stream >> this->thumbnailWidth >> this->thumbnailHeight;
587 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
593 if (this->version < 2) {
596 stream.read(this->depth);
599 if (parseHeaderOnly) {
604 if (this->version >= 3) {
606 auto resourceCount = stream.read<uint32_t>();
608 readResources(resourceCount);
610 this->opened = stream.tell() == headerSize;
612 if (this->opened && this->version >= 6) {
615 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
616 const auto faceCount = this->getFaceCount();
617 std::vector<std::byte> decompressedImageData(
ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth));
618 uint32_t oldOffset = 0;
619 for (
int i = this->mipCount - 1; i >= 0; i--) {
620 for (
int j = 0; j < this->frameCount; j++) {
621 for (
int k = 0; k < faceCount; k++) {
622 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
623 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->getDepth())) {
625 mz_ulong decompressedImageDataSize = newLength * this->depth;
626 switch (auxResource->getDataAsAuxCompressionMethod()) {
629 if (mz_uncompress(
reinterpret_cast<unsigned char*
>(decompressedImageData.data() + newOffset), &decompressedImageDataSize,
reinterpret_cast<const unsigned char*
>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
630 this->opened =
false;
635 if (
const auto decompressedSize = ZSTD_decompress(decompressedImageData.data() + newOffset, decompressedImageDataSize, imageResource->data.data() + oldOffset, oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
636 this->opened =
false;
646 oldOffset += oldLength;
656 this->opened = stream.tell() == headerSize;
658 this->resources.reserve(2);
660 if (this->hasThumbnailData()) {
661 this->resources.push_back({
667 if (this->hasImageData()) {
668 this->resources.push_back({
671 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
677 this->flagsExtra = resource->getDataAsFlags();
681 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
682 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
687 case PLATFORM_XBOX: {
688 if (this->platform == PLATFORM_XBOX) {
689 uint16_t preloadSize = 0, imageOffset = 0;
695 .read(this->frameCount)
698 .read(this->reflectivity[0])
699 .read(this->reflectivity[1])
700 .read(this->reflectivity[2])
701 .read(this->bumpMapScale)
703 .read(this->thumbnailWidth)
704 .read(this->thumbnailHeight)
705 .read(this->fallbackWidth)
706 .read(this->fallbackHeight)
707 .read(this->consoleMipScale)
710 const bool headerSizeIsAccurate = stream.tell() == headerSize;
715 postHeaderReadTransform();
718 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? 6 : 1;
720 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
725 if (!parseHeaderOnly) {
726 this->resources.push_back({
729 .data = stream.read_span<std::byte>(thumbnailSize),
732 stream.skip(thumbnailSize);
737 const auto paletteSize = 256 *
sizeof(ImagePixel::BGRA8888) * this->frameCount;
738 if (!parseHeaderOnly) {
739 this->resources.push_back({
742 .data = stream.read_span<std::byte>(paletteSize),
745 stream.skip_u(paletteSize);
751 std::vector<std::byte> reorderedFallbackData;
752 if (this->hasFallbackData()) {
753 if (stream.tell() + fallbackSize != preloadSize) {
756 if (stream.tell() + fallbackSize != preloadSize) {
757 this->opened =
false;
760 this->fallbackMipCount = 1;
762 this->flags |= FLAG_V0_NO_MIP;
764 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(
false, stream.read_span<std::byte>(fallbackSize), this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, ok);
766 this->opened =
false;
769 ::swapImageDataEndianForConsole<true>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, this->platform);
772 this->opened = headerSizeIsAccurate;
773 if (parseHeaderOnly) {
778 std::vector<std::byte> reorderedImageData;
779 if (this->hasImageData()) {
780 reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(
true, stream.seek(imageOffset).read_span<std::byte>(imageSize), this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, ok);
782 this->opened =
false;
785 ::swapImageDataEndianForConsole<true>(reorderedImageData, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, this->platform);
789 if (this->hasFallbackData()) {
792 if (this->hasImageData()) {
799 case PLATFORM_PS3_ORANGEBOX:
800 case PLATFORM_PS3_PORTAL2: {
801 uint8_t resourceCount;
807 .read(this->frameCount)
809 .read(this->consoleMipScale)
811 .read(this->reflectivity[0])
812 .read(this->reflectivity[1])
813 .read(this->reflectivity[2])
814 .read(this->bumpMapScale)
816 .skip<math::Vec4ui8>()
819 postHeaderReadTransform();
822 if (this->platform == PLATFORM_PS3_PORTAL2) {
823 stream.skip<uint32_t>();
828 if (parseHeaderOnly) {
833 this->resources.reserve(resourceCount);
834 readResources(resourceCount);
836 this->opened = stream.tell() == headerSize;
839 for (
const auto& resource : this->resources) {
843 this->setResourceInternal(resource.type, *decompressedData);
853 ::swapImageDataEndianForConsole<true>(resource.data, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
855 if (this->platform == PLATFORM_PS3_ORANGEBOX) {
857 const auto reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(
false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
858 if (!ok || reorderedFallbackData.size() != resource.data.size()) {
859 this->opened =
false;
862 std::memcpy(resource.data.data(), reorderedFallbackData.data(), resource.data.size());
864 ::swapImageDataEndianForConsole<true>(resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
866 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(resource.data.data()));
874VTF::VTF(std::span<const std::byte> vtfData,
bool parseHeaderOnly,
bool hdr)
875 :
VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly, hdr) {}
877VTF::VTF(
const std::filesystem::path& vtfPath,
bool parseHeaderOnly)
878 :
VTF(
fs::readFileBuffer(vtfPath), parseHeaderOnly, [](std::
string path) {
880 return path.ends_with(
".hdr.vtf") || path.ends_with(
".hdr.360.vtf") || path.ends_with(
".hdr.ps3.vtf");
881 }(vtfPath.filename().
string())) {}
889 this->data = other.
data;
891 this->width = other.
width;
892 this->height = other.
height;
898 this->format = other.
format;
907 this->depth = other.
depth;
909 this->resources.clear();
910 for (
const auto& [otherType, otherFlags, otherData] : other.
resources) {
911 auto& [
type, flags_, data_] = this->resources.emplace_back();
914 data_ = {this->data.data() + (otherData.data() - other.
data.data()), otherData.size()};
926VTF::operator bool()
const {
933 for (
int i = 0; i < writer.
mipCount; i++) {
936 for (
int l = 0; l < writer.
depth; l++) {
996 if (!writer.
setImage(imageDataResized,
format, requestedResizeWidth, requestedResizeHeight, options.
filter)) {
1005 return writer.
bake(vtfPath);
1009 std::vector<std::byte> imageData;
1012 return create(imageData,
format, requestedResizeWidth, requestedResizeHeight, vtfPath, options);
1023 if (!writer.
setImage(imageDataResized,
format, requestedResizeWidth, requestedResizeHeight, options.
filter)) {
1038 std::vector<std::byte> imageData;
1041 return create(imageData,
format, requestedResizeWidth, requestedResizeHeight, options);
1056 return writer.
bake(vtfPath);
1076 return this->platform;
1080 if (this->platform == newPlatform) {
1085 const auto oldPlatform = this->platform;
1087 switch (newPlatform) {
1091 switch (oldPlatform) {
1118 this->platform = newPlatform;
1122 this->
regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, 6, this->depth);
1137 this->thumbnailHeight = 0;
1158 return this->version;
1166 const auto faceCount = (this->
flags &
FLAG_V0_ENVMAP) ? (newVersion < 1 || newVersion > 4 ? 6 : 7) : 1;
1167 this->
regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->depth);
1171 const bool srgb = this->
isSRGB();
1188 this->version = newVersion;
1230 if (newWidth == 0 || newHeight == 0) {
1240 if (this->width == newWidth && this->height == newHeight) {
1243 auto newMipCount = this->mipCount;
1245 newMipCount = maxMipCount;
1251 this->frameCount = 1;
1253 this->width = newWidth;
1254 this->height = newHeight;
1298 if (this->version >= 5) {
1300 }
else if (this->version == 4) {
1304 if (this->version >= 5) {
1306 }
else if (this->version == 4) {
1340 return this->format;
1351 this->format = newFormat;
1354 const auto oldFormat = this->format;
1356 this->
regenerateImageData(newFormat, this->width +
math::paddingForAlignment(4, this->width), this->height +
math::paddingForAlignment(4, this->height), this->mipCount, this->frameCount, this->
getFaceCount(), this->depth, filter, quality);
1358 this->
regenerateImageData(newFormat, this->width, this->height, this->mipCount, this->frameCount, this->
getFaceCount(), this->depth, filter, quality);
1362 const auto fallbackConverted =
ImageConversion::convertSeveralImageDataToFormat(fallbackResource->data, oldFormat, this->format,
ImageDimensions::getMaximumMipCount(this->fallbackWidth, this->fallbackHeight), this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, quality);
1368 return this->mipCount;
1376 if (newMipCount < 1) {
1379 newMipCount = maxMipCount;
1398 if (this->mipCount <= 1) {
1404 auto* outputDataPtr = imageResource->data.data();
1407#ifdef SOURCEPP_BUILD_WITH_THREADS
1408 std::vector<std::future<void>> futures;
1409 futures.reserve(this->frameCount * faceCount * this->depth);
1411 for (
int j = 0; j < this->frameCount; j++) {
1412 for (
int k = 0; k < faceCount; k++) {
1413#ifdef SOURCEPP_BUILD_WITH_THREADS
1414 futures.push_back(std::async(std::launch::async, [
this, filter, outputDataPtr, faceCount, j, k] {
1416 for (
int i = 1; i < this->mipCount; i++) {
1419 for (
int l = 0; l < mipDepth; l++) {
1420 auto mip =
ImageConversion::resizeImageData(this->
getImageDataRaw(i - 1, j, k, l), this->format, mipWidthM1, mipWidth, mipHeightM1, mipHeight, this->
isSRGB(), this->
getFlagsExtra() &
FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
1421 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, l, this->depth) && mip.size() == length) {
1422 std::memcpy(outputDataPtr + offset, mip.data(), length);
1426#ifdef SOURCEPP_BUILD_WITH_THREADS
1428 if (futures.size() >= std::thread::hardware_concurrency()) {
1429 for (
auto& future : futures) {
1437#ifdef SOURCEPP_BUILD_WITH_THREADS
1438 for (
auto& future : futures) {
1445 return this->frameCount;
1467 if (this->platform !=
PLATFORM_PC || this->version >= 6) {
1473 if (this->version >= 1 && this->version <= 4 && expectedLength < image->
data.size()) {
1476 if (expectedLength == image->data.size()) {
1527 static constexpr auto getReflectivityForImage = [](
const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1528 static constexpr auto getReflectivityForPixel = [](
const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1530 math::Vec3f ref{
static_cast<float>(pixel->r()),
static_cast<float>(pixel->g()),
static_cast<float>(pixel->b())};
1532 ref = ref / 255.f * 0.922f;
1541 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1542 out += getReflectivityForPixel(
reinterpret_cast<ImagePixel::RGBA8888*
>(rgba8888Data.data() + i));
1544 return out / (rgba8888Data.size() /
sizeof(ImagePixel::RGBA8888));
1549#ifdef SOURCEPP_BUILD_WITH_THREADS
1550 if (this->frameCount > 1 || faceCount > 1 || this->depth > 1) {
1551 std::vector<std::future<math::Vec3f>> futures;
1552 futures.reserve(this->frameCount * faceCount * this->depth);
1555 for (
int j = 0; j < this->frameCount; j++) {
1556 for (
int k = 0; k < faceCount; k++) {
1557 for (
int l = 0; l < this->depth; l++) {
1558 futures.push_back(std::async(std::launch::async, [
this, j, k, l] {
1559 return getReflectivityForImage(*
this, j, k, l);
1561 if (futures.size() >= std::thread::hardware_concurrency()) {
1562 for (
auto& future : futures) {
1571 for (
auto& future : futures) {
1574 this->
reflectivity /= this->frameCount * faceCount * this->depth;
1576 this->
reflectivity = getReflectivityForImage(*
this, 0, 0, 0);
1580 for (
int j = 0; j < this->frameCount; j++) {
1581 for (
int k = 0; k < faceCount; k++) {
1582 for (
int l = 0; l < this->depth; l++) {
1583 this->
reflectivity += getReflectivityForImage(*
this, j, k, l);
1587 this->
reflectivity /= this->frameCount * faceCount * this->depth;
1608 return this->thumbnailHeight;
1632 return this->version >= 3;
1636 return this->resources;
1640 for (
const auto& resource : this->resources) {
1641 if (resource.type ==
type) {
1649 for (
auto& resource : this->resources) {
1650 if (resource.type ==
type) {
1658 if (
const auto* resource = this->
getResource(
type); resource && resource->
data.size() == data_.size()) {
1659 std::memcpy(resource->data.data(), data_.data(), data_.size());
1664 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1665 for (
const auto& [type_, flags_, dataSpan] : this->resources) {
1666 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1670 if (data_.empty()) {
1671 resourceData.erase(
type);
1673 resourceData[
type] = {{data_.begin(), data_.end()}, 0};
1678 BufferStream writer{this->data};
1680 for (
auto resourceType : resourceData | std::views::keys) {
1681 if (!resourceData.contains(resourceType)) {
1684 auto& [specificResourceData, offset] = resourceData[resourceType];
1685 if (resourceType ==
type) {
1686 if (!this->data.data()) {
1687 this->data.reserve(offset + specificResourceData.size());
1692 .data = {this->data.data() + offset, specificResourceData.size()},
1695 *resourcePtr = newResource;
1697 this->resources.push_back(newResource);
1699 }
else if (!resourceData.contains(resourceType)) {
1702 offset = writer.tell();
1703 writer.write(specificResourceData);
1705 this->data.resize(writer.size());
1707 for (
auto& [type_, flags_, dataSpan] : this->resources) {
1708 if (resourceData.contains(type_)) {
1709 const auto& [specificResourceData, offset] = resourceData[type_];
1710 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1716 std::erase_if(this->resources, [
type](
const Resource& resource) {
return resource.
type ==
type; });
1720 if (!newWidth) newWidth = 1;
1721 if (!newHeight) newHeight = 1;
1722 if (!newMipCount) newMipCount = 1;
1723 if (!newFrameCount) newFrameCount = 1;
1724 if (!newFaceCount) newFaceCount = 1;
1725 if (!newDepth) newDepth = 1;
1727 if (newMipCount > 1) {
1730 if (
ImageFormatDetails::compressed(newFormat) && ((newWidth > 4 && !std::has_single_bit(newWidth)) || (newHeight > 4 && !std::has_single_bit(newHeight)))) {
1740 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1744 std::vector<std::byte> newImageData;
1746 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1750 for (
int i = newMipCount - 1; i >= 0; i--) {
1753 int sourceMipIndex = 0;
1754 for (
int mip = 0, bestScore = std::numeric_limits<int>::max(); mip < this->mipCount; mip++) {
1756 if (mipWidth == newMipWidth && mipHeight == newMipHeight) {
1759 const auto widthDiff =
static_cast<int>(mipWidth) -
static_cast<int>(newMipWidth);
1760 const auto heightDiff =
static_cast<int>(mipHeight) -
static_cast<int>(newMipHeight);
1761 if (widthDiff < 0 || heightDiff < 0) {
1764 if (
const auto score = widthDiff + heightDiff; score < bestScore) {
1766 sourceMipIndex = mip;
1771 for (
int j = 0; j < newFrameCount; j++) {
1772 for (
int k = 0; k < newFaceCount; k++) {
1773 for (
int l = 0; l < newMipDepth; l++) {
1776 std::vector<std::byte> imageBacking;
1777 if (sourceMipWidth != newMipWidth || sourceMipHeight != newMipHeight) {
1779 imageSpan = imageBacking;
1781 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newDepth) && imageSpan.size() == length) {
1782 std::memcpy(newImageData.data() + offset, imageSpan.data(), length);
1789 if (this->format != newFormat) {
1797 this->format = newFormat;
1798 this->width = newWidth;
1799 this->height = newHeight;
1800 this->mipCount = newMipCount;
1801 this->frameCount = newFrameCount;
1802 if (newFaceCount > 1) {
1807 this->depth = newDepth;
1813 if (
const auto targetSize = 256 *
sizeof(ImagePixel::BGRA8888) * this->frameCount; palette->data.size() != targetSize) {
1814 std::vector<std::byte> paletteData{palette->data.begin(), palette->data.end()};
1815 paletteData.resize(targetSize);
1827 return palette->getDataAsPalette(frame);
1832std::vector<std::byte>
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 {
1841 auto sht = shtResource->getDataAsParticleSheet();
1842 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1843 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1851 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1852 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 *
static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1853 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 *
static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1854 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 *
static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1855 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 *
static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1857 if (x1 > x2) [[unlikely]] {
1860 if (y1 > y2) [[unlikely]] {
1863 spriteWidth = x2 - x1;
1864 spriteWidth = y2 - y1;
1866 const auto out =
ImageConversion::cropImageData(this->
getImageDataRaw(mip, frame, face, slice), this->
getFormat(), this->
getWidth(mip), spriteWidth, x1, this->
getHeight(mip), spriteHeight, y1);
1874std::vector<std::byte>
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 {
1875 return ImageConversion::convertImageDataToFormat(this->
getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->
getFormat(), newFormat, spriteWidth, spriteHeight);
1878std::vector<std::byte>
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 {
1883 std::vector<std::byte> particleSheetData;
1884 BufferStream writer{particleSheetData};
1886 const auto bakedSheet = value.
bake();
1887 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1888 particleSheetData.resize(writer.size());
1898 std::vector<std::byte> pccData;
1899 BufferStream writer{pccData};
1908 pccData.resize(writer.size());
1927 BufferStream writer{&lodData,
sizeof(lodData)};
1929 writer << u << v << u360 << v360;
1947 std::vector<std::byte> keyValuesData;
1948 BufferStream writer{keyValuesData};
1950 writer.write<uint32_t>(value.size()).write(value,
false);
1951 keyValuesData.resize(writer.size());
1961 std::vector<std::byte> authorInfo;
1962 BufferStream writer{authorInfo};
1964 writer.write<uint32_t>(value.size()).write(value,
false);
1965 authorInfo.resize(writer.size());
1975 std::vector<std::byte> hotspotData;
1976 BufferStream writer{hotspotData};
1978 const auto bakedHotspotData = value.
bake();
1979 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1980 hotspotData.resize(writer.size());
1994 if (newCompressionLevel < 0) {
2021 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, this->
getFaceCount(), this->width, this->height, slice, this->depth)) {
2022 return imageResource->data.subspan(offset, length);
2030 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, this->
getFaceCount(), this->width, this->height, slice, this->depth)) {
2031 return imageResource->data.subspan(offset, length);
2038 const auto rawImageData = this->
getImageDataRaw(mip, frame, face, slice);
2039 if (rawImageData.empty()) {
2057 if (imageData_.empty()) {
2062 uint16_t resizedWidth = width_, resizedHeight = height_;
2069 mip = newMipCount - 1;
2074 this->
regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 6 ? 6 : face) : 0, slice + 1);
2083 if (!imageResource) {
2086 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->mipCount, frame, this->frameCount, face, faceCount, this->width, this->height, slice, this->depth)) {
2087 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
2089 if (width_ != newWidth || height_ != newHeight) {
2092 if (format_ != this->format) {
2095 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
2102 int inputWidth, inputHeight, inputFrameCount;
2106 if (imageData_.empty() || inputFormat ==
ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2111 auto [requestedResizeWidth, requestedResizeHeight] = resizeBounds.
clamp(inputWidth, inputHeight);
2114 if (inputFrameCount == 1) {
2115 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2117 return this->
setImage(imageDataResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame, face, slice, quality);
2119 return this->
setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice, quality);
2123 bool allSuccess =
true;
2125 for (
int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
2126 std::span currentFrameData{imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize};
2127 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2129 if (!this->
setImage(currentFrameResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2132 }
else if (!this->
setImage(currentFrameData, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2135 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
2148 if (
auto data_ = this->
saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
2160 return thumbnailResource->data;
2167 return thumbnailResource->data;
2174 if (rawThumbnailData.empty()) {
2191 this->thumbnailHeight = height_;
2196 int inputWidth, inputHeight, inputFrameCount;
2200 if (imageData_.empty() || inputFormat ==
ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2205 if (inputFrameCount == 1) {
2206 this->
setThumbnail(imageData_, inputFormat, inputWidth, inputHeight, quality);
2212 this->
setThumbnail({imageData_.data(), frameSize}, inputFormat, inputWidth, inputHeight, quality);
2224 this->thumbnailHeight = 1;
2225 std::array newThumbnail{
2226 static_cast<std::byte
>(
static_cast<uint8_t
>(std::clamp(this->
reflectivity[0], 0.f, 1.f) * 255.f)),
2227 static_cast<std::byte
>(
static_cast<uint8_t
>(std::clamp(this->
reflectivity[1], 0.f, 1.f) * 255.f)),
2228 static_cast<std::byte
>(
static_cast<uint8_t
>(std::clamp(this->
reflectivity[2], 0.f, 1.f) * 255.f)),
2234 this->thumbnailHeight = 16;
2235 this->
setResourceInternal(
Resource::TYPE_THUMBNAIL_DATA,
ImageConversion::convertImageDataToFormat(
ImageConversion::resizeImageData(this->
getImageDataRaw(), this->format, this->width, this->
thumbnailWidth, this->height, this->thumbnailHeight, this->
isSRGB(), this->
getFlagsExtra() &
FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter), this->format, this->
thumbnailFormat, this->
thumbnailWidth, this->thumbnailHeight, quality));
2242 this->thumbnailHeight = 0;
2263 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->
fallbackMipCount, frame, this->frameCount, face, this->
getFaceCount(), this->
fallbackWidth, this->
fallbackHeight)) {
2264 return fallbackResource->data.subspan(offset, length);
2272 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->
fallbackMipCount, frame, this->frameCount, face, this->
getFaceCount(), this->
fallbackWidth, this->
fallbackHeight)) {
2273 return fallbackResource->data.subspan(offset, length);
2281 if (rawFallbackData.empty()) {
2307 std::vector<std::byte> fallbackData;
2311 for (
int j = 0; j < this->frameCount; j++) {
2312 for (
int k = 0; k < faceCount; k++) {
2313 auto mip =
ImageConversion::resizeImageData(this->
getImageDataRaw(0, j, k, 0), this->format, this->width, mipWidth, this->height, mipHeight, this->
isSRGB(), this->
getFlagsExtra() &
FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2314 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->
fallbackMipCount, j, this->frameCount, k, faceCount, this->
fallbackWidth, this->
fallbackHeight) && mip.size() == length) {
2315 std::memcpy(fallbackData.data() + offset, mip.data(), length);
2336 if (
auto data_ = this->
saveFallbackToFile(mip, frame, face, fileFormat); !data_.empty()) {
2357 uint64_t vtfSize = 0;
2359 switch (this->platform) {
2363 switch (this->version) {
2366 vtfSize +=
sizeof(uint64_t);
2367 vtfSize +=
sizeof(uint32_t) * (2 + this->mipCount * this->frameCount * this->
getFaceCount());
2372 vtfSize += 11 +
sizeof(uint32_t) +
sizeof(uint64_t) * this->
getResources().size();
2373 for (
const auto& [resourceType, resourceFlags, resourceData] : this->
getResources()) {
2375 vtfSize += resourceData.size();
2379 vtfSize +=
sizeof(uint16_t);
2382 vtfSize +=
sizeof(uint32_t) * 9 +
sizeof(
float) * 4 +
sizeof(uint16_t) * 4 +
sizeof(uint8_t) * 3;
2383 if (this->version < 3) {
2388 vtfSize += thumbnailResource->data.size();
2392 vtfSize += imageResource->data.size();
2395 vtfSize +=
static_cast<uint64_t
>(
static_cast<double>(imageResource->data.size()) / 1.75);
2404 vtfSize +=
sizeof(uint32_t) * 6 +
sizeof(
float) * 4 +
sizeof(uint16_t) * 6 +
sizeof(uint8_t) * 6;
2407 vtfSize += thumbnailResource->data.size();
2411 vtfSize += paletteResource->data.size();
2421 if (vtfSize > 512) {
2426 vtfSize +=
sizeof(uint32_t);
2429 vtfSize +=
sizeof(uint32_t) * 7 +
sizeof(
float) * 4 +
sizeof(uint16_t) * 5 +
sizeof(uint8_t) * 6;
2430 vtfSize +=
sizeof(uint64_t) * this->
getResources().size();
2432 for (
const auto& [resourceType, resourceFlags, resourceData] : this->
getResources()) {
2434 vtfSize += resourceData.size();
2439 vtfSize += imageResource->data.size();
2442 vtfSize +=
static_cast<uint64_t
>(
static_cast<double>(imageResource->data.size()) / 1.75);
2451 std::vector<std::byte> out;
2452 BufferStream writer{out};
2454 const auto writeResources = [&writer](uint64_t headerLengthPos,
const std::vector<Resource>& sortedResources) -> uint64_t {
2455 auto resourceHeaderCurPos = writer.tell();
2456 writer.pad<uint64_t>(sortedResources.size());
2457 auto resourceDataCurPos = writer.tell();
2458 writer.seek_u(headerLengthPos).write<uint32_t>(resourceDataCurPos);
2460 uint64_t resourceHeaderImagePos = 0;
2461 for (
const auto& resource : sortedResources) {
2462 writer.seek_u(resourceHeaderCurPos);
2464 uint32_t resourceType = resource.type;
2468 if (writer.is_big_endian()) {
2470 BufferStream::swap_endian(&resourceType);
2472 writer.write<uint32_t>(resourceType);
2475 resourceHeaderImagePos = writer.tell();
2476 writer.write<uint32_t>(0);
2477 resourceHeaderCurPos = writer.tell();
2482 writer.write(resource.data);
2483 resourceHeaderCurPos = writer.tell();
2485 writer.write<uint32_t>(resourceDataCurPos);
2486 resourceHeaderCurPos = writer.tell();
2487 writer.seek_u(resourceDataCurPos).write(resource.data);
2488 resourceDataCurPos = writer.tell();
2491 if (resourceHeaderImagePos) {
2492 writer.seek_u(resourceHeaderImagePos).write<uint32_t>(resourceDataCurPos);
2493 for (
auto& resource : sortedResources) {
2495 writer.seek_u(resourceDataCurPos).write(resource.data);
2499 return resourceDataCurPos;
2505 auto bakeFormat = this->format;
2506 auto bakeFlags = this->
flags;
2514 bakeFormat =
static_cast<ImageFormat>(
static_cast<int32_t
>(this->format) + 3);
2523 switch (this->platform) {
2530 .write(this->version);
2532 const auto headerLengthPos = writer.tell();
2533 writer.write<uint32_t>(0);
2537 .write(this->height)
2539 .write(this->frameCount)
2548 .write(this->mipCount)
2551 .write(this->thumbnailHeight);
2553 if (this->version >= 2) {
2554 writer << this->depth;
2557 if (this->version < 3) {
2559 const auto headerSize = writer.tell();
2560 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
2563 writer.write(thumbnailResource->data);
2566 writer.write(imageResource->data);
2569 std::vector<std::byte> auxCompressionResourceData;
2570 std::vector<std::byte> compressedImageResourceData;
2571 bool hasAuxCompression =
false;
2574 if (hasAuxCompression) {
2576 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) *
sizeof(uint32_t));
2577 BufferStream auxWriter{auxCompressionResourceData,
false};
2584 .write<uint32_t>(auxCompressionResourceData.size() -
sizeof(uint32_t))
2588 for (
int i = this->mipCount - 1; i >= 0; i--) {
2589 for (
int j = 0; j < this->frameCount; j++) {
2590 for (
int k = 0; k < faceCount; k++) {
2591 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->depth)) {
2592 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->depth},
static_cast<int16_t
>(this->
compressionLevel), this->
compressionMethod);
2593 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
2594 auxWriter.write<uint32_t>(compressedData.size());
2602 writer.pad(3).write<uint32_t>(this->
getResources().size() + hasAuxCompression + (this->
flagsExtra != 0)).pad(8);
2604 std::vector<Resource> sortedResources = this->
getResources();
2605 auto flagsExtraBacking = std::bit_cast<std::array<std::byte, 4>>(this->
flagsExtra);
2607 sortedResources.push_back({
2610 .data = flagsExtraBacking,
2613 if (hasAuxCompression) {
2614 for (
auto& resource : sortedResources) {
2616 resource.data = compressedImageResourceData;
2620 sortedResources.push_back({
2623 .data = auxCompressionResourceData,
2626 std::ranges::sort(sortedResources, [](
const Resource& lhs,
const Resource& rhs) {
2629 writeResources(headerLengthPos, sortedResources);
2635 writer.write<uint32_t>(0);
2637 const auto headerSizePos = writer.tell();
2642 .write(this->height)
2644 .write(this->frameCount);
2645 const auto preloadSizePos = writer.tell();
2646 writer.write<uint16_t>(0);
2647 const auto imageOffsetPos = writer.tell();
2654 .write(this->format)
2656 .write(this->thumbnailHeight)
2662 const auto headerSize = writer.tell();
2663 writer.seek_u(headerSizePos).write<uint32_t>(headerSize).seek_u(headerSize);
2666 writer.write(thumbnailResource->data);
2671 writer.write(paletteResource->data);
2675 bool hasFallbackResource =
false;
2677 hasFallbackResource =
true;
2678 std::vector<std::byte> reorderedFallbackData{fallbackResource->data.begin(), fallbackResource->data.end()};
2683 writer.write(reorderedFallbackData);
2689 const auto preloadSize = writer.tell();
2690 writer.seek_u(preloadSizePos).write<uint32_t>(preloadSize).seek_u(preloadSize);
2692 if (hasFallbackResource) {
2695 const auto imageOffset = writer.tell();
2696 writer.seek_u(imageOffsetPos).write<uint16_t>(imageOffset).seek_u(imageOffset);
2699 std::vector<std::byte> reorderedImageData{imageResource->data.begin(), imageResource->data.end()};
2700 ::swapImageDataEndianForConsole<false>(reorderedImageData, this->format, this->mipCount, this->frameCount, this->
getFaceCount(), this->width, this->height, this->depth, this->platform);
2702 reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(
true, reorderedImageData, this->format, this->mipCount, this->frameCount, this->
getFaceCount(), this->width, this->height, this->depth, ok);
2704 writer.write(reorderedImageData);
2709 if (writer.tell() > 512) {
2719 writer.set_big_endian(
true);
2723 writer.set_big_endian(
true);
2724 writer << this->platform;
2726 writer.write<uint32_t>(8);
2728 const auto headerLengthPos = writer.tell();
2733 .write(this->height)
2735 .write(this->frameCount);
2736 const auto preloadPos = writer.tell();
2740 .write<uint8_t>(this->resources.size())
2746 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[0] * 255)), 0, 255))
2747 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[1] * 255)), 0, 255))
2748 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[2] * 255)), 0, 255))
2749 .write<uint8_t>(255);
2750 const auto compressionPos = writer.tell();
2751 writer.write<uint32_t>(0);
2755 writer.write<uint32_t>(0);
2758 std::vector<std::byte> thumbnailResourceData;
2759 std::vector<std::byte> imageResourceData;
2760 std::vector<Resource> sortedResources = this->
getResources();
2761 for (
auto& resource : sortedResources) {
2763 thumbnailResourceData = {resource.data.begin(), resource.data.end()};
2764 ::swapImageDataEndianForConsole<false>(thumbnailResourceData, this->
thumbnailFormat, 1, 1, 1, this->
thumbnailWidth, this->thumbnailHeight, 1, this->platform);
2765 resource.data = thumbnailResourceData;
2769 imageResourceData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(
false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2770 if (!ok || imageResourceData.size() != resource.data.size()) {
2774 imageResourceData = {resource.data.begin(), resource.data.end()};
2776 ::swapImageDataEndianForConsole<false>(imageResourceData, this->format, this->mipCount, this->frameCount, this->
getFaceCount(), this->width, this->height, this->depth, this->platform);
2786 fixedCompressionLevel = 6;
2789 imageResourceData = std::move(*compressedData);
2790 const auto curPos = writer.tell();
2791 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
2795 resource.data = imageResourceData;
2799 std::ranges::sort(sortedResources, [](
const Resource& lhs,
const Resource& rhs) {
2802 const auto resourceDataImagePos = writeResources(headerLengthPos, sortedResources);
2803 writer.seek_u(preloadPos).write(std::max<uint16_t>(resourceDataImagePos, 2048));
2807 out.resize(writer.size());
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
std::vector< std::byte > bake() const
std::vector< std::byte > bake() const
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
void setParallaxCorrectedCubemapResource(const Resource::PCC &value)
bool setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth=1)
void removeKeyValuesDataResource()
void computeFallback(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
CompressionMethod compressionMethod
void computeReflectivity()
uint8_t getThumbnailWidth() const
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
bool hasNativeResourceSupport() const
ImageFormat getFormat() const
void setPlatform(Platform newPlatform)
uint16_t getHeight(uint8_t mip=0) const
VTF & operator=(const VTF &other)
void removeFlagsExtra(uint32_t flags_)
uint8_t getPaddedFallbackHeight(uint8_t mip=0) const
uint16_t getCompressionLevel() const
sourcepp::math::Vec3f reflectivity
uint16_t getWidth(uint8_t mip=0) const
void setThumbnail(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
bool setRecommendedMipCount()
static bool createInternal(VTF &writer, CreationOptions options)
std::vector< std::byte > 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
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
void regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newDepth, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
bool hasFallbackData() const
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
void setCompressionLevel(int16_t newCompressionLevel)
ImageConversion::ResizeMethod imageHeightResizeMethod
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
std::vector< std::byte > 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 th...
void addFlagsExtra(uint32_t flags_)
float getBumpMapScale() const
void setKeyValuesDataResource(std::string_view value)
void computeTransparencyFlags()
std::vector< std::byte > saveFallbackToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
std::vector< std::byte > data
Platform getPlatform() const
std::vector< std::byte > getPaletteResourceFrame(uint16_t frame=0) const
void addFlags(uint32_t flags_)
uint8_t getFaceCount() const
ImageFormat thumbnailFormat
void setFlags(uint32_t flags_)
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
void setBumpMapScale(float newBumpMapScale)
ImageFormat getThumbnailFormat() const
uint16_t getStartFrame() const
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
bool hasThumbnailData() const
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
const std::vector< Resource > & getResources() const
void removeHotspotDataResource()
void setVersion(uint32_t newVersion)
std::vector< std::byte > saveImageToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Resource * getResourceInternal(Resource::Type type)
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
std::vector< std::byte > bake() const
bool hasImageData() const
ImageConversion::ResizeMethod imageWidthResizeMethod
uint16_t compressionLevel
std::span< const std::byte > getFallbackDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
void removeParticleSheetResource()
std::vector< std::byte > getFallbackDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
uint8_t getFallbackWidth(uint8_t mip=0) const
static bool create(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path &vtfPath, const CreationOptions &options)
std::vector< std::byte > getImageDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
uint16_t getFrameCount() const
uint8_t getMipCount() const
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
void removeResourceInternal(Resource::Type type)
std::vector< std::byte > getFallbackDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
bool setFrameCount(uint16_t newFrameCount)
void removeAuthorInfoResource()
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
std::vector< std::byte > getImageDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
void setStartFrame(uint16_t newStartFrame)
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
std::span< const std::byte > getImageDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
sourcepp::math::Vec3f getReflectivity() const
uint8_t getFallbackMipCount() const
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
void setFlagsExtra(uint32_t flags_)
uint8_t getConsoleMipScale() const
uint16_t getDepth(uint8_t mip=0) const
uint16_t getPaddedHeight(uint8_t mip=0) const
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
std::span< const std::byte > getThumbnailDataRaw() const
bool setMipCount(uint8_t newMipCount)
void setExtendedFlagsResource(uint32_t value)
void removeParallaxCorrectedCubemapResource()
CompressionMethod getCompressionMethod() const
uint16_t getPaddedWidth(uint8_t mip=0) const
void setAuthorInfoResource(std::string_view value)
void setCRCResource(uint32_t value)
void setCompressionMethod(CompressionMethod newCompressionMethod)
bool setFaceCount(bool isCubeMap)
uint8_t getThumbnailHeight() const
void setConsoleMipScale(uint8_t consoleMipScale_)
const Resource * getResource(Resource::Type type) const
uint32_t getFlags() const
void setParticleSheetResource(const SHT &value)
@ FLAG_V0_MULTI_BIT_ALPHA
uint8_t getFallbackHeight(uint8_t mip=0) const
uint32_t getVersion() const
@ FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE
void setHotspotDataResource(const HOT &value)
void removeFlags(uint32_t flags_)
uint32_t getFlagsExtra() const
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
bool setDepth(uint16_t newDepth)
uint64_t estimateBakeSize() const
std::vector< Resource > resources
uint8_t getPaddedFallbackWidth(uint8_t mip=0) const
bool setImage(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
std::vector< std::byte > 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 th...
void removeExtendedFlagsResource()
std::optional< std::vector< std::byte > > compressValveLZMA(std::span< const std::byte > data, uint8_t compressLevel=6)
constexpr auto VALVE_LZMA_SIGNATURE
std::optional< std::vector< std::byte > > decompressValveLZMA(std::span< const std::byte > data)
bool writeFileBuffer(const std::filesystem::path &filepath, std::span< const std::byte > buffer)
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
constexpr uint32_t log2ceil(uint32_t value)
void toLower(std::string &input)
void invertGreenChannelForImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel on the given image data. Meant for converting normal maps between OpenGL and...
std::vector< std::byte > convertFileToImageData(std::span< const std::byte > fileData, ImageFormat &format, int &width, int &height, int &frameCount)
void setResizedDims(uint16_t &width, ResizeMethod widthResize, uint16_t &height, ResizeMethod heightResize)
Set the new image dimensions given a resize method.
std::vector< std::byte > convertImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint16_t width, uint16_t height, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts an image from one format to another.
std::vector< std::byte > convertSeveralImageDataToFormat(std::span< const std::byte > imageData, ImageFormat oldFormat, ImageFormat newFormat, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
void gammaCorrectImageData(std::span< std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, float gamma)
Perform gamma correction on the given image data. Will not perform gamma correction if the input imag...
std::vector< std::byte > cropImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t xOffset, uint16_t height, uint16_t newHeight, uint16_t yOffset)
Crops the given image to the new dimensions. If the image format is compressed it will be converted t...
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT, float quality=-1.f)
Converts image data to the given file format (PNG or EXR by default). Quality parameter (0-1) is igno...
std::vector< std::byte > resizeImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t newWidth, uint16_t height, uint16_t newHeight, bool srgb, bool premultipliedAlpha, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr uint8_t getMaximumMipCount(uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the largest mip count a texture with the given width, height, and depth can contain.
constexpr std::pair< uint16_t, uint16_t > getMipDims(uint8_t mip, uint16_t width, uint16_t height, bool addCompressedFormatPadding=false)
Get the width and height at a given mip level.
constexpr uint16_t getMipDim(uint8_t mip, uint16_t dim, bool addCompressedFormatPadding=false)
Get the dimension at a given mip level.
std::vector< std::byte > convertP8ImageDataToBGRA8888(std::span< const std::byte > paletteData, std::span< const std::byte > imageData)
Converts a paletted image to something usable.
constexpr uint32_t VTF_SIGNATURE
constexpr uint32_t XTF_SIGNATURE
constexpr uint32_t VTFX_SIGNATURE
constexpr uint32_t VTF3_SIGNATURE
@ SOURCEPP_CONSOLE_RGBA16161616_HDR
@ SOURCEPP_RGBA16161616_HDR
ResizeFilter resizeFilter
std::pair< uint16_t, uint16_t > clamp(uint16_t width, uint16_t height) const
sourcepp::math::Mat4x4f inverseTransform
sourcepp::math::Vec4d origin
std::string getDataAsAuthorInfo() const
HOT getDataAsHotspotData() const
std::tuple< uint8_t, uint8_t, uint8_t, uint8_t > getDataAsLODControlInfo() const
ConvertedData convertData() const
std::string getDataAsKeyValuesData() const
int16_t getDataAsAuxCompressionLevel() const
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT, PCC > ConvertedData
CompressionMethod getDataAsAuxCompressionMethod() const
SHT getDataAsParticleSheet() const
uint32_t getDataAsFlags() const
PCC getDataAsParallaxCorrectedCubemap() const
uint32_t getDataAsCRC() const
uint32_t getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const
@ TYPE_PARALLAX_CORRECTED_CUBEMAP
@ TYPE_PARTICLE_SHEET_DATA
std::vector< std::byte > getDataAsPalette(uint16_t frame) const
std::span< std::byte > data
uint16_t initialFrameCount
ImageConversion::ResizeBounds resizeBounds
float compressedFormatQuality
bool computeTransparencyFlags
ImageConversion::ResizeFilter filter
ImageConversion::ResizeMethod heightResizeMethod
CompressionMethod compressionMethod
ImageConversion::ResizeMethod widthResizeMethod