SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
VTF.cpp
Go to the documentation of this file.
1// ReSharper disable CppDFATimeOver
2// ReSharper disable CppRedundantParentheses
3// ReSharper disable CppRedundantQualifier
4// ReSharper disable CppUseStructuredBinding
5
6#include <vtfpp/VTF.h>
7
8#include <algorithm>
9#include <cstring>
10#include <ranges>
11#include <unordered_map>
12#include <utility>
13
14#ifdef SOURCEPP_BUILD_WITH_TBB
15#include <execution>
16#endif
17
18#ifdef SOURCEPP_BUILD_WITH_THREADS
19#include <future>
20#include <thread>
21#endif
22
23#include <BufferStream.h>
24#include <miniz.h>
25#include <zstd.h>
26
29#include <vtfpp/ImagePixel.h>
30#include <vtfpp/ImageQuantize.h>
31
32using namespace sourcepp;
33using namespace vtfpp;
34
35namespace {
36
37[[nodiscard]] std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level, CompressionMethod method) {
38 switch (method) {
39 using enum CompressionMethod;
40 case DEFLATE: {
41 mz_ulong compressedSize = mz_compressBound(data.size());
42 std::vector<std::byte> out(compressedSize);
43
44 int status = MZ_OK;
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) {
46 compressedSize *= 2;
47 out.resize(compressedSize);
48 }
49
50 if (status != MZ_OK) {
51 return {};
52 }
53 out.resize(compressedSize);
54 return out;
55 }
56 case ZSTD: {
57 if (level < 0) {
58 level = 6;
59 }
60
61 const auto expectedSize = ZSTD_compressBound(data.size());
62 std::vector<std::byte> out(expectedSize);
63
64 const auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
65 if (ZSTD_isError(compressedSize)) {
66 return {};
67 }
68
69 out.resize(compressedSize);
70 return out;
71 }
72 case CONSOLE_LZMA: {
73 if (const auto out = compression::compressValveLZMA(data, level)) {
74 return *out;
75 }
76 return {};
77 }
78 }
79 return {};
80}
81
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) {
84 width *= ImageFormatDetails::bpp(format) / (sizeof(T) * 8);
85
86 const auto zIndex = [
87 widthL2 = static_cast<int>(math::log2ceil(width)),
88 heightL2 = static_cast<int>(math::log2ceil(height)),
89 depthL2 = static_cast<int>(math::log2ceil(depth))
90 ](uint32_t x, uint32_t y, uint32_t z) {
91 auto widthL2m = widthL2;
92 auto heightL2m = heightL2;
93 auto depthL2m = depthL2;
94 uint32_t offset = 0;
95 uint32_t shiftCount = 0;
96 do {
97 if (depthL2m --> 0) {
98 offset |= (z & 1) << shiftCount++;
99 z >>= 1;
100 }
101 if (heightL2m --> 0) {
102 offset |= (y & 1) << shiftCount++;
103 y >>= 1;
104 }
105 if (widthL2m --> 0) {
106 offset |= (x & 1) << shiftCount++;
107 x >>= 1;
108 }
109 } while (x || y || z);
110 return offset;
111 };
112
113 const auto* inputPtr = reinterpret_cast<const T*>(inputData.data());
114 auto* outputPtr = reinterpret_cast<T*>(outputData.data());
115
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)];
121 } else {
122 reinterpret_cast<T*>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
123 }
124 }
125 }
126 }
127}
128
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 = [
132 widthL2 = static_cast<int>(math::log2ceil(width)),
133 heightL2 = static_cast<int>(math::log2ceil(height)),
134 depthL2 = static_cast<int>(math::log2ceil(depth))
135 ](int32_t x, int32_t y, int32_t z) {
136 int widthL2m = widthL2;
137 int heightL2m = heightL2;
138 int depthL2m = depthL2;
139 uint32_t offset = 0;
140 uint32_t shiftCount = 0;
141
142 while (widthL2m > 0 || heightL2m > 0 || depthL2m > 0) {
143 if (widthL2m > 0) {
144 offset |= (x & 1) << shiftCount++;
145 x >>= 1;
146 widthL2m--;
147 }
148 if (heightL2m > 0) {
149 offset |= (y & 1) << shiftCount++;
150 y >>= 1;
151 heightL2m--;
152 }
153 if (depthL2m > 0) {
154 offset |= (z & 1) << shiftCount++;
155 z >>= 1;
156 depthL2m--;
157 }
158 }
159 return offset;
160 };
161
162 const auto stride = ImageFormatDetails::bpp(format) / 8;
163
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];
172 } else {
173 outputData[codedIndex * stride + b] = inputData[linearIndex * stride + b];
174 }
175 }
176 linearIndex++;
177 }
178 }
179 }
180}
181
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) {
184 if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) {
185 return;
186 }
187
188 if (platform == VTF::PLATFORM_X360) {
189 switch (format) {
190 using enum ImageFormat;
191 case BGRA8888:
192 case BGRX8888:
193 case UVWQ8888:
194 case UVLX8888: {
195 const auto newData = ImageConversion::convertSeveralImageDataToFormat(imageData, ARGB8888, BGRA8888, mipCount, frameCount, faceCount, width, height, depth);
196 std::ranges::copy(newData, imageData.begin());
197 break;
198 }
199 case DXT1:
201 case DXT3:
202 case DXT5:
203 case UV88: {
204 std::span dxtData{reinterpret_cast<uint16_t*>(imageData.data()), imageData.size() / sizeof(uint16_t)};
205 std::for_each(
206#ifdef SOURCEPP_BUILD_WITH_TBB
207 std::execution::par_unseq,
208#endif
209 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
210 BufferStream::swap_endian(&value);
211 });
212 break;
213 }
214 default:
215 break;
216 }
217 }
218
219 // todo(vtfpp): should we enable 16-bit wide and 8-bit wide formats outside XBOX?
220 if ((platform == VTF::PLATFORM_XBOX || platform == VTF::PLATFORM_PS3_ORANGEBOX || platform == VTF::PLATFORM_PS3_PORTAL2) && !ImageFormatDetails::compressed(format) && (ImageFormatDetails::bpp(format) % 32 == 0 || (platform == VTF::PLATFORM_XBOX && (ImageFormatDetails::bpp(format) % 16 == 0 || ImageFormatDetails::bpp(format) % 8 == 0)))) {
221 const bool compressed = ImageFormatDetails::compressed(format);
222 std::vector<std::byte> out(imageData.size());
223 for(int mip = mipCount - 1; mip >= 0; mip--) {
224 const auto [mipWidth, mipHeight, mipDepth] = ImageDimensions::getMipDims(mip, width, height, depth, compressed);
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};
230 if (platform == VTF::PLATFORM_XBOX) {
231 ::swizzleUncompressedImageDataXBOX<ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
232 } else if (ImageFormatDetails::bpp(format) % 32 == 0) {
233 ::swizzleUncompressedImageData<uint32_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
234 } else if (ImageFormatDetails::bpp(format) % 16 == 0) {
235 ::swizzleUncompressedImageData<uint16_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
236 } else /*if (ImageFormatDetails::bpp(format) % 8 == 0)*/ {
237 ::swizzleUncompressedImageData<uint8_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, mipDepth);
238 }
239 }
240 }
241 }
242 }
243 std::memcpy(imageData.data(), out.data(), out.size());
244 }
245}
246
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;
250 reorderedImageData.resize(ImageFormatDetails::getDataLengthXBOX(padded, format, mipCount, frameCount, faceCount, width, height, depth));
251 BufferStream reorderedStream{reorderedImageData};
252
253 if constexpr (ConvertingFromDDS) {
254 for (int i = mipCount - 1; i >= 0; i--) {
255 const auto mipDepth = ImageDimensions::getMipDim(i, depth);
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)) {
261 ok = false;
262 return {};
263 }
264 reorderedStream << imageData.subspan(oldOffset, length);
265 }
266 }
267 }
268 }
269 } else {
270 for (int j = 0; j < frameCount; j++) {
271 for (int k = 0; k < faceCount; k++) {
272 for (int i = 0; i < mipCount; i++) {
273 const auto mipDepth = ImageDimensions::getMipDim(i, depth);
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)) {
277 ok = false;
278 return {};
279 }
280 reorderedStream << imageData.subspan(oldOffset, length);
281 }
282 }
283 }
284 if (padded && j + 1 != frameCount && reorderedStream.tell() > 512) {
285 reorderedStream.pad(math::paddingForAlignment(512, reorderedStream.tell()));
286 }
287 }
288 }
289
290 ok = true;
291 return reorderedImageData;
292}
293
294} // namespace
295
297 switch (this->type) {
299 if (this->data.size() <= sizeof(uint32_t)) {
300 return {};
301 }
302 return SHT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
303 case TYPE_CRC:
306 if (this->data.size() != sizeof(uint32_t)) {
307 return {};
308 }
309 return *reinterpret_cast<const uint32_t*>(this->data.data());
311 if (this->data.size() != sizeof(uint32_t)) {
312 return {};
313 }
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));
320 case TYPE_AUTHOR_INFO:
321 if (this->data.size() <= sizeof(uint32_t)) {
322 return "";
323 }
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)) {
327 return {};
328 }
329 return HOT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
330 default:
331 break;
332 }
333 return {};
334}
335
336std::vector<std::byte> Resource::getDataAsPalette(uint16_t frame) const {
337 static constexpr auto PALETTE_FRAME_SIZE = 256 * sizeof(ImagePixel::BGRA8888);
338 if (this->data.size() % PALETTE_FRAME_SIZE != 0 || PALETTE_FRAME_SIZE * frame > this->data.size()) {
339 return {};
340 }
341 return {this->data.data() + PALETTE_FRAME_SIZE * frame, this->data.data() + PALETTE_FRAME_SIZE * (frame + 1)};
342}
343
345 return std::get<SHT>(this->convertData());
346}
347
348uint32_t Resource::getDataAsCRC() const {
349 return std::get<uint32_t>(this->convertData());
350}
351
352uint32_t Resource::getDataAsFlags() const {
353 return std::get<uint32_t>(this->convertData());
354}
355
356std::tuple<uint8_t, uint8_t, uint8_t, uint8_t> Resource::getDataAsLODControlInfo() const {
357 return std::get<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>>(this->convertData());
358}
359
361 return std::get<std::string>(this->convertData());
362}
363
364std::string Resource::getDataAsAuthorInfo() const {
365 return std::get<std::string>(this->convertData());
366}
367
369 return std::get<HOT>(this->convertData());
370}
371
373 if (this->data.size() < sizeof(uint32_t) * 2) {
374 return 0;
375 }
376 return static_cast<int16_t>(BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff);
377}
378
380 if (this->data.size() < sizeof(uint32_t) * 2) {
382 }
383 const auto method = static_cast<int16_t>((BufferStream{this->data}.skip<uint32_t>().read<uint32_t>() & 0xffff0000) >> 16);
384 if (method <= 0) {
386 }
387 return static_cast<CompressionMethod>(method);
388}
389
390uint32_t Resource::getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const {
391 if (this->data.size() < ((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2) * sizeof(uint32_t)) {
392 return 0;
393 }
394 return BufferStream{this->data}.skip<uint32_t>((mipCount - 1 - mip) * frameCount * faceCount + frame * faceCount + face + 2).read<uint32_t>();
395}
396
398 this->opened = true;
399}
400
401VTF::VTF(std::vector<std::byte>&& vtfData, bool parseHeaderOnly, bool hdr)
402 : data(std::move(vtfData)) {
403 BufferStreamReadOnly stream{this->data};
404
405 if (const auto signature = stream.read<uint32_t>(); signature == VTF_SIGNATURE) {
406 stream >> this->platform;
407 if (this->platform != PLATFORM_PC) {
408 return;
409 }
410 stream >> this->version;
411 if (this->version > 6) {
412 return;
413 }
414 } else if (signature == VTFX_SIGNATURE || signature == VTF3_SIGNATURE) {
415 stream.set_big_endian(true);
416 stream >> this->platform;
417 if (this->platform != PLATFORM_X360 && this->platform != PLATFORM_PS3_ORANGEBOX && this->platform != PLATFORM_PS3_PORTAL2) {
418 return;
419 }
420 stream >> this->version;
421 if (this->version != 8) {
422 return;
423 }
424 // Now fix up the actual version as it would be on PC
425 if (signature == VTF3_SIGNATURE) {
426 this->platform = PLATFORM_PS3_PORTAL2;
427 this->version = 5;
428 } else {
429 this->version = 4;
430 }
431 } else if (signature == XTF_SIGNATURE) {
432 stream >> this->platform;
433 if (this->platform != PLATFORM_XBOX) {
434 return;
435 }
436 stream >> this->version;
437 if (this->version != 0) {
438 return;
439 }
440 // Now fix up the actual version as it would be on PC
441 this->version = 2;
442 } else {
443 return;
444 }
445
446 const auto headerSize = stream.read<uint32_t>();
447
448 const auto readResources = [this, &stream](uint32_t resourceCount) {
449 // Read resource header info
450 this->resources.reserve(resourceCount);
451 for (int i = 0; i < resourceCount; i++) {
452 auto& [type, flags_, data_] = this->resources.emplace_back();
453
454 auto typeAndFlags = stream.read<uint32_t>();
455 if (stream.is_big_endian()) {
456 // This field is little-endian
457 BufferStream::swap_endian(&typeAndFlags);
458 }
459 type = static_cast<Resource::Type>(typeAndFlags & 0xffffff); // last 3 bytes
460 flags_ = static_cast<Resource::Flags>(typeAndFlags >> 24); // first byte
461 data_ = stream.read_span<std::byte>(4);
462
463 if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) {
464 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(data_.data()));
465 }
466 }
467
468 // Sort resources by their offset, in case certain VTFs are written
469 // weirdly and have resource data written out of order. So far I have
470 // found only one VTF in an official Valve game where this is the case.
471 // UPDATE: We do this intentionally to write image data at the end now!
472 // It fixes mip skipping issues, and should still work fine in every branch.
473 std::ranges::sort(this->resources, [](const Resource& lhs, const Resource& rhs) {
475 return lhs.type < rhs.type;
476 }
478 return true;
479 }
481 return false;
482 }
483 return *reinterpret_cast<uint32_t*>(lhs.data.data()) < *reinterpret_cast<uint32_t*>(rhs.data.data());
484 });
485
486 // Fix up data spans to point to the actual data
487 Resource* lastResource = nullptr;
488 for (auto& resource : this->resources) {
489 if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) {
490 if (lastResource) {
491 const auto lastOffset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
492 const auto currentOffset = *reinterpret_cast<uint32_t*>(resource.data.data());
493 const auto curPos = stream.tell();
494 stream.seek(lastOffset);
495 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
496 stream.seek(static_cast<int64_t>(curPos));
497 }
498 lastResource = &resource;
499 }
500 }
501 if (lastResource) {
502 const auto offset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
503 const auto curPos = stream.tell();
504 stream.seek(offset);
505 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
506 stream.seek(static_cast<int64_t>(curPos));
507 }
508 };
509
510 // A couple tweaks to fix engine bugs or branch differences
511 const auto postHeaderReadTransform = [this, hdr] {
512 // Change the format to DXT1_ONE_BIT_ALPHA to get compressonator to recognize it.
513 // No source game recognizes this format, so we will do additional transform in bake back to DXT1.
514 // We also need to check MULTI_BIT_ALPHA flag because stupid third party tools will sometimes set it???
515 if (this->format == ImageFormat::DXT1 && this->flags & (FLAG_V0_ONE_BIT_ALPHA | FLAG_V0_MULTI_BIT_ALPHA)) {
516 this->format = ImageFormat::DXT1_ONE_BIT_ALPHA;
517 }
518 // If the version is below 7.5, NV_NULL / ATI2N / ATI1N are at different positions in the enum.
519 // It is safe to do this because these formats didn't exist before v7.5.
520 if (this->version < 5 && (this->format == ImageFormat::RGBA1010102 || this->format == ImageFormat::BGRA1010102 || this->format == ImageFormat::R16F)) {
521 this->format = static_cast<ImageFormat>(static_cast<int32_t>(this->format) - 3);
522 }
523 // If this is an HDR VTF, and it has one of the following formats, it's actually compressed.
524 if (hdr) {
525 if (this->format == ImageFormat::BGRA8888) {
527 } else if (this->format == ImageFormat::RGBA16161616) {
528 if (this->platform == PLATFORM_PC) {
530 } else {
532 }
533 }
534 }
535 // We need to apply this transform because of the broken Borealis skybox in HL2.
536 // Thanks Valve! If this transform isn't applied it breaks conversion to console formats.
537 if (this->flags & FLAG_V0_NO_MIP && this->mipCount > 1) {
538 this->removeFlags(FLAG_V0_NO_MIP);
539 }
540 };
541
542 switch (this->platform) {
543 case PLATFORM_UNKNOWN:
544 return;
545 case PLATFORM_PC: {
546 stream
547 .read(this->width)
548 .read(this->height)
549 .read(this->flags)
550 .read(this->frameCount)
551 .read(this->startFrame)
552 .skip(4)
553 .read(this->reflectivity[0])
554 .read(this->reflectivity[1])
555 .read(this->reflectivity[2])
556 .skip(4)
557 .read(this->bumpMapScale)
558 .read(this->format)
559 .read(this->mipCount);
560
561 postHeaderReadTransform();
562
563 // This will always be DXT1
564 stream.skip<ImageFormat>();
565 stream >> this->thumbnailWidth >> this->thumbnailHeight;
566 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
567 this->thumbnailFormat = ImageFormat::EMPTY;
568 } else {
569 this->thumbnailFormat = ImageFormat::DXT1;
570 }
571
572 if (this->version < 2) {
573 this->depth = 1;
574 } else {
575 stream.read(this->depth);
576 }
577
578 if (parseHeaderOnly) {
579 this->opened = true;
580 return;
581 }
582
583 if (this->version >= 3) {
584 stream.skip(3);
585 auto resourceCount = stream.read<uint32_t>();
586 stream.skip(8);
587 readResources(resourceCount);
588
589 this->opened = stream.tell() == headerSize;
590
591 if (this->opened && this->version >= 6) {
592 const auto* auxResource = this->getResource(Resource::TYPE_AUX_COMPRESSION);
593 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
594 if (auxResource && imageResource) {
595 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
596 const auto faceCount = this->getFaceCount();
597 std::vector<std::byte> decompressedImageData(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth));
598 uint32_t oldOffset = 0;
599 for (int i = this->mipCount - 1; i >= 0; i--) {
600 for (int j = 0; j < this->frameCount; j++) {
601 for (int k = 0; k < faceCount; k++) {
602 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
603 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())) {
604 // Keep in mind that slices are compressed together
605 mz_ulong decompressedImageDataSize = newLength * this->depth;
606 switch (auxResource->getDataAsAuxCompressionMethod()) {
607 using enum CompressionMethod;
608 case DEFLATE:
609 if (mz_uncompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
610 this->opened = false;
611 return;
612 }
613 break;
614 case ZSTD:
615 if (const auto decompressedSize = ZSTD_decompress(decompressedImageData.data() + newOffset, decompressedImageDataSize, imageResource->data.data() + oldOffset, oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
616 this->opened = false;
617 return;
618 }
619 break;
620 case CONSOLE_LZMA:
621 // Shouldn't be here!
623 break;
624 }
625 }
626 oldOffset += oldLength;
627 }
628 }
629 }
630 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, decompressedImageData);
631 }
632 }
633 }
634 } else {
635 stream.skip(math::paddingForAlignment(16, stream.tell()));
636 this->opened = stream.tell() == headerSize;
637
638 this->resources.reserve(2);
639
640 if (this->hasThumbnailData()) {
641 this->resources.push_back({
643 .flags = Resource::FLAG_NONE,
644 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
645 });
646 }
647 if (this->hasImageData()) {
648 this->resources.push_back({
650 .flags = Resource::FLAG_NONE,
651 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
652 });
653 }
654 }
655
656 if (const auto* resource = this->getResource(Resource::TYPE_SOURCEPP_FLAGS)) {
657 this->flagsExtra = resource->getDataAsFlags();
658 this->removeResourceInternal(Resource::TYPE_SOURCEPP_FLAGS);
659 }
660 if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
661 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
662 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
663 this->removeResourceInternal(Resource::TYPE_AUX_COMPRESSION);
664 }
665 return;
666 }
667 case PLATFORM_XBOX: {
668 if (this->platform == PLATFORM_XBOX) {
669 uint16_t preloadSize = 0, imageOffset = 0;
670 stream
671 .read(this->flags)
672 .read(this->width)
673 .read(this->height)
674 .read(this->depth)
675 .read(this->frameCount)
676 .read(preloadSize)
677 .read(imageOffset)
678 .read(this->reflectivity[0])
679 .read(this->reflectivity[1])
680 .read(this->reflectivity[2])
681 .read(this->bumpMapScale)
682 .read(this->format)
683 .read(this->thumbnailWidth)
684 .read(this->thumbnailHeight)
685 .read(this->fallbackWidth)
686 .read(this->fallbackHeight)
687 .read(this->consoleMipScale)
688 .skip<uint8_t>(); // padding
689
690 const bool headerSizeIsAccurate = stream.tell() == headerSize;
691
692 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth);
693 this->fallbackMipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->fallbackWidth, this->fallbackHeight);
694
695 postHeaderReadTransform();
696
697 // Can't use VTF::getFaceCount yet because there's no image data
698 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? 6 : 1;
699
700 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
701 this->thumbnailFormat = ImageFormat::EMPTY;
702 } else {
703 this->thumbnailFormat = ImageFormat::RGB888;
704 const auto thumbnailSize = ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight);
705 if (!parseHeaderOnly) {
706 this->resources.push_back({
708 .flags = Resource::FLAG_NONE,
709 .data = stream.read_span<std::byte>(thumbnailSize),
710 });
711 } else {
712 stream.skip(thumbnailSize);
713 }
714 }
715
716 if (this->format == ImageFormat::P8) {
717 const auto paletteSize = 256 * sizeof(ImagePixel::BGRA8888) * this->frameCount;
718 if (!parseHeaderOnly) {
719 this->resources.push_back({
721 .flags = Resource::FLAG_NONE,
722 .data = stream.read_span<std::byte>(paletteSize),
723 });
724 } else {
725 stream.skip_u(paletteSize);
726 }
727 }
728
729 bool ok;
730 auto fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
731 std::vector<std::byte> reorderedFallbackData;
732 if (this->hasFallbackData()) {
733 if (stream.tell() + fallbackSize != preloadSize) {
734 // A couple XTFs that shipped with HL2 are missing the NO_MIP flag. We can detect them by checking the size of the fallback
735 fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, 1, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
736 if (stream.tell() + fallbackSize != preloadSize) {
737 this->opened = false;
738 return;
739 }
740 this->fallbackMipCount = 1;
741 this->mipCount = 1;
742 this->flags |= FLAG_V0_NO_MIP;
743 }
744 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(false, stream.read_span<std::byte>(fallbackSize), this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, ok);
745 if (!ok) {
746 this->opened = false;
747 return;
748 }
749 ::swapImageDataEndianForConsole<true>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, this->platform);
750 }
751
752 this->opened = headerSizeIsAccurate;
753 if (parseHeaderOnly) {
754 return;
755 }
756
757 const auto imageSize = ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth);
758 std::vector<std::byte> reorderedImageData;
759 if (this->hasImageData()) {
760 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);
761 if (!ok) {
762 this->opened = false;
763 return;
764 }
765 ::swapImageDataEndianForConsole<true>(reorderedImageData, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, this->platform);
766 }
767
768 // By this point we cannot use spans over data, it will change here
769 if (this->hasFallbackData()) {
770 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, reorderedFallbackData);
771 }
772 if (this->hasImageData()) {
773 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, reorderedImageData);
774 }
775 return;
776 }
777 }
778 case PLATFORM_X360:
779 case PLATFORM_PS3_ORANGEBOX:
780 case PLATFORM_PS3_PORTAL2: {
781 uint8_t resourceCount;
782 stream
783 .read(this->flags)
784 .read(this->width)
785 .read(this->height)
786 .read(this->depth)
787 .read(this->frameCount)
788 .skip<uint16_t>() // preload
789 .read(this->consoleMipScale)
790 .read(resourceCount)
791 .read(this->reflectivity[0])
792 .read(this->reflectivity[1])
793 .read(this->reflectivity[2])
794 .read(this->bumpMapScale)
795 .read(this->format)
796 .skip<math::Vec4ui8>() // lowResImageSample (replacement for thumbnail resource, linear color pixel)
797 .skip<uint32_t>(); // compressedLength
798
799 postHeaderReadTransform();
800
801 // Align to 16 bytes
802 if (this->platform == PLATFORM_PS3_PORTAL2) {
803 stream.skip<uint32_t>();
804 }
805
806 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth);
807
808 if (parseHeaderOnly) {
809 this->opened = true;
810 return;
811 }
812
813 this->resources.reserve(resourceCount);
814 readResources(resourceCount);
815
816 this->opened = stream.tell() == headerSize;
817
818 // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine
819 for (const auto& resource : this->resources) {
820 // Decompress LZMA resources
821 if (BufferStreamReadOnly rsrcStream{resource.data}; rsrcStream.read<uint32_t>() == compression::VALVE_LZMA_SIGNATURE) {
822 if (auto decompressedData = compression::decompressValveLZMA(resource.data)) {
823 this->setResourceInternal(resource.type, *decompressedData);
824
825 if (resource.type == Resource::TYPE_IMAGE_DATA) {
826 // Do this here because compressionLength in header can be garbage on PS3 orange box
827 this->compressionMethod = CompressionMethod::CONSOLE_LZMA;
828 }
829 }
830 }
831
832 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
833 ::swapImageDataEndianForConsole<true>(resource.data, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
834 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
835 if (this->platform == PLATFORM_PS3_ORANGEBOX) {
836 bool ok;
837 const auto reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
838 if (!ok || reorderedFallbackData.size() != resource.data.size()) {
839 this->opened = false;
840 return;
841 }
842 std::memcpy(resource.data.data(), reorderedFallbackData.data(), resource.data.size());
843 }
844 ::swapImageDataEndianForConsole<true>(resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
845 } else if (!(resource.flags & Resource::FLAG_LOCAL_DATA) && resource.data.size() >= sizeof(uint32_t)) {
846 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resource.data.data()));
847 }
848 }
849 return;
850 }
851 }
852}
853
854VTF::VTF(std::span<const std::byte> vtfData, bool parseHeaderOnly, bool hdr)
855 : VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly, hdr) {}
856
857VTF::VTF(const std::filesystem::path& vtfPath, bool parseHeaderOnly)
858 : VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly, [](std::string path) {
860 return path.ends_with(".hdr.vtf") || path.ends_with(".hdr.360.vtf") || path.ends_with(".hdr.ps3.vtf");
861 }(vtfPath.filename().string())) {}
862
863VTF::VTF(const VTF& other) {
864 *this = other;
865}
866
867VTF& VTF::operator=(const VTF& other) {
868 this->opened = other.opened;
869 this->data = other.data;
870 this->version = other.version;
871 this->width = other.width;
872 this->height = other.height;
873 this->flags = other.flags;
874 this->frameCount = other.frameCount;
875 this->startFrame = other.startFrame;
876 this->reflectivity = other.reflectivity;
877 this->bumpMapScale = other.bumpMapScale;
878 this->format = other.format;
879 this->mipCount = other.mipCount;
880 this->thumbnailFormat = other.thumbnailFormat;
881 this->thumbnailWidth = other.thumbnailWidth;
882 this->thumbnailHeight = other.thumbnailHeight;
883 this->fallbackWidth = other.fallbackWidth;
884 this->fallbackHeight = other.fallbackHeight;
886 this->consoleMipScale = other.consoleMipScale;
887 this->depth = other.depth;
888
889 this->resources.clear();
890 for (const auto& [otherType, otherFlags, otherData] : other.resources) {
891 auto& [type, flags_, data_] = this->resources.emplace_back();
892 type = otherType;
893 flags_ = otherFlags;
894 data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
895 }
896
897 this->platform = other.platform;
902
903 return *this;
904}
905
906VTF::operator bool() const {
907 return this->opened;
908}
909
911 bool out = true;
912 if (writer.hasImageData() && (options.invertGreenChannel || options.gammaCorrection != 1.f)) {
913 for (int i = 0; i < writer.mipCount; i++) {
914 for (int j = 0; j < writer.frameCount; j++) {
915 for (int k = 0; k < writer.getFaceCount(); k++) {
916 for (int l = 0; l < writer.depth; l++) {
917 if (options.invertGreenChannel) {
918 ImageConversion::invertGreenChannelForImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i));
919 }
920 if (options.gammaCorrection != 1.f) {
921 ImageConversion::gammaCorrectImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i), options.gammaCorrection);
922 }
923 }
924 }
925 }
926 }
927 }
928 writer.setPlatform(options.platform);
929 if (options.computeReflectivity) {
930 writer.computeReflectivity();
931 }
932 if (options.initialFrameCount > 1 || options.isCubeMap || options.initialDepth > 1) {
933 if (!writer.setFrameFaceAndDepth(options.initialFrameCount, options.isCubeMap, options.initialDepth)) {
934 out = false;
935 }
936 }
937 writer.setStartFrame(options.startFrame);
938 writer.setBumpMapScale(options.bumpMapScale);
939 if (options.computeThumbnail) {
940 writer.computeThumbnail();
941 }
942 if (options.outputFormat == FORMAT_UNCHANGED) {
943 options.outputFormat = writer.getFormat();
944 } else if (options.outputFormat == FORMAT_DEFAULT) {
945 options.outputFormat = VTF::getDefaultCompressedFormat(writer.getFormat(), writer.getVersion(), options.isCubeMap);
946 }
947 if (options.computeMips) {
948 if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(writer.getWidth(), writer.getHeight(), writer.getDepth()); maxMipCount > 1) {
949 if (!writer.setMipCount(maxMipCount)) {
950 out = false;
951 }
952 writer.computeMips(options.filter);
953 }
954 } else {
955 // Already has NO_MIP
956 writer.addFlags(FLAG_V0_NO_LOD);
957 }
959 if (options.computeTransparencyFlags) {
961 }
964 writer.setConsoleMipScale(options.consoleMipScale);
965 return out;
966}
967
968bool VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& vtfPath, const CreationOptions& options) {
969 VTF writer;
970 writer.setVersion(options.version);
971 writer.addFlags(options.flags);
972 writer.addFlagsExtra(options.flagsExtra);
974 if (const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height); requestedResizeWidth != width || requestedResizeHeight != height) {
975 const auto imageDataResized = ImageConversion::resizeImageData(imageData, format, width, requestedResizeWidth, height, requestedResizeHeight, !ImageFormatDetails::large(format), writer.getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, options.filter);
976 if (!writer.setImage(imageDataResized, format, requestedResizeWidth, requestedResizeHeight, options.filter)) {
977 return false;
978 }
979 } else if (!writer.setImage(imageData, format, width, height, options.filter)) {
980 return false;
981 }
982 if (!createInternal(writer, options)) {
983 return false;
984 }
985 return writer.bake(vtfPath);
986}
987
988bool VTF::create(ImageFormat format, uint16_t width, uint16_t height, const std::filesystem::path& vtfPath, const CreationOptions& options) {
989 std::vector<std::byte> imageData;
990 const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height);
991 imageData.resize(static_cast<uint32_t>(requestedResizeWidth) * requestedResizeHeight * ImageFormatDetails::bpp(format) / 8);
992 return create(imageData, format, requestedResizeWidth, requestedResizeHeight, vtfPath, options);
993}
994
995VTF VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
996 VTF writer;
997 writer.setVersion(options.version);
998 writer.addFlags(options.flags);
999 writer.addFlagsExtra(options.flagsExtra);
1001 if (const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height); requestedResizeWidth != width || requestedResizeHeight != height) {
1002 const auto imageDataResized = ImageConversion::resizeImageData(imageData, format, width, requestedResizeWidth, height, requestedResizeHeight, !ImageFormatDetails::large(format), writer.getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, options.filter);
1003 if (!writer.setImage(imageDataResized, format, requestedResizeWidth, requestedResizeHeight, options.filter)) {
1004 writer.opened = false;
1005 return writer;
1006 }
1007 } else if (!writer.setImage(imageData, format, width, height, options.filter)) {
1008 writer.opened = false;
1009 return writer;
1010 }
1011 if (!createInternal(writer, options)) {
1012 writer.opened = false;
1013 }
1014 return writer;
1015}
1016
1017VTF VTF::create(ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
1018 std::vector<std::byte> imageData;
1019 const auto [requestedResizeWidth, requestedResizeHeight] = options.resizeBounds.clamp(width, height);
1020 imageData.resize(static_cast<uint32_t>(requestedResizeWidth) * requestedResizeHeight * ImageFormatDetails::bpp(format) / 8);
1021 return create(imageData, format, requestedResizeWidth, requestedResizeHeight, options);
1022}
1023
1024bool VTF::create(const std::filesystem::path& imagePath, const std::filesystem::path& vtfPath, const CreationOptions& options) {
1025 VTF writer;
1026 writer.setVersion(options.version);
1027 writer.addFlags(options.flags);
1028 writer.addFlagsExtra(options.flagsExtra);
1030 if (!writer.setImage(imagePath, options.filter, 0, 0, 0, 0, options.compressedFormatQuality, options.resizeBounds)) {
1031 return false;
1032 }
1033 if (!createInternal(writer, options)) {
1034 return false;
1035 }
1036 return writer.bake(vtfPath);
1037}
1038
1039VTF VTF::create(const std::filesystem::path& imagePath, const CreationOptions& options) {
1040 VTF writer;
1041 writer.setVersion(options.version);
1042 writer.addFlags(options.flags);
1043 writer.addFlagsExtra(options.flagsExtra);
1045 if (!writer.setImage(imagePath, options.filter, 0, 0, 0, 0, options.compressedFormatQuality, options.resizeBounds)) {
1046 writer.opened = false;
1047 return writer;
1048 }
1049 if (!createInternal(writer, options)) {
1050 writer.opened = false;
1051 }
1052 return writer;
1053}
1054
1056 return this->platform;
1057}
1058
1059void VTF::setPlatform(Platform newPlatform) {
1060 if (this->platform == newPlatform) {
1061 return;
1062 }
1063
1064 // hack to allow VTF::setVersion to work
1065 const auto oldPlatform = this->platform;
1066 this->platform = PLATFORM_PC;
1067 switch (newPlatform) {
1068 case PLATFORM_UNKNOWN:
1069 return;
1070 case PLATFORM_PC:
1071 switch (oldPlatform) {
1072 case PLATFORM_UNKNOWN:
1073 case PLATFORM_PC:
1074 return;
1075 case PLATFORM_XBOX:
1076 this->setVersion(2);
1077 break;
1078 case PLATFORM_X360:
1080 this->setVersion(4);
1081 break;
1083 this->setVersion(5);
1084 break;
1085 }
1086 break;
1087 case PLATFORM_XBOX:
1088 this->setVersion(2);
1089 break;
1090 case PLATFORM_X360:
1092 this->setVersion(4);
1093 break;
1095 this->setVersion(5);
1096 break;
1097 }
1098 this->platform = newPlatform;
1099
1100 // Remove spheremap if on console (VTF::setVersion has already added it back on PC)
1101 if (newPlatform != PLATFORM_PC && this->hasImageData() && (this->flags & FLAG_V0_ENVMAP)) {
1102 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, 6, this->depth);
1103 }
1104
1105 // Update flags
1106 if (this->platform == PLATFORM_XBOX || newPlatform == PLATFORM_XBOX) {
1108 }
1109
1110 // XBOX stores thumbnail as single RGB888 pixel, but we assume thumbnail is DXT1 on other platforms
1111 if (this->hasThumbnailData()) {
1112 if (this->platform == PLATFORM_XBOX) {
1113 this->computeThumbnail();
1114 } else if (oldPlatform == PLATFORM_XBOX) {
1116 this->thumbnailWidth = 0;
1117 this->thumbnailHeight = 0;
1118 }
1119 }
1120
1121 // Add/remove fallback data for XBOX
1122 if (this->platform != PLATFORM_XBOX && this->hasFallbackData()) {
1123 this->removeFallback();
1124 } else if (this->platform == PLATFORM_XBOX) {
1125 this->computeFallback();
1126 }
1127
1129
1130 if (this->platform != PLATFORM_PC) {
1131 const auto maxMipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth);
1132 if (this->mipCount != maxMipCount) {
1133 this->setMipCount(maxMipCount);
1134 }
1135 }
1136}
1137
1138uint32_t VTF::getVersion() const {
1139 return this->version;
1140}
1141
1142void VTF::setVersion(uint32_t newVersion) {
1143 if (this->platform != PLATFORM_PC) {
1144 return;
1145 }
1146 if (this->hasImageData()) {
1147 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? (newVersion < 1 || newVersion > 4 ? 6 : 7) : 1;
1148 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->depth);
1149 }
1150
1151 // Fix up flags
1152 const bool srgb = this->isSRGB();
1153 if ((this->version < 1 && newVersion >= 1) || (this->version >= 1 && newVersion < 1)) {
1155 }
1156 if ((this->version < 2 && newVersion >= 2) || (this->version >= 2 && newVersion < 2)) {
1158 }
1159 if ((this->version < 3 && newVersion >= 3) || (this->version >= 3 && newVersion < 3)) {
1161 }
1162 if ((this->version < 4 && newVersion >= 4) || (this->version >= 4 && newVersion < 4)) {
1164 }
1165 if ((this->version < 5 && newVersion >= 5) || (this->version >= 5 && newVersion < 5)) {
1167 }
1168
1169 this->version = newVersion;
1170 this->setSRGB(srgb);
1171}
1172
1176
1180
1181void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_) {
1182 this->imageWidthResizeMethod = imageWidthResizeMethod_;
1183 this->imageHeightResizeMethod = imageHeightResizeMethod_;
1184}
1185
1187 this->imageWidthResizeMethod = imageWidthResizeMethod_;
1188}
1189
1191 this->imageHeightResizeMethod = imageHeightResizeMethod_;
1192}
1193
1194uint16_t VTF::getWidth(uint8_t mip) const {
1195 return ImageDimensions::getMipDim(mip, this->width);
1196}
1197
1198uint16_t VTF::getPaddedWidth(uint8_t mip) const {
1199 return ImageDimensions::getMipDim(mip, this->width, ImageFormatDetails::compressed(this->format));
1200}
1201
1202uint16_t VTF::getHeight(uint8_t mip) const {
1203 return ImageDimensions::getMipDim(mip, this->height);
1204}
1205
1206uint16_t VTF::getPaddedHeight(uint8_t mip) const {
1207 return ImageDimensions::getMipDim(mip, this->height, ImageFormatDetails::compressed(this->format));
1208}
1209
1210void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter) {
1211 if (newWidth == 0 || newHeight == 0) {
1212 this->format = ImageFormat::EMPTY;
1213 this->width = 0;
1214 this->height = 0;
1216 return;
1217 }
1218
1220 if (this->hasImageData()) {
1221 if (this->width == newWidth && this->height == newHeight) {
1222 return;
1223 }
1224 auto newMipCount = this->mipCount;
1225 if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(newWidth, newHeight, this->depth); this->platform != PLATFORM_PC || newMipCount > maxMipCount) {
1226 newMipCount = maxMipCount;
1227 }
1228 this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->depth, filter);
1229 } else {
1230 this->format = ImageFormat::RGBA8888;
1231 this->mipCount = 1;
1232 this->frameCount = 1;
1233 this->flags &= ~FLAG_V0_ENVMAP;
1234 this->width = newWidth;
1235 this->height = newHeight;
1236 this->depth = 1;
1237 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, std::vector<std::byte>(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 1, this->width, this->height, this->depth)));
1238 }
1239}
1240
1241uint32_t VTF::getFlags() const {
1242 return this->flags;
1243}
1244
1245void VTF::setFlags(uint32_t flags_) {
1246 this->flags = (this->flags & FLAG_MASK_INTERNAL) | (flags_ & ~FLAG_MASK_INTERNAL);
1247}
1248
1249void VTF::addFlags(uint32_t flags_) {
1250 this->flags |= flags_ & ~FLAG_MASK_INTERNAL;
1251}
1252
1253void VTF::removeFlags(uint32_t flags_) {
1254 this->flags &= ~flags_ | FLAG_MASK_INTERNAL;
1255}
1256
1257uint32_t VTF::getFlagsExtra() const {
1258 return this->flagsExtra;
1259}
1260
1261void VTF::setFlagsExtra(uint32_t flags_) {
1262 this->flagsExtra = flags_;
1263}
1264
1265void VTF::addFlagsExtra(uint32_t flags_) {
1266 this->flagsExtra |= flags_;
1267}
1268
1269void VTF::removeFlagsExtra(uint32_t flags_) {
1270 this->flagsExtra &= ~flags_;
1271}
1272
1273bool VTF::isSRGB() const {
1274 return !ImageFormatDetails::large(this->format) && (this->version < 4 ? false : this->version < 5 ? this->flags & FLAG_V4_SRGB : this->flags & FLAG_V5_PWL_CORRECTED || this->flags & FLAG_V5_SRGB);
1275}
1276
1277void VTF::setSRGB(bool srgb) {
1278 if (srgb) {
1279 if (this->version >= 5) {
1280 this->addFlags(FLAG_V5_SRGB);
1281 } else if (this->version == 4) {
1282 this->addFlags(FLAG_V4_SRGB);
1283 }
1284 } else {
1285 if (this->version >= 5) {
1287 } else if (this->version == 4) {
1289 }
1290 }
1291}
1292
1294 if (ImageFormatDetails::transparent(this->format)) {
1295 if (ImageFormatDetails::decompressedAlpha(this->format) > 1) {
1298 } else {
1301 }
1302 } else {
1304 }
1305}
1306
1307ImageFormat VTF::getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap) {
1308 if (version >= 6) {
1309 if (isCubeMap) {
1311 }
1313 }
1314 if (ImageFormatDetails::decompressedAlpha(inputFormat) > 0) {
1315 return ImageFormat::DXT5;
1316 }
1317 return ImageFormat::DXT1;
1318}
1319
1321 return this->format;
1322}
1323
1324void VTF::setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter, float quality) {
1325 if (newFormat == FORMAT_UNCHANGED || newFormat == this->format) {
1326 return;
1327 }
1328 if (newFormat == FORMAT_DEFAULT) {
1329 newFormat = VTF::getDefaultCompressedFormat(this->format, this->version, this->getFaceCount() > 1);
1330 }
1331 if (!this->hasImageData()) {
1332 this->format = newFormat;
1333 return;
1334 }
1335 const auto oldFormat = this->format;
1336 if (ImageFormatDetails::compressed(newFormat)) {
1337 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);
1338 } else {
1339 this->regenerateImageData(newFormat, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), this->depth, filter, quality);
1340 }
1341
1342 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
1343 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);
1344 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, fallbackConverted);
1345 }
1346}
1347
1348uint8_t VTF::getMipCount() const {
1349 return this->mipCount;
1350}
1351
1352bool VTF::setMipCount(uint8_t newMipCount) {
1353 if (!this->hasImageData()) {
1354 return false;
1355 }
1356
1357 if (const auto maxMipCount = ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth); (this->platform != PLATFORM_PC && newMipCount > 1) || (this->platform == PLATFORM_PC && newMipCount > maxMipCount)) {
1358 newMipCount = maxMipCount;
1359 }
1360 this->regenerateImageData(this->format, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->depth);
1361 return true;
1362}
1363
1365 // Something we could do here for v5 and up is check if FLAG_V5_LOAD_MOST_MIPS is set and change mip count accordingly
1366 // (eliminate anything below 32x32). Noting we can't do that check for v4 and down because TF2 branch added small mip
1367 // loading, didn't bother adding the most mips flag, and doesn't handle VTFs missing lower mips correctly. Thanks Valve!
1368 return this->setMipCount(ImageDimensions::getMaximumMipCount(this->width, this->height, this->depth));
1369}
1370
1372 auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
1373 if (!imageResource || !this->hasImageData()) {
1374 return;
1375 }
1376
1377 if (this->mipCount <= 1) {
1378 if (!this->setRecommendedMipCount() || this->mipCount <= 1) {
1379 return;
1380 }
1381 }
1382
1383 auto* outputDataPtr = imageResource->data.data();
1384 const auto faceCount = this->getFaceCount();
1385
1386#ifdef SOURCEPP_BUILD_WITH_THREADS
1387 std::vector<std::future<void>> futures;
1388 futures.reserve(this->frameCount * faceCount * this->depth);
1389#endif
1390 for (int j = 0; j < this->frameCount; j++) {
1391 for (int k = 0; k < faceCount; k++) {
1392#ifdef SOURCEPP_BUILD_WITH_THREADS
1393 futures.push_back(std::async(std::launch::async, [this, filter, outputDataPtr, faceCount, j, k] {
1394#endif
1395 for (int i = 1; i < this->mipCount; i++) {
1396 const auto [mipWidth, mipHeight, mipDepth] = ImageDimensions::getMipDims(i, this->width, this->height, this->depth);
1397 const auto [mipWidthM1, mipHeightM1] = ImageDimensions::getMipDims(i - 1, this->width, this->height);
1398 for (int l = 0; l < mipDepth; l++) {
1399 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);
1400 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) {
1401 std::memcpy(outputDataPtr + offset, mip.data(), length);
1402 }
1403 }
1404 }
1405#ifdef SOURCEPP_BUILD_WITH_THREADS
1406 }));
1407 if (futures.size() >= std::thread::hardware_concurrency()) {
1408 for (auto& future : futures) {
1409 future.get();
1410 }
1411 futures.clear();
1412 }
1413#endif
1414 }
1415 }
1416#ifdef SOURCEPP_BUILD_WITH_THREADS
1417 for (auto& future : futures) {
1418 future.get();
1419 }
1420#endif
1421}
1422
1423uint16_t VTF::getFrameCount() const {
1424 return this->frameCount;
1425}
1426
1427bool VTF::setFrameCount(uint16_t newFrameCount) {
1428 if (!this->hasImageData()) {
1429 return false;
1430 }
1431 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, this->getFaceCount(), this->depth);
1432 return true;
1433}
1434
1435uint8_t VTF::getFaceCount() const {
1436 if (!this->hasImageData()) {
1437 return 0;
1438 }
1439 const auto* image = this->getResource(Resource::TYPE_IMAGE_DATA);
1440 if (!image) {
1441 return 0;
1442 }
1443 if (!(this->flags & FLAG_V0_ENVMAP)) {
1444 return 1;
1445 }
1446 if (this->platform != PLATFORM_PC || this->version >= 6) {
1447 // All v7.6 VTFs are sane, and we need this special case to fix a bug in the parser where
1448 // it won't recognize cubemaps as cubemaps because the image resource is compressed!
1449 return 6;
1450 }
1451 const auto expectedLength = ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 6, this->width, this->height, this->depth);
1452 if (this->version >= 1 && this->version <= 4 && expectedLength < image->data.size()) {
1453 return 7;
1454 }
1455 if (expectedLength == image->data.size()) {
1456 return 6;
1457 }
1458 return 1;
1459}
1460
1461bool VTF::setFaceCount(bool isCubeMap) {
1462 if (!this->hasImageData()) {
1463 return false;
1464 }
1465 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, isCubeMap ? (this->version < 1 || this->version > 4 ? 6 : 7) : 1, this->depth);
1466 return true;
1467}
1468
1469uint16_t VTF::getDepth(uint8_t mip) const {
1470 return ImageDimensions::getMipDim(mip, this->depth);
1471}
1472
1473bool VTF::setDepth(uint16_t newDepth) {
1474 if (!this->hasImageData()) {
1475 return false;
1476 }
1477 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), newDepth);
1478 return true;
1479}
1480
1481bool VTF::setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth) {
1482 if (!this->hasImageData()) {
1483 return false;
1484 }
1485 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, isCubeMap ? (this->version < 1 || this->version > 4 ? 6 : 7) : 1, newDepth);
1486 return true;
1487}
1488
1489uint16_t VTF::getStartFrame() const {
1490 return this->startFrame;
1491}
1492
1493void VTF::setStartFrame(uint16_t newStartFrame) {
1494 this->startFrame = newStartFrame;
1495}
1496
1497math::Vec3f VTF::getReflectivity() const {
1498 return this->reflectivity;
1499}
1500
1501void VTF::setReflectivity(math::Vec3f newReflectivity) {
1502 this->reflectivity = newReflectivity;
1503}
1504
1506 static constexpr auto getReflectivityForImage = [](const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1507 static constexpr auto getReflectivityForPixel = [](const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1508 // http://markjstock.org/doc/gsd_talk_11_notes.pdf page 11
1509 math::Vec3f ref{static_cast<float>(pixel->r()), static_cast<float>(pixel->g()), static_cast<float>(pixel->b())};
1510 // I tweaked it a bit to produce 0.85 reflectivity at pure white
1511 ref = ref / 255.f * 0.922f;
1512 ref[0] *= ref[0];
1513 ref[1] *= ref[1];
1514 ref[2] *= ref[2];
1515 return ref;
1516 };
1517
1518 auto rgba8888Data = vtf.getImageDataAsRGBA8888(0, frame, face, slice);
1519 math::Vec3f out{};
1520 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1521 out += getReflectivityForPixel(reinterpret_cast<ImagePixel::RGBA8888*>(rgba8888Data.data() + i));
1522 }
1523 return out / (rgba8888Data.size() / sizeof(ImagePixel::RGBA8888));
1524 };
1525
1526 const auto faceCount = this->getFaceCount();
1527
1528#ifdef SOURCEPP_BUILD_WITH_THREADS
1529 if (this->frameCount > 1 || faceCount > 1 || this->depth > 1) {
1530 std::vector<std::future<math::Vec3f>> futures;
1531 futures.reserve(this->frameCount * faceCount * this->depth);
1532
1533 this->reflectivity = {};
1534 for (int j = 0; j < this->frameCount; j++) {
1535 for (int k = 0; k < faceCount; k++) {
1536 for (int l = 0; l < this->depth; l++) {
1537 futures.push_back(std::async(std::launch::async, [this, j, k, l] {
1538 return getReflectivityForImage(*this, j, k, l);
1539 }));
1540 if (futures.size() >= std::thread::hardware_concurrency()) {
1541 for (auto& future : futures) {
1542 this->reflectivity += future.get();
1543 }
1544 futures.clear();
1545 }
1546 }
1547 }
1548 }
1549
1550 for (auto& future : futures) {
1551 this->reflectivity += future.get();
1552 }
1553 this->reflectivity /= this->frameCount * faceCount * this->depth;
1554 } else {
1555 this->reflectivity = getReflectivityForImage(*this, 0, 0, 0);
1556 }
1557#else
1558 this->reflectivity = {};
1559 for (int j = 0; j < this->frameCount; j++) {
1560 for (int k = 0; k < faceCount; k++) {
1561 for (int l = 0; l < this->depth; l++) {
1562 this->reflectivity += getReflectivityForImage(*this, j, k, l);
1563 }
1564 }
1565 }
1566 this->reflectivity /= this->frameCount * faceCount * this->depth;
1567#endif
1568}
1569
1571 return this->bumpMapScale;
1572}
1573
1574void VTF::setBumpMapScale(float newBumpMapScale) {
1575 this->bumpMapScale = newBumpMapScale;
1576}
1577
1579 return this->thumbnailFormat;
1580}
1581
1582uint8_t VTF::getThumbnailWidth() const {
1583 return this->thumbnailWidth;
1584}
1585
1587 return this->thumbnailHeight;
1588}
1589
1590uint8_t VTF::getFallbackWidth(uint8_t mip) const {
1591 return ImageDimensions::getMipDim(mip, this->fallbackWidth);
1592}
1593
1594uint8_t VTF::getPaddedFallbackWidth(uint8_t mip) const {
1596}
1597
1598uint8_t VTF::getFallbackHeight(uint8_t mip) const {
1599 return ImageDimensions::getMipDim(mip, this->fallbackHeight);
1600}
1601
1602uint8_t VTF::getPaddedFallbackHeight(uint8_t mip) const {
1604}
1605
1607 return this->fallbackMipCount;
1608}
1609
1611 return this->version >= 3;
1612}
1613
1614const std::vector<Resource>& VTF::getResources() const {
1615 return this->resources;
1616}
1617
1619 for (const auto& resource : this->resources) {
1620 if (resource.type == type) {
1621 return &resource;
1622 }
1623 }
1624 return nullptr;
1625}
1626
1628 for (auto& resource : this->resources) {
1629 if (resource.type == type) {
1630 return &resource;
1631 }
1632 }
1633 return nullptr;
1634}
1635
1636void VTF::setResourceInternal(Resource::Type type, std::span<const std::byte> data_) {
1637 if (const auto* resource = this->getResource(type); resource && resource->data.size() == data_.size()) {
1638 std::memcpy(resource->data.data(), data_.data(), data_.size());
1639 return;
1640 }
1641
1642 // Store resource data
1643 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1644 for (const auto& [type_, flags_, dataSpan] : this->resources) {
1645 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1646 }
1647
1648 // Set new resource
1649 if (data_.empty()) {
1650 resourceData.erase(type);
1651 } else {
1652 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1653 }
1654
1655 // Save the data
1656 this->data.clear();
1657 BufferStream writer{this->data};
1658
1659 for (auto resourceType : resourceData | std::views::keys) {
1660 if (!resourceData.contains(resourceType)) {
1661 continue;
1662 }
1663 auto& [specificResourceData, offset] = resourceData[resourceType];
1664 if (resourceType == type) {
1665 if (!this->data.data()) {
1666 this->data.reserve(offset + specificResourceData.size());
1667 }
1668 Resource newResource{
1669 type,
1670 specificResourceData.size() <= sizeof(uint32_t) ? Resource::FLAG_LOCAL_DATA : Resource::FLAG_NONE,
1671 {this->data.data() + offset, specificResourceData.size()},
1672 };
1673 if (auto* resourcePtr = this->getResourceInternal(type)) {
1674 *resourcePtr = newResource;
1675 } else {
1676 this->resources.push_back(newResource);
1677 }
1678 } else if (!resourceData.contains(resourceType)) {
1679 continue;
1680 }
1681 offset = writer.tell();
1682 writer.write(specificResourceData);
1683 }
1684 this->data.resize(writer.size());
1685
1686 for (auto& [type_, flags_, dataSpan] : this->resources) {
1687 if (resourceData.contains(type_)) {
1688 const auto& [specificResourceData, offset] = resourceData[type_];
1689 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1690 }
1691 }
1692}
1693
1695 std::erase_if(this->resources, [type](const Resource& resource) { return resource.type == type; });
1696}
1697
1698void VTF::regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newDepth, ImageConversion::ResizeFilter filter, float quality) {
1699 if (!newWidth) newWidth = 1;
1700 if (!newHeight) newHeight = 1;
1701 if (!newMipCount) newMipCount = 1;
1702 if (!newFrameCount) newFrameCount = 1;
1703 if (!newFaceCount) newFaceCount = 1;
1704 if (!newDepth) newDepth = 1;
1705
1706 if (newMipCount > 1) {
1707 // Valve code doesn't like it when you have compressed mips with padding unless they're lower than 4 on a given dimension!
1708 // We could do some extra work to calculate this better, but I don't care, sorry
1709 if (ImageFormatDetails::compressed(newFormat) && ((newWidth > 4 && !std::has_single_bit(newWidth)) || (newHeight > 4 && !std::has_single_bit(newHeight)))) {
1710 newMipCount = 1;
1711 }
1712
1713 this->flags &= ~FLAG_V0_NO_MIP;
1714 } else {
1715 this->flags |= FLAG_V0_NO_MIP;
1716 }
1717
1718 const auto faceCount = this->getFaceCount();
1719 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1720 return;
1721 }
1722
1723 std::vector<std::byte> newImageData;
1724 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1725 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1726 newImageData = ImageConversion::convertSeveralImageDataToFormat(imageResource->data, this->format, newFormat, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, quality);
1727 } else {
1728 newImageData.resize(ImageFormatDetails::getDataLength(this->format, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1729 for (int i = newMipCount - 1; i >= 0; i--) {
1730 const auto [newMipWidth, newMipHeight, newMipDepth] = ImageDimensions::getMipDims(i, newWidth, newHeight, newDepth);
1731
1732 int sourceMipIndex = 0;
1733 for (int mip = 0, bestScore = std::numeric_limits<int>::max(); mip < this->mipCount; mip++) {
1734 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height, false);
1735 if (mipWidth == newMipWidth && mipHeight == newMipHeight) {
1736 break;
1737 }
1738 const auto widthDiff = static_cast<int>(mipWidth) - static_cast<int>(newMipWidth);
1739 const auto heightDiff = static_cast<int>(mipHeight) - static_cast<int>(newMipHeight);
1740 if (widthDiff < 0 || heightDiff < 0) {
1741 continue;
1742 }
1743 if (const auto score = widthDiff + heightDiff; score < bestScore) {
1744 bestScore = score;
1745 sourceMipIndex = mip;
1746 }
1747 }
1748 const auto [sourceMipWidth, sourceMipHeight] = ImageDimensions::getMipDims(sourceMipIndex, this->width, this->height);
1749
1750 for (int j = 0; j < newFrameCount; j++) {
1751 for (int k = 0; k < newFaceCount; k++) {
1752 for (int l = 0; l < newMipDepth; l++) {
1753 if (j < this->frameCount && k < faceCount && l < this->depth) {
1754 auto imageSpan = this->getImageDataRaw(sourceMipIndex, j, k, l);
1755 std::vector<std::byte> imageBacking;
1756 if (sourceMipWidth != newMipWidth || sourceMipHeight != newMipHeight) {
1757 imageBacking = ImageConversion::resizeImageData(imageSpan, this->format, sourceMipWidth, newMipWidth, sourceMipHeight, newMipHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
1758 imageSpan = imageBacking;
1759 }
1760 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newDepth) && imageSpan.size() == length) {
1761 std::memcpy(newImageData.data() + offset, imageSpan.data(), length);
1762 }
1763 }
1764 }
1765 }
1766 }
1767 }
1768 if (this->format != newFormat) {
1769 newImageData = ImageConversion::convertSeveralImageDataToFormat(newImageData, this->format, newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth, quality);
1770 }
1771 }
1772 } else {
1773 newImageData.resize(ImageFormatDetails::getDataLength(newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1774 }
1775
1776 this->format = newFormat;
1777 this->width = newWidth;
1778 this->height = newHeight;
1779 this->mipCount = newMipCount;
1780 this->frameCount = newFrameCount;
1781 if (newFaceCount > 1) {
1782 this->flags |= FLAG_V0_ENVMAP;
1783 } else {
1784 this->flags &= ~FLAG_V0_ENVMAP;
1785 }
1786 this->depth = newDepth;
1787
1789
1790 // Fix up XBOX resources that depend on image resource
1791 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1792 const auto targetSize = 256 * sizeof(ImagePixel::BGRA8888) * this->frameCount;
1793 if (palette->data.size() != targetSize) {
1794 std::vector<std::byte> paletteData{palette->data.begin(), palette->data.end()};
1795 paletteData.resize(targetSize);
1797 }
1798 }
1799 if (this->hasFallbackData()) {
1800 this->removeFallback();
1801 this->computeFallback();
1802 }
1803}
1804
1805std::vector<std::byte> VTF::getPaletteResourceFrame(uint16_t frame) const {
1806 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1807 return palette->getDataAsPalette(frame);
1808 }
1809 return {};
1810}
1811
1812std::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 {
1813 spriteWidth = 0;
1814 spriteHeight = 0;
1815
1816 const auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA);
1817 if (!shtResource) {
1818 return {};
1819 }
1820
1821 auto sht = shtResource->getDataAsParticleSheet();
1822 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1823 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1824 return {};
1825 }
1826
1827 // These values are likely slightly too large thanks to float magic, use a
1828 // shader that scales UVs instead of this function if precision is a concern
1829 // This will also break if any of the bounds are above 1 or below 0, but that
1830 // hasn't been observed in official textures
1831 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1832 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 * static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1833 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 * static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1834 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 * static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1835 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 * static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1836
1837 if (x1 > x2) [[unlikely]] {
1838 std::swap(x1, x2);
1839 }
1840 if (y1 > y2) [[unlikely]] {
1841 std::swap(y1, y2);
1842 }
1843 spriteWidth = x2 - x1;
1844 spriteWidth = y2 - y1;
1845
1846 const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1);
1847 if (out.empty()) {
1848 spriteWidth = 0;
1849 spriteHeight = 0;
1850 }
1851 return out;
1852}
1853
1854std::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 {
1855 return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight);
1856}
1857
1858std::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 {
1859 return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice);
1860}
1861
1863 std::vector<std::byte> particleSheetData;
1864 BufferStream writer{particleSheetData};
1865
1866 const auto bakedSheet = value.bake();
1867 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1868 particleSheetData.resize(writer.size());
1869
1871}
1872
1876
1877void VTF::setCRCResource(uint32_t value) {
1878 this->setResourceInternal(Resource::TYPE_CRC, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1879}
1880
1884
1885void VTF::setLODResource(uint8_t u, uint8_t v, uint8_t u360, uint8_t v360) {
1886 uint32_t lodData;
1887 BufferStream writer{&lodData, sizeof(lodData)};
1888
1889 writer << u << v << u360 << v360;
1890
1891 this->setResourceInternal(Resource::TYPE_LOD_CONTROL_INFO, {reinterpret_cast<const std::byte*>(&lodData), sizeof(lodData)});
1892}
1893
1897
1898void VTF::setExtendedFlagsResource(uint32_t value) {
1899 this->setResourceInternal(Resource::TYPE_EXTENDED_FLAGS, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1900}
1901
1905
1906void VTF::setKeyValuesDataResource(std::string_view value) {
1907 std::vector<std::byte> keyValuesData;
1908 BufferStream writer{keyValuesData};
1909
1910 writer.write<uint32_t>(value.size()).write(value, false);
1911 keyValuesData.resize(writer.size());
1912
1914}
1915
1919
1920void VTF::setAuthorInfoResource(std::string_view value) {
1921 std::vector<std::byte> authorInfo;
1922 BufferStream writer{authorInfo};
1923
1924 writer.write<uint32_t>(value.size()).write(value, false);
1925 authorInfo.resize(writer.size());
1926
1928}
1929
1933
1935 std::vector<std::byte> hotspotData;
1936 BufferStream writer{hotspotData};
1937
1938 const auto bakedHotspotData = value.bake();
1939 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1940 hotspotData.resize(writer.size());
1941
1943}
1944
1948
1950 return this->compressionLevel;
1951}
1952
1953void VTF::setCompressionLevel(int16_t newCompressionLevel) {
1954 if (newCompressionLevel < 0) {
1955 this->compressionLevel = 6;
1956 } else {
1957 this->compressionLevel = newCompressionLevel;
1958 }
1959}
1960
1964
1966 if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == PLATFORM_PC) {
1968 } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != PLATFORM_PC) {
1970 } else {
1971 this->compressionMethod = newCompressionMethod;
1972 }
1973}
1974
1975bool VTF::hasImageData() const {
1976 return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
1977}
1978
1979std::span<const std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1980 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1981 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)) {
1982 return imageResource->data.subspan(offset, length);
1983 }
1984 }
1985 return {};
1986}
1987
1988std::span<std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) {
1989 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1990 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)) {
1991 return imageResource->data.subspan(offset, length);
1992 }
1993 }
1994 return {};
1995}
1996
1997std::vector<std::byte> VTF::getImageDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1998 const auto rawImageData = this->getImageDataRaw(mip, frame, face, slice);
1999 if (rawImageData.empty()) {
2000 return {};
2001 }
2002 if (this->format == ImageFormat::P8) {
2003 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
2004 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2005 return ImageConversion::convertImageDataToFormat(ImageQuantize::convertP8ImageDataToBGRA8888(paletteData, rawImageData), ImageFormat::BGRA8888, newFormat, mipWidth, mipHeight);
2006 }
2007 }
2008 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2009 return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, mipWidth, mipHeight);
2010}
2011
2012std::vector<std::byte> VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
2013 return this->getImageDataAs(ImageFormat::RGBA8888, mip, frame, face, slice);
2014}
2015
2016bool VTF::setImage(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality) {
2017 if (imageData_.empty()) {
2018 return false;
2019 }
2020
2021 if (!this->hasImageData()) {
2022 uint16_t resizedWidth = width_, resizedHeight = height_;
2023 ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
2024 if (ImageFormatDetails::compressed(format_)) {
2025 resizedWidth += math::paddingForAlignment(4, resizedWidth);
2026 resizedHeight += math::paddingForAlignment(4, resizedHeight);
2027 }
2028 if (const auto newMipCount = ImageDimensions::getMaximumMipCount(resizedWidth, resizedHeight, this->depth); newMipCount <= mip) {
2029 mip = newMipCount - 1;
2030 }
2031 if (face > 6 || (face == 6 && (this->platform != PLATFORM_PC || this->version < 1 || this->version > 4))) {
2032 return false;
2033 }
2034 this->regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 6 ? 6 : face) : 0, slice + 1);
2035 }
2036
2037 const auto faceCount = this->getFaceCount();
2038 if (this->mipCount <= mip || this->frameCount <= frame || faceCount <= face || this->depth <= slice) {
2039 return false;
2040 }
2041
2042 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
2043 if (!imageResource) {
2044 return false;
2045 }
2046 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)) {
2047 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
2048 const auto [newWidth, newHeight] = ImageDimensions::getMipDims(mip, this->width, this->height);
2049 if (width_ != newWidth || height_ != newHeight) {
2050 image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->isSRGB(), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2051 }
2052 if (format_ != this->format) {
2053 image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight, quality);
2054 }
2055 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
2056 }
2057 return true;
2058}
2059
2060bool VTF::setImage(const std::filesystem::path& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality, ImageConversion::ResizeBounds resizeBounds) {
2061 ImageFormat inputFormat;
2062 int inputWidth, inputHeight, inputFrameCount;
2063 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
2064
2065 // Unable to decode file
2066 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2067 return false;
2068 }
2069
2070 // Image dimension overrides
2071 auto [requestedResizeWidth, requestedResizeHeight] = resizeBounds.clamp(inputWidth, inputHeight);
2072
2073 // One frame (normal)
2074 if (inputFrameCount == 1) {
2075 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2076 const auto imageDataResized = ImageConversion::resizeImageData(imageData_, inputFormat, inputWidth, requestedResizeWidth, inputHeight, requestedResizeHeight, !ImageFormatDetails::large(inputFormat), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2077 return this->setImage(imageDataResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame, face, slice, quality);
2078 }
2079 return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice, quality);
2080 }
2081
2082 // Multiple frames (GIF)
2083 bool allSuccess = true;
2084 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
2085 for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
2086 std::span currentFrameData{imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize};
2087 if (requestedResizeWidth != inputWidth || requestedResizeHeight != inputHeight) {
2088 const auto currentFrameResized = ImageConversion::resizeImageData(currentFrameData, inputFormat, inputWidth, requestedResizeWidth, inputHeight, requestedResizeHeight, !ImageFormatDetails::large(inputFormat), this->getFlagsExtra() & FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE, filter);
2089 if (!this->setImage(currentFrameResized, inputFormat, requestedResizeWidth, requestedResizeHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2090 allSuccess = false;
2091 }
2092 } else if (!this->setImage(currentFrameData, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
2093 allSuccess = false;
2094 }
2095 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
2096 // Call this after setting the first image, this function is a no-op if no image data is present yet
2097 this->setFrameCount(frame + inputFrameCount);
2098 }
2099 }
2100 return allSuccess;
2101}
2102
2103std::vector<std::byte> VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
2104 return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, this->getWidth(mip), this->getHeight(mip), fileFormat);
2105}
2106
2107bool VTF::saveImageToFile(const std::filesystem::path& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
2108 if (auto data_ = this->saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
2109 return fs::writeFileBuffer(imagePath, data_);
2110 }
2111 return false;
2112}
2113
2115 return this->thumbnailFormat != ImageFormat::EMPTY && this->thumbnailWidth > 0 && this->thumbnailHeight > 0;
2116}
2117
2118std::span<const std::byte> VTF::getThumbnailDataRaw() const {
2119 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
2120 return thumbnailResource->data;
2121 }
2122 return {};
2123}
2124
2125std::span<std::byte> VTF::getThumbnailDataRaw() {
2126 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
2127 return thumbnailResource->data;
2128 }
2129 return {};
2130}
2131
2132std::vector<std::byte> VTF::getThumbnailDataAs(ImageFormat newFormat) const {
2133 const auto rawThumbnailData = this->getThumbnailDataRaw();
2134 if (rawThumbnailData.empty()) {
2135 return {};
2136 }
2137 return ImageConversion::convertImageDataToFormat(rawThumbnailData, this->thumbnailFormat, newFormat, this->thumbnailWidth, this->thumbnailHeight);
2138}
2139
2140std::vector<std::byte> VTF::getThumbnailDataAsRGBA8888() const {
2142}
2143
2144void VTF::setThumbnail(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality) {
2145 if (format_ != this->thumbnailFormat) {
2146 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(imageData_, format_, this->thumbnailFormat, width_, height_, quality));
2147 } else {
2149 }
2150 this->thumbnailWidth = width_;
2151 this->thumbnailHeight = height_;
2152}
2153
2154bool VTF::setThumbnail(const std::filesystem::path& imagePath, float quality) {
2155 ImageFormat inputFormat;
2156 int inputWidth, inputHeight, inputFrameCount;
2157 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
2158
2159 // Unable to decode file
2160 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
2161 return false;
2162 }
2163
2164 // One frame (normal)
2165 if (inputFrameCount == 1) {
2166 this->setThumbnail(imageData_, inputFormat, inputWidth, inputHeight, quality);
2167 return true;
2168 }
2169
2170 // Multiple frames (GIF) - we will just use the first one
2171 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
2172 this->setThumbnail({imageData_.data(), frameSize}, inputFormat, inputWidth, inputHeight, quality);
2173 return true;
2174}
2175
2177 if (!this->hasImageData()) {
2178 return;
2179 }
2180
2181 if (this->platform == PLATFORM_XBOX) {
2183 this->thumbnailWidth = 1;
2184 this->thumbnailHeight = 1;
2185 std::array newThumbnail{
2186 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[0], 0.f, 1.f) * 255.f)),
2187 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[1], 0.f, 1.f) * 255.f)),
2188 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[2], 0.f, 1.f) * 255.f)),
2189 };
2191 } else {
2193 this->thumbnailWidth = 16;
2194 this->thumbnailHeight = 16;
2195 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));
2196 }
2197}
2198
2201 this->thumbnailWidth = 0;
2202 this->thumbnailHeight = 0;
2204}
2205
2206std::vector<std::byte> VTF::saveThumbnailToFile(ImageConversion::FileFormat fileFormat) const {
2207 return ImageConversion::convertImageDataToFile(this->getThumbnailDataRaw(), this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, fileFormat);
2208}
2209
2210bool VTF::saveThumbnailToFile(const std::filesystem::path& imagePath, ImageConversion::FileFormat fileFormat) const {
2211 if (auto data_ = this->saveThumbnailToFile(fileFormat); !data_.empty()) {
2212 return fs::writeFileBuffer(imagePath, data_);
2213 }
2214 return false;
2215}
2216
2218 return this->fallbackWidth > 0 && this->fallbackHeight > 0 && this->fallbackMipCount > 0;
2219}
2220
2221std::span<const std::byte> VTF::getFallbackDataRaw(uint8_t mip, uint16_t frame, uint8_t face) const {
2222 if (const auto fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
2223 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->fallbackMipCount, frame, this->frameCount, face, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight)) {
2224 return fallbackResource->data.subspan(offset, length);
2225 }
2226 }
2227 return {};
2228}
2229
2230std::span<std::byte> VTF::getFallbackDataRaw(uint8_t mip, uint16_t frame, uint8_t face) {
2231 if (const auto fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
2232 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->fallbackMipCount, frame, this->frameCount, face, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight)) {
2233 return fallbackResource->data.subspan(offset, length);
2234 }
2235 }
2236 return {};
2237}
2238
2239std::vector<std::byte> VTF::getFallbackDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face) const {
2240 const auto rawFallbackData = this->getFallbackDataRaw(mip, frame, face);
2241 if (rawFallbackData.empty()) {
2242 return {};
2243 }
2244 if (this->format == ImageFormat::P8) {
2245 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
2247 }
2248 }
2249 return ImageConversion::convertImageDataToFormat(rawFallbackData, this->format, newFormat, this->fallbackWidth, this->fallbackHeight);
2250}
2251
2252std::vector<std::byte> VTF::getFallbackDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face) const {
2253 return this->getFallbackDataAs(ImageFormat::RGBA8888, mip, frame, face);
2254}
2255
2257 if (!this->hasImageData()) {
2258 return;
2259 }
2260
2261 const auto faceCount = this->getFaceCount();
2262
2263 this->fallbackWidth = 8;
2264 this->fallbackHeight = 8;
2266
2267 std::vector<std::byte> fallbackData;
2268 fallbackData.resize(ImageFormatDetails::getDataLength(this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight));
2269 for (int i = this->fallbackMipCount - 1; i >= 0; i--) {
2270 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(i, this->fallbackWidth, this->fallbackHeight);
2271 for (int j = 0; j < this->frameCount; j++) {
2272 for (int k = 0; k < faceCount; k++) {
2273 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);
2274 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) {
2275 std::memcpy(fallbackData.data() + offset, mip.data(), length);
2276 }
2277 }
2278 }
2279 }
2281}
2282
2284 this->fallbackWidth = 0;
2285 this->fallbackHeight = 0;
2286 this->fallbackMipCount = 0;
2288}
2289
2290std::vector<std::byte> VTF::saveFallbackToFile(uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
2291 const auto [mipWidth, mipHeight] = ImageDimensions::getMipDims(mip, this->fallbackWidth, this->fallbackHeight);
2292 return ImageConversion::convertImageDataToFile(this->getFallbackDataRaw(mip, frame, face), this->format, mipWidth, mipHeight, fileFormat);
2293}
2294
2295bool VTF::saveFallbackToFile(const std::filesystem::path& imagePath, uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
2296 if (auto data_ = this->saveFallbackToFile(mip, frame, face, fileFormat); !data_.empty()) {
2297 return fs::writeFileBuffer(imagePath, data_);
2298 }
2299 return false;
2300}
2301
2303 return this->consoleMipScale;
2304}
2305
2306void VTF::setConsoleMipScale(uint8_t consoleMipScale_) {
2307 this->consoleMipScale = consoleMipScale_;
2308}
2309
2310uint64_t VTF::estimateBakeSize() const {
2311 bool isExact;
2312 return this->estimateBakeSize(isExact);
2313}
2314
2315uint64_t VTF::estimateBakeSize(bool& isExact) const {
2316 isExact = true;
2317 uint64_t vtfSize = 0;
2318
2319 switch (this->platform) {
2320 case PLATFORM_UNKNOWN:
2321 break;
2322 case PLATFORM_PC:
2323 switch (this->version) {
2324 case 6:
2325 if (this->compressionLevel > 0) {
2326 vtfSize += sizeof(uint64_t); // AXC header size
2327 vtfSize += sizeof(uint32_t) * (2 + this->mipCount * this->frameCount * this->getFaceCount()); // AXC resource size
2328 }
2329 case 5:
2330 case 4:
2331 case 3:
2332 vtfSize += 11 + sizeof(uint32_t) + sizeof(uint64_t) * this->getResources().size(); // resources header size
2333 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2334 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_THUMBNAIL_DATA && resourceType != Resource::TYPE_IMAGE_DATA) {
2335 vtfSize += resourceData.size(); // resource size
2336 }
2337 }
2338 case 2:
2339 vtfSize += sizeof(uint16_t); // depth header field
2340 case 1:
2341 case 0:
2342 vtfSize += sizeof(uint32_t) * 9 + sizeof(float) * 4 + sizeof(uint16_t) * 4 + sizeof(uint8_t) * 3; // header fields
2343 if (this->version < 3) {
2344 vtfSize += math::paddingForAlignment(16, vtfSize); // align to 16 bytes
2345 }
2346
2347 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2348 vtfSize += thumbnailResource->data.size(); // thumbnail size
2349 }
2350 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2352 vtfSize += imageResource->data.size(); // uncompressed image size
2353 } else {
2354 isExact = false;
2355 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2356 }
2357 }
2358 break;
2359 default:
2360 break;
2361 }
2362 break;
2363 case PLATFORM_XBOX:
2364 vtfSize += sizeof(uint32_t) * 6 + sizeof(float) * 4 + sizeof(uint16_t) * 6 + sizeof(uint8_t) * 6; // header fields
2365
2366 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2367 vtfSize += thumbnailResource->data.size(); // thumbnail size
2368 }
2369 if (this->format == ImageFormat::P8) {
2370 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2371 vtfSize += paletteResource->data.size(); // palette size
2372 }
2373 }
2374 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2375 vtfSize += ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight); // fallback size
2376 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if fallback present
2377 }
2378 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2379 vtfSize += ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth); // image size
2380 }
2381 if (vtfSize > 512) {
2382 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if longer than 512 bytes
2383 }
2384 break;
2386 vtfSize += sizeof(uint32_t); // align to 16 bytes
2387 case PLATFORM_X360:
2389 vtfSize += sizeof(uint32_t) * 7 + sizeof(float) * 4 + sizeof(uint16_t) * 5 + sizeof(uint8_t) * 6; // header fields
2390 vtfSize += sizeof(uint64_t) * this->getResources().size(); // resources header size
2391
2392 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2393 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_IMAGE_DATA) {
2394 vtfSize += resourceData.size(); // resource size
2395 }
2396 }
2397 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2399 vtfSize += imageResource->data.size(); // uncompressed image data
2400 } else {
2401 isExact = false;
2402 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2403 }
2404 }
2405 break;
2406 }
2407 return vtfSize;
2408}
2409
2410std::vector<std::byte> VTF::bake() const {
2411 std::vector<std::byte> out;
2412 BufferStream writer{out};
2413
2414 const auto writeResources = [&writer](uint64_t headerLengthPos, const std::vector<Resource>& sortedResources) -> uint64_t {
2415 auto resourceHeaderCurPos = writer.tell();
2416 writer.pad<uint64_t>(sortedResources.size());
2417 auto resourceDataCurPos = writer.tell();
2418 writer.seek_u(headerLengthPos).write<uint32_t>(resourceDataCurPos);
2419
2420 uint64_t resourceHeaderImagePos = 0;
2421 for (const auto& resource : sortedResources) {
2422 writer.seek_u(resourceHeaderCurPos);
2423
2424 uint32_t resourceType = resource.type;
2425 if (resource.flags & Resource::FLAG_LOCAL_DATA) {
2426 resourceType |= Resource::FLAG_LOCAL_DATA << 24;
2427 }
2428 if (writer.is_big_endian()) {
2429 // type threeCC is little-endian
2430 BufferStream::swap_endian(&resourceType);
2431 }
2432 writer.write<uint32_t>(resourceType);
2433
2434 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2435 resourceHeaderImagePos = writer.tell();
2436 writer.write<uint32_t>(0);
2437 resourceHeaderCurPos = writer.tell();
2438 continue;
2439 }
2440
2441 if (resource.flags & Resource::FLAG_LOCAL_DATA) {
2442 writer.write(resource.data);
2443 resourceHeaderCurPos = writer.tell();
2444 } else {
2445 writer.write<uint32_t>(resourceDataCurPos);
2446 resourceHeaderCurPos = writer.tell();
2447 writer.seek_u(resourceDataCurPos).write(resource.data);
2448 resourceDataCurPos = writer.tell();
2449 }
2450 }
2451 if (resourceHeaderImagePos) {
2452 writer.seek_u(resourceHeaderImagePos).write<uint32_t>(resourceDataCurPos);
2453 for (auto& resource : sortedResources) {
2454 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2455 writer.seek_u(resourceDataCurPos).write(resource.data);
2456 break;
2457 }
2458 }
2459 return resourceDataCurPos;
2460 }
2461 return 0;
2462 };
2463
2464 // Miscellaneous fixups to write to the output file
2465 auto bakeFormat = this->format;
2466 auto bakeFlags = this->flags;
2467 // No source game supports this format, but they do support the flag with reg. DXT1
2468 if (bakeFormat == ImageFormat::DXT1_ONE_BIT_ALPHA) {
2469 bakeFormat = ImageFormat::DXT1;
2470 bakeFlags |= FLAG_V0_ONE_BIT_ALPHA;
2471 }
2472 // NV_NULL / ATI2N / ATI1N are at a different position based on engine branch
2473 if (this->version < 5 && (this->format == ImageFormat::EMPTY || this->format == ImageFormat::ATI2N || this->format == ImageFormat::ATI1N)) {
2474 bakeFormat = static_cast<ImageFormat>(static_cast<int32_t>(this->format) + 3);
2475 }
2476 // Fix up the HDR format
2477 if (bakeFormat == ImageFormat::SOURCEPP_BGRA8888_HDR) {
2478 bakeFormat = ImageFormat::BGRA8888;
2480 bakeFormat = ImageFormat::RGBA16161616;
2481 }
2482
2483 switch (this->platform) {
2484 case PLATFORM_UNKNOWN:
2485 return out;
2486 case PLATFORM_PC: {
2487 writer
2488 .write(VTF_SIGNATURE)
2489 .write<int32_t>(7)
2490 .write(this->version);
2491
2492 const auto headerLengthPos = writer.tell();
2493 writer.write<uint32_t>(0);
2494
2495 writer
2496 .write(this->width)
2497 .write(this->height)
2498 .write(bakeFlags)
2499 .write(this->frameCount)
2500 .write(this->startFrame)
2501 .pad<uint32_t>()
2502 .write(this->reflectivity[0])
2503 .write(this->reflectivity[1])
2504 .write(this->reflectivity[2])
2505 .pad<uint32_t>()
2506 .write(this->bumpMapScale)
2507 .write(bakeFormat)
2508 .write(this->mipCount)
2509 .write(this->thumbnailFormat)
2510 .write(this->thumbnailWidth)
2511 .write(this->thumbnailHeight);
2512
2513 if (this->version >= 2) {
2514 writer << this->depth;
2515 }
2516
2517 if (this->version < 3) {
2518 writer.pad(math::paddingForAlignment(16, writer.tell()));
2519 const auto headerSize = writer.tell();
2520 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
2521
2522 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2523 writer.write(thumbnailResource->data);
2524 }
2525 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2526 writer.write(imageResource->data);
2527 }
2528 } else {
2529 std::vector<std::byte> auxCompressionResourceData;
2530 std::vector<std::byte> compressedImageResourceData;
2531 bool hasAuxCompression = false;
2532 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2533 hasAuxCompression = this->version >= 6 && this->compressionLevel != 0;
2534 if (hasAuxCompression) {
2535 const auto faceCount = this->getFaceCount();
2536 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
2537 BufferStream auxWriter{auxCompressionResourceData, false};
2538
2539 // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
2540 // - Size of resource in bytes, not counting this int
2541 // - Compression level, method (2 byte integers)
2542 // - (X times) Size of each mip-face-frame combo
2543 auxWriter
2544 .write<uint32_t>(auxCompressionResourceData.size() - sizeof(uint32_t))
2545 .write(this->compressionLevel)
2546 .write(this->compressionMethod);
2547
2548 for (int i = this->mipCount - 1; i >= 0; i--) {
2549 for (int j = 0; j < this->frameCount; j++) {
2550 for (int k = 0; k < faceCount; k++) {
2551 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)) {
2552 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->depth}, this->compressionLevel, this->compressionMethod);
2553 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
2554 auxWriter.write<uint32_t>(compressedData.size());
2555 }
2556 }
2557 }
2558 }
2559 }
2560 }
2561
2562 writer.pad(3).write<uint32_t>(this->getResources().size() + hasAuxCompression + (this->flagsExtra != 0)).pad(8);
2563
2564 std::vector<Resource> sortedResources = this->getResources();
2565 auto flagsExtraBacking = std::bit_cast<std::array<std::byte, 4>>(this->flagsExtra);
2566 if (this->flagsExtra != 0) {
2567 sortedResources.push_back({
2570 .data = flagsExtraBacking,
2571 });
2572 }
2573 if (hasAuxCompression) {
2574 for (auto& resource : sortedResources) {
2575 if (resource.type == Resource::TYPE_IMAGE_DATA) {
2576 resource.data = compressedImageResourceData;
2577 break;
2578 }
2579 }
2580 sortedResources.push_back({
2582 .flags = Resource::FLAG_NONE,
2583 .data = auxCompressionResourceData,
2584 });
2585 }
2586 std::ranges::sort(sortedResources, [](const Resource& lhs, const Resource& rhs) {
2587 return lhs.type < rhs.type;
2588 });
2589 writeResources(headerLengthPos, sortedResources);
2590 }
2591 break;
2592 }
2593 case PLATFORM_XBOX: {
2594 writer << XTF_SIGNATURE << PLATFORM_XBOX;
2595 writer.write<uint32_t>(0);
2596
2597 const auto headerSizePos = writer.tell();
2598 writer
2599 .write<uint32_t>(0)
2600 .write(this->flags)
2601 .write(this->width)
2602 .write(this->height)
2603 .write(this->depth)
2604 .write(this->frameCount);
2605 const auto preloadSizePos = writer.tell();
2606 writer.write<uint16_t>(0);
2607 const auto imageOffsetPos = writer.tell();
2608 writer
2609 .write<uint16_t>(0)
2610 .write(this->reflectivity[0])
2611 .write(this->reflectivity[1])
2612 .write(this->reflectivity[2])
2613 .write(this->bumpMapScale)
2614 .write(this->format)
2615 .write(this->thumbnailWidth)
2616 .write(this->thumbnailHeight)
2617 .write(this->fallbackWidth)
2618 .write(this->fallbackHeight)
2619 .write(this->consoleMipScale)
2620 .write<uint8_t>(0);
2621
2622 const auto headerSize = writer.tell();
2623 writer.seek_u(headerSizePos).write<uint32_t>(headerSize).seek_u(headerSize);
2624
2625 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2626 writer.write(thumbnailResource->data);
2627 }
2628
2629 if (this->format == ImageFormat::P8) {
2630 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2631 writer.write(paletteResource->data);
2632 }
2633 }
2634
2635 bool hasFallbackResource = false;
2636 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2637 hasFallbackResource = true;
2638 std::vector<std::byte> reorderedFallbackData{fallbackResource->data.begin(), fallbackResource->data.end()};
2639 ::swapImageDataEndianForConsole<false>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, this->platform);
2640 bool ok;
2641 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(false, reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, ok);
2642 if (ok) {
2643 writer.write(reorderedFallbackData);
2644 } else {
2645 return {};
2646 }
2647 }
2648
2649 const auto preloadSize = writer.tell();
2650 writer.seek_u(preloadSizePos).write<uint32_t>(preloadSize).seek_u(preloadSize);
2651
2652 if (hasFallbackResource) {
2653 writer.pad(math::paddingForAlignment(512, writer.tell()));
2654 }
2655 const auto imageOffset = writer.tell();
2656 writer.seek_u(imageOffsetPos).write<uint16_t>(imageOffset).seek_u(imageOffset);
2657
2658 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2659 std::vector<std::byte> reorderedImageData{imageResource->data.begin(), imageResource->data.end()};
2660 ::swapImageDataEndianForConsole<false>(reorderedImageData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2661 bool ok;
2662 reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(true, reorderedImageData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2663 if (ok) {
2664 writer.write(reorderedImageData);
2665 } else {
2666 return {};
2667 }
2668 }
2669 if (writer.tell() > 512) {
2670 writer.pad(math::paddingForAlignment(512, writer.tell()));
2671 }
2672 break;
2673 }
2674 case PLATFORM_X360:
2676 case PLATFORM_PS3_PORTAL2: {
2677 if (this->platform == PLATFORM_PS3_PORTAL2) {
2678 writer << VTF3_SIGNATURE;
2679 writer.set_big_endian(true);
2680 writer << PLATFORM_PS3_ORANGEBOX; // Intentional
2681 } else {
2682 writer << VTFX_SIGNATURE;
2683 writer.set_big_endian(true);
2684 writer << this->platform;
2685 }
2686 writer.write<uint32_t>(8);
2687
2688 const auto headerLengthPos = writer.tell();
2689 writer
2690 .write<uint32_t>(0)
2691 .write(bakeFlags)
2692 .write(this->width)
2693 .write(this->height)
2694 .write(this->depth)
2695 .write(this->frameCount);
2696 const auto preloadPos = writer.tell();
2697 writer
2698 .write<uint16_t>(0) // preload size
2699 .write(this->consoleMipScale)
2700 .write<uint8_t>(this->resources.size())
2701 .write(this->reflectivity[0])
2702 .write(this->reflectivity[1])
2703 .write(this->reflectivity[2])
2704 .write(this->bumpMapScale)
2705 .write(bakeFormat)
2706 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[0] * 255)), 0, 255))
2707 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[1] * 255)), 0, 255))
2708 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[2] * 255)), 0, 255))
2709 .write<uint8_t>(255);
2710 const auto compressionPos = writer.tell();
2711 writer.write<uint32_t>(0); // compressed length
2712
2713 if (this->platform == PLATFORM_PS3_PORTAL2) {
2714 // 16 byte aligned
2715 writer.write<uint32_t>(0);
2716 }
2717
2718 std::vector<std::byte> thumbnailResourceData;
2719 std::vector<std::byte> imageResourceData;
2720 std::vector<Resource> sortedResources = this->getResources();
2721 for (auto& resource : sortedResources) {
2722 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
2723 thumbnailResourceData = {resource.data.begin(), resource.data.end()};
2724 ::swapImageDataEndianForConsole<false>(thumbnailResourceData, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
2725 resource.data = thumbnailResourceData;
2726 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
2727 if (this->platform == PLATFORM_PS3_ORANGEBOX) {
2728 bool ok;
2729 imageResourceData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(false, resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2730 if (!ok || imageResourceData.size() != resource.data.size()) {
2731 return {};
2732 }
2733 } else {
2734 imageResourceData = {resource.data.begin(), resource.data.end()};
2735 }
2736 ::swapImageDataEndianForConsole<false>(imageResourceData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2737
2738 // LZMA compression has not been observed on the PS3 copy of The Orange Box
2739 // todo(vtfpp): check cubemaps
2741 auto fixedCompressionLevel = this->compressionLevel;
2742 if (this->compressionLevel == 0) {
2743 // Compression level defaults to 0, so it works differently on console.
2744 // Rather than not compress on 0, 0 will be replaced with the default
2745 // compression level (6) if the compression method is LZMA.
2746 fixedCompressionLevel = 6;
2747 }
2748 if (auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel)) {
2749 imageResourceData = std::move(*compressedData);
2750 const auto curPos = writer.tell();
2751 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
2752 }
2753 }
2754
2755 resource.data = imageResourceData;
2756 break;
2757 }
2758 }
2759 std::ranges::sort(sortedResources, [](const Resource& lhs, const Resource& rhs) {
2760 return lhs.type < rhs.type;
2761 });
2762 const auto resourceDataImagePos = writeResources(headerLengthPos, sortedResources);
2763 writer.seek_u(preloadPos).write(std::max<uint16_t>(resourceDataImagePos, 2048));
2764 break;
2765 }
2766 }
2767 out.resize(writer.size());
2768 return out;
2769}
2770
2771bool VTF::bake(const std::filesystem::path& vtfPath) const {
2772 return fs::writeFileBuffer(vtfPath, this->bake());
2773}
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
Definition Macros.h:19
std::vector< std::byte > bake() const
Definition HOT.cpp:61
std::vector< std::byte > bake() const
Definition SHT.cpp:84
uint16_t width
Definition VTF.h:517
@ PLATFORM_PC
Definition VTF.h:199
@ PLATFORM_X360
Definition VTF.h:201
@ PLATFORM_PS3_PORTAL2
Definition VTF.h:203
@ PLATFORM_PS3_ORANGEBOX
Definition VTF.h:202
@ PLATFORM_UNKNOWN
Definition VTF.h:198
@ PLATFORM_XBOX
Definition VTF.h:200
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition VTF.cpp:1190
bool setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth=1)
Definition VTF.cpp:1481
void removeKeyValuesDataResource()
Definition VTF.cpp:1916
void computeFallback(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition VTF.cpp:2256
CompressionMethod compressionMethod
Definition VTF.h:551
void computeReflectivity()
Definition VTF.cpp:1505
uint8_t getThumbnailWidth() const
Definition VTF.cpp:1582
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition VTF.cpp:2206
uint8_t consoleMipScale
Definition VTF.h:540
uint8_t fallbackWidth
Definition VTF.h:535
bool hasNativeResourceSupport() const
Definition VTF.cpp:1610
ImageFormat getFormat() const
Definition VTF.cpp:1320
void setPlatform(Platform newPlatform)
Definition VTF.cpp:1059
uint16_t getHeight(uint8_t mip=0) const
Definition VTF.cpp:1202
uint32_t flagsExtra
Definition VTF.h:520
VTF & operator=(const VTF &other)
Definition VTF.cpp:867
void removeFallback()
Definition VTF.cpp:2283
void removeFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1269
uint8_t getPaddedFallbackHeight(uint8_t mip=0) const
Definition VTF.cpp:1602
uint16_t getCompressionLevel() const
Definition VTF.cpp:1949
sourcepp::math::Vec3f reflectivity
Definition VTF.h:525
uint16_t getWidth(uint8_t mip=0) const
Definition VTF.cpp:1194
uint16_t startFrame
Definition VTF.h:523
void setThumbnail(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:2144
uint8_t mipCount
Definition VTF.h:529
bool setRecommendedMipCount()
Definition VTF.cpp:1364
static bool createInternal(VTF &writer, CreationOptions options)
Definition VTF.cpp:910
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...
Definition VTF.cpp:1858
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)
Definition VTF.cpp:1698
bool hasFallbackData() const
Definition VTF.cpp:2217
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition VTF.cpp:1371
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
Definition VTF.cpp:1186
uint8_t thumbnailWidth
Definition VTF.h:532
void setCompressionLevel(int16_t newCompressionLevel)
Definition VTF.cpp:1953
uint8_t fallbackHeight
Definition VTF.h:536
ImageConversion::ResizeMethod imageHeightResizeMethod
Definition VTF.h:553
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:1324
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition VTF.cpp:1181
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...
Definition VTF.cpp:1854
void addFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1265
float getBumpMapScale() const
Definition VTF.cpp:1570
void setKeyValuesDataResource(std::string_view value)
Definition VTF.cpp:1906
void computeTransparencyFlags()
Definition VTF.cpp:1293
std::vector< std::byte > saveFallbackToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition VTF.cpp:2290
std::vector< std::byte > data
Definition VTF.h:513
ImageFormat format
Definition VTF.h:528
Platform getPlatform() const
Definition VTF.cpp:1055
std::vector< std::byte > getPaletteResourceFrame(uint16_t frame=0) const
Definition VTF.cpp:1805
void addFlags(uint32_t flags_)
Definition VTF.cpp:1249
uint8_t getFaceCount() const
Definition VTF.cpp:1435
ImageFormat thumbnailFormat
Definition VTF.h:531
void setFlags(uint32_t flags_)
Definition VTF.cpp:1245
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
Definition VTF.cpp:1210
void removeLODResource()
Definition VTF.cpp:1894
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
Definition VTF.cpp:1177
void setBumpMapScale(float newBumpMapScale)
Definition VTF.cpp:1574
ImageFormat getThumbnailFormat() const
Definition VTF.cpp:1578
uint16_t getStartFrame() const
Definition VTF.cpp:1489
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
Definition VTF.h:237
bool hasThumbnailData() const
Definition VTF.cpp:2114
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
Definition VTF.cpp:1501
void setSRGB(bool srgb)
Definition VTF.cpp:1277
const std::vector< Resource > & getResources() const
Definition VTF.cpp:1614
void removeHotspotDataResource()
Definition VTF.cpp:1945
void setVersion(uint32_t newVersion)
Definition VTF.cpp:1142
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
Definition VTF.cpp:2103
Resource * getResourceInternal(Resource::Type type)
Definition VTF.cpp:1627
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition VTF.cpp:2176
std::vector< std::byte > bake() const
Definition VTF.cpp:2410
bool hasImageData() const
Definition VTF.cpp:1975
ImageConversion::ResizeMethod imageWidthResizeMethod
Definition VTF.h:552
uint16_t compressionLevel
Definition VTF.h:550
std::span< const std::byte > getFallbackDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2221
uint8_t fallbackMipCount
Definition VTF.h:537
void removeParticleSheetResource()
Definition VTF.cpp:1873
std::vector< std::byte > getFallbackDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2239
uint8_t getFallbackWidth(uint8_t mip=0) const
Definition VTF.cpp:1590
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)
Definition VTF.cpp:968
std::vector< std::byte > getImageDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:2012
uint16_t getFrameCount() const
Definition VTF.cpp:1423
uint8_t getMipCount() const
Definition VTF.cpp:1348
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
Definition VTF.cpp:1885
void removeResourceInternal(Resource::Type type)
Definition VTF.cpp:1694
std::vector< std::byte > getFallbackDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition VTF.cpp:2252
bool setFrameCount(uint16_t newFrameCount)
Definition VTF.cpp:1427
void removeAuthorInfoResource()
Definition VTF.cpp:1930
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
Definition VTF.cpp:1173
std::vector< std::byte > getImageDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:1997
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
Definition VTF.cpp:1307
void setStartFrame(uint16_t newStartFrame)
Definition VTF.cpp:1493
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
Definition VTF.cpp:2132
std::span< const std::byte > getImageDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
Definition VTF.cpp:1979
uint16_t depth
Definition VTF.h:543
sourcepp::math::Vec3f getReflectivity() const
Definition VTF.cpp:1497
uint8_t getFallbackMipCount() const
Definition VTF.cpp:1606
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
Definition VTF.cpp:2140
void setFlagsExtra(uint32_t flags_)
Definition VTF.cpp:1261
uint8_t getConsoleMipScale() const
Definition VTF.cpp:2302
uint16_t getDepth(uint8_t mip=0) const
Definition VTF.cpp:1469
bool isSRGB() const
Definition VTF.cpp:1273
uint32_t flags
Definition VTF.h:519
uint16_t getPaddedHeight(uint8_t mip=0) const
Definition VTF.cpp:1206
void removeThumbnail()
Definition VTF.cpp:2199
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
Definition VTF.h:234
uint32_t version
Definition VTF.h:515
std::span< const std::byte > getThumbnailDataRaw() const
Definition VTF.cpp:2118
bool setMipCount(uint8_t newMipCount)
Definition VTF.cpp:1352
void setExtendedFlagsResource(uint32_t value)
Definition VTF.cpp:1898
CompressionMethod getCompressionMethod() const
Definition VTF.cpp:1961
uint16_t getPaddedWidth(uint8_t mip=0) const
Definition VTF.cpp:1198
void setAuthorInfoResource(std::string_view value)
Definition VTF.cpp:1920
void setCRCResource(uint32_t value)
Definition VTF.cpp:1877
void setCompressionMethod(CompressionMethod newCompressionMethod)
Definition VTF.cpp:1965
bool setFaceCount(bool isCubeMap)
Definition VTF.cpp:1461
uint8_t getThumbnailHeight() const
Definition VTF.cpp:1586
void setConsoleMipScale(uint8_t consoleMipScale_)
Definition VTF.cpp:2306
const Resource * getResource(Resource::Type type) const
Definition VTF.cpp:1618
uint32_t getFlags() const
Definition VTF.cpp:1241
void setParticleSheetResource(const SHT &value)
Definition VTF.cpp:1862
@ FLAG_MASK_V5
Definition VTF.h:177
@ FLAG_V0_ONE_BIT_ALPHA
Definition VTF.h:128
@ FLAG_MASK_V4_TF2
Definition VTF.h:171
@ FLAG_V0_MULTI_BIT_ALPHA
Definition VTF.h:129
@ FLAG_MASK_V2
Definition VTF.h:148
@ FLAG_MASK_V5_CSGO
Definition VTF.h:184
@ FLAG_V0_NO_LOD
Definition VTF.h:125
@ FLAG_V4_SRGB
Definition VTF.h:163
@ FLAG_V0_NO_MIP
Definition VTF.h:124
@ FLAG_V5_SRGB
Definition VTF.h:174
@ FLAG_MASK_XBOX
Definition VTF.h:154
@ FLAG_V5_PWL_CORRECTED
Definition VTF.h:173
@ FLAG_MASK_V4
Definition VTF.h:164
@ FLAG_MASK_V3
Definition VTF.h:161
@ FLAG_V0_ENVMAP
Definition VTF.h:130
@ FLAG_MASK_V1
Definition VTF.h:141
@ FLAG_MASK_INTERNAL
Definition VTF.h:189
uint16_t height
Definition VTF.h:518
void removeCRCResource()
Definition VTF.cpp:1881
uint16_t frameCount
Definition VTF.h:522
uint8_t getFallbackHeight(uint8_t mip=0) const
Definition VTF.cpp:1598
uint32_t getVersion() const
Definition VTF.cpp:1138
@ FLAG_EXTRA_USING_PREMULTIPLIED_ALPHA_RESIZE
Definition VTF.h:193
void setHotspotDataResource(const HOT &value)
Definition VTF.cpp:1934
void removeFlags(uint32_t flags_)
Definition VTF.cpp:1253
uint32_t getFlagsExtra() const
Definition VTF.cpp:1257
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
Definition VTF.cpp:1636
bool setDepth(uint16_t newDepth)
Definition VTF.cpp:1473
uint64_t estimateBakeSize() const
Definition VTF.cpp:2310
std::vector< Resource > resources
Definition VTF.h:546
uint8_t thumbnailHeight
Definition VTF.h:533
Platform platform
Definition VTF.h:549
uint8_t getPaddedFallbackWidth(uint8_t mip=0) const
Definition VTF.cpp:1594
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)
Definition VTF.cpp:2016
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...
Definition VTF.cpp:1812
void removeExtendedFlagsResource()
Definition VTF.cpp:1902
bool opened
Definition VTF.h:511
float bumpMapScale
Definition VTF.h:527
std::optional< std::vector< std::byte > > compressValveLZMA(std::span< const std::byte > data, uint8_t compressLevel=6)
Definition LZMA.cpp:8
constexpr auto VALVE_LZMA_SIGNATURE
Definition LZMA.h:13
std::optional< std::vector< std::byte > > decompressValveLZMA(std::span< const std::byte > data)
Definition LZMA.cpp:51
bool writeFileBuffer(const std::filesystem::path &filepath, std::span< const std::byte > buffer)
Definition FS.cpp:37
std::vector< std::byte > readFileBuffer(const std::filesystem::path &filepath, std::size_t startOffset=0)
Definition FS.cpp:7
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
Definition Math.h:64
constexpr uint32_t log2ceil(uint32_t value)
Definition Math.h:60
void toLower(std::string &input)
Definition String.cpp:167
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)
std::vector< std::byte > convertImageDataToFile(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, FileFormat fileFormat=FileFormat::DEFAULT)
Converts image data to the given file format (PNG or EXR by default).
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 > 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.
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the amount of data required to store a texture with the given format and dimensions.
constexpr bool getDataPosition(uint32_t &offset, uint32_t &length, ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice=0, uint16_t depth=1)
Find the position of a specific mip, frame, face, and slice within a texture.
constexpr bool large(ImageFormat format)
Check if the given format is representable by RGBA8888 without losing data.
constexpr bool transparent(ImageFormat format)
Check if the given format can store transparency.
constexpr uint32_t getDataLengthXBOX(bool padded, ImageFormat format, uint8_t mipCount, uint16_t frameCount, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t depth=1)
Calculate the amount of data required to store an XBOX platform texture with the given format,...
constexpr int8_t decompressedAlpha(ImageFormat format)
Get the number of bits of precision of the alpha channel in the given format regardless of compressio...
constexpr uint8_t bpp(ImageFormat format)
Find the bits per pixel of the given format.
constexpr bool getDataPositionXBOX(uint32_t &offset, uint32_t &length, bool padded, ImageFormat format, uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint8_t face, uint8_t faceCount, uint16_t width, uint16_t height, uint16_t slice=0, uint16_t depth=1)
Find the position of a specific mip, frame, face, and slice within an XBOX platform texture.
constexpr bool compressed(ImageFormat format)
Check if the given format is a compressed format (DXT1, DXT3, DXT5, ATI1N, ATI2N, BC7,...
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
Definition VTF.h:23
constexpr uint32_t XTF_SIGNATURE
Definition VTF.h:24
constexpr uint32_t VTFX_SIGNATURE
Definition VTF.h:25
constexpr uint32_t VTF3_SIGNATURE
Definition VTF.h:26
CompressionMethod
Definition VTF.h:28
std::pair< uint16_t, uint16_t > clamp(uint16_t width, uint16_t height) const
std::string getDataAsAuthorInfo() const
Definition VTF.cpp:364
HOT getDataAsHotspotData() const
Definition VTF.cpp:368
std::tuple< uint8_t, uint8_t, uint8_t, uint8_t > getDataAsLODControlInfo() const
Definition VTF.cpp:356
Type type
Definition VTF.h:60
ConvertedData convertData() const
Definition VTF.cpp:296
std::string getDataAsKeyValuesData() const
Definition VTF.cpp:360
int16_t getDataAsAuxCompressionLevel() const
Definition VTF.cpp:372
CompressionMethod getDataAsAuxCompressionMethod() const
Definition VTF.cpp:379
SHT getDataAsParticleSheet() const
Definition VTF.cpp:344
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT > ConvertedData
Definition VTF.h:64
uint32_t getDataAsFlags() const
Definition VTF.cpp:352
@ FLAG_LOCAL_DATA
Definition VTF.h:57
uint32_t getDataAsCRC() const
Definition VTF.cpp:348
uint32_t getDataAsAuxCompressionLength(uint8_t mip, uint8_t mipCount, uint16_t frame, uint16_t frameCount, uint16_t face, uint16_t faceCount) const
Definition VTF.cpp:390
@ TYPE_KEYVALUES_DATA
Definition VTF.h:50
@ TYPE_FALLBACK_DATA
Definition VTF.h:42
@ TYPE_PALETTE_DATA
Definition VTF.h:41
@ TYPE_PARTICLE_SHEET_DATA
Definition VTF.h:43
@ TYPE_AUTHOR_INFO
Definition VTF.h:51
@ TYPE_EXTENDED_FLAGS
Definition VTF.h:46
@ TYPE_IMAGE_DATA
Definition VTF.h:45
@ TYPE_AUX_COMPRESSION
Definition VTF.h:48
@ TYPE_LOD_CONTROL_INFO
Definition VTF.h:49
@ TYPE_THUMBNAIL_DATA
Definition VTF.h:40
@ TYPE_SOURCEPP_FLAGS
Definition VTF.h:52
@ TYPE_HOTSPOT_DATA
Definition VTF.h:44
Flags flags
Definition VTF.h:61
std::vector< std::byte > getDataAsPalette(uint16_t frame) const
Definition VTF.cpp:336
std::span< std::byte > data
Definition VTF.h:62
uint16_t initialFrameCount
Definition VTF.h:216
ImageConversion::ResizeBounds resizeBounds
Definition VTF.h:215
ImageConversion::ResizeFilter filter
Definition VTF.h:212
ImageConversion::ResizeMethod heightResizeMethod
Definition VTF.h:211
CompressionMethod compressionMethod
Definition VTF.h:226
ImageConversion::ResizeMethod widthResizeMethod
Definition VTF.h:210
ImageFormat outputFormat
Definition VTF.h:208