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#include <vtfpp/ImageQuantize.h>
24
25using namespace sourcepp;
26using namespace vtfpp;
27
28namespace {
29
30[[nodiscard]] std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level, CompressionMethod method) {
31 switch (method) {
32 using enum CompressionMethod;
33 case DEFLATE: {
34 mz_ulong compressedSize = mz_compressBound(data.size());
35 std::vector<std::byte> out(compressedSize);
36
37 int status = MZ_OK;
38 while ((status = mz_compress2(reinterpret_cast<unsigned char*>(out.data()), &compressedSize, reinterpret_cast<const unsigned char*>(data.data()), data.size(), level)) == MZ_BUF_ERROR) {
39 compressedSize *= 2;
40 out.resize(compressedSize);
41 }
42
43 if (status != MZ_OK) {
44 return {};
45 }
46 out.resize(compressedSize);
47 return out;
48 }
49 case ZSTD: {
50 if (level < 0) {
51 level = 6;
52 }
53
54 const auto expectedSize = ZSTD_compressBound(data.size());
55 std::vector<std::byte> out(expectedSize);
56
57 const auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
58 if (ZSTD_isError(compressedSize)) {
59 return {};
60 }
61
62 out.resize(compressedSize);
63 return out;
64 }
65 case CONSOLE_LZMA: {
66 if (const auto out = compression::compressValveLZMA(data, level)) {
67 return *out;
68 }
69 return {};
70 }
71 }
72 return {};
73}
74
75template<std::unsigned_integral T, bool ExistingDataIsSwizzled>
76constexpr void swizzleUncompressedImageData(std::span<std::byte> inputData, std::span<std::byte> outputData, ImageFormat format, uint16_t width, uint16_t height, uint16_t depth) {
77 width *= ImageFormatDetails::bpp(format) / (sizeof(T) * 8);
78 const auto zIndex = [
79 widthL2 = static_cast<int>(math::log2ceil(width)),
80 heightL2 = static_cast<int>(math::log2ceil(height)),
81 depthL2 = static_cast<int>(math::log2ceil(depth))
82 ](uint32_t x, uint32_t y, uint32_t z) {
83 auto widthL2m = widthL2;
84 auto heightL2m = heightL2;
85 auto depthL2m = depthL2;
86 uint32_t offset = 0;
87 uint32_t shiftCount = 0;
88 do {
89 if (depthL2m --> 0) {
90 offset |= (z & 1) << shiftCount++;
91 z >>= 1;
92 }
93 if (heightL2m --> 0) {
94 offset |= (y & 1) << shiftCount++;
95 y >>= 1;
96 }
97 if (widthL2m --> 0) {
98 offset |= (x & 1) << shiftCount++;
99 x >>= 1;
100 }
101 } while (x | y | z);
102 return offset;
103 };
104
105 const auto* inputPtr = reinterpret_cast<const T*>(inputData.data());
106 auto* outputPtr = reinterpret_cast<T*>(outputData.data());
107 for (uint16_t x = 0; x < width; x++) {
108 for (uint16_t y = 0; y < height; y++) {
109 for (uint16_t z = 0; z < depth; z++) {
110 if constexpr (ExistingDataIsSwizzled) {
111 *outputPtr++ = reinterpret_cast<const T*>(inputData.data())[zIndex(x, y, z)];
112 } else {
113 reinterpret_cast<T*>(outputData.data())[zIndex(x, y, z)] = *inputPtr++;
114 }
115 }
116 }
117 }
118}
119
120template<bool ConvertingFromSource>
121void 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) {
122 if (imageData.empty() || format == ImageFormat::EMPTY || platform == VTF::PLATFORM_PC) {
123 return;
124 }
125
126 if (platform == VTF::PLATFORM_X360) {
127 switch (format) {
128 using enum ImageFormat;
129 case BGRA8888:
130 case BGRX8888:
131 case UVWQ8888:
132 case UVLX8888: {
133 const auto newData = ImageConversion::convertSeveralImageDataToFormat(imageData, ARGB8888, BGRA8888, mipCount, frameCount, faceCount, width, height, depth);
134 std::ranges::copy(newData, imageData.begin());
135 break;
136 }
137 case DXT1:
139 case DXT3:
140 case DXT5:
141 case UV88: {
142 std::span dxtData{reinterpret_cast<uint16_t*>(imageData.data()), imageData.size() / sizeof(uint16_t)};
143 std::for_each(
144#ifdef SOURCEPP_BUILD_WITH_TBB
145 std::execution::par_unseq,
146#endif
147 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
148 BufferStream::swap_endian(&value);
149 });
150 break;
151 }
152 default:
153 break;
154 }
155 }
156
157 // todo(vtfpp): should we enable 16-bit wide and 8-bit wide formats outside XBOX?
158 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)))) {
159 std::vector<std::byte> out(imageData.size());
160 for(int mip = mipCount - 1; mip >= 0; mip--) {
161 const auto mipWidth = ImageDimensions::getMipDim(mip, width);
162 const auto mipHeight = ImageDimensions::getMipDim(mip, height);
163 for (int frame = 0; frame < frameCount; frame++) {
164 for (int face = 0; face < faceCount; face++) {
165 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, format, mip, mipCount, frame, frameCount, face, faceCount, width, height)) {
166 std::span imageDataSpan{imageData.data() + offset, length * depth};
167 std::span outSpan{out.data() + offset, length * depth};
168 if (ImageFormatDetails::bpp(format) % 32 == 0) {
169 ::swizzleUncompressedImageData<uint32_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, depth);
170 } else if (ImageFormatDetails::bpp(format) % 16 == 0) {
171 ::swizzleUncompressedImageData<uint16_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, depth);
172 } else /*if (ImageFormatDetails::bpp(format) % 8 == 0)*/ {
173 ::swizzleUncompressedImageData<uint8_t, ConvertingFromSource>(imageDataSpan, outSpan, format, mipWidth, mipHeight, depth);
174 }
175 }
176 }
177 }
178 }
179 std::memcpy(imageData.data(), out.data(), out.size());
180 }
181}
182
183template<bool ConvertingFromDDS>
184[[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) {
185 std::vector<std::byte> reorderedImageData;
186 reorderedImageData.resize(ImageFormatDetails::getDataLengthXBOX(padded, format, mipCount, frameCount, faceCount, width, height, depth));
187 BufferStream reorderedStream{reorderedImageData};
188
189 if constexpr (ConvertingFromDDS) {
190 for (int i = mipCount - 1; i >= 0; i--) {
191 for (int j = 0; j < frameCount; j++) {
192 for (int k = 0; k < faceCount; k++) {
193 for (int l = 0; l < depth; l++) {
194 uint32_t oldOffset, length;
195 if (!ImageFormatDetails::getDataPositionXbox(oldOffset, length, padded, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
196 ok = false;
197 return {};
198 }
199 reorderedStream << imageData.subspan(oldOffset, length);
200 }
201 }
202 }
203 }
204 } else {
205 for (int j = 0; j < frameCount; j++) {
206 for (int k = 0; k < faceCount; k++) {
207 for (int i = 0; i < mipCount; i++) {
208 for (int l = 0; l < depth; l++) {
209 uint32_t oldOffset, length;
210 if (!ImageFormatDetails::getDataPosition(oldOffset, length, format, i, mipCount, j, frameCount, k, faceCount, width, height, l, depth)) {
211 ok = false;
212 return {};
213 }
214 reorderedStream << imageData.subspan(oldOffset, length);
215 }
216 }
217 }
218 if (padded && j + 1 != frameCount && reorderedStream.tell() > 512) {
219 reorderedStream.pad(math::paddingForAlignment(512, reorderedStream.tell()));
220 }
221 }
222 }
223
224 ok = true;
225 return reorderedImageData;
226}
227
228} // namespace
229
231 switch (this->type) {
233 if (this->data.size() <= sizeof(uint32_t)) {
234 return {};
235 }
236 return SHT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
237 case TYPE_CRC:
239 if (this->data.size() != sizeof(uint32_t)) {
240 return {};
241 }
242 return *reinterpret_cast<const uint32_t*>(this->data.data());
244 if (this->data.size() != sizeof(uint32_t)) {
245 return {};
246 }
247 return std::make_tuple(
248 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 0),
249 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 1),
250 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 2),
251 *(reinterpret_cast<const uint8_t*>(this->data.data()) + 3));
253 if (this->data.size() <= sizeof(uint32_t)) {
254 return "";
255 }
256 return std::string(reinterpret_cast<const char*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data()));
258 if (this->data.size() <= sizeof(uint32_t)) {
259 return {};
260 }
261 return HOT{{reinterpret_cast<const std::byte*>(this->data.data()) + sizeof(uint32_t), *reinterpret_cast<const uint32_t*>(this->data.data())}};
262 default:
263 break;
264 }
265 return {};
266}
267
269 this->opened = true;
270}
271
272VTF::VTF(std::vector<std::byte>&& vtfData, bool parseHeaderOnly)
273 : data(std::move(vtfData)) {
274 BufferStreamReadOnly stream{this->data};
275
276 if (const auto signature = stream.read<uint32_t>(); signature == VTF_SIGNATURE) {
277 stream >> this->platform;
278 if (this->platform != PLATFORM_PC) {
279 return;
280 }
281 stream >> this->version;
282 if (this->version > 6) {
283 return;
284 }
285 } else if (signature == VTFX_SIGNATURE || signature == VTF3_SIGNATURE) {
286 stream.set_big_endian(true);
287 stream >> this->platform;
288 if (this->platform != PLATFORM_X360 && this->platform != PLATFORM_PS3_ORANGEBOX && this->platform != PLATFORM_PS3_PORTAL2) {
289 return;
290 }
291 stream >> this->version;
292 if (this->version != 8) {
293 return;
294 }
295 // Now fix up the actual version as it would be on PC
296 if (signature == VTF3_SIGNATURE) {
297 this->platform = PLATFORM_PS3_PORTAL2;
298 this->version = 5;
299 } else {
300 this->version = 4;
301 }
302 } else if (signature == XTF_SIGNATURE) {
303 stream >> this->platform;
304 if (this->platform != PLATFORM_XBOX) {
305 return;
306 }
307 stream >> this->version;
308 if (this->version != 0) {
309 return;
310 }
311 // Now fix up the actual version as it would be on PC
312 this->version = 2;
313 } else {
314 return;
315 }
316
317 const auto headerSize = stream.read<uint32_t>();
318
319 const auto readResources = [this, &stream](uint32_t resourceCount) {
320 // Read resource header info
321 this->resources.reserve(resourceCount);
322 for (int i = 0; i < resourceCount; i++) {
323 auto& [type, flags_, data_] = this->resources.emplace_back();
324
325 auto typeAndFlags = stream.read<uint32_t>();
326 if (stream.is_big_endian()) {
327 // This field is little-endian
328 BufferStream::swap_endian(&typeAndFlags);
329 }
330 type = static_cast<Resource::Type>(typeAndFlags & 0xffffff); // last 3 bytes
331 flags_ = static_cast<Resource::Flags>(typeAndFlags >> 24); // first byte
332 data_ = stream.read_span<std::byte>(4);
333
334 if (stream.is_big_endian() && !(flags_ & Resource::FLAG_LOCAL_DATA)) {
335 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(data_.data()));
336 }
337 }
338
339 // Sort resources by their offset, in case certain VTFs are written
340 // weirdly and have resource data written out of order. So far I have
341 // found only one VTF in an official Valve game where this is the case
342 std::ranges::sort(this->resources, [](const Resource& lhs, const Resource& rhs) {
344 return lhs.type < rhs.type;
345 }
347 return true;
348 }
350 return false;
351 }
352 return *reinterpret_cast<uint32_t*>(lhs.data.data()) < *reinterpret_cast<uint32_t*>(rhs.data.data());
353 });
354
355 // Fix up data spans to point to the actual data
356 Resource* lastResource = nullptr;
357 for (auto& resource : this->resources) {
358 if (!(resource.flags & Resource::FLAG_LOCAL_DATA)) {
359 if (lastResource) {
360 const auto lastOffset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
361 const auto currentOffset = *reinterpret_cast<uint32_t*>(resource.data.data());
362 const auto curPos = stream.tell();
363 stream.seek(lastOffset);
364 lastResource->data = stream.read_span<std::byte>(currentOffset - lastOffset);
365 stream.seek(static_cast<int64_t>(curPos));
366 }
367 lastResource = &resource;
368 }
369 }
370 if (lastResource) {
371 const auto offset = *reinterpret_cast<uint32_t*>(lastResource->data.data());
372 const auto curPos = stream.tell();
373 stream.seek(offset);
374 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
375 stream.seek(static_cast<int64_t>(curPos));
376 }
377 };
378
379 // HACK: a couple tweaks to fix engine bugs or branch differences
380 const auto postReadTransform = [this] {
381 // Change the format to DXT1_ONE_BIT_ALPHA to get compressonator to recognize it.
382 // No source game recognizes this format, so we will do additional transform in bake back to DXT1.
383 // We also need to check MULTI_BIT_ALPHA flag because stupid third party tools will sometimes set it???
384 if (this->format == ImageFormat::DXT1 && this->flags & (FLAG_V0_ONE_BIT_ALPHA | FLAG_V0_MULTI_BIT_ALPHA)) {
385 this->format = ImageFormat::DXT1_ONE_BIT_ALPHA;
386 }
387 // If the version is below 7.5, NV_NULL / ATI2N / ATI1N are at different positions in the enum.
388 // It is safe to do this because these formats didn't exist before v7.5.
389 if (this->version < 5 && (this->format == ImageFormat::RGBA1010102 || this->format == ImageFormat::BGRA1010102 || this->format == ImageFormat::R16F)) {
390 this->format = static_cast<ImageFormat>(static_cast<int32_t>(this->format) - 3);
391 }
392 };
393
394 switch (this->platform) {
395 case PLATFORM_UNKNOWN:
396 return;
397 case PLATFORM_PC: {
398 stream
399 .read(this->width)
400 .read(this->height)
401 .read(this->flags)
402 .read(this->frameCount)
403 .read(this->startFrame)
404 .skip(4)
405 .read(this->reflectivity[0])
406 .read(this->reflectivity[1])
407 .read(this->reflectivity[2])
408 .skip(4)
409 .read(this->bumpMapScale)
410 .read(this->format)
411 .read(this->mipCount);
412
413 postReadTransform();
414
415 // This will always be DXT1
416 stream.skip<ImageFormat>();
417 stream >> this->thumbnailWidth >> this->thumbnailHeight;
418 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
420 } else {
422 }
423
424 if (this->version < 2) {
425 this->depth = 1;
426 } else {
427 stream.read(this->depth);
428 }
429
430 if (parseHeaderOnly) {
431 this->opened = true;
432 return;
433 }
434
435 if (this->version >= 3) {
436 stream.skip(3);
437 auto resourceCount = stream.read<uint32_t>();
438 stream.skip(8);
439 readResources(resourceCount);
440
441 this->opened = stream.tell() == headerSize;
442
443 if (this->opened && this->version >= 6) {
444 const auto* auxResource = this->getResource(Resource::TYPE_AUX_COMPRESSION);
445 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
446 if (auxResource && imageResource) {
447 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
448 const auto faceCount = this->getFaceCount();
449 std::vector<std::byte> decompressedImageData(ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth));
450 uint32_t oldOffset = 0;
451 for (int i = this->mipCount - 1; i >= 0; i--) {
452 for (int j = 0; j < this->frameCount; j++) {
453 for (int k = 0; k < faceCount; k++) {
454 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
455 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())) {
456 // Keep in mind that slices are compressed together
457 mz_ulong decompressedImageDataSize = newLength * this->depth;
458 switch (auxResource->getDataAsAuxCompressionMethod()) {
459 using enum CompressionMethod;
460 case DEFLATE:
461 if (mz_uncompress(reinterpret_cast<unsigned char*>(decompressedImageData.data() + newOffset), &decompressedImageDataSize, reinterpret_cast<const unsigned char*>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
462 this->opened = false;
463 return;
464 }
465 break;
466 case ZSTD:
467 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) {
468 this->opened = false;
469 return;
470 }
471 break;
472 case CONSOLE_LZMA:
473 // Shouldn't be here!
475 break;
476 }
477 }
478 oldOffset += oldLength;
479 }
480 }
481 }
482 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, decompressedImageData);
483 }
484 }
485 }
486 } else {
487 stream.skip(math::paddingForAlignment(16, stream.tell()));
488 this->opened = stream.tell() == headerSize;
489
490 this->resources.reserve(2);
491
492 if (this->hasThumbnailData()) {
493 this->resources.push_back({
495 .flags = Resource::FLAG_NONE,
496 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
497 });
498 }
499 if (this->hasImageData()) {
500 this->resources.push_back({
502 .flags = Resource::FLAG_NONE,
503 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
504 });
505 }
506 }
507
508 if (const auto* resource = this->getResource(Resource::TYPE_AUX_COMPRESSION)) {
509 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
510 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
512 }
513 return;
514 }
515 case PLATFORM_XBOX: {
516 if (this->platform == PLATFORM_XBOX) {
517 uint16_t preloadSize = 0, imageOffset = 0;
518 stream
519 .read(this->flags)
520 .read(this->width)
521 .read(this->height)
522 .read(this->depth)
523 .read(this->frameCount)
524 .read(preloadSize)
525 .read(imageOffset)
526 .read(this->reflectivity[0])
527 .read(this->reflectivity[1])
528 .read(this->reflectivity[2])
529 .read(this->bumpMapScale)
530 .read(this->format)
531 .read(this->thumbnailWidth)
532 .read(this->thumbnailHeight)
533 .read(this->fallbackWidth)
534 .read(this->fallbackHeight)
535 .read(this->consoleMipScale)
536 .skip<uint8_t>(); // padding
537
538 const bool headerSizeIsAccurate = stream.tell() == headerSize;
539
540 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height, this->depth);
542
543 postReadTransform();
544
545 // Can't use VTF::getFaceCount yet because there's no image data
546 const auto faceCount = (this->flags & FLAG_V0_ENVMAP) ? 6 : 1;
547
548 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
550 } else {
552 this->resources.push_back({
554 .flags = Resource::FLAG_NONE,
555 .data = stream.read_span<std::byte>(ImageFormatDetails::getDataLength(this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight)),
556 });
557 }
558
559 if (this->format == ImageFormat::P8) {
560 this->resources.push_back({
562 .flags = Resource::FLAG_NONE,
563 .data = stream.read_span<std::byte>(256 * sizeof(ImagePixel::BGRA8888) * this->frameCount),
564 });
565 }
566
567 bool ok;
568 auto fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
569 std::vector<std::byte> reorderedFallbackData;
570 if (this->hasFallbackData()) {
571 if (stream.tell() + fallbackSize != preloadSize) {
572 // A couple XTFs that shipped with HL2 are missing the NO_MIP flag. We can detect them by checking the size of the fallback
573 fallbackSize = ImageFormatDetails::getDataLengthXBOX(false, this->format, 1, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight);
574 if (stream.tell() + fallbackSize != preloadSize) {
575 this->opened = false;
576 return;
577 }
578 this->fallbackMipCount = 1;
579 this->mipCount = 1;
580 this->flags |= FLAG_V0_NO_MIP;
581 }
582 reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<true>(false, stream.read_span<std::byte>(fallbackSize), this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, ok);
583 if (!ok) {
584 this->opened = false;
585 return;
586 }
587 ::swapImageDataEndianForConsole<true>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight, 1, this->platform);
588 }
589
590 this->opened = headerSizeIsAccurate;
591 if (parseHeaderOnly) {
592 return;
593 }
594
595 const auto imageSize = ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth);
596 std::vector<std::byte> reorderedImageData;
597 if (this->hasImageData()) {
598 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);
599 if (!ok) {
600 this->opened = false;
601 return;
602 }
603 ::swapImageDataEndianForConsole<true>(reorderedImageData, this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, this->platform);
604 }
605
606 // By this point we cannot use spans over data, it will change here
607 if (this->hasFallbackData()) {
608 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, reorderedFallbackData);
609 }
610 if (this->hasImageData()) {
611 this->setResourceInternal(Resource::TYPE_IMAGE_DATA, reorderedImageData);
612 }
613 return;
614 }
615 }
616 case PLATFORM_X360:
619 uint8_t resourceCount;
620 stream
621 .read(this->flags)
622 .read(this->width)
623 .read(this->height)
624 .read(this->depth)
625 .read(this->frameCount)
626 .skip<uint16_t>() // preload
627 .read(this->consoleMipScale)
628 .read(resourceCount)
629 .read(this->reflectivity[0])
630 .read(this->reflectivity[1])
631 .read(this->reflectivity[2])
632 .read(this->bumpMapScale)
633 .read(this->format)
634 .skip<math::Vec4ui8>() // lowResImageSample (replacement for thumbnail resource, linear color pixel)
635 .skip<uint32_t>(); // compressedLength
636
637 postReadTransform();
638
639 // Align to 16 bytes
640 if (this->platform == PLATFORM_PS3_PORTAL2) {
641 stream.skip<uint32_t>();
642 }
643
644 this->mipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height, this->depth);
645
646 if (parseHeaderOnly) {
647 this->opened = true;
648 return;
649 }
650
651 this->resources.reserve(resourceCount);
652 readResources(resourceCount);
653
654 this->opened = stream.tell() == headerSize;
655
656 // The resources vector isn't modified by setResourceInternal when we're not adding a new one, so this is fine
657 for (const auto& resource : this->resources) {
658 // Decompress LZMA resources
659 if (BufferStreamReadOnly rsrcStream{resource.data.data(), resource.data.size()}; rsrcStream.read<uint32_t>() == compression::VALVE_LZMA_SIGNATURE) {
660 if (auto decompressedData = compression::decompressValveLZMA(resource.data)) {
661 this->setResourceInternal(resource.type, *decompressedData);
662
663 if (resource.type == Resource::TYPE_IMAGE_DATA) {
664 // Do this here because compressionLength in header can be garbage on PS3 orange box
666 }
667 }
668 }
669
670 if (resource.type == Resource::TYPE_THUMBNAIL_DATA) {
671 ::swapImageDataEndianForConsole<true>(resource.data, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
672 } else if (resource.type == Resource::TYPE_IMAGE_DATA) {
673 ::swapImageDataEndianForConsole<true>(resource.data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
674 } else if (!(resource.flags & Resource::FLAG_LOCAL_DATA) && resource.data.size() >= sizeof(uint32_t)) {
675 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resource.data.data()));
676 }
677 }
678 return;
679 }
680 }
681}
682
683VTF::VTF(std::span<const std::byte> vtfData, bool parseHeaderOnly)
684 : VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly) {}
685
686VTF::VTF(const std::string& vtfPath, bool parseHeaderOnly)
687 : VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly) {}
688
689VTF::VTF(const VTF& other) {
690 *this = other;
691}
692
693VTF& VTF::operator=(const VTF& other) {
694 this->opened = other.opened;
695 this->data = other.data;
696 this->version = other.version;
697 this->width = other.width;
698 this->height = other.height;
699 this->flags = other.flags;
700 this->frameCount = other.frameCount;
701 this->startFrame = other.startFrame;
702 this->reflectivity = other.reflectivity;
703 this->bumpMapScale = other.bumpMapScale;
704 this->format = other.format;
705 this->mipCount = other.mipCount;
706 this->thumbnailFormat = other.thumbnailFormat;
707 this->thumbnailWidth = other.thumbnailWidth;
708 this->thumbnailHeight = other.thumbnailHeight;
709 this->fallbackWidth = other.fallbackWidth;
710 this->fallbackHeight = other.fallbackHeight;
712 this->consoleMipScale = other.consoleMipScale;
713 this->depth = other.depth;
714
715 this->resources.clear();
716 for (const auto& [otherType, otherFlags, otherData] : other.resources) {
717 auto& [type, flags_, data_] = this->resources.emplace_back();
718 type = otherType;
719 flags_ = otherFlags;
720 data_ = {this->data.data() + (otherData.data() - other.data.data()), otherData.size()};
721 }
722
723 this->platform = other.platform;
728
729 return *this;
730}
731
732VTF::operator bool() const {
733 return this->opened;
734}
735
737 bool out = true;
738 if (writer.hasImageData() && (options.invertGreenChannel || options.gammaCorrection != 1.f)) {
739 for (int i = 1; i < writer.mipCount; i++) {
740 for (int j = 0; j < writer.frameCount; j++) {
741 for (int k = 0; k < writer.getFaceCount(); k++) {
742 for (int l = 0; l < writer.depth; l++) {
743 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)) {
744 out = false;
745 }
746 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)) {
747 out = false;
748 }
749 }
750 }
751 }
752 }
753 }
754 writer.setPlatform(options.platform);
755 if (options.computeReflectivity) {
756 writer.computeReflectivity();
757 }
758 if (options.initialFrameCount > 1 || options.isCubeMap || options.initialDepth > 1) {
759 if (!writer.setFrameFaceAndDepth(options.initialFrameCount, options.isCubeMap, options.initialDepth)) {
760 out = false;
761 }
762 }
763 writer.setStartFrame(options.startFrame);
764 writer.setBumpMapScale(options.bumpMapScale);
765 if (options.computeThumbnail) {
766 writer.computeThumbnail();
767 }
768 if (options.outputFormat == VTF::FORMAT_UNCHANGED) {
769 options.outputFormat = writer.getFormat();
770 } else if (options.outputFormat == VTF::FORMAT_DEFAULT) {
771 options.outputFormat = VTF::getDefaultCompressedFormat(writer.getFormat(), writer.getVersion(), options.isCubeMap);
772 }
773 if (options.computeMips) {
774 if (const auto recommendedMipCount = ImageDimensions::getRecommendedMipCountForDims(options.outputFormat, writer.getWidth(), writer.getHeight()); recommendedMipCount > 1) {
775 if (!writer.setMipCount(recommendedMipCount)) {
776 out = false;
777 }
778 writer.computeMips(options.filter);
779 }
780 }
782 if (options.computeTransparencyFlags) {
784 }
787 writer.setConsoleMipScale(options.consoleMipScale);
788 return out;
789}
790
791bool VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, const CreationOptions& options) {
792 VTF writer;
793 writer.setVersion(options.version);
794 writer.addFlags(options.flags);
796 if (!writer.setImage(imageData, format, width, height, options.filter)) {
797 return false;
798 }
799 if (!createInternal(writer, options)) {
800 return false;
801 }
802 return writer.bake(vtfPath);
803}
804
805bool VTF::create(ImageFormat format, uint16_t width, uint16_t height, const std::string& vtfPath, const CreationOptions& options) {
806 std::vector<std::byte> imageData;
807 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
808 return create(imageData, format, width, height, vtfPath, options);
809}
810
811VTF VTF::create(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
812 VTF writer;
813 writer.setVersion(options.version);
814 writer.addFlags(options.flags);
816 writer.setImage(imageData, format, width, height, options.filter);
817 createInternal(writer, options);
818 return writer;
819}
820
821VTF VTF::create(ImageFormat format, uint16_t width, uint16_t height, const CreationOptions& options) {
822 std::vector<std::byte> imageData;
823 imageData.resize(static_cast<uint32_t>(width) * height * ImageFormatDetails::bpp(format) / 8);
824 return create(imageData, format, width, height, options);
825}
826
827bool VTF::create(const std::string& imagePath, const std::string& vtfPath, const CreationOptions& options) {
828 VTF writer;
829 writer.setVersion(options.version);
830 writer.addFlags(options.flags);
832 if (!writer.setImage(imagePath, options.filter)) {
833 return false;
834 }
835 if (!createInternal(writer, options)) {
836 return false;
837 }
838 return writer.bake(vtfPath);
839}
840
841VTF VTF::create(const std::string& imagePath, const CreationOptions& options) {
842 VTF writer;
843 writer.setVersion(options.version);
844 writer.addFlags(options.flags);
846 writer.setImage(imagePath, options.filter);
847 createInternal(writer, options);
848 return writer;
849}
850
852 return this->platform;
853}
854
855void VTF::setPlatform(Platform newPlatform) {
856 if (this->platform == newPlatform) {
857 return;
858 }
859
860 // hack to allow VTF::setVersion to work
861 const auto oldPlatform = this->platform;
862 this->platform = PLATFORM_PC;
863 switch (newPlatform) {
864 case PLATFORM_UNKNOWN:
865 case PLATFORM_PC:
866 break;
867 case PLATFORM_XBOX:
868 this->setVersion(2);
869 break;
870 case PLATFORM_X360:
872 this->setVersion(4);
873 break;
875 this->setVersion(5);
876 break;
877 }
878 this->platform = newPlatform;
879
880 // Update flags
881 if (this->platform == PLATFORM_XBOX || newPlatform == PLATFORM_XBOX) {
883 }
884
885 // XBOX stores thumbnail as single RGB888 pixel, but we assume thumbnail is DXT1 on other platforms
886 if (this->hasThumbnailData()) {
887 if (this->platform == PLATFORM_XBOX) {
889 this->thumbnailWidth = 1;
890 this->thumbnailHeight = 1;
891 std::array newThumbnail{
892 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[0], 0.f, 1.f) * 255.f)),
893 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[1], 0.f, 1.f) * 255.f)),
894 static_cast<std::byte>(static_cast<uint8_t>(std::clamp(this->reflectivity[2], 0.f, 1.f) * 255.f)),
895 };
897 } else if (oldPlatform == PLATFORM_XBOX) {
899 this->thumbnailWidth = 0;
900 this->thumbnailHeight = 0;
901 }
902 }
903
904 // Add/remove fallback data for XBOX
905 if (this->platform != PLATFORM_XBOX && this->hasFallbackData()) {
906 this->removeFallback();
907 } else if (this->platform == PLATFORM_XBOX) {
908 this->computeFallback();
909 }
910
912
913 if (this->platform != PLATFORM_PC) {
914 const auto recommendedCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height, this->depth);
915 if (this->mipCount != recommendedCount) {
916 this->setMipCount(recommendedCount);
917 }
918 } else if (oldPlatform != PLATFORM_PC) {
919 const auto recommendedMipCount = ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height);
920 if (this->mipCount > recommendedMipCount) {
921 this->setMipCount(recommendedMipCount);
922 }
923 }
924}
925
926uint32_t VTF::getVersion() const {
927 return this->version;
928}
929
930void VTF::setVersion(uint32_t newVersion) {
931 if (this->platform != PLATFORM_PC) {
932 return;
933 }
934 if (this->hasImageData()) {
935 auto faceCount = this->getFaceCount();
936 if (faceCount == 7 && (newVersion < 1 || newVersion > 4)) {
937 faceCount = 6;
938 }
939 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, faceCount, this->depth);
940 }
941
942 // Fix up flags
943 const bool srgb = this->isSRGB();
944 if ((this->version < 1 && newVersion >= 1) || (this->version >= 1 && newVersion < 1)) {
946 }
947 if ((this->version < 2 && newVersion >= 2) || (this->version >= 2 && newVersion < 2)) {
949 }
950 if ((this->version < 3 && newVersion >= 3) || (this->version >= 3 && newVersion < 3)) {
952 }
953 if ((this->version < 4 && newVersion >= 4) || (this->version >= 4 && newVersion < 4)) {
956 }
957 if ((this->version < 5 && newVersion >= 5) || (this->version >= 5 && newVersion < 5)) {
960 }
961 this->setSRGB(srgb);
962
963 this->version = newVersion;
964}
965
967 return this->imageWidthResizeMethod;
968}
969
971 return this->imageHeightResizeMethod;
972}
973
974void VTF::setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_) {
975 this->imageWidthResizeMethod = imageWidthResizeMethod_;
976 this->imageHeightResizeMethod = imageHeightResizeMethod_;
977}
978
980 this->imageWidthResizeMethod = imageWidthResizeMethod_;
981}
982
984 this->imageHeightResizeMethod = imageHeightResizeMethod_;
985}
986
987uint16_t VTF::getWidth(uint8_t mip) const {
988 return mip > 0 ? ImageDimensions::getMipDim(mip, this->width) : this->width;
989}
990
991uint16_t VTF::getHeight(uint8_t mip) const {
992 return mip > 0 ? ImageDimensions::getMipDim(mip, this->height) : this->height;
993}
994
995void VTF::setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter) {
996 if (newWidth == 0 || newHeight == 0) {
997 this->format = ImageFormat::EMPTY;
998 this->width = 0;
999 this->height = 0;
1001 return;
1002 }
1003
1005 if (this->hasImageData()) {
1006 if (this->width == newWidth && this->height == newHeight) {
1007 return;
1008 }
1009 auto newMipCount = this->mipCount;
1010 if (this->platform == PLATFORM_PC) {
1011 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(this->format, newWidth, newHeight); newMipCount > recommendedCount) {
1012 newMipCount = recommendedCount;
1013 }
1014 } else {
1015 newMipCount = (this->flags & FLAG_V0_NO_MIP) ? 1 : ImageDimensions::getActualMipCountForDimsOnConsole(newWidth, newHeight, this->depth);
1016 }
1017 this->regenerateImageData(this->format, newWidth, newHeight, newMipCount, this->frameCount, this->getFaceCount(), this->depth, filter);
1018 } else {
1019 this->format = ImageFormat::RGBA8888;
1020 this->mipCount = 1;
1021 this->frameCount = 1;
1022 this->flags &= ~FLAG_V0_ENVMAP;
1023 this->width = newWidth;
1024 this->height = newHeight;
1025 this->depth = 1;
1026 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)));
1027 }
1028}
1029
1030uint32_t VTF::getFlags() const {
1031 return this->flags;
1032}
1033
1034void VTF::setFlags(uint32_t flags_) {
1035 this->flags = (this->flags & FLAG_MASK_INTERNAL) | (flags_ & ~FLAG_MASK_INTERNAL);
1036}
1037
1038void VTF::addFlags(uint32_t flags_) {
1039 this->flags |= flags_ & ~FLAG_MASK_INTERNAL;
1040}
1041
1042void VTF::removeFlags(uint32_t flags_) {
1043 this->flags &= ~flags_ | FLAG_MASK_INTERNAL;
1044}
1045
1046bool VTF::isSRGB() const {
1047 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);
1048}
1049
1050void VTF::setSRGB(bool srgb) {
1051 if (srgb) {
1052 if (this->version >= 5) {
1053 this->addFlags(FLAG_V5_SRGB);
1054 } else if (this->version >= 4) {
1055 this->addFlags(FLAG_V4_SRGB);
1056 }
1057 } else {
1058 if (this->version >= 5) {
1060 } else if (this->version >= 4) {
1062 }
1063 }
1064}
1065
1067 if (ImageFormatDetails::transparent(this->format)) {
1068 if (ImageFormatDetails::decompressedAlpha(this->format) > 1) {
1069 this->flags &= ~FLAG_V0_ONE_BIT_ALPHA;
1071 } else {
1073 this->flags &= ~FLAG_V0_MULTI_BIT_ALPHA;
1074 }
1075 } else {
1076 this->flags &= ~FLAG_V0_ONE_BIT_ALPHA;
1077 this->flags &= ~FLAG_V0_MULTI_BIT_ALPHA;
1078 }
1079}
1080
1081ImageFormat VTF::getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap) {
1082 if (version >= 6) {
1083 if (isCubeMap) {
1084 return ImageFormat::BC6H;
1085 }
1086 return ImageFormat::BC7;
1087 }
1088 if (ImageFormatDetails::decompressedAlpha(inputFormat) > 0) {
1089 return ImageFormat::DXT5;
1090 }
1091 return ImageFormat::DXT1;
1092}
1093
1095 return this->format;
1096}
1097
1098void VTF::setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter, float quality) {
1099 if (newFormat == VTF::FORMAT_UNCHANGED || newFormat == this->format) {
1100 return;
1101 }
1102 if (newFormat == VTF::FORMAT_DEFAULT) {
1103 newFormat = VTF::getDefaultCompressedFormat(this->format, this->version, this->getFaceCount() > 1);
1104 }
1105 if (!this->hasImageData()) {
1106 this->format = newFormat;
1107 return;
1108 }
1109 const auto oldFormat = this->format;
1110 auto newMipCount = this->mipCount;
1111 if (const auto recommendedCount = ImageDimensions::getRecommendedMipCountForDims(newFormat, this->width, this->height); newMipCount > recommendedCount) {
1112 newMipCount = recommendedCount;
1113 }
1114 if (ImageFormatDetails::compressed(newFormat)) {
1115 this->regenerateImageData(newFormat, this->width + math::paddingForAlignment(4, this->width), this->height + math::paddingForAlignment(4, this->height), newMipCount, this->frameCount, this->getFaceCount(), this->depth, filter, quality);
1116 } else {
1117 this->regenerateImageData(newFormat, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->depth, filter, quality);
1118 }
1119
1120 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
1121 const auto fallbackConverted = ImageConversion::convertSeveralImageDataToFormat(fallbackResource->data, oldFormat, this->format, ImageDimensions::getActualMipCountForDimsOnConsole(this->fallbackWidth, this->fallbackHeight), this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, quality);
1122 this->setResourceInternal(Resource::TYPE_FALLBACK_DATA, fallbackConverted);
1123 }
1124}
1125
1126uint8_t VTF::getMipCount() const {
1127 return this->mipCount;
1128}
1129
1130bool VTF::setMipCount(uint8_t newMipCount) {
1131 if (!this->hasImageData()) {
1132 return false;
1133 }
1134 if (this->platform != PLATFORM_PC && newMipCount > 1) {
1135 newMipCount = ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height, this->depth);
1136 } else if (const auto recommended = ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height); newMipCount > recommended) {
1137 newMipCount = recommended;
1138 if (newMipCount == 1) {
1139 return false;
1140 }
1141 }
1142 this->regenerateImageData(this->format, this->width, this->height, newMipCount, this->frameCount, this->getFaceCount(), this->depth);
1143 return true;
1144}
1145
1147 if (this->platform == PLATFORM_PC) {
1148 return this->setMipCount(ImageDimensions::getRecommendedMipCountForDims(this->format, this->width, this->height));
1149 }
1150 return this->setMipCount(ImageDimensions::getActualMipCountForDimsOnConsole(this->width, this->height, this->depth));
1151}
1152
1154 auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
1155 if (!imageResource || !this->hasImageData()) {
1156 return;
1157 }
1158
1159 if (this->mipCount <= 1) {
1160 if (!this->setRecommendedMipCount() || this->mipCount <= 1) {
1161 return;
1162 }
1163 }
1164
1165 auto* outputDataPtr = imageResource->data.data();
1166 const auto faceCount = this->getFaceCount();
1167
1168#ifdef SOURCEPP_BUILD_WITH_THREADS
1169 std::vector<std::future<void>> futures;
1170 futures.reserve(this->frameCount * faceCount * this->depth);
1171#endif
1172 for (int j = 0; j < this->frameCount; j++) {
1173 for (int k = 0; k < faceCount; k++) {
1174 for (int l = 0; l < this->depth; l++) {
1175#ifdef SOURCEPP_BUILD_WITH_THREADS
1176 futures.push_back(std::async(std::launch::async, [this, filter, outputDataPtr, faceCount, j, k, l] {
1177#endif
1178 for (int i = 1; i < this->mipCount; i++) {
1179 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);
1180 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) {
1181 std::memcpy(outputDataPtr + offset, mip.data(), length);
1182 }
1183 }
1184#ifdef SOURCEPP_BUILD_WITH_THREADS
1185 }));
1186 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
1187 for (auto& future : futures) {
1188 future.get();
1189 }
1190 futures.clear();
1191 }
1192#endif
1193 }
1194 }
1195 }
1196#ifdef SOURCEPP_BUILD_WITH_THREADS
1197 for (auto& future : futures) {
1198 future.get();
1199 }
1200#endif
1201}
1202
1203uint16_t VTF::getFrameCount() const {
1204 return this->frameCount;
1205}
1206
1207bool VTF::setFrameCount(uint16_t newFrameCount) {
1208 if (!this->hasImageData()) {
1209 return false;
1210 }
1211 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, this->getFaceCount(), this->depth);
1212 return true;
1213}
1214
1215uint8_t VTF::getFaceCount() const {
1216 if (!this->hasImageData()) {
1217 return 0;
1218 }
1219 const auto* image = this->getResource(Resource::TYPE_IMAGE_DATA);
1220 if (!image) {
1221 return 0;
1222 }
1223 if (!(this->flags & FLAG_V0_ENVMAP)) {
1224 return 1;
1225 }
1226 if (this->version >= 6) {
1227 // All v7.6 VTFs are sane, and we need this special case to fix a bug in the parser where
1228 // it won't recognize cubemaps as cubemaps because the image resource is compressed!
1229 return 6;
1230 }
1231 const auto expectedLength = ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, 6, this->width, this->height, this->depth);
1232 if (this->version >= 1 && this->version <= 4 && expectedLength < image->data.size()) {
1233 return 7;
1234 }
1235 if (expectedLength == image->data.size()) {
1236 return 6;
1237 }
1238 return 1;
1239}
1240
1241bool VTF::setFaceCount(bool isCubemap) {
1242 if (!this->hasImageData()) {
1243 return false;
1244 }
1245 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, this->depth);
1246 return true;
1247}
1248
1249uint16_t VTF::getDepth() const {
1250 return this->depth;
1251}
1252
1253bool VTF::setDepth(uint16_t newDepth) {
1254 if (!this->hasImageData()) {
1255 return false;
1256 }
1257 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, this->frameCount, this->getFaceCount(), newDepth);
1258 return true;
1259}
1260
1261bool VTF::setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubemap, uint16_t newDepth) {
1262 if (!this->hasImageData()) {
1263 return false;
1264 }
1265 this->regenerateImageData(this->format, this->width, this->height, this->mipCount, newFrameCount, isCubemap ? ((this->version >= 1 && this->version <= 4) ? 7 : 6) : 1, newDepth);
1266 return true;
1267}
1268
1269uint16_t VTF::getStartFrame() const {
1270 return this->startFrame;
1271}
1272
1273void VTF::setStartFrame(uint16_t newStartFrame) {
1274 this->startFrame = newStartFrame;
1275}
1276
1277math::Vec3f VTF::getReflectivity() const {
1278 return this->reflectivity;
1279}
1280
1281void VTF::setReflectivity(sourcepp::math::Vec3f newReflectivity) {
1282 this->reflectivity = newReflectivity;
1283}
1284
1286 static constexpr auto getReflectivityForImage = [](const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1287 static constexpr auto getReflectivityForPixel = [](const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1288 // http://markjstock.org/doc/gsd_talk_11_notes.pdf page 11
1289 math::Vec3f ref{static_cast<float>(pixel->r), static_cast<float>(pixel->g), static_cast<float>(pixel->b)};
1290 ref /= 255.f * 0.9f;
1291 ref[0] *= ref[0];
1292 ref[1] *= ref[1];
1293 ref[2] *= ref[2];
1294 return ref;
1295 };
1296
1297 auto rgba8888Data = vtf.getImageDataAsRGBA8888(0, frame, face, slice);
1298 math::Vec3f out{};
1299 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1300 out += getReflectivityForPixel(reinterpret_cast<ImagePixel::RGBA8888*>(rgba8888Data.data() + i));
1301 }
1302 return out / (rgba8888Data.size() / sizeof(ImagePixel::RGBA8888));
1303 };
1304
1305 const auto faceCount = this->getFaceCount();
1306
1307#ifdef SOURCEPP_BUILD_WITH_THREADS
1308 if (this->frameCount > 1 || faceCount > 1 || this->depth > 1) {
1309 std::vector<std::future<math::Vec3f>> futures;
1310 futures.reserve(this->frameCount * faceCount * this->depth);
1311
1312 this->reflectivity = {};
1313 for (int j = 0; j < this->frameCount; j++) {
1314 for (int k = 0; k < faceCount; k++) {
1315 for (int l = 0; l < this->depth; l++) {
1316 futures.push_back(std::async(std::launch::async, [this, j, k, l] {
1317 return getReflectivityForImage(*this, j, k, l);
1318 }));
1319 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
1320 for (auto& future : futures) {
1321 this->reflectivity += future.get();
1322 }
1323 futures.clear();
1324 }
1325 }
1326 }
1327 }
1328
1329 for (auto& future : futures) {
1330 this->reflectivity += future.get();
1331 }
1332 this->reflectivity /= this->frameCount * faceCount * this->depth;
1333 } else {
1334 this->reflectivity = getReflectivityForImage(*this, 0, 0, 0);
1335 }
1336#else
1337 this->reflectivity = {};
1338 for (int j = 0; j < this->frameCount; j++) {
1339 for (int k = 0; k < faceCount; k++) {
1340 for (int l = 0; l < this->depth; l++) {
1341 this->reflectivity += getReflectivityForImage(*this, j, k, l);
1342 }
1343 }
1344 }
1345 this->reflectivity /= this->frameCount * faceCount * this->depth;
1346#endif
1347}
1348
1350 return this->bumpMapScale;
1351}
1352
1353void VTF::setBumpMapScale(float newBumpMapScale) {
1354 this->bumpMapScale = newBumpMapScale;
1355}
1356
1358 return this->thumbnailFormat;
1359}
1360
1361uint8_t VTF::getThumbnailWidth() const {
1362 return this->thumbnailWidth;
1363}
1364
1366 return this->thumbnailHeight;
1367}
1368
1369uint8_t VTF::getFallbackWidth() const {
1370 return this->fallbackWidth;
1371}
1372
1373uint8_t VTF::getFallbackHeight() const {
1374 return this->fallbackHeight;
1375}
1376
1378 return this->fallbackMipCount;
1379}
1380
1381const std::vector<Resource>& VTF::getResources() const {
1382 return this->resources;
1383}
1384
1386 for (const auto& resource : this->resources) {
1387 if (resource.type == type) {
1388 return &resource;
1389 }
1390 }
1391 return nullptr;
1392}
1393
1395 for (auto& resource : this->resources) {
1396 if (resource.type == type) {
1397 return &resource;
1398 }
1399 }
1400 return nullptr;
1401}
1402
1403void VTF::setResourceInternal(Resource::Type type, std::span<const std::byte> data_) {
1404 if (const auto* resource = this->getResource(type); resource && resource->data.size() == data_.size()) {
1405 std::memcpy(resource->data.data(), data_.data(), data_.size());
1406 return;
1407 }
1408
1409 // Store resource data
1410 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1411 for (const auto& [type_, flags_, dataSpan] : this->resources) {
1412 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1413 }
1414
1415 // Set new resource
1416 if (data_.empty()) {
1417 resourceData.erase(type);
1418 } else {
1419 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1420 }
1421
1422 // Save the data
1423 this->data.clear();
1424 BufferStream writer{this->data};
1425
1426 for (auto resourceType : Resource::getOrder()) {
1427 if (!resourceData.contains(resourceType)) {
1428 continue;
1429 }
1430 auto& [specificResourceData, offset] = resourceData[resourceType];
1431 if (resourceType == type) {
1432 Resource newResource{
1433 type,
1434 specificResourceData.size() <= sizeof(uint32_t) ? Resource::FLAG_LOCAL_DATA : Resource::FLAG_NONE,
1435 {this->data.data() + offset, specificResourceData.size()},
1436 };
1437 if (auto* resourcePtr = this->getResourceInternal(type)) {
1438 *resourcePtr = newResource;
1439 } else {
1440 this->resources.push_back(newResource);
1441 }
1442 } else if (!resourceData.contains(resourceType)) {
1443 continue;
1444 }
1445 offset = writer.tell();
1446 writer.write(specificResourceData);
1447 }
1448 this->data.resize(writer.size());
1449
1450 for (auto& [type_, flags_, dataSpan] : this->resources) {
1451 if (resourceData.contains(type_)) {
1452 const auto& [specificResourceData, offset] = resourceData[type_];
1453 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1454 }
1455 }
1456}
1457
1459 std::erase_if(this->resources, [type](const Resource& resource) { return resource.type == type; });
1460}
1461
1462void 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) {
1463 if (!newWidth) { newWidth = 1; }
1464 if (!newHeight) { newHeight = 1; }
1465 if (!newMipCount) { newMipCount = 1; }
1466 if (!newFrameCount) { newFrameCount = 1; }
1467 if (!newFaceCount) { newFaceCount = 1; }
1468 if (!newDepth) { newDepth = 1; }
1469
1470 const auto faceCount = this->getFaceCount();
1471 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1472 return;
1473 }
1474
1475 if (newMipCount > 1) {
1476 this->flags &= ~(FLAG_V0_NO_MIP | FLAG_V0_NO_LOD);
1477 } else {
1479 }
1480
1481 std::vector<std::byte> newImageData;
1482 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
1483 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->depth == newDepth) {
1484 newImageData = ImageConversion::convertSeveralImageDataToFormat(imageResource->data, this->format, newFormat, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->depth, quality);
1485 } else {
1486 newImageData.resize(ImageFormatDetails::getDataLength(this->format, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1487 for (int i = newMipCount - 1; i >= 0; i--) {
1488 for (int j = 0; j < newFrameCount; j++) {
1489 for (int k = 0; k < newFaceCount; k++) {
1490 for (int l = 0; l < newDepth; l++) {
1491 if (i < this->mipCount && j < this->frameCount && k < faceCount && l < this->depth) {
1492 auto imageSpan = this->getImageDataRaw(i, j, k, l);
1493 std::vector<std::byte> image{imageSpan.begin(), imageSpan.end()};
1494 if (this->width != newWidth || this->height != newHeight) {
1495 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);
1496 }
1497 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newDepth) && image.size() == length) {
1498 std::memcpy(newImageData.data() + offset, image.data(), length);
1499 }
1500 }
1501 }
1502 }
1503 }
1504 }
1505 if (this->format != newFormat) {
1506 newImageData = ImageConversion::convertSeveralImageDataToFormat(newImageData, this->format, newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth, quality);
1507 }
1508 }
1509 } else {
1510 newImageData.resize(ImageFormatDetails::getDataLength(newFormat, newMipCount, newFrameCount, newFaceCount, newWidth, newHeight, newDepth));
1511 }
1512
1513 this->format = newFormat;
1514 this->width = newWidth;
1515 this->height = newHeight;
1516 this->mipCount = newMipCount;
1517 this->frameCount = newFrameCount;
1518 if (newFaceCount > 1) {
1519 this->flags |= FLAG_V0_ENVMAP;
1520 } else {
1521 this->flags &= ~FLAG_V0_ENVMAP;
1522 }
1523 this->depth = newDepth;
1524
1526
1527 // Fix up XBOX resources that depend on image resource
1528 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1529 const auto targetSize = 256 * sizeof(ImagePixel::BGRA8888) * this->frameCount;
1530 if (palette->data.size() != targetSize) {
1531 std::vector<std::byte> paletteData{palette->data.begin(), palette->data.end()};
1532 paletteData.resize(targetSize);
1534 }
1535 }
1536 if (this->hasFallbackData()) {
1537 this->removeFallback();
1538 this->computeFallback();
1539 }
1540}
1541
1542std::vector<std::byte> VTF::getPaletteResourceFrame(uint16_t frame) const {
1543 if (const auto* palette = this->getResource(Resource::TYPE_PALETTE_DATA)) {
1544 return palette->getDataAsPalette(frame);
1545 }
1546 return {};
1547}
1548
1549std::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 {
1550 spriteWidth = 0;
1551 spriteHeight = 0;
1552
1553 auto shtResource = this->getResource(Resource::TYPE_PARTICLE_SHEET_DATA);
1554 if (!shtResource) {
1555 return {};
1556 }
1557
1558 auto sht = shtResource->getDataAsParticleSheet();
1559 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1560 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1561 return {};
1562 }
1563
1564 // These values are likely slightly too large thanks to float magic, use a
1565 // shader that scales UVs instead of this function if precision is a concern
1566 // This will also break if any of the bounds are above 1 or below 0, but that
1567 // hasn't been observed in official textures
1568 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1569 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 * static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1570 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 * static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1571 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 * static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1572 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 * static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1573
1574 if (x1 > x2) [[unlikely]] {
1575 std::swap(x1, x2);
1576 }
1577 if (y1 > y2) [[unlikely]] {
1578 std::swap(y1, y2);
1579 }
1580 spriteWidth = x2 - x1;
1581 spriteWidth = y2 - y1;
1582
1583 const auto out = ImageConversion::cropImageData(this->getImageDataRaw(mip, frame, face, slice), this->getFormat(), this->getWidth(mip), spriteWidth, x1, this->getHeight(mip), spriteHeight, y1);
1584 if (out.empty()) {
1585 spriteWidth = 0;
1586 spriteHeight = 0;
1587 }
1588 return out;
1589}
1590
1591std::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 {
1592 return ImageConversion::convertImageDataToFormat(this->getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->getFormat(), newFormat, spriteWidth, spriteHeight);
1593}
1594
1595std::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 {
1596 return this->getParticleSheetFrameDataAs(ImageFormat::RGBA8888, spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice);
1597}
1598
1600 std::vector<std::byte> particleSheetData;
1601 BufferStream writer{particleSheetData};
1602
1603 const auto bakedSheet = value.bake();
1604 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1605 particleSheetData.resize(writer.size());
1606
1608}
1609
1612}
1613
1614void VTF::setCRCResource(uint32_t value) {
1615 this->setResourceInternal(Resource::TYPE_CRC, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1616}
1617
1620}
1621
1622void VTF::setLODResource(uint8_t u, uint8_t v, uint8_t u360, uint8_t v360) {
1623 uint32_t lodData;
1624 BufferStream writer{&lodData, sizeof(lodData)};
1625
1626 writer << u << v << u360 << v360;
1627
1628 this->setResourceInternal(Resource::TYPE_LOD_CONTROL_INFO, {reinterpret_cast<const std::byte*>(&lodData), sizeof(lodData)});
1629}
1630
1633}
1634
1635void VTF::setExtendedFlagsResource(uint32_t value) {
1636 this->setResourceInternal(Resource::TYPE_EXTENDED_FLAGS, {reinterpret_cast<const std::byte*>(&value), sizeof(value)});
1637}
1638
1641}
1642
1643void VTF::setKeyValuesDataResource(const std::string& value) {
1644 std::vector<std::byte> keyValuesData;
1645 BufferStream writer{keyValuesData};
1646
1647 writer.write<uint32_t>(value.size()).write(value, false);
1648 keyValuesData.resize(writer.size());
1649
1651}
1652
1655}
1656
1658 std::vector<std::byte> hotspotData;
1659 BufferStream writer{hotspotData};
1660
1661 const auto bakedHotspotData = value.bake();
1662 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1663 hotspotData.resize(writer.size());
1664
1666}
1667
1670}
1671
1673 return this->compressionLevel;
1674}
1675
1676void VTF::setCompressionLevel(int16_t newCompressionLevel) {
1677 this->compressionLevel = newCompressionLevel;
1678}
1679
1681 return this->compressionMethod;
1682}
1683
1685 if (newCompressionMethod == CompressionMethod::CONSOLE_LZMA && this->platform == PLATFORM_PC) {
1687 } else if (newCompressionMethod != CompressionMethod::CONSOLE_LZMA && this->platform != PLATFORM_PC) {
1689 } else {
1690 this->compressionMethod = newCompressionMethod;
1691 }
1692}
1693
1694bool VTF::hasImageData() const {
1695 return this->format != ImageFormat::EMPTY && this->width > 0 && this->height > 0;
1696}
1697
1698std::span<const std::byte> VTF::getImageDataRaw(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1699 if (const auto imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
1700 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)) {
1701 return imageResource->data.subspan(offset, length);
1702 }
1703 }
1704 return {};
1705}
1706
1707std::vector<std::byte> VTF::getImageDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1708 const auto rawImageData = this->getImageDataRaw(mip, frame, face, slice);
1709 if (rawImageData.empty()) {
1710 return {};
1711 }
1712 if (this->format == ImageFormat::P8) {
1713 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
1715 }
1716 }
1717 return ImageConversion::convertImageDataToFormat(rawImageData, this->format, newFormat, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height));
1718}
1719
1720std::vector<std::byte> VTF::getImageDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice) const {
1721 return this->getImageDataAs(ImageFormat::RGBA8888, mip, frame, face, slice);
1722}
1723
1724bool 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) {
1725 if (imageData_.empty()) {
1726 return false;
1727 }
1728
1729 if (!this->hasImageData()) {
1730 uint16_t resizedWidth = width_, resizedHeight = height_;
1731 ImageConversion::setResizedDims(resizedWidth, this->imageWidthResizeMethod, resizedHeight, this->imageHeightResizeMethod);
1732 if (ImageFormatDetails::compressed(format_)) {
1733 resizedWidth += math::paddingForAlignment(4, resizedWidth);
1734 resizedHeight += math::paddingForAlignment(4, resizedHeight);
1735 }
1736 if (const auto newMipCount = ImageDimensions::getRecommendedMipCountForDims(format_, resizedWidth, resizedHeight); newMipCount <= mip) {
1737 mip = newMipCount - 1;
1738 }
1739 if (face > 6 || (face == 6 && (this->version < 1 || this->version > 4))) {
1740 return false;
1741 }
1742 this->regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 5 ? 5 : face) : 0, slice + 1);
1743 }
1744
1745 const auto faceCount = this->getFaceCount();
1746 if (this->mipCount <= mip || this->frameCount <= frame || faceCount <= face || this->depth <= slice) {
1747 return false;
1748 }
1749
1750 const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA);
1751 if (!imageResource) {
1752 return false;
1753 }
1754 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)) {
1755 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
1756 const auto newWidth = ImageDimensions::getMipDim(mip, this->width);
1757 const auto newHeight = ImageDimensions::getMipDim(mip, this->height);
1758 if (width_ != newWidth || height_ != newHeight) {
1759 image = ImageConversion::resizeImageData(image, format_, width_, newWidth, height_, newHeight, this->isSRGB(), filter);
1760 }
1761 if (format_ != this->format) {
1762 image = ImageConversion::convertImageDataToFormat(image, format_, this->format, newWidth, newHeight, quality);
1763 }
1764 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
1765 }
1766 return true;
1767}
1768
1769bool VTF::setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, float quality) {
1770 ImageFormat inputFormat;
1771 int inputWidth, inputHeight, inputFrameCount;
1772 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
1773
1774 // Unable to decode file
1775 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
1776 return false;
1777 }
1778
1779 // One frame (normal)
1780 if (inputFrameCount == 1) {
1781 return this->setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice, quality);
1782 }
1783
1784 // Multiple frames (GIF)
1785 bool allSuccess = true;
1786 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
1787 for (int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
1788 if (!this->setImage({imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize}, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice, quality)) {
1789 allSuccess = false;
1790 }
1791 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
1792 // Call this after setting the first image, this function is a no-op if no image data is present yet
1793 this->setFrameCount(frame + inputFrameCount);
1794 }
1795 }
1796 return allSuccess;
1797}
1798
1799std::vector<std::byte> VTF::saveImageToFile(uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1800 return ImageConversion::convertImageDataToFile(this->getImageDataRaw(mip, frame, face, slice), this->format, ImageDimensions::getMipDim(mip, this->width), ImageDimensions::getMipDim(mip, this->height), fileFormat);
1801}
1802
1803bool VTF::saveImageToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, uint16_t slice, ImageConversion::FileFormat fileFormat) const {
1804 if (auto data_ = this->saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
1805 return fs::writeFileBuffer(imagePath, data_);
1806 }
1807 return false;
1808}
1809
1811 return this->thumbnailFormat != ImageFormat::EMPTY && this->thumbnailWidth > 0 && this->thumbnailHeight > 0;
1812}
1813
1814std::span<const std::byte> VTF::getThumbnailDataRaw() const {
1815 if (const auto thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA)) {
1816 return thumbnailResource->data;
1817 }
1818 return {};
1819}
1820
1821std::vector<std::byte> VTF::getThumbnailDataAs(ImageFormat newFormat) const {
1822 const auto rawThumbnailData = this->getThumbnailDataRaw();
1823 if (rawThumbnailData.empty()) {
1824 return {};
1825 }
1826 return ImageConversion::convertImageDataToFormat(rawThumbnailData, this->thumbnailFormat, newFormat, this->thumbnailWidth, this->thumbnailHeight);
1827}
1828
1829std::vector<std::byte> VTF::getThumbnailDataAsRGBA8888() const {
1831}
1832
1833void VTF::setThumbnail(std::span<const std::byte> imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, float quality) {
1834 if (format_ != this->thumbnailFormat) {
1835 this->setResourceInternal(Resource::TYPE_THUMBNAIL_DATA, ImageConversion::convertImageDataToFormat(imageData_, format_, this->thumbnailFormat, width_, height_, quality));
1836 } else {
1838 }
1839 this->thumbnailWidth = width_;
1840 this->thumbnailHeight = height_;
1841}
1842
1843bool VTF::setThumbnail(const std::string& imagePath, float quality) {
1844 ImageFormat inputFormat;
1845 int inputWidth, inputHeight, inputFrameCount;
1846 auto imageData_ = ImageConversion::convertFileToImageData(fs::readFileBuffer(imagePath), inputFormat, inputWidth, inputHeight, inputFrameCount);
1847
1848 // Unable to decode file
1849 if (imageData_.empty() || inputFormat == ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
1850 return false;
1851 }
1852
1853 // One frame (normal)
1854 if (inputFrameCount == 1) {
1855 this->setThumbnail(imageData_, inputFormat, inputWidth, inputHeight, quality);
1856 return true;
1857 }
1858
1859 // Multiple frames (GIF) - we will just use the first one
1860 const auto frameSize = ImageFormatDetails::getDataLength(inputFormat, inputWidth, inputHeight);
1861 this->setThumbnail({imageData_.data(), frameSize}, inputFormat, inputWidth, inputHeight, quality);
1862 return true;
1863}
1864
1866 if (!this->hasImageData()) {
1867 return;
1868 }
1870 this->thumbnailWidth = 16;
1871 this->thumbnailHeight = 16;
1872 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));
1873}
1874
1877 this->thumbnailWidth = 0;
1878 this->thumbnailHeight = 0;
1880}
1881
1882std::vector<std::byte> VTF::saveThumbnailToFile(ImageConversion::FileFormat fileFormat) const {
1884}
1885
1886bool VTF::saveThumbnailToFile(const std::string& imagePath, ImageConversion::FileFormat fileFormat) const {
1887 if (auto data_ = this->saveThumbnailToFile(fileFormat); !data_.empty()) {
1888 return fs::writeFileBuffer(imagePath, data_);
1889 }
1890 return false;
1891}
1892
1894 return this->fallbackWidth > 0 && this->fallbackHeight > 0 && this->fallbackMipCount > 0;
1895}
1896
1897std::span<const std::byte> VTF::getFallbackDataRaw(uint8_t mip, uint16_t frame, uint8_t face) const {
1898 if (const auto fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA)) {
1899 if (uint32_t offset, length; ImageFormatDetails::getDataPosition(offset, length, this->format, mip, this->fallbackMipCount, frame, this->frameCount, face, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight)) {
1900 return fallbackResource->data.subspan(offset, length);
1901 }
1902 }
1903 return {};
1904}
1905
1906std::vector<std::byte> VTF::getFallbackDataAs(ImageFormat newFormat, uint8_t mip, uint16_t frame, uint8_t face) const {
1907 const auto rawFallbackData = this->getFallbackDataRaw(mip, frame, face);
1908 if (rawFallbackData.empty()) {
1909 return {};
1910 }
1911 if (this->format == ImageFormat::P8) {
1912 if (const auto paletteData = this->getPaletteResourceFrame(frame); !paletteData.empty()) {
1914 }
1915 }
1916 return ImageConversion::convertImageDataToFormat(rawFallbackData, this->format, newFormat, this->fallbackWidth, this->fallbackHeight);
1917}
1918
1919std::vector<std::byte> VTF::getFallbackDataAsRGBA8888(uint8_t mip, uint16_t frame, uint8_t face) const {
1920 return this->getFallbackDataAs(ImageFormat::RGBA8888, mip, frame, face);
1921}
1922
1924 if (!this->hasImageData()) {
1925 return;
1926 }
1927
1928 const auto* imageResource = this->getResourceInternal(Resource::TYPE_IMAGE_DATA);
1929 if (!imageResource) {
1930 return;
1931 }
1932
1933 const auto faceCount = this->getFaceCount();
1934
1935 this->fallbackWidth = 8;
1936 this->fallbackHeight = 8;
1938
1939 std::vector<std::byte> fallbackData;
1940 fallbackData.resize(ImageFormatDetails::getDataLength(this->format, this->fallbackMipCount, this->frameCount, faceCount, this->fallbackWidth, this->fallbackHeight));
1941 for (int i = this->fallbackMipCount - 1; i >= 0; i--) {
1942 for (int j = 0; j < this->frameCount; j++) {
1943 for (int k = 0; k < faceCount; k++) {
1944 auto mip = ImageConversion::resizeImageData(this->getImageDataRaw(0, j, k, 0), this->format, this->width, ImageDimensions::getMipDim(i, this->fallbackWidth), this->height, ImageDimensions::getMipDim(i, this->fallbackHeight), this->isSRGB(), filter);
1945 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) {
1946 std::memcpy(fallbackData.data() + offset, mip.data(), length);
1947 }
1948 }
1949 }
1950 }
1952}
1953
1955 this->fallbackWidth = 0;
1956 this->fallbackHeight = 0;
1957 this->fallbackMipCount = 0;
1959}
1960
1961std::vector<std::byte> VTF::saveFallbackToFile(uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
1962 return ImageConversion::convertImageDataToFile(this->getFallbackDataRaw(mip, frame, face), this->format, ImageDimensions::getMipDim(mip, this->fallbackWidth), ImageDimensions::getMipDim(mip, this->fallbackHeight), fileFormat);
1963}
1964
1965bool VTF::saveFallbackToFile(const std::string& imagePath, uint8_t mip, uint16_t frame, uint8_t face, ImageConversion::FileFormat fileFormat) const {
1966 if (auto data_ = this->saveFallbackToFile(mip, frame, face, fileFormat); !data_.empty()) {
1967 return fs::writeFileBuffer(imagePath, data_);
1968 }
1969 return false;
1970}
1971
1973 return this->consoleMipScale;
1974}
1975
1976void VTF::setConsoleMipScale(uint8_t consoleMipScale_) {
1977 this->consoleMipScale = consoleMipScale_;
1978}
1979
1980uint64_t VTF::estimateBakeSize() const {
1981 bool isExact;
1982 return this->estimateBakeSize(isExact);
1983}
1984
1985uint64_t VTF::estimateBakeSize(bool& isExact) const {
1986 isExact = true;
1987 uint64_t vtfSize = 0;
1988
1989 switch (this->platform) {
1990 case PLATFORM_UNKNOWN:
1991 break;
1992 case PLATFORM_PC:
1993 switch (this->version) {
1994 case 6:
1995 if (this->compressionLevel > 0) {
1996 vtfSize += sizeof(uint64_t); // AXC header size
1997 vtfSize += sizeof(uint32_t) * (2 + this->mipCount * this->frameCount * this->getFaceCount()); // AXC resource size
1998 }
1999 case 5:
2000 case 4:
2001 case 3:
2002 vtfSize += 11 + sizeof(uint32_t) + sizeof(uint64_t) * this->getResources().size(); // resources header size
2003 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2004 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_THUMBNAIL_DATA && resourceType != Resource::TYPE_IMAGE_DATA) {
2005 vtfSize += resourceData.size(); // resource size
2006 }
2007 }
2008 case 2:
2009 vtfSize += sizeof(uint16_t); // depth header field
2010 case 1:
2011 case 0:
2012 vtfSize += sizeof(uint32_t) * 9 + sizeof(float) * 4 + sizeof(uint16_t) * 4 + sizeof(uint8_t) * 3; // header fields
2013 if (this->version < 3) {
2014 vtfSize += math::paddingForAlignment(16, vtfSize); // align to 16 bytes
2015 }
2016
2017 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2018 vtfSize += thumbnailResource->data.size(); // thumbnail size
2019 }
2020 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2021 if (this->version < 6 || this->compressionLevel == 0) {
2022 vtfSize += imageResource->data.size(); // uncompressed image size
2023 } else {
2024 isExact = false;
2025 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2026 }
2027 }
2028 break;
2029 default:
2030 break;
2031 }
2032 break;
2033 case PLATFORM_XBOX:
2034 vtfSize += sizeof(uint32_t) * 6 + sizeof(float) * 4 + sizeof(uint16_t) * 6 + sizeof(uint8_t) * 6; // header fields
2035
2036 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2037 vtfSize += thumbnailResource->data.size(); // thumbnail size
2038 }
2039 if (this->format == ImageFormat::P8) {
2040 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2041 vtfSize += paletteResource->data.size(); // palette size
2042 }
2043 }
2044 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2045 vtfSize += ImageFormatDetails::getDataLengthXBOX(false, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight); // fallback size
2046 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if fallback present
2047 }
2048 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2049 vtfSize += ImageFormatDetails::getDataLengthXBOX(true, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth); // image size
2050 }
2051 if (vtfSize > 512) {
2052 vtfSize += math::paddingForAlignment(512, vtfSize); // align to 512 bytes if longer than 512 bytes
2053 }
2054 break;
2056 vtfSize += sizeof(uint32_t); // align to 16 bytes
2057 case PLATFORM_X360:
2059 vtfSize += sizeof(uint32_t) * 7 + sizeof(float) * 4 + sizeof(uint16_t) * 5 + sizeof(uint8_t) * 6; // header fields
2060 vtfSize += sizeof(uint64_t) * this->getResources().size(); // resources header size
2061
2062 for (const auto& [resourceType, resourceFlags, resourceData] : this->getResources()) {
2063 if (!(resourceFlags & Resource::FLAG_LOCAL_DATA) && resourceType != Resource::TYPE_IMAGE_DATA) {
2064 vtfSize += resourceData.size(); // resource size
2065 }
2066 }
2067 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2069 vtfSize += imageResource->data.size(); // uncompressed image data
2070 } else {
2071 isExact = false;
2072 vtfSize += static_cast<uint64_t>(static_cast<double>(imageResource->data.size()) / 1.75); // compressed image data
2073 }
2074 }
2075 break;
2076 }
2077 return vtfSize;
2078}
2079
2080std::vector<std::byte> VTF::bake() const {
2081 std::vector<std::byte> out;
2082 BufferStream writer{out};
2083
2084 static constexpr auto writeNonLocalResource = [](BufferStream& writer_, Resource::Type type, std::span<const std::byte> data, VTF::Platform platform) {
2085 if (platform != PLATFORM_PC) {
2086 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(&type));
2087 }
2088 writer_.write<uint32_t>(type);
2089 const auto resourceOffsetPos = writer_.tell();
2090 writer_.seek(0, std::ios::end);
2091 const auto resourceOffsetValue = writer_.tell();
2092 writer_.write(data);
2093 writer_.seek_u(resourceOffsetPos).write<uint32_t>(resourceOffsetValue);
2094 };
2095
2096 // HACK: no source game supports this format, but they do support the flag with reg. DXT1
2097 auto bakeFormat = this->format;
2098 auto bakeFlags = this->flags;
2099 if (bakeFormat == ImageFormat::DXT1_ONE_BIT_ALPHA) {
2100 bakeFormat = ImageFormat::DXT1;
2101 bakeFlags |= FLAG_V0_ONE_BIT_ALPHA;
2102 }
2103 // HACK: NV_NULL / ATI2N / ATI1N are at a different position based on engine branch
2104 if (this->version < 5 && (this->format == ImageFormat::EMPTY || this->format == ImageFormat::ATI2N || this->format == ImageFormat::ATI1N)) {
2105 bakeFormat = static_cast<ImageFormat>(static_cast<int32_t>(this->format) + 3);
2106 }
2107
2108 switch (this->platform) {
2109 case PLATFORM_UNKNOWN:
2110 return out;
2111 case PLATFORM_PC: {
2112 writer
2113 .write(VTF_SIGNATURE)
2114 .write<int32_t>(7)
2115 .write(this->version);
2116
2117 const auto headerLengthPos = writer.tell();
2118 writer.write<uint32_t>(0);
2119
2120 writer
2121 .write(this->width)
2122 .write(this->height)
2123 .write(bakeFlags)
2124 .write(this->frameCount)
2125 .write(this->startFrame)
2126 .pad<uint32_t>()
2127 .write(this->reflectivity)
2128 .pad<uint32_t>()
2129 .write(this->bumpMapScale)
2130 .write(bakeFormat)
2131 .write(this->mipCount)
2132 .write(this->thumbnailFormat)
2133 .write(this->thumbnailWidth)
2134 .write(this->thumbnailHeight);
2135
2136 if (this->version >= 2) {
2137 writer << this->depth;
2138 }
2139
2140 if (this->version < 3) {
2141 writer.pad(math::paddingForAlignment(16, writer.tell()));
2142 const auto headerSize = writer.tell();
2143 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
2144
2145 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2146 writer.write(thumbnailResource->data);
2147 }
2148 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2149 writer.write(imageResource->data);
2150 }
2151 } else {
2152 std::vector<std::byte> auxCompressionResourceData;
2153 std::vector<std::byte> compressedImageResourceData;
2154 bool hasAuxCompression = false;
2155 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2156 hasAuxCompression = this->version >= 6 && this->compressionLevel != 0;
2157 if (hasAuxCompression) {
2158 const auto faceCount = this->getFaceCount();
2159 auxCompressionResourceData.resize((this->mipCount * this->frameCount * faceCount + 2) * sizeof(uint32_t));
2160 BufferStream auxWriter{auxCompressionResourceData, false};
2161
2162 // Format of aux resource is as follows, with each item of unspecified type being a 4 byte integer:
2163 // - Size of resource in bytes, not counting this int
2164 // - Compression level, method (2 byte integers)
2165 // - (X times) Size of each mip-face-frame combo
2166 auxWriter
2167 .write<uint32_t>(auxCompressionResourceData.size() - sizeof(uint32_t))
2168 .write(this->compressionLevel)
2169 .write(this->compressionMethod);
2170
2171 for (int i = this->mipCount - 1; i >= 0; i--) {
2172 for (int j = 0; j < this->frameCount; j++) {
2173 for (int k = 0; k < faceCount; k++) {
2174 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)) {
2175 auto compressedData = ::compressData({imageResource->data.data() + offset, length * this->depth}, this->compressionLevel, this->compressionMethod);
2176 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
2177 auxWriter.write<uint32_t>(compressedData.size());
2178 }
2179 }
2180 }
2181 }
2182 }
2183 }
2184
2185 writer.pad(3).write<uint32_t>(this->getResources().size() + hasAuxCompression).pad(8);
2186
2187 const auto resourceStart = writer.tell();
2188 const auto headerSize = resourceStart + ((this->getResources().size() + hasAuxCompression) * sizeof(uint64_t));
2189 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
2190 while (writer.tell() < headerSize) {
2191 writer.write<uint64_t>(0);
2192 }
2193 writer.seek_u(resourceStart);
2194
2195 for (const auto resourceType : Resource::getOrder()) {
2196 if (hasAuxCompression && resourceType == Resource::TYPE_AUX_COMPRESSION) {
2197 writeNonLocalResource(writer, resourceType, auxCompressionResourceData, this->platform);
2198 } else if (hasAuxCompression && resourceType == Resource::TYPE_IMAGE_DATA) {
2199 writeNonLocalResource(writer, resourceType, compressedImageResourceData, this->platform);
2200 } else if (const auto* resource = this->getResource(resourceType)) {
2201 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
2202 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
2203 writer.write(resource->data);
2204 } else {
2205 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
2206 }
2207 }
2208 }
2209 }
2210 break;
2211 }
2212 case PLATFORM_XBOX: {
2213 writer << XTF_SIGNATURE << PLATFORM_XBOX;
2214 writer.write<uint32_t>(0);
2215
2216 const auto headerSizePos = writer.tell();
2217 writer
2218 .write<uint32_t>(0)
2219 .write(this->flags)
2220 .write(this->width)
2221 .write(this->height)
2222 .write(this->depth)
2223 .write(this->frameCount);
2224 const auto preloadSizePos = writer.tell();
2225 writer.write<uint16_t>(0);
2226 const auto imageOffsetPos = writer.tell();
2227 writer
2228 .write<uint16_t>(0)
2229 .write(this->reflectivity[0])
2230 .write(this->reflectivity[1])
2231 .write(this->reflectivity[2])
2232 .write(this->bumpMapScale)
2233 .write(this->format)
2234 .write(this->thumbnailWidth)
2235 .write(this->thumbnailHeight)
2236 .write(this->fallbackWidth)
2237 .write(this->fallbackHeight)
2238 .write(this->consoleMipScale)
2239 .write<uint8_t>(0);
2240
2241 const auto headerSize = writer.tell();
2242 writer.seek_u(headerSizePos).write<uint32_t>(headerSize).seek_u(headerSize);
2243
2244 if (const auto* thumbnailResource = this->getResource(Resource::TYPE_THUMBNAIL_DATA); thumbnailResource && this->hasThumbnailData()) {
2245 writer.write(thumbnailResource->data);
2246 }
2247
2248 if (this->format == ImageFormat::P8) {
2249 if (const auto* paletteResource = this->getResource(Resource::TYPE_PALETTE_DATA)) {
2250 writer.write(paletteResource->data);
2251 }
2252 }
2253
2254 bool hasFallbackResource = false;
2255 if (const auto* fallbackResource = this->getResource(Resource::TYPE_FALLBACK_DATA); fallbackResource && this->hasFallbackData()) {
2256 hasFallbackResource = true;
2257 bool ok;
2258 auto reorderedFallbackData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(false, fallbackResource->data, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, ok);
2259 if (ok) {
2260 ::swapImageDataEndianForConsole<false>(reorderedFallbackData, this->format, this->fallbackMipCount, this->frameCount, this->getFaceCount(), this->fallbackWidth, this->fallbackHeight, 1, this->platform);
2261 writer.write(reorderedFallbackData);
2262 } else {
2263 writer.pad(fallbackResource->data.size());
2264 }
2265 }
2266
2267 const auto preloadSize = writer.tell();
2268 writer.seek_u(preloadSizePos).write<uint32_t>(preloadSize).seek_u(preloadSize);
2269
2270 if (hasFallbackResource) {
2271 writer.pad(math::paddingForAlignment(512, writer.tell()));
2272 }
2273 const auto imageOffset = writer.tell();
2274 writer.seek_u(imageOffsetPos).write<uint16_t>(imageOffset).seek_u(imageOffset);
2275
2276 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA); imageResource && this->hasImageData()) {
2277 bool ok;
2278 auto reorderedImageData = ::convertBetweenDDSAndVTFMipOrderForXBOX<false>(true, imageResource->data, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, ok);
2279 if (ok) {
2280 ::swapImageDataEndianForConsole<false>(reorderedImageData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2281 writer.write(reorderedImageData);
2282 } else {
2283 writer.pad(imageResource->data.size());
2284 }
2285 }
2286 if (writer.tell() > 512) {
2287 writer.pad(math::paddingForAlignment(512, writer.tell()));
2288 }
2289 break;
2290 }
2291 case PLATFORM_X360:
2293 case PLATFORM_PS3_PORTAL2: {
2294 if (this->platform == PLATFORM_PS3_PORTAL2) {
2295 writer << VTF3_SIGNATURE;
2296 writer.set_big_endian(true);
2297 writer << PLATFORM_PS3_ORANGEBOX; // Intentional
2298 } else {
2299 writer << VTFX_SIGNATURE;
2300 writer.set_big_endian(true);
2301 writer << this->platform;
2302 }
2303 writer.write<uint32_t>(8);
2304
2305 const auto headerLengthPos = writer.tell();
2306 writer
2307 .write<uint32_t>(0)
2308 .write(bakeFlags)
2309 .write(this->width)
2310 .write(this->height)
2311 .write(this->depth)
2312 .write(this->frameCount);
2313 const auto preloadPos = writer.tell();
2314 writer
2315 .write<uint16_t>(0) // preload size
2316 .write(this->consoleMipScale)
2317 .write<uint8_t>(this->resources.size())
2318 .write(this->reflectivity[0])
2319 .write(this->reflectivity[1])
2320 .write(this->reflectivity[2])
2321 .write(this->bumpMapScale)
2322 .write(bakeFormat)
2323 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[0] * 255)), 0, 255))
2324 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[1] * 255)), 0, 255))
2325 .write<uint8_t>(std::clamp(static_cast<int>(std::roundf(this->reflectivity[2] * 255)), 0, 255))
2326 .write<uint8_t>(255);
2327 const auto compressionPos = writer.tell();
2328 writer.write<uint32_t>(0); // compressed length
2329
2330 if (this->platform == PLATFORM_PS3_PORTAL2) {
2331 // 16 byte aligned
2332 writer.write<uint32_t>(0);
2333 }
2334
2335 // LZMA compression has not been observed on the PS3 copy of The Orange Box
2336 // todo(vtfpp): check cubemaps
2337 std::vector<std::byte> imageResourceData;
2338 bool hasCompression = false;
2339 if (const auto* imageResource = this->getResource(Resource::TYPE_IMAGE_DATA)) {
2340 imageResourceData.assign(imageResource->data.begin(), imageResource->data.end());
2341 ::swapImageDataEndianForConsole<false>(imageResourceData, this->format, this->mipCount, this->frameCount, this->getFaceCount(), this->width, this->height, this->depth, this->platform);
2342
2343 if (this->platform != PLATFORM_PS3_ORANGEBOX) {
2344 hasCompression = this->compressionMethod == CompressionMethod::CONSOLE_LZMA;
2345 if (hasCompression) {
2346 auto fixedCompressionLevel = this->compressionLevel;
2347 if (this->compressionLevel == 0) {
2348 // Compression level defaults to 0, so it works differently on console.
2349 // Rather than not compress on 0, 0 will be replaced with the default
2350 // compression level (6) if the compression method is LZMA.
2351 fixedCompressionLevel = 6;
2352 }
2353 if (const auto compressedData = compression::compressValveLZMA(imageResourceData, fixedCompressionLevel)) {
2354 imageResourceData.assign(compressedData->begin(), compressedData->end());
2355 } else {
2356 hasCompression = false;
2357 }
2358 }
2359 }
2360 }
2361
2362 const auto resourceStart = writer.tell();
2363 const auto headerSize = resourceStart + (this->getResources().size() * sizeof(uint64_t));
2364 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
2365 while (writer.tell() < headerSize) {
2366 writer.write<uint64_t>(0);
2367 }
2368 writer.seek_u(resourceStart);
2369
2370 for (const auto resourceType : Resource::getOrder()) {
2371 if (resourceType == Resource::TYPE_IMAGE_DATA) {
2372 auto curPos = writer.tell();
2373 const auto imagePos = writer.seek(0, std::ios::end).tell();
2374 writer.seek_u(preloadPos).write(std::max<uint16_t>(imagePos, 2048)).seek_u(curPos);
2375
2376 writeNonLocalResource(writer, resourceType, imageResourceData, this->platform);
2377
2378 if (hasCompression) {
2379 curPos = writer.tell();
2380 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
2381 }
2382 } else if (const auto* resource = this->getResource(resourceType)) {
2383 std::vector<std::byte> resData{resource->data.begin(), resource->data.end()};
2384
2385 if (resource->type == Resource::TYPE_THUMBNAIL_DATA) {
2386 ::swapImageDataEndianForConsole<false>(resData, this->thumbnailFormat, 1, 1, 1, this->thumbnailWidth, this->thumbnailHeight, 1, this->platform);
2387 } else if (!(resource->flags & Resource::FLAG_LOCAL_DATA) && resData.size() >= sizeof(uint32_t)) {
2388 BufferStream::swap_endian(reinterpret_cast<uint32_t*>(resData.data()));
2389 }
2390
2391 if ((resource->flags & Resource::FLAG_LOCAL_DATA) && resource->data.size() == sizeof(uint32_t)) {
2392 writer.set_big_endian(false);
2393 writer.write<uint32_t>((Resource::FLAG_LOCAL_DATA << 24) | resource->type);
2394 writer.set_big_endian(true);
2395 writer.write(resData);
2396 } else {
2397 writeNonLocalResource(writer, resource->type, resData, this->platform);
2398 }
2399 }
2400 }
2401 break;
2402 }
2403 }
2404 out.resize(writer.size());
2405 return out;
2406}
2407
2408bool VTF::bake(const std::string& vtfPath) const {
2409 return fs::writeFileBuffer(vtfPath, this->bake());
2410}
#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
uint16_t width
Definition: VTF.h:528
Platform
Definition: VTF.h:238
@ PLATFORM_PC
Definition: VTF.h:240
@ PLATFORM_X360
Definition: VTF.h:242
@ PLATFORM_PS3_PORTAL2
Definition: VTF.h:244
@ PLATFORM_PS3_ORANGEBOX
Definition: VTF.h:243
@ PLATFORM_UNKNOWN
Definition: VTF.h:239
@ PLATFORM_XBOX
Definition: VTF.h:241
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:983
bool setFrameFaceAndDepth(uint16_t newFrameCount, bool isCubeMap, uint16_t newDepth=1)
Definition: VTF.cpp:1261
void removeKeyValuesDataResource()
Definition: VTF.cpp:1653
void computeFallback(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:1923
uint8_t getFallbackHeight() const
Definition: VTF.cpp:1373
CompressionMethod compressionMethod
Definition: VTF.h:561
void computeReflectivity()
Definition: VTF.cpp:1285
uint8_t getThumbnailWidth() const
Definition: VTF.cpp:1361
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Definition: VTF.cpp:1882
uint8_t consoleMipScale
Definition: VTF.h:550
uint8_t fallbackWidth
Definition: VTF.h:545
ImageFormat getFormat() const
Definition: VTF.cpp:1094
void setPlatform(Platform newPlatform)
Definition: VTF.cpp:855
uint16_t getHeight(uint8_t mip=0) const
Definition: VTF.cpp:991
VTF & operator=(const VTF &other)
Definition: VTF.cpp:693
void removeFallback()
Definition: VTF.cpp:1954
uint8_t getFallbackWidth() const
Definition: VTF.cpp:1369
int16_t compressionLevel
Definition: VTF.h:560
sourcepp::math::Vec3f reflectivity
Definition: VTF.h:535
uint16_t getWidth(uint8_t mip=0) const
Definition: VTF.cpp:987
uint16_t startFrame
Definition: VTF.h:533
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:1833
uint8_t mipCount
Definition: VTF.h:539
bool setRecommendedMipCount()
Definition: VTF.cpp:1146
static bool createInternal(VTF &writer, CreationOptions options)
Definition: VTF.cpp:736
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:1595
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:1462
bool hasFallbackData() const
Definition: VTF.cpp:1893
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
Definition: VTF.cpp:1153
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
Definition: VTF.cpp:979
uint8_t thumbnailWidth
Definition: VTF.h:542
void setCompressionLevel(int16_t newCompressionLevel)
Definition: VTF.cpp:1676
uint8_t fallbackHeight
Definition: VTF.h:546
uint16_t getDepth() const
Definition: VTF.cpp:1249
ImageConversion::ResizeMethod imageHeightResizeMethod
Definition: VTF.h:563
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition: VTF.cpp:1098
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
Definition: VTF.cpp:974
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:1591
float getBumpMapScale() const
Definition: VTF.cpp:1349
void computeTransparencyFlags()
Definition: VTF.cpp:1066
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:1961
std::vector< std::byte > data
Definition: VTF.h:524
ImageFormat format
Definition: VTF.h:538
Platform getPlatform() const
Definition: VTF.cpp:851
std::vector< std::byte > getPaletteResourceFrame(uint16_t frame=0) const
Definition: VTF.cpp:1542
void addFlags(uint32_t flags_)
Definition: VTF.cpp:1038
uint8_t getFaceCount() const
Definition: VTF.cpp:1215
ImageFormat thumbnailFormat
Definition: VTF.h:541
void setFlags(uint32_t flags_)
Definition: VTF.cpp:1034
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
Definition: VTF.cpp:995
void removeLODResource()
Definition: VTF.cpp:1631
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
Definition: VTF.cpp:970
void setBumpMapScale(float newBumpMapScale)
Definition: VTF.cpp:1353
ImageFormat getThumbnailFormat() const
Definition: VTF.cpp:1357
uint16_t getStartFrame() const
Definition: VTF.cpp:1269
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
Definition: VTF.h:276
bool hasThumbnailData() const
Definition: VTF.cpp:1810
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
Definition: VTF.cpp:1281
void setSRGB(bool srgb)
Definition: VTF.cpp:1050
const std::vector< Resource > & getResources() const
Definition: VTF.cpp:1381
void removeHotspotDataResource()
Definition: VTF.cpp:1668
void setVersion(uint32_t newVersion)
Definition: VTF.cpp:930
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:1799
Resource * getResourceInternal(Resource::Type type)
Definition: VTF.cpp:1394
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, float quality=ImageConversion::DEFAULT_COMPRESSED_QUALITY)
Definition: VTF.cpp:1865
std::vector< std::byte > bake() const
Definition: VTF.cpp:2080
bool hasImageData() const
Definition: VTF.cpp:1694
ImageConversion::ResizeMethod imageWidthResizeMethod
Definition: VTF.h:562
std::span< const std::byte > getFallbackDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition: VTF.cpp:1897
uint8_t fallbackMipCount
Definition: VTF.h:547
void removeParticleSheetResource()
Definition: VTF.cpp:1610
std::vector< std::byte > getFallbackDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition: VTF.cpp:1906
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:1720
uint16_t getFrameCount() const
Definition: VTF.cpp:1203
uint8_t getMipCount() const
Definition: VTF.cpp:1126
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
Definition: VTF.cpp:1622
void removeResourceInternal(Resource::Type type)
Definition: VTF.cpp:1458
std::vector< std::byte > getFallbackDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0) const
Definition: VTF.cpp:1919
bool setFrameCount(uint16_t newFrameCount)
Definition: VTF.cpp:1207
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
Definition: VTF.cpp:966
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:1707
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
Definition: VTF.cpp:1081
void setStartFrame(uint16_t newStartFrame)
Definition: VTF.cpp:1273
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
Definition: VTF.cpp:1821
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:1698
uint16_t depth
Definition: VTF.h:553
sourcepp::math::Vec3f getReflectivity() const
Definition: VTF.cpp:1277
uint8_t getFallbackMipCount() const
Definition: VTF.cpp:1377
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
Definition: VTF.cpp:1829
uint8_t getConsoleMipScale() const
Definition: VTF.cpp:1972
bool isSRGB() const
Definition: VTF.cpp:1046
uint32_t flags
Definition: VTF.h:530
void removeThumbnail()
Definition: VTF.cpp:1875
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
Definition: VTF.h:273
uint32_t version
Definition: VTF.h:526
std::span< const std::byte > getThumbnailDataRaw() const
Definition: VTF.cpp:1814
bool setMipCount(uint8_t newMipCount)
Definition: VTF.cpp:1130
void setExtendedFlagsResource(uint32_t value)
Definition: VTF.cpp:1635
CompressionMethod getCompressionMethod() const
Definition: VTF.cpp:1680
void setCRCResource(uint32_t value)
Definition: VTF.cpp:1614
void setCompressionMethod(CompressionMethod newCompressionMethod)
Definition: VTF.cpp:1684
bool setFaceCount(bool isCubeMap)
Definition: VTF.cpp:1241
uint8_t getThumbnailHeight() const
Definition: VTF.cpp:1365
void setConsoleMipScale(uint8_t consoleMipScale_)
Definition: VTF.cpp:1976
const Resource * getResource(Resource::Type type) const
Definition: VTF.cpp:1385
uint32_t getFlags() const
Definition: VTF.cpp:1030
void setParticleSheetResource(const SHT &value)
Definition: VTF.cpp:1599
void setKeyValuesDataResource(const std::string &value)
Definition: VTF.cpp:1643
@ FLAG_MASK_V5
Definition: VTF.h:226
@ FLAG_V0_ONE_BIT_ALPHA
Definition: VTF.h:177
@ FLAG_MASK_V4_TF2
Definition: VTF.h:220
@ FLAG_V0_MULTI_BIT_ALPHA
Definition: VTF.h:178
@ FLAG_MASK_V2
Definition: VTF.h:197
@ FLAG_MASK_V5_CSGO
Definition: VTF.h:233
@ FLAG_V0_NO_LOD
Definition: VTF.h:174
@ FLAG_V4_SRGB
Definition: VTF.h:212
@ FLAG_V0_NO_MIP
Definition: VTF.h:173
@ FLAG_V5_SRGB
Definition: VTF.h:223
@ FLAG_MASK_XBOX
Definition: VTF.h:203
@ FLAG_V5_PWL_CORRECTED
Definition: VTF.h:222
@ FLAG_MASK_V4
Definition: VTF.h:213
@ FLAG_MASK_V3
Definition: VTF.h:210
@ FLAG_V0_ENVMAP
Definition: VTF.h:179
@ FLAG_MASK_V1
Definition: VTF.h:190
@ FLAG_MASK_INTERNAL
Definition: VTF.h:235
uint16_t height
Definition: VTF.h:529
void removeCRCResource()
Definition: VTF.cpp:1618
uint16_t frameCount
Definition: VTF.h:532
uint32_t getVersion() const
Definition: VTF.cpp:926
int16_t getCompressionLevel() const
Definition: VTF.cpp:1672
void setHotspotDataResource(const HOT &value)
Definition: VTF.cpp:1657
void removeFlags(uint32_t flags_)
Definition: VTF.cpp:1042
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
Definition: VTF.cpp:1403
bool setDepth(uint16_t newDepth)
Definition: VTF.cpp:1253
uint64_t estimateBakeSize() const
Definition: VTF.cpp:1980
std::vector< Resource > resources
Definition: VTF.h:556
uint8_t thumbnailHeight
Definition: VTF.h:543
Platform platform
Definition: VTF.h:559
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:1724
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:1549
void removeExtendedFlagsResource()
Definition: VTF.cpp:1639
bool opened
Definition: VTF.h:522
float bumpMapScale
Definition: VTF.h:537
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:791
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 > 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.
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 > 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, uint16_t depth=1)
Definition: ImageFormats.h:685
constexpr uint8_t getRecommendedMipCountForDims(ImageFormat format, uint16_t width, uint16_t height)
Definition: ImageFormats.h:657
constexpr uint32_t getDataLength(ImageFormat format, uint16_t width, uint16_t height, uint16_t depth=1)
Definition: ImageFormats.h:715
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)
Definition: ImageFormats.h:755
constexpr bool large(ImageFormat format)
Definition: ImageFormats.h:571
constexpr bool transparent(ImageFormat format)
Definition: ImageFormats.h:583
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)
Definition: ImageFormats.h:742
constexpr int8_t decompressedAlpha(ImageFormat format)
Definition: ImageFormats.h:414
constexpr uint8_t bpp(ImageFormat format)
Definition: ImageFormats.h:436
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)
Definition: ImageFormats.h:776
constexpr bool compressed(ImageFormat format)
Definition: ImageFormats.h:579
std::vector< std::byte > convertP8ImageDataToBGRA8888(std::span< const std::byte > paletteData, std::span< const std::byte > imageData)
Converts a paletted image to something usable.
Definition: HOT.h:11
constexpr uint32_t VTF_SIGNATURE
Definition: VTF.h:21
constexpr uint32_t XTF_SIGNATURE
Definition: VTF.h:22
constexpr uint32_t VTFX_SIGNATURE
Definition: VTF.h:23
constexpr uint32_t VTF3_SIGNATURE
Definition: VTF.h:24
CompressionMethod
Definition: VTF.h:26
ImageFormat
Definition: ImageFormats.h:7
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT > ConvertedData
Definition: VTF.h:85
Type type
Definition: VTF.h:74
ConvertedData convertData() const
Definition: VTF.cpp:230
static consteval std::array< Type, 11 > getOrder()
Definition: VTF.h:50
@ FLAG_LOCAL_DATA
Definition: VTF.h:71
@ FLAG_NONE
Definition: VTF.h:70
@ TYPE_KEYVALUES_DATA
Definition: VTF.h:48
@ TYPE_FALLBACK_DATA
Definition: VTF.h:40
@ TYPE_CRC
Definition: VTF.h:45
@ TYPE_PALETTE_DATA
Definition: VTF.h:39
@ TYPE_PARTICLE_SHEET_DATA
Definition: VTF.h:41
@ TYPE_EXTENDED_FLAGS
Definition: VTF.h:44
@ TYPE_IMAGE_DATA
Definition: VTF.h:43
@ TYPE_AUX_COMPRESSION
Definition: VTF.h:46
@ TYPE_LOD_CONTROL_INFO
Definition: VTF.h:47
@ TYPE_THUMBNAIL_DATA
Definition: VTF.h:38
@ TYPE_HOTSPOT_DATA
Definition: VTF.h:42
Flags flags
Definition: VTF.h:75
std::span< std::byte > data
Definition: VTF.h:76
uint16_t initialFrameCount
Definition: VTF.h:255
float compressedFormatQuality
Definition: VTF.h:250
int16_t compressionLevel
Definition: VTF.h:264
ImageConversion::ResizeFilter filter
Definition: VTF.h:253
ImageConversion::ResizeMethod heightResizeMethod
Definition: VTF.h:252
CompressionMethod compressionMethod
Definition: VTF.h:265
ImageConversion::ResizeMethod widthResizeMethod
Definition: VTF.h:251
ImageFormat outputFormat
Definition: VTF.h:249