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