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#include <vtfpp/VTF.h>
2
3#include <algorithm>
4#include <cstring>
5#include <unordered_map>
6#include <utility>
7
8#ifdef SOURCEPP_BUILD_WITH_TBB
9#include <execution>
10#endif
11
12#ifdef SOURCEPP_BUILD_WITH_THREADS
13#include <future>
14#include <thread>
15#endif
16
17#include <BufferStream.h>
18#include <miniz.h>
19#include <zstd.h>
20
23
24using namespace sourcepp;
25using namespace vtfpp;
26
27namespace {
28
29[[nodiscard]] std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level, CompressionMethod method) {
30 switch (method) {
31 using enum CompressionMethod;
32 case DEFLATE: {
33 mz_ulong compressedSize = mz_compressBound(data.size());
34 std::vector<std::byte> out(compressedSize);
35
36 int status = MZ_OK;
37 while ((status = mz_compress2(reinterpret_cast<unsigned char*>(out.data()), &compressedSize, reinterpret_cast<const unsigned char*>(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
38 compressedSize *= 2;
39 out.resize(compressedSize);
40 }
41
42 if (status != MZ_OK) {
43 return {};
44 }
45 out.resize(compressedSize);
46 return out;
47 }
48 case ZSTD: {
49 if (level < 0) {
50 level = 6;
51 }
52
53 const auto expectedSize = ZSTD_compressBound(data.size());
54 std::vector<std::byte> out(expectedSize);
55
56 const auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
57 if (ZSTD_isError(compressedSize)) {
58 return {};
59 }
60
61 out.resize(compressedSize);
62 return out;
63 }
64 case CONSOLE_LZMA: {
65 if (const auto out = compression::compressValveLZMA(data, level)) {
66 return *out;
67 }
68 return {};
69 }
70 }
71 return {};
72}
73
74template<bool ExistingDataIsSwizzled>
75constexpr void swizzleImageDataForPS3(std::span<std::byte> inputData, std::span<std::byte> outputData, ImageFormat format, uint16_t width, uint16_t height, uint16_t slice) {
76 width *= ImageFormatDetails::bpp(format) / 32;
77 const auto zIndex = [
78 widthL2 = static_cast<int>(math::log2ceil(width)),
79 heightL2 = static_cast<int>(math::log2ceil(height)),
80 sliceL2 = static_cast<int>(math::log2ceil(slice))
81 ](uint32_t x, uint32_t y, uint32_t z) {
82 auto widthL2m = widthL2;
83 auto heightL2m = heightL2;
84 auto sliceL2m = sliceL2;
85 uint32_t offset = 0;
86 uint32_t shiftCount = 0;
87 do {
88 if (sliceL2m --> 0) {
89 offset |= (z & 1) << shiftCount++;
90 z >>= 1;
91 }
92 if (heightL2m --> 0) {
93 offset |= (y & 1) << shiftCount++;
94 y >>= 1;
95 }
96 if (widthL2m --> 0) {
97 offset |= (x & 1) << shiftCount++;
98 x >>= 1;
99 }
100 } while (x | y | z);
101 return offset;
102 };
103
104 const auto* inputPtr = reinterpret_cast<const uint32_t*>(inputData.data());
105 auto* outputPtr = reinterpret_cast<uint32_t*>(outputData.data());
106 for (uint16_t x = 0; x < width; x++) {
107 for (uint16_t y = 0; y < height; y++) {
108 for (uint16_t z = 0; z < slice; z++) {
109 if constexpr (ExistingDataIsSwizzled) {
110 *outputPtr++ = reinterpret_cast<const uint32_t*>(inputData.data())[zIndex(x, y, z)];
111 } else {
112 reinterpret_cast<uint32_t*>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
113 }
114 }
115 }
116 }
117}
118
119template<bool ConvertingFromSource>
120void 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 sliceCount, VTF::Platform platform) {
121 if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) {
122 return;
123 }
124
125 if (platform == VTF::PLATFORM_X360) {
126 switch (format) {
127 using enum ImageFormat;
128 case BGRA8888:
129 case BGRX8888:
130 case UVWQ8888:
131 case UVLX8888: {
132 const auto newData = ImageConversion::convertSeveralImageDataToFormat(imageData, ARGB8888, BGRA8888, mipCount, frameCount, faceCount, width, height, sliceCount);
133 std::ranges::copy(newData, imageData.begin());
134 break;
135 }
136 case DXT1:
138 case DXT3:
139 case DXT5:
140 case UV88: {
141 std::span dxtData{reinterpret_cast<uint16_t*>(imageData.data()), imageData.size() / sizeof(uint16_t)};
142 std::for_each(
143#ifdef SOURCEPP_BUILD_WITH_TBB
144 std::execution::par_unseq,
145#endif
146 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
147 BufferStream::swap_endian(&value);
148 });
149 break;
150 }
151 default:
152 break;
153 }
154 }
155
156 if ((platform == VTF::PLATFORM_PS3_ORANGEBOX || platform == VTF::PLATFORM_PS3_PORTAL2) && !ImageFormatDetails::compressed(format) && ImageFormatDetails::bpp(format) % 32 == 0) {
157 std::vector<std::byte> out(imageData.size());
158 for(int mip = mipCount - 1; mip >= 0; mip--) {
159 const auto mipWidth = ImageDimensions::getMipDim(mip, width);
160 const auto mipHeight = ImageDimensions::getMipDim(mip, height);
161 for (int frame = 0; frame < frameCount; frame++) {
162 for (int face = 0; face < faceCount; face++) {
163 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height)) {
164 std::span imageDataSpan{imageData.data() + offset, length * sliceCount};
165 std::span outSpan{out.data() + offset, length * sliceCount};
166 ::swizzleImageDataForPS3<ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, sliceCount);
167 }
168 }
169 }
170 }
171 std::memcpy(imageData.data(), out.data(), out.size());
172 }
173}
174
175} // namespace
176
178 switch (this->type) {
180 if (this->data.size() <= sizeof(uint32_t)) {
181 return {};
182 }
183 return SHT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
184 case TYPE_CRC:
186 if (this->data.size() != sizeof(uint32_t)) {
187 return {};
188 }
189 return *reinterpret_cast<const uint32_t*>(this->data.data());
191 if (this->data.size() != sizeof(uint32_t)) {
192 return {};
193 }
194 return std::make_tuple(
195 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 0),
196 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 1),
197 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 2),
198 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 3));
200 if (this->data.size() <= sizeof(uint32_t)) {
201 return "";
202 }
203 return std::string(reinterpret_cast<const char*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data()));
205 if (this->data.size() <= sizeof(uint32_t)) {
206 return {};
207 }
208 return HOT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
210 if (this->data.size() <= sizeof(uint32_t) || this->data.size() % sizeof(uint32_t) != 0) {
211 return {};
212 }
213 return std::span{reinterpret_cast<uint32_t*>(this->data.data()), this->data.size() / 4};
214 default:
215 break;
216 }
217 return {};
218}
219
221 this->version = 4;
222
224
225 this->format = ImageFormat::EMPTY;
227
228 this->width = 0;
229 this->height = 0;
230 this->mipCount = 0;
231 this->frameCount = 0;
232 this->sliceCount = 0;
233
234 this->opened = true;
235}
236
237VTF::VTF(std::vector<std::byte>&& vtfData, bool parseHeaderOnly)
238 : data(std::move(vtfData)) {
239 BufferStreamReadOnly stream{this->data};
240
241 if (const auto signature = stream.read<uint32_t>(); signature == VTF_SIGNATURE) {
242 stream >> this->platform;
243 if (this->platform != PLATFORM_PC) {
244 return;
245 }
246 stream >> this->version;
247 if (this->version > 6) {
248 return;
249 }
250 } else if (signature == VTFX_SIGNATURE || signature == VTF3_SIGNATURE) {
251 stream.set_big_endian(true);
252 stream >> this->platform;
253 if (this->platform != PLATFORM_X360 && this->platform != PLATFORM_PS3_ORANGEBOX && this->platform != PLATFORM_PS3_PORTAL2) {
254 return;
255 }
256 stream >> this->version;
257 if (this->version != 8) {
258 return;
259 }
260 // Now fix up the actual version as it would be on PC
261 if (signature == VTF3_SIGNATURE) {
262 this->platform = PLATFORM_PS3_PORTAL2;
263 this->version = 5;
264 } else {
265 this->version = 4;
266 }
267 } else {
268 return;
269 }
270
271 const auto headerSize = stream.read<uint32_t>();
272
273 const auto readResources = [this, &stream](uint32_t resourceCount) {
274 // Read resource header info
275 this->resources.reserve(resourceCount);
276 for (int i = 0; i < resourceCount; i++) {
277 auto& [type, flags_, data_] = this->resources.emplace_back();
278
279 auto typeAndFlags = stream.read<uint32_t>();
280 if (stream.is_big_endian()) {
281 // This field is little-endian
282 BufferStream::swap_endian(&typeAndFlags);
283 }
284 type = static_cast<Resource::Type>(typeAndFlags & 0xffffff); // last 3 bytes
285 flags_ = static_cast<Resource::Flags>(typeAndFlags >> 24); // first byte
286 data_ = stream.read_span<std::byte>(4);
287
288 if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) {
289 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(data_.data()));
290 }
291 }
292
293 // Sort resources by their offset, in case certain VTFs are written
294 // weirdly and have resource data written out of order. So far I have
295 // found only one VTF in an official Valve game where this is the case
296 std::ranges::sort(this->resources, [](const Resource& lhs, const Resource& rhs) {
298 return lhs.type < rhs.type;
299 }
301 return true;
302 }
304 return false;
305 }
306 return *reinterpret_cast<uint32_t*>(lhs.data.data()) < *reinterpret_cast<uint32_t*>(rhs.data.data());
307 });
308
309 // Fix up data spans to point to the actual data
310 Resource* lastResource = nullptr;
311 for (auto& resource : this->resources) {
312 if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) {
313 if (lastResource) {
314 const auto lastOffset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
315 const auto currentOffset = *reinterpret_cast<uint32_t*>(resource.data.data());
316 const auto curPos = stream.tell();
317 stream.seek(lastOffset);
318 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
319 stream.seek(static_cast<int64_t>(curPos));
320 }
321 lastResource = &resource;
322 }
323 }
324 if (lastResource) {
325 auto offset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
326 auto curPos = stream.tell();
327 stream.seek(offset);
328 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
329 stream.seek(static_cast<int64_t>(curPos));
330 }
331 };
332
333 // HACK: a couple tweaks to fix engine bugs or branch differences
334 const auto postReadTransform = [this] {
335 // Change the format to DXT1_ONE_BIT_ALPHA to get compressonator to recognize it.
336 // No source game recognizes this format, so we will do additional transform in bake back to DXT1.
337 // We also need to check MULTI_BIT_ALPHA flag because stupid third party tools will sometimes set it???
338 if (this->format == ImageFormat::DXT1 && this->flags & (FLAG_ONE_BIT_ALPHA | FLAG_MULTI_BIT_ALPHA)) {
339 this->format = ImageFormat::DXT1_ONE_BIT_ALPHA;
340 }
341 // If the version is below 7.5, NV_NULL / ATI2N / ATI1N are at different positions in the enum.
342 // It is safe to do this because these formats didn't exist before v7.5.
343 if (this->version < 5 && (this->format == ImageFormat::RGBA1010102 || this->format == ImageFormat::BGRA1010102 || this->format == ImageFormat::R16F)) {
344 this->format = static_cast<ImageFormat>(static_cast<int32_t>(this->format) - 3);
345 }
346 };
347
348 switch (this->platform) {
349 case PLATFORM_UNKNOWN:
350 return;
351 case PLATFORM_PC: {
352 stream
353 .read(this->width)
354 .read(this->height)
355 .read(this->flags)
356 .read(this->frameCount)
357 .read(this->startFrame)
358 .skip(4)
359 .read(this->reflectivity[0])
360 .read(this->reflectivity[1])
361 .read(this->reflectivity[2])
362 .skip(4)
363 .read(this->bumpMapScale)
364 .read(this->format)
365 .read(this->mipCount);
366
367 postReadTransform();
368
369 // This will always be DXT1
370 stream.skip<ImageFormat>();
371 stream >> this->thumbnailWidth >> this->thumbnailHeight;
372 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
374 } else {
376 }
377
378 if (this->version < 2) {
379 this->sliceCount = 1;
380 } else {
381 stream.read(this->sliceCount);
382 }
383
384 if (parseHeaderOnly) {
385 this->opened = true;
386 return;
387 }
388
389 if (this->version >= 3) {
390 stream.skip(3);
391 auto resourceCount = stream.read<uint32_t>();
392 stream.skip(8);
393 readResources(resourceCount);
394
395 this->opened = stream.tell() == headerSize;
396
397 if (this->opened && this->version >= 6) {
398 const auto* auxResource = this->getResource(Resource::TYPE_AUX_COMPRESSION);
399 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
400 if (auxResource && imageResource) {
401 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
402 const auto faceCount = this->getFaceCount();
403 std::vector<std::byte> decompressedImageData(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->sliceCount));
404 uint32_t oldOffset = 0;
405 for (int i = this->mipCount - 1; i >= 0; i--) {
406 for (int j = 0; j < this->frameCount; j++) {
407 for (int k = 0; k < faceCount; k++) {
408 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
409 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->getSliceCount())) {
410 // Keep in mind that slices are compressed together
411 mz_ulong decompressedImageDataSize = newLength * this->sliceCount;
412 switch (auxResource->getDataAsAuxCompressionMethod()) {
413 using enum CompressionMethod;
414 case DEFLATE:
415 if (mz_uncompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
416 this->opened = false;
417 return;
418 }
419 break;
420 case ZSTD:
421 if (auto decompressedSize = ZSTD_decompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength); ZSTD_isError(decompressedSize) || decompressedSize != decompressedImageDataSize) {
422 this->opened = false;
423 return;
424 }
425 break;
426 case CONSOLE_LZMA:
427 // Shouldn't be here!
429 break;
430 }
431 }
432 oldOffset += oldLength;
433 }
434 }
435 }
436 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, decompressedImageData);
437 }
438 }
439 }
440 } else {
441 stream.skip(math::paddingForAlignment(16, stream.tell()));
442 this->opened = stream.tell() == headerSize;
443
444 this->resources.reserve(2);
445
446 if (this->hasThumbnailData()) {
447 this->resources.push_back({
449 .flags = Resource::FLAG_NONE,
450 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
451 });
452 }
453 if (this->hasImageData()) {
454 this->resources.push_back({
456 .flags = Resource::FLAG_NONE,
457 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
458 });
459 }
460 }
461
462 if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
463 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
464 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
466 }
467 break;
468 }
469 case PLATFORM_X360:
472 uint8_t resourceCount;
473 stream
474 .read(this->flags)
475 .read(this->width)
476 .read(this->height)
477 .read(this->sliceCount)
478 .read(this->frameCount)
479 .skip<uint16_t>() // preload
480 .skip<uint8_t>() // skip high mip levels
481 .read(resourceCount)
482 .read(this->reflectivity[0])
483 .read(this->reflectivity[1])
484 .read(this->reflectivity[2])
485 .read(this->bumpMapScale)
486 .read(this->format)
487 .skip<math::Vec4ui8>() // lowResImageSample (replacement for thumbnail resource, presumably linear color)
488 .skip<uint32_t>(); // compressedLength
489
490 postReadTransform();
491
492 // Align to 16 bytes
493 if (this->platform == PLATFORM_PS3_PORTAL2) {
494 stream.skip<uint32_t>();
495 }
496
497 this->mipCount = (this->flags & FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height);
498
499 if (parseHeaderOnly) {
500 this->opened = true;
501 return;
502 }
503
504 this->resources.reserve(resourceCount);
505 readResources(resourceCount);
506
507 this->opened = stream.tell() == headerSize;
508
509 // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine
510 for (const auto& resource : this->resources) {
511 // Decompress LZMA resources
512 if (BufferStreamReadOnly rsrcStream{resource.data.data(), resource.data.size()}; rsrcStream.read<uint32_t>() == compression::VALVE_LZMA_SIGNATURE) {
513 if (auto decompressedData = compression::decompressValveLZMA(resource.data)) {
514 this->setResourceInternal(resource.type, *decompressedData);
515
516 if (resource.type == Resource::TYPE_IMAGE_DATA) {
517 // Do this here because compressionLength in header can be garbage on PS3 orange box
519 }
520 }
521 }
522
523 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
524 ::swapImageDataEndianForConsole<true>(resource.data, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
525 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
526 ::swapImageDataEndianForConsole<true>(resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->sliceCount, this->platform);
527 } else if (!(resource.flags & Resource::FLAG_LOCAL_DATA) && resource.data.size() >= sizeof(uint32_t)) {
528 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resource.data.data()));
529 }
530 }
531 break;
532 }
533 }
534}
535
536VTF::VTF(std::span<const std::byte> vtfData, bool parseHeaderOnly)
537 : VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly) {}
538
539VTF::VTF(const std::string& vtfPath, bool parseHeaderOnly)
540 : VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly) {}
541
542VTF::VTF(const VTF& other) {
543 *this = other;
544}
545
546VTF& VTF::operator=(const VTF& other) {
547 this->opened = other.opened;
548 this->data = other.data;
549 this->version = other.version;
550 this->width = other.width;
551 this->height = other.height;
552 this->flags = other.flags;
553 this->frameCount = other.frameCount;
554 this->startFrame = other.startFrame;
555 this->reflectivity = other.reflectivity;
556 this->bumpMapScale = other.bumpMapScale;
557 this->format = other.format;
558 this->mipCount = other.mipCount;
559 this->thumbnailFormat = other.thumbnailFormat;
560 this->thumbnailWidth = other.thumbnailWidth;
561 this->thumbnailHeight = other.thumbnailHeight;
562 this->sliceCount = other.sliceCount;
563
564 this->resources.clear();
565 for (const auto& [otherType, otherFlags, otherData] : other.resources) {
566 auto& [type, flags_, data_] = this->resources.emplace_back();
567 type = otherType;
568 flags_ = otherFlags;
569 data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
570 }
571
572 this->platform = other.platform;
577
578 return *this;
579}
580
581VTF::operator bool() const {
582 return this->opened;
583}
584
586 bool out = true;
587 if (writer.hasImageData() && (options.invertGreenChannel || options.gammaCorrection != 1.f)) {
588 for (int i = 1; i < writer.mipCount; i++) {
589 for (int j = 0; j < writer.frameCount; j++) {
590 for (int k = 0; k < writer.getFaceCount(); k++) {
591 for (int l = 0; l < writer.sliceCount; l++) {
592 if (options.invertGreenChannel && !writer.setImage(ImageConversion::invertGreenChannelForImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i)), writer.getFormat(), writer.getWidth(i), writer.getHeight(i), ImageConversion::ResizeFilter::DEFAULT, i, j, k, l)) {
593 out = false;
594 }
595 if (options.gammaCorrection != 1.f && !writer.setImage(ImageConversion::gammaCorrectImageData(writer.getImageDataRaw(i, j, k, l), writer.getFormat(), writer.getWidth(i), writer.getHeight(i), options.gammaCorrection), writer.getFormat(), writer.getWidth(i), writer.getHeight(i), ImageConversion::ResizeFilter::DEFAULT, i, j, k, l)) {
596 out = false;
597 }
598 }
599 }
600 }
601 }
602 }
603 writer.setPlatform(options.platform);
604 if (options.computeReflectivity) {
605 writer.computeReflectivity();
606 }
607 if (options.initialFrameCount > 1 || options.isCubeMap || options.initialSliceCount > 1) {
608 if (!writer.setFrameFaceAndSliceCount(options.initialFrameCount, options.isCubeMap, options.initialSliceCount)) {
609 out = false;
610 }
611 }
612 writer.setStartFrame(options.startFrame);
613 writer.setBumpMapScale(options.bumpMapScale);
614 if (options.computeThumbnail) {
615 writer.computeThumbnail();
616 }
617 if (options.outputFormat == VTF::FORMAT_UNCHANGED) {
618 options.outputFormat = writer.getFormat();
619 } else if (options.outputFormat == VTF::FORMAT_DEFAULT) {
620 options.outputFormat = VTF::getDefaultCompressedFormat(writer.getFormat(), writer.getVersion(), options.isCubeMap);
621 }
622 if (options.computeMips) {
623 if (const auto recommendedMipCount = ImageDimensions::getRecommendedMipCountForDims(options.outputFormat, writer.getWidth(), writer.getHeight()); recommendedMipCount > 1) {
624 if (!writer.setMipCount(recommendedMipCount)) {
625 out = false;
626 }
627 writer.computeMips(options.filter);
628 }
629 }
631 if (options.computeTransparencyFlags) {
633 }
636 return out;
637}
638
639bool VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, const CreationOptions& options) {
640 VTF writer;
641 writer.setVersion(options.version);
642 writer.addFlags(options.flags);
644 if (!writer.setImage(imageData, format, width, height, options.filter)) {
645 return false;
646 }
647 if (!createInternal(writer, options)) {
648 return false;
649 }
650 return writer.bake(vtfPath);
651}
652
653bool VTF::create(ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, const CreationOptions& options) {
654 std::vector<std::byte> imageData;
655 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
656 return create(imageData, format, width, height, vtfPath, options);
657}
658
659VTF VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
660 VTF writer;
661 writer.setVersion(options.version);
662 writer.addFlags(options.flags);
664 writer.setImage(imageData, format, width, height, options.filter);
665 createInternal(writer, options);
666 return writer;
667}
668
669VTF VTF::create(ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
670 std::vector<std::byte> imageData;
671 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
672 return create(imageData, format, width, height, options);
673}
674
675bool VTF::create(const std::string& imagePath, const std::string& vtfPath, const CreationOptions& options) {
676 VTF writer;
677 writer.setVersion(options.version);
678 writer.addFlags(options.flags);
680 if (!writer.setImage(imagePath, options.filter)) {
681 return false;
682 }
683 if (!createInternal(writer, options)) {
684 return false;
685 }
686 return writer.bake(vtfPath);
687}
688
689VTF VTF::create(const std::string& imagePath, const CreationOptions& options) {
690 VTF writer;
691 writer.setVersion(options.version);
692 writer.addFlags(options.flags);
694 writer.setImage(imagePath, options.filter);
695 createInternal(writer, options);
696 return writer;
697}
698
700 return this->platform;
701}
702
703void VTF::setPlatform(Platform newPlatform) {
704 if (this->platform == newPlatform) {
705 return;
706 }
707
708 // hack to allow VTF::setVersion to work
709 const auto oldPlatform = this->platform;
710 this->platform = PLATFORM_PC;
711 switch (newPlatform) {
712 case PLATFORM_UNKNOWN:
713 case PLATFORM_PC:
714 break;
715 case PLATFORM_X360:
717 this->setVersion(4);
718 break;
720 this->setVersion(5);
721 break;
722 }
723 this->platform = newPlatform;
725
726 if (this->platform != PLATFORM_PC) {
727 const auto recommendedCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height);
728 if (this->mipCount != recommendedCount) {
729 this->setMipCount(recommendedCount);
730 }
731 } else if (oldPlatform != PLATFORM_PC) {
732 const auto recommendedMipCount = ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height);
733 if (this->mipCount > recommendedMipCount) {
734 this->setMipCount(recommendedMipCount);
735 }
736 }
737}
738
739uint32_t VTF::getVersion() const {
740 return this->version;
741}
742
743void VTF::setVersion(uint32_t newVersion) {
744 if (this->platform != PLATFORM_PC) {
745 return;
746 }
747 if (this->hasImageData()) {
748 auto faceCount = this->getFaceCount();
749 if (faceCount == 7 && (newVersion < 1 || newVersion > 4)) {
750 faceCount = 6;
751 }
752 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->sliceCount);
753 }
754
755 // Fix up flags
756 bool srgb = this->isSRGB();
757 if ((this->version < 2 && newVersion >= 2) || (this->version >= 2 && newVersion < 2)) {
759 }
760 if ((this->version < 3 && newVersion >= 3) || (this->version >= 3 && newVersion < 3)) {
762 }
763 if ((this->version < 4 && newVersion >= 4) || (this->version >= 4 && newVersion < 4)) {
766 }
767 if ((this->version < 5 && newVersion >= 5) || (this->version >= 5 && newVersion < 5)) {
770 }
771 this->setSRGB(srgb);
772
773 this->version = newVersion;
774}
775
777 return this->imageWidthResizeMethod;
778}
779
781 return this->imageHeightResizeMethod;
782}
783
784void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_) {
785 this->imageWidthResizeMethod = imageWidthResizeMethod_;
786 this->imageHeightResizeMethod = imageHeightResizeMethod_;
787}
788
790 this->imageWidthResizeMethod = imageWidthResizeMethod_;
791}
792
794 this->imageHeightResizeMethod = imageHeightResizeMethod_;
795}
796
797uint16_t VTF::getWidth(uint8_t mip) const {
798 return mip > 0 ? ImageDimensions::getMipDim(mip, this->width) : this->width;
799}
800
801uint16_t VTF::getHeight(uint8_t mip) const {
802 return mip > 0 ? ImageDimensions::getMipDim(mip, this->height) : this->height;
803}
804
805void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter) {
806 if (newWidth == 0 || newHeight == 0) {
807 this->format = ImageFormat::EMPTY;
808 this->width = 0;
809 this->height = 0;
811 return;
812 }
813
815 if (this->hasImageData()) {
816 if (this->width == newWidth && this->height == newHeight) {
817 return;
818 }
819 auto newMipCount = this->mipCount;
820 if (this->platform == VTF::PLATFORM_PC) {
821 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(this->format, newWidth, newHeight); newMipCount > recommendedCount) {
822 newMipCount = recommendedCount;
823 }
824 } else {
825 newMipCount = (this->flags & VTF::FLAG_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(newWidth, newHeight);
826 }
827 this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter);
828 } else {
829 this->format = ImageFormat::RGBA8888;
830 this->mipCount = 1;
831 this->frameCount = 1;
832 this->flags &= ~FLAG_ENVMAP;
833 this->width = newWidth;
834 this->height = newHeight;
835 this->sliceCount = 1;
836 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, std::vector<std::byte>(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 1, this->width, this->height, this->sliceCount)));
837 }
838}
839
840uint32_t VTF::getFlags() const {
841 return this->flags;
842}
843
844void VTF::setFlags(uint32_t flags_) {
845 this->flags = (this->flags & FLAGS_MASK_INTERNAL) | (flags_ & ~FLAGS_MASK_INTERNAL);
846}
847
848void VTF::addFlags(uint32_t flags_) {
849 this->flags |= flags_ & ~FLAGS_MASK_INTERNAL;
850}
851
852void VTF::removeFlags(uint32_t flags_) {
853 this->flags &= ~flags_ | FLAGS_MASK_INTERNAL;
854}
855
856bool VTF::isSRGB() const {
857 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);
858}
859
860void VTF::setSRGB(bool srgb) {
861 if (srgb) {
862 if (this->version >= 5) {
863 this->addFlags(FLAG_V5_SRGB);
864 } else if (this->version >= 4) {
865 this->addFlags(FLAG_V4_SRGB);
866 }
867 } else {
868 if (this->version >= 5) {
870 } else if (this->version >= 4) {
872 }
873 }
874}
875
877 if (ImageFormatDetails::transparent(this->format)) {
878 if (ImageFormatDetails::decompressedAlpha(this->format) > 1) {
881 } else {
884 }
885 } else {
888 }
889}
890
891ImageFormat VTF::getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap) {
892 if (version >= 6) {
893 if (isCubeMap) {
894 return ImageFormat::BC6H;
895 }
896 return ImageFormat::BC7;
897 }
898 if (ImageFormatDetails::decompressedAlpha(inputFormat) > 0) {
899 return ImageFormat::DXT5;
900 }
901 return ImageFormat::DXT1;
902}
903
905 return this->format;
906}
907
908void VTF::setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter, float quality) {
909 if (newFormat == VTF::FORMAT_UNCHANGED || newFormat == this->format) {
910 return;
911 }
912 if (newFormat == VTF::FORMAT_DEFAULT) {
913 newFormat = VTF::getDefaultCompressedFormat(this->format, this->version, this->getFaceCount() > 1);
914 }
915 if (!this->hasImageData()) {
916 this->format = newFormat;
917 return;
918 }
919 auto newMipCount = this->mipCount;
920 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(newFormat, this->width, this->height); newMipCount > recommendedCount) {
921 newMipCount = recommendedCount;
922 }
923 if (ImageFormatDetails::compressed(newFormat)) {
924 this->regenerateImageData(newFormat, this->width + math::paddingForAlignment(4, this->width), this->height + math::paddingForAlignment(4, this->height), newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter, quality);
925 } else {
926 this->regenerateImageData(newFormat, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount, filter, quality);
927 }
928}
929
930uint8_t VTF::getMipCount() const {
931 return this->mipCount;
932}
933
934bool VTF::setMipCount(uint8_t newMipCount) {
935 if (!this->hasImageData()) {
936 return false;
937 }
938 if (this->platform != PLATFORM_PC && newMipCount > 1) {
939 newMipCount = ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height);
940 } else if (const auto recommended = ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height); newMipCount > recommended) {
941 newMipCount = recommended;
942 if (newMipCount == 1) {
943 return false;
944 }
945 }
946 this->regenerateImageData(this->format, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->sliceCount);
947 return true;
948}
949
951 if (this->platform == VTF::PLATFORM_PC) {
952 return this->setMipCount(ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height));
953 }
954 return this->setMipCount(ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height));
955}
956
958 auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
959 if (!imageResource || !this->hasImageData()) {
960 return;
961 }
962
963 if (this->mipCount <= 1) {
964 if (!this->setRecommendedMipCount() || this->mipCount <= 1) {
965 return;
966 }
967 }
968
969 auto* outputDataPtr = imageResource->data.data();
970 const auto faceCount = this->getFaceCount();
971
972#ifdef SOURCEPP_BUILD_WITH_THREADS
973 std::vector<std::future<void>> futures;
974 futures.reserve(this->frameCount * faceCount * this->sliceCount);
975#endif
976 for (int j = 0; j < this->frameCount; j++) {
977 for (int k = 0; k < faceCount; k++) {
978 for (int l = 0; l < this->sliceCount; l++) {
979#ifdef SOURCEPP_BUILD_WITH_THREADS
980 futures.push_back(std::async(std::launch::async, [this, filter, outputDataPtr, faceCount, j, k, l] {
981#endif
982 for (int i = 1; i < this->mipCount; i++) {
983 auto mip = ImageConversion::resizeImageData(this->getImageDataRaw(i - 1, j, k, l), this->format, ImageDimensions::getMipDim(i - 1, this->width), ImageDimensions::getMipDim(i, this->width), ImageDimensions::getMipDim(i - 1, this->height), ImageDimensions::getMipDim(i, this->height), this->isSRGB(), filter);
984 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->sliceCount) && mip.size() == length) {
985 std::memcpy(outputDataPtr + offset, mip.data(), length);
986 }
987 }
988#ifdef SOURCEPP_BUILD_WITH_THREADS
989 }));
990 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
991 for (auto& future : futures) {
992 future.get();
993 }
994 futures.clear();
995 }
996#endif
997 }
998 }
999 }
1000#ifdef SOURCEPP_BUILD_WITH_THREADS
1001 for (auto& future : futures) {
1002 future.get();
1003 }
1004#endif
1005}
1006
1007uint16_t VTF::getFrameCount() const {
1008 return this->frameCount;
1009}
1010
1011bool VTF::setFrameCount(uint16_t newFrameCount) {
1012 if (!this->hasImageData()) {
1013 return false;
1014 }
1015 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, this->getFaceCount(), this->sliceCount);
1016 return true;
1017}
1018
1019uint8_t VTF::getFaceCount() const {
1020 if (!this->hasImageData()) {
1021 return 0;
1022 }
1023 const auto* image = this->getResource(Resource::TYPE_IMAGE_DATA);
1024 if (!image) {
1025 return 0;
1026 }
1027 if (!(this->flags & VTF::FLAG_ENVMAP)) {
1028 return 1;
1029 }
1030 if (this->version >= 6) {
1031 // All v7.6 VTFs are sane, and we need this special case to fix a bug in the parser where
1032 // it won't recognize cubemaps as cubemaps because the image resource is compressed!
1033 return 6;
1034 }
1035 const auto expectedLength = ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 6, this->width, this->height, this->sliceCount);
1036 if (this->version >= 1 && this->version <= 4 && expectedLength < image->data.size()) {
1037 return 7;
1038 }
1039 if (expectedLength == image->data.size()) {
1040 return 6;
1041 }
1042 return 1;
1043}
1044
1045bool VTF::setFaceCount(bool isCubemap) {
1046 if (!this->hasImageData()) {
1047 return false;
1048 }
1049 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, this->sliceCount);
1050 return true;
1051}
1052
1053uint16_t VTF::getSliceCount() const {
1054 return this->sliceCount;
1055}
1056
1057bool VTF::setSliceCount(uint16_t newSliceCount) {
1058 if (!this->hasImageData()) {
1059 return false;
1060 }
1061 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), newSliceCount);
1062 return true;
1063}
1064
1065bool VTF::setFrameFaceAndSliceCount(uint16_t newFrameCount, bool isCubemap, uint16_t newSliceCount) {
1066 if (!this->hasImageData()) {
1067 return false;
1068 }
1069 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, newSliceCount);
1070 return true;
1071}
1072
1073uint16_t VTF::getStartFrame() const {
1074 return this->startFrame;
1075}
1076
1077void VTF::setStartFrame(uint16_t newStartFrame) {
1078 this->startFrame = newStartFrame;
1079}
1080
1081math::Vec3f VTF::getReflectivity() const {
1082 return this->reflectivity;
1083}
1084
1085void VTF::setReflectivity(sourcepp::math::Vec3f newReflectivity) {
1086 this->reflectivity = newReflectivity;
1087}
1088
1090 static constexpr auto getReflectivityForImage = [](const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1091 static constexpr auto getReflectivityForPixel = [](const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1092 // http://markjstock.org/doc/gsd_talk_11_notes.pdf page 11
1093 math::Vec3f ref{static_cast<float>(pixel->r), static_cast<float>(pixel->g), static_cast<float>(pixel->b)};
1094 ref /= 255.f * 0.9f;
1095 ref[0] *= ref[0];
1096 ref[1] *= ref[1];
1097 ref[2] *= ref[2];
1098 return ref;
1099 };
1100
1101 auto rgba8888Data = vtf.getImageDataAsRGBA8888(0, frame, face, slice);
1102 math::Vec3f out{};
1103 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1104 out += getReflectivityForPixel(reinterpret_cast<ImagePixel::RGBA8888*>(rgba8888Data.data() + i));
1105 }
1106 return out / (rgba8888Data.size() / sizeof(ImagePixel::RGBA8888));
1107 };
1108
1109 const auto faceCount = this->getFaceCount();
1110
1111#ifdef SOURCEPP_BUILD_WITH_THREADS
1112 if (this->frameCount > 1 || faceCount > 1 || this->sliceCount > 1) {
1113 std::vector<std::future<math::Vec3f>> futures;
1114 futures.reserve(this->frameCount * faceCount * this->sliceCount);
1115
1116 this->reflectivity = {};
1117 for (int j = 0; j < this->frameCount; j++) {
1118 for (int k = 0; k < faceCount; k++) {
1119 for (int l = 0; l < this->sliceCount; l++) {
1120 futures.push_back(std::async(std::launch::async, [this, j, k, l] {
1121 return getReflectivityForImage(*this, j, k, l);
1122 }));
1123 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
1124 for (auto& future : futures) {
1125 this->reflectivity += future.get();
1126 }
1127 futures.clear();
1128 }
1129 }
1130 }
1131 }
1132
1133 for (auto& future : futures) {
1134 this->reflectivity += future.get();
1135 }
1136 this->reflectivity /= this->frameCount * faceCount * this->sliceCount;
1137 } else {
1138 this->reflectivity = getReflectivityForImage(*this, 0, 0, 0);
1139 }
1140#else
1141 this->reflectivity = {};
1142 for (int j = 0; j < this->frameCount; j++) {
1143 for (int k = 0; k < faceCount; k++) {
1144 for (int l = 0; l < this->sliceCount; l++) {
1145 this->reflectivity += getReflectivityForImage(*this, j, k, l);
1146 }
1147 }
1148 }
1149 this->reflectivity /= this->frameCount * faceCount * this->sliceCount;
1150#endif
1151}
1152
1154 return this->bumpMapScale;
1155}
1156
1157void VTF::setBumpMapScale(float newBumpMapScale) {
1158 this->bumpMapScale = newBumpMapScale;
1159}
1160
1162 return this->thumbnailFormat;
1163}
1164
1165uint8_t VTF::getThumbnailWidth() const {
1166 return this->thumbnailWidth;
1167}
1168
1170 return this->thumbnailHeight;
1171}
1172
1173const std::vector<Resource>& VTF::getResources() const {
1174 return this->resources;
1175}
1176
1178 for (const auto& resource : this->resources) {
1179 if (resource.type == type) {
1180 return &resource;
1181 }
1182 }
1183 return nullptr;
1184}
1185
1187 for (auto& resource : this->resources) {
1188 if (resource.type == type) {
1189 return &resource;
1190 }
1191 }
1192 return nullptr;
1193}
1194
1195void VTF::setResourceInternal(Resource::Type type, std::span<const std::byte> data_) {
1196 if (const auto* resource = this->getResource(type); resource && resource->data.size() == data_.size()) {
1197 std::memcpy(resource->data.data(), data_.data(), data_.size());
1198 return;
1199 }
1200
1201 // Store resource data
1202 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1203 for (const auto& [type_, flags_, dataSpan] : this->resources) {
1204 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1205 }
1206
1207 // Set new resource
1208 if (data_.empty()) {
1209 resourceData.erase(type);
1210 } else {
1211 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1212 }
1213
1214 // Save the data
1215 this->data.clear();
1216 BufferStream writer{this->data};
1217
1218 for (auto resourceType : Resource::getOrder()) {
1219 if (!resourceData.contains(resourceType)) {
1220 continue;
1221 }
1222 auto& [specificResourceData, offset] = resourceData[resourceType];
1223 if (resourceType == type) {
1224 Resource newResource{
1225 type,
1226 specificResourceData.size() <= sizeof(uint32_t) ? Resource::FLAG_LOCAL_DATA : Resource::FLAG_NONE,
1227 {this->data.data() + offset, specificResourceData.size()},
1228 };
1229 if (auto* resourcePtr = this->getResourceInternal(type)) {
1230 *resourcePtr = newResource;
1231 } else {
1232 this->resources.push_back(newResource);
1233 }
1234 } else if (!resourceData.contains(resourceType)) {
1235 continue;
1236 }
1237 offset = writer.tell();
1238 writer.write(specificResourceData);
1239 }
1240 this->data.resize(writer.size());
1241
1242 for (auto& [type_, flags_, dataSpan] : this->resources) {
1243 if (resourceData.contains(type_)) {
1244 const auto& [specificResourceData, offset] = resourceData[type_];
1245 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1246 }
1247 }
1248}
1249
1251 std::erase_if(this->resources, [type](const Resource& resource) { return resource.type == type; });
1252}
1253
1254void VTF::regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newSliceCount, ImageConversion::ResizeFilter filter, float quality) {
1255 if (!newWidth) { newWidth = 1; }
1256 if (!newHeight) { newHeight = 1; }
1257 if (!newMipCount) { newMipCount = 1; }
1258 if (!newFrameCount) { newFrameCount = 1; }
1259 if (!newFaceCount) { newFaceCount = 1; }
1260 if (!newSliceCount) { newSliceCount = 1; }
1261
1262 const auto faceCount = this->getFaceCount();
1263 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->sliceCount == newSliceCount) {
1264 return;
1265 }
1266
1267 if (newMipCount > 1) {
1269 } else {
1271 }
1272
1273 std::vector<std::byte> newImageData;
1274 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1275 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->sliceCount == newSliceCount) {
1276 newImageData = ImageConversion::convertSeveralImageDataToFormat(imageResource->data, this->format, newFormat, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->sliceCount, quality);
1277 } else {
1278 newImageData.resize(ImageFormatDetails::getDataLength(this->format, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount));
1279 for (int i = newMipCount - 1; i >= 0; i--) {
1280 for (int j = 0; j < newFrameCount; j++) {
1281 for (int k = 0; k < newFaceCount; k++) {
1282 for (int l = 0; l < newSliceCount; l++) {
1283 if (i < this->mipCount && j < this->frameCount && k < faceCount && l < this->sliceCount) {
1284 auto imageSpan = this->getImageDataRaw(i, j, k, l);
1285 std::vector<std::byte> image{imageSpan.begin(), imageSpan.end()};
1286 if (this->width != newWidth || this->height != newHeight) {
1287 image = ImageConversion::resizeImageData(image, this->format, ImageDimensions::getMipDim(i, this->width), ImageDimensions::getMipDim(i, newWidth), ImageDimensions::getMipDim(i, this->height), ImageDimensions::getMipDim(i, newHeight), this->isSRGB(), filter);
1288 }
1289 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newSliceCount) && image.size() == length) {
1290 std::memcpy(newImageData.data() + offset, image.data(), length);
1291 }
1292 }
1293 }
1294 }
1295 }
1296 }
1297 if (this->format != newFormat) {
1298 newImageData = ImageConversion::convertSeveralImageDataToFormat(newImageData, this->format, newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount, quality);
1299 }
1300 }
1301 } else {
1302 newImageData.resize(ImageFormatDetails::getDataLength(newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newSliceCount));
1303 }
1304
1305 this->format = newFormat;
1306 this->width = newWidth;
1307 this->height = newHeight;
1308 this->mipCount = newMipCount;
1309 this->frameCount = newFrameCount;
1310 if (newFaceCount > 1) {
1311 this->flags |= FLAG_ENVMAP;
1312 } else {
1313 this->flags &= ~FLAG_ENVMAP;
1314 }
1315 this->sliceCount = newSliceCount;
1316
1318}
1319
1320std::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 {
1321 spriteWidth = 0;
1322 spriteHeight = 0;
1323
1324 auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA);
1325 if (!shtResource) {
1326 return {};
1327 }
1328
1329 auto sht = shtResource->getDataAsParticleSheet();
1330 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1331 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1332 return {};
1333 }
1334
1335 // These values are likely slightly too large thanks to float magic, use a
1336 // shader that scales UVs instead of this function if precision is a concern
1337 // This will also break if any of the bounds are above 1 or below 0, but that
1338 // hasn't been observed in official textures
1339 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1340 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 * static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1341 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 * static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1342 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 * static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1343 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 * static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1344
1345 if (x1 > x2) [[unlikely]] {
1346 std::swap(x1, x2);
1347 }
1348 if (y1 > y2) [[unlikely]] {
1349 std::swap(y1, y2);
1350 }
1351 spriteWidth = x2 - x1;
1352 spriteWidth = y2 - y1;
1353
1354 const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1);
1355 if (out.empty()) {
1356 spriteWidth = 0;
1357 spriteHeight = 0;
1358 }
1359 return out;
1360}
1361
1362std::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 {
1363 return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight);
1364}
1365
1366std::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 {
1367 return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice);
1368}
1369
1371 std::vector<std::byte> particleSheetData;
1372 BufferStream writer{particleSheetData};
1373
1374 const auto bakedSheet = value.bake();
1375 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1376 particleSheetData.resize(writer.size());
1377
1379}
1380
1383}
1384
1385void VTF::setCRCResource(uint32_t value) {
1386 this->setResourceInternal(Resource::TYPE_CRC, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1387}
1388
1391}
1392
1393void VTF::setLODResource(uint8_t u, uint8_t v, uint8_t u360, uint8_t v360) {
1394 uint32_t lodData;
1395 BufferStream writer{&lodData, sizeof(lodData)};
1396
1397 writer << u << v << u360 << v360;
1398
1399 this->setResourceInternal(Resource::TYPE_LOD_CONTROL_INFO, {reinterpret_cast<const std::byte*>(&lodData), sizeof(lodData)});
1400}
1401
1404}
1405
1406void VTF::setExtendedFlagsResource(uint32_t value) {
1407 this->setResourceInternal(Resource::TYPE_EXTENDED_FLAGS, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1408}
1409
1412}
1413
1414void VTF::setKeyValuesDataResource(const std::string& value) {
1415 std::vector<std::byte> keyValuesData;
1416 BufferStream writer{keyValuesData};
1417
1418 writer.write<uint32_t>(value.size()).write(value, false);
1419 keyValuesData.resize(writer.size());
1420
1422}
1423
1426}
1427
1429 std::vector<std::byte> hotspotData;
1430 BufferStream writer{hotspotData};
1431
1432 const auto bakedHotspotData = value.bake();
1433 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1434 hotspotData.resize(writer.size());
1435
1437}
1438
1441}
1442
1444 return this->compressionLevel;
1445}
1446
1447void VTF::setCompressionLevel(int16_t newCompressionLevel) {
1448 this->compressionLevel = newCompressionLevel;
1449}
1450
1452 return this->compressionMethod;
1453}
1454
1456 if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == VTF::PLATFORM_PC) {
1458 } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != VTF::PLATFORM_PC) {
1460 } else {
1461 this->compressionMethod = newCompressionMethod;
1462 }
1463}
1464
1465bool VTF::hasImageData() const {
1466 return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
1467}
1468
1469std::span<const std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1470 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1471 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->sliceCount)) {
1472 return imageResource->data.subspan(offset, length);
1473 }
1474 }
1475 return {};
1476}
1477
1478std::vector<std::byte> VTF::getImageDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1479 const auto rawImageData = this->getImageDataRaw(mip, frame, face, slice);
1480 if (rawImageData.empty()) {
1481 return {};
1482 }
1483 return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height));
1484}
1485
1486std::vector<std::byte> VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1487 return this->getImageDataAs(ImageFormat::RGBA8888, mip, frame, face, slice);
1488}
1489
1490bool 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) {
1491 if (imageData_.empty()) {
1492 return false;
1493 }
1494
1495 if (!this->hasImageData()) {
1496 uint16_t resizedWidth = width_, resizedHeight = height_;
1497 ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
1498 if (ImageFormatDetails::compressed(format_)) {
1499 resizedWidth += math::paddingForAlignment(4, resizedWidth);
1500 resizedHeight += math::paddingForAlignment(4, resizedHeight);
1501 }
1502 if (const auto newMipCount = ImageDimensions::getRecommendedMipCountForDims(format_, resizedWidth, resizedHeight); newMipCount <= mip) {
1503 mip = newMipCount - 1;
1504 }
1505 if (face > 6 || (face == 6 && (this->version < 1 || this->version > 4))) {
1506 return false;
1507 }
1508 this->regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 5 ? 5 : face) : 0, slice + 1);
1509 }
1510
1511 const auto faceCount = this->getFaceCount();
1512 if (this->mipCount <= mip || this->frameCount <= frame || faceCount <= face || this->sliceCount <= slice) {
1513 return false;
1514 }
1515
1516 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
1517 if (!imageResource) {
1518 return false;
1519 }
1520 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->sliceCount)) {
1521 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
1522 const auto newWidth = ImageDimensions::getMipDim(mip, this->width);
1523 const auto newHeight = ImageDimensions::getMipDim(mip, this->height);
1524 if (width_ != newWidth || height_ != newHeight) {
1525 image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->isSRGB(), filter);
1526 }
1527 if (format_ != this->format) {
1528 image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight, quality);
1529 }
1530 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
1531 }
1532 return true;
1533}
1534
1535bool VTF::setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality) {
1536 ImageFormat inputFormat;
1537 int inputWidth, inputHeight, inputFrameCount;
1538 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
1539
1540 // Unable to decode file
1541 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
1542 return false;
1543 }
1544
1545 // One frame (normal)
1546 if (inputFrameCount == 1) {
1547 return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice, quality);
1548 }
1549
1550 // Multiple frames (GIF)
1551 bool allSuccess = true;
1552 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
1553 for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
1554 if (!this->setImage({imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize}, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
1555 allSuccess = false;
1556 }
1557 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
1558 // Call this after setting the first image, this function is a no-op if no image data is present yet
1559 this->setFrameCount(frame + inputFrameCount);
1560 }
1561 }
1562 return allSuccess;
1563}
1564
1565std::vector<std::byte> VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1566 return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height), fileFormat);
1567}
1568
1569bool VTF::saveImageToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1570 if (auto data_ = this->saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
1571 return fs::writeFileBuffer(imagePath, data_);
1572 }
1573 return false;
1574}
1575
1577 return this->thumbnailFormat != ImageFormat::EMPTY && this->thumbnailWidth > 0 && this->thumbnailHeight > 0;
1578}
1579
1580std::span<const std::byte> VTF::getThumbnailDataRaw() const {
1581 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
1582 return thumbnailResource->data;
1583 }
1584 return {};
1585}
1586
1587std::vector<std::byte> VTF::getThumbnailDataAs(ImageFormat newFormat) const {
1588 const auto rawThumbnailData = this->getThumbnailDataRaw();
1589 if (rawThumbnailData.empty()) {
1590 return {};
1591 }
1592 return ImageConversion::convertImageDataToFormat(rawThumbnailData, this->thumbnailFormat, newFormat, this->thumbnailWidth, this->thumbnailHeight);
1593}
1594
1595std::vector<std::byte> VTF::getThumbnailDataAsRGBA8888() const {
1597}
1598
1599void VTF::setThumbnail(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality) {
1600 if (format_ != this->thumbnailFormat) {
1601 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(imageData_, format_, this->thumbnailFormat, width_, height_, quality));
1602 } else {
1604 }
1605 this->thumbnailWidth = width_;
1606 this->thumbnailHeight = height_;
1607}
1608
1610 if (!this->hasImageData()) {
1611 return;
1612 }
1614 this->thumbnailWidth = 16;
1615 this->thumbnailHeight = 16;
1616 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(ImageConversion::resizeImageData(this->getImageDataRaw(), this->format, this->width, this->thumbnailWidth, this->height, this->thumbnailHeight, this->isSRGB(), filter), this->format, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, quality));
1617}
1618
1621 this->thumbnailWidth = 0;
1622 this->thumbnailHeight = 0;
1624}
1625
1626std::vector<std::byte> VTF::saveThumbnailToFile(ImageConversion::FileFormat fileFormat) const {
1628}
1629
1630bool VTF::saveThumbnailToFile(const std::string& imagePath, ImageConversion::FileFormat fileFormat) const {
1631 if (auto data_ = this->saveThumbnailToFile(fileFormat); !data_.empty()) {
1632 return fs::writeFileBuffer(imagePath, data_);
1633 }
1634 return false;
1635}
1636
1637std::vector<std::byte> VTF::bake() const {
1638 std::vector<std::byte> out;
1639 BufferStream writer{out};
1640
1641 static constexpr auto writeNonLocalResource = [](BufferStream& writer_, Resource::Type type, std::span<const std::byte> data, VTF::Platform platform) {
1642 if (platform != VTF::PLATFORM_PC) {
1643 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(&type));
1644 }
1645 writer_.write<uint32_t>(type);
1646 const auto resourceOffsetPos = writer_.tell();
1647 writer_.seek(0, std::ios::end);
1648 const auto resourceOffsetValue = writer_.tell();
1649 writer_.write(data);
1650 writer_.seek_u(resourceOffsetPos).write<uint32_t>(resourceOffsetValue);
1651 };
1652
1653 // HACK: no source game supports this format, but they do support the flag with reg. DXT1
1654 auto bakeFormat = this->format;
1655 auto bakeFlags = this->flags;
1656 if (bakeFormat == ImageFormat::DXT1_ONE_BIT_ALPHA) {
1657 bakeFormat = ImageFormat::DXT1;
1658 bakeFlags |= FLAG_ONE_BIT_ALPHA;
1659 }
1660 // HACK: NV_NULL / ATI2N / ATI1N are at a different position based on engine branch
1661 if (this->version < 5 && (this->format == ImageFormat::EMPTY || this->format == ImageFormat::ATI2N || this->format == ImageFormat::ATI1N)) {
1662 bakeFormat = static_cast<ImageFormat>(static_cast<int32_t>(this->format) + 3);
1663 }
1664
1665 switch (this->platform) {
1666 case PLATFORM_UNKNOWN:
1667 break;
1668 case PLATFORM_PC: {
1669 writer
1670 .write(VTF_SIGNATURE)
1671 .write<int32_t>(7)
1672 .write(this->version);
1673
1674 const auto headerLengthPos = writer.tell();
1675 writer.write<uint32_t>(0);
1676
1677 writer
1678 .write(this->width)
1679 .write(this->height)
1680 .write(bakeFlags)
1681 .write(this->frameCount)
1682 .write(this->startFrame)
1683 .write<uint32_t>(0) // padding
1684 .write(this->reflectivity)
1685 .write<uint32_t>(0) // padding
1686 .write(this->bumpMapScale)
1687 .write(bakeFormat)
1688 .write(this->mipCount)
1689 .write(ImageFormat::DXT1)
1690 .write(this->thumbnailWidth)
1691 .write(this->thumbnailHeight);
1692
1693 if (this->version >= 2) {
1694 writer << this->sliceCount;
1695 }
1696
1697 if (this->version < 3) {
1698 const auto headerAlignment = math::paddingForAlignment(16, writer.tell());
1699 for (uint16_t i = 0; i < headerAlignment; i++) {
1700 writer.write<std::byte>({});
1701 }
1702 const auto headerSize = writer.tell();
1703 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
1704
1705 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
1706 writer.write(thumbnailResource->data);
1707 }
1708 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1709 writer.write(imageResource->data);
1710 }
1711 } else {
1712 std::vector<std::byte> auxCompressionResourceData;
1713 std::vector<std::byte> compressedImageResourceData;
1714 bool hasAuxCompression = false;
1715 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1716 hasAuxCompression = this->version >= 6 && this->compressionLevel != 0;
1717 if (hasAuxCompression) {
1718 const auto faceCount = this->getFaceCount();
1719 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
1720 BufferStream auxWriter{auxCompressionResourceData, false};
1721
1722 // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
1723 // - Size of resource in bytes, not counting this int
1724 // - Compression level, method (2 byte integers)
1725 // - (X times) Size of each mip-face-frame combo
1726 auxWriter
1727 .write<uint32_t>(auxCompressionResourceData.size() - sizeof(uint32_t))
1728 .write(this->compressionLevel)
1729 .write(this->compressionMethod);
1730
1731 for (int i = this->mipCount - 1; i >= 0; i--) {
1732 for (int j = 0; j < this->frameCount; j++) {
1733 for (int k = 0; k < faceCount; k++) {
1734 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->sliceCount)) {
1735 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->sliceCount}, this->compressionLevel, this->compressionMethod);
1736 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
1737 auxWriter.write<uint32_t>(compressedData.size());
1738 }
1739 }
1740 }
1741 }
1742 }
1743 }
1744
1745 writer
1746 .write<uint8_t>(0) // padding
1747 .write<uint8_t>(0) // padding
1748 .write<uint8_t>(0) // padding
1749 .write<uint32_t>(this->getResources().size() + hasAuxCompression)
1750 .write<uint64_t>(0); // padding
1751
1752 const auto resourceStart = writer.tell();
1753 const auto headerSize = resourceStart + ((this->getResources().size() + hasAuxCompression) * sizeof(uint64_t));
1754 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1755 while (writer.tell() < headerSize) {
1756 writer.write<uint64_t>(0);
1757 }
1758 writer.seek_u(resourceStart);
1759
1760 for (const auto resourceType : Resource::getOrder()) {
1761 if (hasAuxCompression && resourceType == Resource::TYPE_AUX_COMPRESSION) {
1762 writeNonLocalResource(writer, resourceType, auxCompressionResourceData, this->platform);
1763 } else if (hasAuxCompression && resourceType == Resource::TYPE_IMAGE_DATA) {
1764 writeNonLocalResource(writer, resourceType, compressedImageResourceData, this->platform);
1765 } else if (const auto* resource = this->getResource(resourceType)) {
1766 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
1767 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
1768 writer.write(resource->data);
1769 } else {
1770 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
1771 }
1772 }
1773 }
1774 }
1775
1776 out.resize(writer.size());
1777 return out;
1778 }
1779 case PLATFORM_X360:
1781 case PLATFORM_PS3_PORTAL2: {
1782 if (this->platform == PLATFORM_PS3_PORTAL2) {
1783 writer << VTF3_SIGNATURE;
1784 writer.set_big_endian(true);
1785 writer << PLATFORM_PS3_ORANGEBOX; // Intentional
1786 } else {
1787 writer << VTFX_SIGNATURE;
1788 writer.set_big_endian(true);
1789 writer << this->platform;
1790 }
1791
1792 // Go down until top level texture is <1mb, matches makegamedata.exe output
1793 uint8_t mipSkip = 0;
1794 for (int mip = 0; mip < this->mipCount; mip++, mipSkip++) {
1795 if (ImageFormatDetails::getDataLength(this->format, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height), ImageDimensions::getMipDim(mip, this->sliceCount)) < 1024 * 1024) {
1796 break;
1797 }
1798 }
1799
1800 writer.write<uint32_t>(8);
1801 const auto headerLengthPos = writer.tell();
1802 writer
1803 .write<uint32_t>(0)
1804 .write(bakeFlags)
1805 .write(this->width)
1806 .write(this->height)
1807 .write(this->sliceCount)
1808 .write(this->frameCount);
1809 const auto preloadPos = writer.tell();
1810 writer
1811 .write<uint16_t>(0) // preload size
1812 .write(mipSkip)
1813 .write<uint8_t>(this->resources.size())
1814 .write(this->reflectivity[0])
1815 .write(this->reflectivity[1])
1816 .write(this->reflectivity[2])
1817 .write(this->bumpMapScale)
1818 .write(bakeFormat)
1819 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[0] * 255)), 0, 255))
1820 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[1] * 255)), 0, 255))
1821 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[2] * 255)), 0, 255))
1822 .write<uint8_t>(255);
1823 const auto compressionPos = writer.tell();
1824 writer.write<uint32_t>(0); // compressed length
1825
1826 if (this->platform == PLATFORM_PS3_PORTAL2) {
1827 // 16 byte aligned
1828 writer.write<uint32_t>(0);
1829 }
1830
1831 // LZMA compression has not been observed on the PS3 copy of The Orange Box
1832 // todo(vtfpp): check cubemaps
1833 std::vector<std::byte> imageResourceData;
1834 bool hasCompression = false;
1835 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1836 imageResourceData.assign(imageResource->data.begin(), imageResource->data.end());
1837 ::swapImageDataEndianForConsole<false>(imageResourceData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->sliceCount, this->platform);
1838
1839 if (this->platform != PLATFORM_PS3_ORANGEBOX) {
1840 hasCompression = this->compressionMethod == CompressionMethod::CONSOLE_LZMA;
1841 if (hasCompression) {
1842 auto fixedCompressionLevel = this->compressionLevel;
1843 if (this->compressionLevel == 0) {
1844 // Compression level defaults to 0, so it works differently on console.
1845 // Rather than not compress on 0, 0 will be replaced with the default
1846 // compression level (6) if the compression method is LZMA.
1847 fixedCompressionLevel = 6;
1848 }
1849 if (const auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel)) {
1850 imageResourceData.assign(compressedData->begin(), compressedData->end());
1851 } else {
1852 hasCompression = false;
1853 }
1854 }
1855 }
1856 }
1857
1858 const auto resourceStart = writer.tell();
1859 const auto headerSize = resourceStart + (this->getResources().size() * sizeof(uint64_t));
1860 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1861 while (writer.tell() < headerSize) {
1862 writer.write<uint64_t>(0);
1863 }
1864 writer.seek_u(resourceStart);
1865
1866 for (const auto resourceType : Resource::getOrder()) {
1867 if (resourceType == Resource::TYPE_IMAGE_DATA) {
1868 auto curPos = writer.tell();
1869 const auto imagePos = writer.seek(0, std::ios::end).tell();
1870 writer.seek_u(preloadPos).write(std::max<uint16_t>(imagePos, 2048)).seek_u(curPos);
1871
1872 writeNonLocalResource(writer, resourceType, imageResourceData, this->platform);
1873
1874 if (hasCompression) {
1875 curPos = writer.tell();
1876 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
1877 }
1878 } else if (const auto* resource = this->getResource(resourceType)) {
1879 std::vector<std::byte> resData{resource->data.begin(), resource->data.end()};
1880
1881 if (resource->type == Resource::TYPE_THUMBNAIL_DATA) {
1882 ::swapImageDataEndianForConsole<false>(resData, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
1883 } else if (!(resource->flags & Resource::FLAG_LOCAL_DATA) && resData.size() >= sizeof(uint32_t)) {
1884 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resData.data()));
1885 }
1886
1887 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
1888 writer.set_big_endian(false);
1889 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
1890 writer.set_big_endian(true);
1891 writer.write(resData);
1892 } else {
1893 writeNonLocalResource(writer, resource->type, resData, this->platform);
1894 }
1895 }
1896 }
1897
1898 out.resize(writer.size());
1899 return out;
1900 }
1901 }
1902 return {};
1903}
1904
1905bool VTF::bake(const std::string& vtfPath) const {
1906 return fs::writeFileBuffer(vtfPath, this->bake());
1907}
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
Definition: Macros.h:19
Definition: HOT.h:13
std::vector< std::byte > bake() const
Definition: HOT.cpp:61
Definition: SHT.h:13
std::vector< std::byte > bake() const
Definition: SHT.cpp:82
static constexpr uint32_t FLAGS_MASK_V5
Definition: VTF.h:196
uint16_t width
Definition: VTF.h:465
static constexpr uint32_t FLAGS_MASK_V2
Definition: VTF.h:166
Platform
Definition: VTF.h:209
@ PLATFORM_PC
Definition: VTF.h:211
@ PLATFORM_X360
Definition: VTF.h:212
@ PLATFORM_PS3_PORTAL2
Definition: VTF.h:214
@ PLATFORM_PS3_ORANGEBOX
Definition: VTF.h:213
@ PLATFORM_UNKNOWN
Definition: VTF.h:210
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:793
void removeKeyValuesDataResource()
Definition: VTF.cpp:1424
CompressionMethod compressionMethod
Definition: VTF.h:496
void computeReflectivity()
Definition: VTF.cpp:1089
uint8_t getThumbnailWidth() const
Definition: VTF.cpp:1165
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition: VTF.cpp:1626
void regenerateImageData(ImageFormat newFormat, uint16_t newWidth, uint16_t newHeight, uint8_t newMipCount, uint16_t newFrameCount, uint8_t newFaceCount, uint16_t newSliceCount, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition: VTF.cpp:1254
ImageFormat getFormat() const
Definition: VTF.cpp:904
void setPlatform(Platform newPlatform)
Definition: VTF.cpp:703
uint16_t getHeight(uint8_t mip=0) const
Definition: VTF.cpp:801
VTF & operator=(const VTF &other)
Definition: VTF.cpp:546
int16_t compressionLevel
Definition: VTF.h:495
sourcepp::math::Vec3f reflectivity
Definition: VTF.h:473
uint16_t getWidth(uint8_t mip=0) const
Definition: VTF.cpp:797
uint16_t startFrame
Definition: VTF.h:470
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:1599
static constexpr uint32_t FLAGS_MASK_INTERNAL
Definition: VTF.h:207
uint8_t mipCount
Definition: VTF.h:478
bool setRecommendedMipCount()
Definition: VTF.cpp:950
static bool createInternal(VTF &writer, CreationOptions options)
Definition: VTF.cpp:585
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:1366
@ FLAG_NO_LOD
Definition: VTF.h:149
@ FLAG_MULTI_BIT_ALPHA
Definition: VTF.h:153
@ FLAG_ONE_BIT_ALPHA
Definition: VTF.h:152
@ FLAG_ENVMAP
Definition: VTF.h:154
@ FLAG_NO_MIP
Definition: VTF.h:148
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:957
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
Definition: VTF.cpp:789
uint8_t thumbnailWidth
Definition: VTF.h:481
void setCompressionLevel(int16_t newCompressionLevel)
Definition: VTF.cpp:1447
ImageConversion::ResizeMethod imageHeightResizeMethod
Definition: VTF.h:498
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition: VTF.cpp:908
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:784
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:1362
static constexpr uint32_t FLAGS_MASK_V3
Definition: VTF.h:174
float getBumpMapScale() const
Definition: VTF.cpp:1153
void computeTransparencyFlags()
Definition: VTF.cpp:876
static constexpr uint32_t FLAGS_MASK_V4_TF2
Definition: VTF.h:188
std::vector< std::byte > data
Definition: VTF.h:459
bool setFrameFaceAndSliceCount(uint16_t newFrameCount, bool isCubeMap, uint16_t newSliceCount=1)
Definition: VTF.cpp:1065
ImageFormat format
Definition: VTF.h:477
uint16_t sliceCount
Definition: VTF.h:485
Platform getPlatform() const
Definition: VTF.cpp:699
void addFlags(uint32_t flags_)
Definition: VTF.cpp:848
uint8_t getFaceCount() const
Definition: VTF.cpp:1019
ImageFormat thumbnailFormat
Definition: VTF.h:480
void setFlags(uint32_t flags_)
Definition: VTF.cpp:844
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
Definition: VTF.cpp:805
void removeLODResource()
Definition: VTF.cpp:1402
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
Definition: VTF.cpp:780
void setBumpMapScale(float newBumpMapScale)
Definition: VTF.cpp:1157
ImageFormat getThumbnailFormat() const
Definition: VTF.cpp:1161
uint16_t getStartFrame() const
Definition: VTF.cpp:1073
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
Definition: VTF.h:245
bool hasThumbnailData() const
Definition: VTF.cpp:1576
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
Definition: VTF.cpp:1085
void setSRGB(bool srgb)
Definition: VTF.cpp:860
const std::vector< Resource > & getResources() const
Definition: VTF.cpp:1173
void removeHotspotDataResource()
Definition: VTF.cpp:1439
@ FLAG_V4_SRGB
Definition: VTF.h:177
void setVersion(uint32_t newVersion)
Definition: VTF.cpp:743
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:1565
Resource * getResourceInternal(Resource::Type type)
Definition: VTF.cpp:1186
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition: VTF.cpp:1609
std::vector< std::byte > bake() const
Definition: VTF.cpp:1637
bool hasImageData() const
Definition: VTF.cpp:1465
ImageConversion::ResizeMethod imageWidthResizeMethod
Definition: VTF.h:497
uint16_t getSliceCount() const
Definition: VTF.cpp:1053
void removeParticleSheetResource()
Definition: VTF.cpp:1381
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:1486
uint16_t getFrameCount() const
Definition: VTF.cpp:1007
uint8_t getMipCount() const
Definition: VTF.cpp:930
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
Definition: VTF.cpp:1393
void removeResourceInternal(Resource::Type type)
Definition: VTF.cpp:1250
bool setFrameCount(uint16_t newFrameCount)
Definition: VTF.cpp:1011
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
Definition: VTF.cpp:776
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:1478
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
Definition: VTF.cpp:891
void setStartFrame(uint16_t newStartFrame)
Definition: VTF.cpp:1077
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
Definition: VTF.cpp:1587
static constexpr uint32_t FLAGS_MASK_V5_CSGO
Definition: VTF.h:205
@ FLAG_V5_SRGB
Definition: VTF.h:192
@ FLAG_V5_PWL_CORRECTED
Definition: VTF.h:191
bool setSliceCount(uint16_t newSliceCount)
Definition: VTF.cpp:1057
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:1469
sourcepp::math::Vec3f getReflectivity() const
Definition: VTF.cpp:1081
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
Definition: VTF.cpp:1595
bool isSRGB() const
Definition: VTF.cpp:856
uint32_t flags
Definition: VTF.h:467
void removeThumbnail()
Definition: VTF.cpp:1619
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
Definition: VTF.h:242
uint32_t version
Definition: VTF.h:462
std::span< const std::byte > getThumbnailDataRaw() const
Definition: VTF.cpp:1580
bool setMipCount(uint8_t newMipCount)
Definition: VTF.cpp:934
void setExtendedFlagsResource(uint32_t value)
Definition: VTF.cpp:1406
CompressionMethod getCompressionMethod() const
Definition: VTF.cpp:1451
void setCRCResource(uint32_t value)
Definition: VTF.cpp:1385
void setCompressionMethod(CompressionMethod newCompressionMethod)
Definition: VTF.cpp:1455
bool setFaceCount(bool isCubeMap)
Definition: VTF.cpp:1045
uint8_t getThumbnailHeight() const
Definition: VTF.cpp:1169
const Resource * getResource(Resource::Type type) const
Definition: VTF.cpp:1177
static constexpr uint32_t FLAGS_MASK_V4
Definition: VTF.h:179
uint32_t getFlags() const
Definition: VTF.cpp:840
void setParticleSheetResource(const SHT &value)
Definition: VTF.cpp:1370
void setKeyValuesDataResource(const std::string &value)
Definition: VTF.cpp:1414
uint16_t height
Definition: VTF.h:466
void removeCRCResource()
Definition: VTF.cpp:1389
uint16_t frameCount
Definition: VTF.h:469
uint32_t getVersion() const
Definition: VTF.cpp:739
int16_t getCompressionLevel() const
Definition: VTF.cpp:1443
void setHotspotDataResource(const HOT &value)
Definition: VTF.cpp:1428
void removeFlags(uint32_t flags_)
Definition: VTF.cpp:852
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
Definition: VTF.cpp:1195
std::vector< Resource > resources
Definition: VTF.h:490
uint8_t thumbnailHeight
Definition: VTF.h:482
Platform platform
Definition: VTF.h:494
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:1490
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:1320
void removeExtendedFlagsResource()
Definition: VTF.cpp:1410
bool opened
Definition: VTF.h:457
float bumpMapScale
Definition: VTF.h:476
static bool create(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string &vtfPath, const CreationOptions &options)
Definition: VTF.cpp:639
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
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
Definition: FS.cpp:9
bool writeFileBuffer(const std::string &filepath, std::span< const std::byte > buffer)
Definition: FS.cpp:27
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
Definition: Math.h:61
constexpr uint32_t log2ceil(uint32_t value)
Definition: Math.h:57
Definition: LZMA.h:11
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 > gammaCorrectImageData(std::span< const 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 > 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 sliceCount, float quality=DEFAULT_COMPRESSED_QUALITY)
Converts several images from one format to another.
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 > invertGreenChannelForImageData(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height)
Invert the green channel. Meant for converting normal maps between OpenGL and DirectX formats.
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, ResizeFilter filter, ResizeEdge edge=ResizeEdge::CLAMP)
Resize given image data to the new dimensions.
constexpr uint32_t getMipDim(uint8_t mip, uint16_t dim)
Definition: ImageFormats.h:650
constexpr uint8_t getActualMipCountForDimsOnConsole(uint16_t width, uint16_t height)
Definition: ImageFormats.h:685
constexpr uint8_t getRecommendedMipCountForDims(ImageFormat format, uint16_t width, uint16_t height)
Definition: ImageFormats.h:657
constexpr bool large(ImageFormat format)
Definition: ImageFormats.h:571
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t sliceCount=1)
Definition: ImageFormats.h:711
constexpr bool transparent(ImageFormat format)
Definition: ImageFormats.h:583
constexpr int8_t decompressedAlpha(ImageFormat format)
Definition: ImageFormats.h:414
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 sliceCount=1)
Definition: ImageFormats.h:737
constexpr uint8_t bpp(ImageFormat format)
Definition: ImageFormats.h:436
constexpr bool compressed(ImageFormat format)
Definition: ImageFormats.h:579
Definition: HOT.h:11
constexpr uint32_t VTF_SIGNATURE
Definition: VTF.h:21
constexpr uint32_t VTFX_SIGNATURE
Definition: VTF.h:22
constexpr uint32_t VTF3_SIGNATURE
Definition: VTF.h:23
CompressionMethod
Definition: VTF.h:25
ImageFormat
Definition: ImageFormats.h:7
static consteval std::array< Type, 9 > getOrder()
Definition: VTF.h:47
Type type
Definition: VTF.h:66
ConvertedData convertData() const
Definition: VTF.cpp:177
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT, std::span< uint32_t > > ConvertedData
Definition: VTF.h:78
@ FLAG_LOCAL_DATA
Definition: VTF.h:63
@ FLAG_NONE
Definition: VTF.h:62
@ TYPE_KEYVALUES_DATA
Definition: VTF.h:45
@ TYPE_CRC
Definition: VTF.h:42
@ TYPE_PARTICLE_SHEET_DATA
Definition: VTF.h:38
@ TYPE_EXTENDED_FLAGS
Definition: VTF.h:41
@ TYPE_IMAGE_DATA
Definition: VTF.h:40
@ TYPE_AUX_COMPRESSION
Definition: VTF.h:43
@ TYPE_LOD_CONTROL_INFO
Definition: VTF.h:44
@ TYPE_THUMBNAIL_DATA
Definition: VTF.h:37
@ TYPE_HOTSPOT_DATA
Definition: VTF.h:39
Flags flags
Definition: VTF.h:67
std::span< std::byte > data
Definition: VTF.h:68
uint16_t initialFrameCount
Definition: VTF.h:225
uint16_t initialSliceCount
Definition: VTF.h:228
float compressedFormatQuality
Definition: VTF.h:220
int16_t compressionLevel
Definition: VTF.h:234
ImageConversion::ResizeFilter filter
Definition: VTF.h:223
ImageConversion::ResizeMethod heightResizeMethod
Definition: VTF.h:222
CompressionMethod compressionMethod
Definition: VTF.h:235
ImageConversion::ResizeMethod widthResizeMethod
Definition: VTF.h:221
ImageFormat outputFormat
Definition: VTF.h:219