5#include <unordered_map>
8#ifdef SOURCEPP_BUILD_WITH_TBB
12#ifdef SOURCEPP_BUILD_WITH_THREADS
17#include <BufferStream.h>
29std::vector<std::byte> compressData(std::span<const std::byte> data, int16_t level,
CompressionMethod method) {
33 mz_ulong compressedSize = mz_compressBound(data.size());
34 std::vector<std::byte> out(compressedSize);
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) {
39 out.resize(compressedSize);
42 if (status != MZ_OK) {
45 out.resize(compressedSize);
53 auto expectedSize = ZSTD_compressBound(data.size());
54 std::vector<std::byte> out(expectedSize);
56 auto compressedSize = ZSTD_compress(out.data(), expectedSize, data.data(), data.size(), level);
57 if (ZSTD_isError(compressedSize)) {
61 out.resize(compressedSize);
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) {
87 std::copy(newData.begin(), newData.end(), imageData.begin());
98 std::span<uint16_t> dxtData{
reinterpret_cast<uint16_t*
>(imageData.data()), imageData.size() /
sizeof(uint16_t)};
100#ifdef SOURCEPP_BUILD_WITH_TBB
101 std::execution::par_unseq,
103 dxtData.begin(), dxtData.end(), [](uint16_t& value) {
104 BufferStream::swap_endian(&value);
117 switch (this->
type) {
119 if (this->data.size() <=
sizeof(uint32_t)) {
122 return SHT{{
reinterpret_cast<const std::byte*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data())}};
125 if (this->data.size() !=
sizeof(uint32_t)) {
128 return *
reinterpret_cast<const uint32_t*
>(this->data.data());
130 if (this->data.size() !=
sizeof(uint32_t)) {
133 return std::make_tuple(
134 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 0),
135 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 1),
136 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 2),
137 *(
reinterpret_cast<const uint8_t*
>(this->data.data()) + 3));
139 if (this->data.size() <=
sizeof(uint32_t)) {
142 return std::string(
reinterpret_cast<const char*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data()));
144 if (this->data.size() <=
sizeof(uint32_t)) {
147 return HOT{{
reinterpret_cast<const std::byte*
>(this->data.data()) +
sizeof(uint32_t), *
reinterpret_cast<const uint32_t*
>(this->data.data())}};
149 if (this->data.size() <=
sizeof(uint32_t) || this->data.size() %
sizeof(uint32_t) != 0) {
152 return std::span{
reinterpret_cast<uint32_t*
>(this->data.data()), this->data.size() / 4};
176VTF::VTF(std::vector<std::byte>&& vtfData,
bool parseHeaderOnly)
177 : data(std::move(vtfData)) {
178 BufferStreamReadOnly stream{this->data};
180 if (
auto signature = stream.read<uint32_t>(); signature ==
VTF_SIGNATURE) {
182 if (stream.read<uint32_t>() != 7) {
186 if (this->version > 6) {
190 stream.set_big_endian(
true);
191 uint32_t minorConsoleVersion = 0;
192 stream >> this->platform >> minorConsoleVersion;
193 if (minorConsoleVersion != 8) {
199 switch (this->platform) {
215 const auto headerSize = stream.read<uint32_t>();
217 const auto readResources = [
this, &stream](uint32_t resourceCount) {
220 for (
int i = 0; i < resourceCount; i++) {
221 auto& [type, flags_, data_] = this->
resources.emplace_back();
223 auto typeAndFlags = stream.read<uint32_t>();
224 if (stream.is_big_endian()) {
226 BufferStream::swap_endian(&typeAndFlags);
230 data_ = stream.read_span<std::byte>(4);
233 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(data_.data()));
241 if ((lhs.flags & Resource::FLAG_LOCAL_DATA) && (rhs.flags & Resource::FLAG_LOCAL_DATA)) {
242 return lhs.type < rhs.type;
250 return *
reinterpret_cast<uint32_t*
>(lhs.
data.data()) < *
reinterpret_cast<uint32_t*
>(rhs.
data.data());
258 auto lastOffset = *
reinterpret_cast<uint32_t*
>(lastResource->data.data());
259 auto currentOffset = *
reinterpret_cast<uint32_t*
>(resource.data.data());
260 auto curPos = stream.tell();
261 stream.seek(lastOffset);
262 lastResource->
data = stream.read_span<std::byte>(currentOffset - lastOffset);
263 stream.seek(
static_cast<int64_t
>(curPos));
265 lastResource = &resource;
269 auto offset = *
reinterpret_cast<uint32_t*
>(lastResource->data.data());
270 auto curPos = stream.tell();
272 lastResource->data = stream.read_span<std::byte>(stream.size() - offset);
273 stream.seek(
static_cast<int64_t
>(curPos));
277 if (this->platform != PLATFORM_PC) {
278 uint8_t resourceCount;
283 .read(this->sliceCount)
284 .read(this->frameCount)
288 .read(this->reflectivity[0])
289 .read(this->reflectivity[1])
290 .read(this->reflectivity[2])
291 .read(this->bumpMapScale)
293 .skip<math::Vec4ui8>()
296 if (this->platform == PLATFORM_PS3_PORTAL2) {
297 stream.skip<uint32_t>();
302 if (parseHeaderOnly) {
307 this->resources.reserve(resourceCount);
308 readResources(resourceCount);
310 this->opened = stream.tell() == headerSize;
313 for (
const auto& resource : this->resources) {
317 this->setResourceInternal(resource.type, *decompressedData);
328 switch (resource.type) {
336 ::swapImageDataEndianForConsole(resource.data, this->thumbnailFormat, this->thumbnailWidth, this->thumbnailHeight, this->platform);
339 ::swapImageDataEndianForConsole(resource.data, this->format, this->width, this->height, this->platform);
344 if (resource.data.size() >=
sizeof(uint32_t)) {
345 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(resource.data.data()));
357 .read(this->frameCount)
358 .read(this->startFrame)
360 .read(this->reflectivity[0])
361 .read(this->reflectivity[1])
362 .read(this->reflectivity[2])
364 .read(this->bumpMapScale)
366 .read(this->mipCount);
370 stream >> this->thumbnailWidth >> this->thumbnailHeight;
371 if (this->thumbnailWidth == 0 || this->thumbnailHeight == 0) {
377 if (this->version < 2) {
378 this->sliceCount = 1;
380 stream.read(this->sliceCount);
383 if (parseHeaderOnly) {
388 if (this->version >= 3) {
390 auto resourceCount = stream.read<uint32_t>();
392 readResources(resourceCount);
394 this->opened = stream.tell() == headerSize;
396 if (this->opened && this->version >= 6) {
399 if (auxResource && imageResource) {
400 if (auxResource->getDataAsAuxCompressionLevel() != 0) {
401 const auto faceCount = this->getFaceCount();
402 std::vector<std::byte> decompressedImageData(
ImageFormatDetails::getDataLength(this->format, this->mipCount, this->frameCount, faceCount, this->width, this->height, this->sliceCount));
403 uint32_t oldOffset = 0;
404 for (
int i = this->mipCount - 1; i >= 0; i--) {
405 for (
int j = 0; j < this->frameCount; j++) {
406 for (
int k = 0; k < faceCount; k++) {
407 uint32_t oldLength = auxResource->getDataAsAuxCompressionLength(i, this->mipCount, j, this->frameCount, k, faceCount);
408 if (uint32_t newOffset, newLength;
ImageFormatDetails::getDataPosition(newOffset, newLength, this->format, i, this->mipCount, j, this->frameCount, k, faceCount, this->width, this->height, 0, this->getSliceCount())) {
410 mz_ulong decompressedImageDataSize = newLength * this->sliceCount;
411 switch (auxResource->getDataAsAuxCompressionMethod()) {
414 if (mz_uncompress(
reinterpret_cast<unsigned char*
>(decompressedImageData.data() + newOffset), &decompressedImageDataSize,
reinterpret_cast<const unsigned char*
>(imageResource->data.data() + oldOffset), oldLength) != MZ_OK) {
415 this->opened =
false;
420 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) {
421 this->opened =
false;
431 oldOffset += oldLength;
441 this->opened = stream.tell() == headerSize;
443 this->resources.reserve(2);
445 if (this->hasThumbnailData()) {
446 this->resources.push_back({
452 if (this->hasImageData()) {
453 this->resources.push_back({
456 .data = stream.read_span<std::byte>(stream.size() - stream.tell()),
462 this->compressionLevel = resource->getDataAsAuxCompressionLevel();
463 this->compressionMethod = resource->getDataAsAuxCompressionMethod();
468VTF::VTF(std::span<const std::byte> vtfData,
bool parseHeaderOnly)
469 :
VTF(std::vector<std::byte>{vtfData.begin(), vtfData.end()}, parseHeaderOnly) {}
471VTF::VTF(
const std::string& vtfPath,
bool parseHeaderOnly)
472 :
VTF(fs::readFileBuffer(vtfPath), parseHeaderOnly) {}
480 this->data = other.
data;
482 this->width = other.
width;
483 this->height = other.
height;
489 this->format = other.
format;
496 this->resources.clear();
497 for (
const auto& [otherType, otherFlags, otherData] : other.
resources) {
498 auto& [type, flags_, data_] = this->resources.emplace_back();
501 data_ = {this->data.data() + (otherData.data() - other.
data.data()), otherData.size()};
513VTF::operator bool()
const {
520 for (
int i = 1; i < writer.
mipCount; i++) {
524 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)) {
527 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)) {
580 return writer.
bake(vtfPath);
584 std::vector<std::byte> imageData;
600 std::vector<std::byte> imageData;
616 return writer.
bake(vtfPath);
642 if (this->
mipCount != recommendedCount) {
646 this->platform = newPlatform;
660 if (faceCount == 7 && (newVersion < 1 || newVersion > 4)) {
667 bool srgb = this->
isSRGB();
668 if ((this->version < 2 && newVersion >= 2) || (this->
version >= 2 && newVersion < 2)) {
671 if ((this->version < 3 && newVersion >= 3) || (this->
version >= 3 && newVersion < 3)) {
674 if ((this->version < 4 && newVersion >= 4) || (this->
version >= 4 && newVersion < 4)) {
678 if ((this->version < 5 && newVersion >= 5) || (this->
version >= 5 && newVersion < 5)) {
717 if (newWidth == 0 || newHeight == 0) {
727 if (this->width == newWidth && this->height == newHeight) {
733 newMipCount = recommendedCount;
742 this->frameCount = 1;
743 this->
flags &= ~FLAG_ENVMAP;
744 this->width = newWidth;
745 this->height = newHeight;
760 this->
flags |= flags_ & ~FLAGS_MASK_INTERNAL;
775 }
else if (this->
version >= 4) {
781 }
else if (this->
version >= 4) {
804 if (
version >= 6 && isCubeMap) {
829 this->format = newFormat;
834 newMipCount = recommendedCount;
852 newMipCount = recommended;
853 if (newMipCount == 1) {
857 if (newMipCount > 1) {
885 auto* outputDataPtr = imageResource->data.data();
888#ifdef SOURCEPP_BUILD_WITH_THREADS
889 std::vector<std::future<void>> futures;
890 futures.reserve((this->
mipCount - 1) * this->frameCount * faceCount * this->
sliceCount);
893 for (
int k = 0; k < faceCount; k++) {
895#ifdef SOURCEPP_BUILD_WITH_THREADS
896 futures.push_back(std::async(std::launch::async, [
this, filter, outputDataPtr, faceCount, j, k, l] {
898 for (
int i = 1; i < this->
mipCount; i++) {
899 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);
900 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) {
901 std::memcpy(outputDataPtr + offset, mip.data(), length);
904#ifdef SOURCEPP_BUILD_WITH_THREADS
906 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
907 for (
auto& future : futures) {
916#ifdef SOURCEPP_BUILD_WITH_THREADS
917 for (
auto& future : futures) {
952 if (this->
version >= 1 && this->
version <= 4 && expectedLength < image->
data.size()) {
955 if (expectedLength == image->data.size()) {
1006 static constexpr auto getReflectivityForImage = [](
const VTF& vtf, uint16_t frame, uint8_t face, uint16_t slice) {
1007 static constexpr auto getReflectivityForPixel = [](
const ImagePixel::RGBA8888* pixel) -> math::Vec3f {
1009 math::Vec3f ref{
static_cast<float>(pixel->r),
static_cast<float>(pixel->g),
static_cast<float>(pixel->b)};
1010 ref /= 255.f * 0.9f;
1019 for (uint64_t i = 0; i < rgba8888Data.size(); i += 4) {
1020 out += getReflectivityForPixel(
reinterpret_cast<ImagePixel::RGBA8888*
>(rgba8888Data.data() + i));
1027#ifdef SOURCEPP_BUILD_WITH_THREADS
1028 if (this->frameCount > 1 || faceCount > 1 || this->
sliceCount > 1) {
1029 std::vector<std::future<math::Vec3f>> futures;
1030 futures.reserve(this->frameCount * faceCount * this->
sliceCount);
1034 for (
int k = 0; k < faceCount; k++) {
1036 futures.push_back(std::async(std::launch::async, [
this, j, k, l] {
1037 return getReflectivityForImage(*
this, j, k, l);
1039 if (std::thread::hardware_concurrency() > 0 && futures.size() >= std::thread::hardware_concurrency()) {
1040 for (
auto& future : futures) {
1049 for (
auto& future : futures) {
1054 this->
reflectivity = getReflectivityForImage(*
this, 0, 0, 0);
1059 for (
int k = 0; k < faceCount; k++) {
1061 this->
reflectivity += getReflectivityForImage(*
this, j, k, l);
1094 for (
const auto& resource : this->resources) {
1095 if (resource.type == type) {
1103 for (
auto& resource : this->resources) {
1104 if (resource.type == type) {
1112 if (
const auto* resource = this->
getResource(type); resource && resource->
data.size() == data_.size()) {
1113 std::memcpy(resource->data.data(), data_.data(), data_.size());
1118 std::unordered_map<Resource::Type, std::pair<std::vector<std::byte>, uint64_t>> resourceData;
1119 for (
const auto& [type_, flags_, dataSpan] : this->resources) {
1120 resourceData[type_] = {std::vector<std::byte>{dataSpan.begin(), dataSpan.end()}, 0};
1124 if (data_.empty()) {
1125 resourceData.erase(type);
1127 resourceData[type] = {{data_.begin(), data_.end()}, 0};
1132 BufferStream writer{this->data};
1135 if (!resourceData.contains(resourceType)) {
1138 auto& [specificResourceData, offset] = resourceData[resourceType];
1139 if (resourceType == type) {
1143 {this->data.data() + offset, specificResourceData.size()},
1146 *resourcePtr = newResource;
1148 this->resources.push_back(newResource);
1150 }
else if (!resourceData.contains(resourceType)) {
1153 offset = writer.tell();
1154 writer.write(specificResourceData);
1156 this->data.resize(writer.size());
1158 for (
auto& [type_, flags_, dataSpan] : this->resources) {
1159 if (resourceData.contains(type_)) {
1160 const auto& [specificResourceData, offset] = resourceData[type_];
1161 dataSpan = {this->data.data() + offset, specificResourceData.size()};
1167 std::erase_if(this->resources, [type](
const Resource& resource) {
return resource.
type == type; });
1171 if (!newWidth) { newWidth = 1; }
1172 if (!newHeight) { newHeight = 1; }
1173 if (!newMipCount) { newMipCount = 1; }
1174 if (!newFrameCount) { newFrameCount = 1; }
1175 if (!newFaceCount) { newFaceCount = 1; }
1176 if (!newSliceCount) { newSliceCount = 1; }
1179 if (this->format == newFormat && this->width == newWidth && this->height == newHeight && this->
mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->
sliceCount == newSliceCount) {
1183 std::vector<std::byte> newImageData;
1185 if (this->format != newFormat && this->width == newWidth && this->height == newHeight && this->
mipCount == newMipCount && this->frameCount == newFrameCount && faceCount == newFaceCount && this->
sliceCount == newSliceCount) {
1189 for (
int i = newMipCount - 1; i >= 0; i--) {
1190 for (
int j = 0; j < newFrameCount; j++) {
1191 for (
int k = 0; k < newFaceCount; k++) {
1192 for (
int l = 0; l < newSliceCount; l++) {
1195 std::vector<std::byte> image{imageSpan.begin(), imageSpan.end()};
1196 if (this->width != newWidth || this->height != newHeight) {
1197 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);
1199 if (uint32_t offset, length;
ImageFormatDetails::getDataPosition(offset, length, this->format, i, newMipCount, j, newFrameCount, k, newFaceCount, newWidth, newHeight, l, newSliceCount) && image.size() == length) {
1200 std::memcpy(newImageData.data() + offset, image.data(), length);
1207 if (this->format != newFormat) {
1215 this->format = newFormat;
1216 this->width = newWidth;
1217 this->height = newHeight;
1219 this->frameCount = newFrameCount;
1220 if (newFaceCount > 1) {
1223 this->
flags &= ~FLAG_ENVMAP;
1230std::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 {
1239 auto sht = shtResource->getDataAsParticleSheet();
1240 const auto* sequence = sht.getSequenceFromID(shtSequenceID);
1241 if (!sequence || sequence->frames.size() <= shtFrame || shtBounds >= sht.getFrameBoundsCount()) {
1249 const auto& bounds = sequence->frames[shtFrame].bounds[shtBounds];
1250 uint16_t x1 = std::clamp<uint16_t>(std::floor(bounds.x1 *
static_cast<float>(this->getWidth(mip))), 0, this->getWidth(mip));
1251 uint16_t y1 = std::clamp<uint16_t>(std::ceil( bounds.y1 *
static_cast<float>(this->getHeight(mip))), 0, this->getHeight(mip));
1252 uint16_t x2 = std::clamp<uint16_t>(std::ceil( bounds.x2 *
static_cast<float>(this->getWidth(mip))), 0, this->getHeight(mip));
1253 uint16_t y2 = std::clamp<uint16_t>(std::floor(bounds.y2 *
static_cast<float>(this->getHeight(mip))), 0, this->getWidth(mip));
1255 if (x1 > x2) [[unlikely]] {
1258 if (y1 > y2) [[unlikely]] {
1261 spriteWidth = x2 - x1;
1262 spriteWidth = y2 - y1;
1264 const auto out =
ImageConversion::cropImageData(this->
getImageDataRaw(mip, frame, face, slice), this->
getFormat(), this->
getWidth(mip), spriteWidth, x1, this->
getHeight(mip), spriteHeight, y1);
1272std::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 {
1273 return ImageConversion::convertImageDataToFormat(this->
getParticleSheetFrameDataRaw(spriteWidth, spriteHeight, shtSequenceID, shtFrame, shtBounds, mip, frame, face, slice), this->
getFormat(), newFormat, spriteWidth, spriteHeight);
1276std::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 {
1281 std::vector<std::byte> particleSheetData;
1282 BufferStream writer{particleSheetData};
1284 const auto bakedSheet = value.
bake();
1285 writer.write<uint32_t>(bakedSheet.size()).write(bakedSheet);
1286 particleSheetData.resize(writer.size());
1305 BufferStream writer{&lodData,
sizeof(lodData)};
1307 writer << u << v << u360 << v360;
1325 std::vector<std::byte> keyValuesData;
1326 BufferStream writer{keyValuesData};
1328 writer.write<uint32_t>(value.size()).write(value,
false);
1329 keyValuesData.resize(writer.size());
1339 std::vector<std::byte> hotspotData;
1340 BufferStream writer{hotspotData};
1342 const auto bakedHotspotData = value.
bake();
1343 writer.write<uint32_t>(bakedHotspotData.size()).write(bakedHotspotData);
1344 hotspotData.resize(writer.size());
1381 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)) {
1382 return imageResource->data.subspan(offset, length);
1389 const auto rawImageData = this->
getImageDataRaw(mip, frame, face, slice);
1390 if (rawImageData.empty()) {
1401 if (imageData_.empty()) {
1406 uint16_t resizedWidth = width_, resizedHeight = height_;
1413 mip = newMipCount - 1;
1415 if (face > 6 || (face == 6 && (this->version < 1 || this->
version > 4))) {
1418 this->
regenerateImageData(format_, resizedWidth, resizedHeight, mip + 1, frame + 1, face ? (face < 5 ? 5 : face) : 0, slice + 1);
1422 if (this->mipCount <= mip || this->
frameCount <= frame || faceCount <= face || this->
sliceCount <= slice) {
1427 if (!imageResource) {
1430 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)) {
1431 std::vector<std::byte> image{imageData_.begin(), imageData_.end()};
1434 if (width_ != newWidth || height_ != newHeight) {
1437 if (format_ != this->format) {
1440 std::memcpy(imageResource->data.data() + offset, image.data(), image.size());
1447 int inputWidth, inputHeight, inputFrameCount;
1451 if (imageData_.empty() || inputFormat ==
ImageFormat::EMPTY || !inputWidth || !inputHeight || !inputFrameCount) {
1456 if (inputFrameCount == 1) {
1457 return this->
setImage(imageData_, inputFormat, inputWidth, inputHeight, filter, mip, frame, face, slice);
1461 bool allSuccess =
true;
1463 for (
int currentFrame = 0; currentFrame < inputFrameCount; currentFrame++) {
1464 if (!this->
setImage({imageData_.data() + currentFrame * frameSize, imageData_.data() + currentFrame * frameSize + frameSize}, inputFormat, inputWidth, inputHeight, filter, mip, frame + currentFrame, face, slice)) {
1467 if (currentFrame == 0 && this->frameCount < frame + inputFrameCount) {
1480 if (
auto data_ = this->
saveImageToFile(mip, frame, face, slice, fileFormat); !data_.empty()) {
1492 return thumbnailResource->data;
1499 if (rawThumbnailData.empty()) {
1516 this->thumbnailHeight = height_;
1525 this->thumbnailHeight = 16;
1526 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));
1532 this->thumbnailHeight = 0;
1548 std::vector<std::byte> out;
1549 BufferStream writer{out};
1553 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(&type));
1555 writer_.write<uint32_t>(type);
1556 const auto resourceOffsetPos = writer_.tell();
1557 writer_.seek(0, std::ios::end);
1558 const auto resourceOffsetValue = writer_.tell();
1559 writer_.write(
data);
1560 writer_.seek_u(resourceOffsetPos).write<uint32_t>(resourceOffsetValue);
1565 writer.set_big_endian(
true);
1572 writer.write<uint32_t>(8);
1573 const auto headerLengthPos = writer.tell();
1578 .write(this->height)
1580 .write(this->frameCount);
1581 const auto preloadPos = writer.tell();
1585 .write<uint8_t>(this->resources.size())
1590 .write(this->format)
1591 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[0] * 255)), 0, 255))
1592 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[1] * 255)), 0, 255))
1593 .write<uint8_t>(std::clamp(
static_cast<int>(std::roundf(this->
reflectivity[2] * 255)), 0, 255))
1594 .write<uint8_t>(255);
1595 const auto compressionPos = writer.tell();
1596 writer.write<uint32_t>(0);
1600 writer.write<uint32_t>(0);
1603 std::vector<std::byte> imageResourceData;
1604 bool hasCompression =
false;
1606 imageResourceData.assign(imageResource->data.begin(), imageResource->data.end());
1607 ::swapImageDataEndianForConsole(imageResourceData, this->format, this->width, this->height, this->platform);
1617 fixedCompressionLevel = 6;
1620 if (compressedData) {
1621 imageResourceData.assign(compressedData->begin(), compressedData->end());
1623 hasCompression =
false;
1628 const auto resourceStart = writer.tell();
1629 const auto headerSize = resourceStart + (this->
getResources().size() *
sizeof(uint64_t));
1630 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1631 while (writer.tell() < headerSize) {
1632 writer.write<uint64_t>(0);
1634 writer.seek_u(resourceStart);
1638 auto curPos = writer.tell();
1639 const auto imagePos = writer.seek(0, std::ios::end).tell();
1640 writer.seek_u(preloadPos).write<uint16_t>(imagePos).seek_u(curPos);
1642 writeNonLocalResource(writer, resourceType, imageResourceData, this->platform);
1644 if (hasCompression) {
1645 curPos = writer.tell();
1646 writer.seek_u(compressionPos).write<uint32_t>(imageResourceData.size()).seek_u(curPos);
1648 }
else if (
const auto* resource = this->
getResource(resourceType)) {
1649 std::vector<std::byte> resData{resource->data.begin(), resource->data.end()};
1652 switch (resource->type) {
1665 if (resData.size() >=
sizeof(uint32_t)) {
1666 BufferStream::swap_endian(
reinterpret_cast<uint32_t*
>(resData.data()));
1672 writer.set_big_endian(
false);
1674 writer.set_big_endian(
true);
1675 writer.write(resource->data);
1677 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
1682 out.resize(writer.size());
1691 const auto headerLengthPos = writer.tell();
1692 writer.write<uint32_t>(0);
1696 .write(this->height)
1698 .write(this->frameCount)
1704 .write(this->format)
1708 .write(this->thumbnailHeight);
1716 for (uint16_t i = 0; i < headerAlignment; i++) {
1717 writer.write<std::byte>({});
1719 const auto headerSize = writer.tell();
1720 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(headerSize);
1723 writer.write(thumbnailResource->data);
1726 writer.write(imageResource->data);
1729 std::vector<std::byte> auxCompressionResourceData;
1730 std::vector<std::byte> compressedImageResourceData;
1731 bool hasAuxCompression =
false;
1734 if (hasAuxCompression) {
1736 auxCompressionResourceData.resize((this->
mipCount * this->frameCount * faceCount + 2) *
sizeof(uint32_t));
1737 BufferStream auxWriter{auxCompressionResourceData,
false};
1744 .write<uint32_t>(auxCompressionResourceData.size() -
sizeof(uint32_t))
1748 for (
int i = this->
mipCount - 1; i >= 0; i--) {
1750 for (
int k = 0; k < faceCount; k++) {
1751 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)) {
1753 compressedImageResourceData.insert(compressedImageResourceData.end(), compressedData.begin(), compressedData.end());
1754 auxWriter.write<uint32_t>(compressedData.size());
1766 .write<uint32_t>(this->
getResources().size() + hasAuxCompression)
1767 .write<uint64_t>(0);
1769 const auto resourceStart = writer.tell();
1770 const auto headerSize = resourceStart + ((this->
getResources().size() + hasAuxCompression) *
sizeof(uint64_t));
1771 writer.seek_u(headerLengthPos).write<uint32_t>(headerSize).seek_u(resourceStart);
1772 while (writer.tell() < headerSize) {
1773 writer.write<uint64_t>(0);
1775 writer.seek_u(resourceStart);
1779 writeNonLocalResource(writer, resourceType, auxCompressionResourceData, this->platform);
1781 writeNonLocalResource(writer, resourceType, compressedImageResourceData, this->platform);
1782 }
else if (
const auto* resource = this->
getResource(resourceType)) {
1785 writer.write(resource->data);
1787 writeNonLocalResource(writer, resource->type, resource->data, this->platform);
1793 out.resize(writer.size());
#define SOURCEPP_DEBUG_BREAK
Create a breakpoint in debug.
std::vector< std::byte > bake() const
std::vector< std::byte > bake() const
static constexpr uint32_t FLAGS_MASK_V5
static constexpr uint32_t FLAGS_MASK_V2
void setImageHeightResizeMethod(ImageConversion::ResizeMethod imageHeightResizeMethod_)
void removeKeyValuesDataResource()
CompressionMethod compressionMethod
void computeReflectivity()
uint8_t getThumbnailWidth() const
std::vector< std::byte > saveThumbnailToFile(ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
ImageFormat getFormat() const
void setPlatform(Platform newPlatform)
uint16_t getHeight(uint8_t mip=0) const
VTF & operator=(const VTF &other)
sourcepp::math::Vec3f reflectivity
uint16_t getWidth(uint8_t mip=0) const
static constexpr uint32_t FLAGS_MASK_INTERNAL
bool setRecommendedMipCount()
static bool createInternal(VTF &writer, CreationOptions options)
std::vector< std::byte > getParticleSheetFrameDataAsRGBA8888(uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
void computeMips(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
void setImageWidthResizeMethod(ImageConversion::ResizeMethod imageWidthResizeMethod_)
void setCompressionLevel(int16_t newCompressionLevel)
ImageConversion::ResizeMethod imageHeightResizeMethod
void setImageResizeMethods(ImageConversion::ResizeMethod imageWidthResizeMethod_, ImageConversion::ResizeMethod imageHeightResizeMethod_)
std::vector< std::byte > getParticleSheetFrameDataAs(ImageFormat newFormat, uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
static constexpr uint32_t FLAGS_MASK_V3
float getBumpMapScale() const
void computeTransparencyFlags()
static constexpr uint32_t FLAGS_MASK_V4_TF2
std::vector< std::byte > data
bool setFrameFaceAndSliceCount(uint16_t newFrameCount, bool isCubeMap, uint16_t newSliceCount=1)
Platform getPlatform() const
void addFlags(uint32_t flags_)
uint8_t getFaceCount() const
ImageFormat thumbnailFormat
void setFlags(uint32_t flags_)
void setSize(uint16_t newWidth, uint16_t newHeight, ImageConversion::ResizeFilter filter)
ImageConversion::ResizeMethod getImageHeightResizeMethod() const
void setBumpMapScale(float newBumpMapScale)
ImageFormat getThumbnailFormat() const
uint16_t getStartFrame() const
void setThumbnail(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_)
static constexpr auto FORMAT_DEFAULT
This value is only valid when passed to VTF::create through CreationOptions or VTF::setFormat.
bool hasThumbnailData() const
void setReflectivity(sourcepp::math::Vec3f newReflectivity)
const std::vector< Resource > & getResources() const
void removeHotspotDataResource()
void setVersion(uint32_t newVersion)
std::vector< std::byte > saveImageToFile(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0, ImageConversion::FileFormat fileFormat=ImageConversion::FileFormat::DEFAULT) const
Resource * getResourceInternal(Resource::Type type)
std::vector< std::byte > bake() const
bool setImage(std::span< const std::byte > imageData_, ImageFormat format_, uint16_t width_, uint16_t height_, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0)
bool hasImageData() const
ImageConversion::ResizeMethod imageWidthResizeMethod
uint16_t getSliceCount() const
void removeParticleSheetResource()
std::vector< std::byte > getImageDataAsRGBA8888(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
uint16_t getFrameCount() const
uint8_t getMipCount() const
void setLODResource(uint8_t u, uint8_t v, uint8_t u360=0, uint8_t v360=0)
void removeResourceInternal(Resource::Type type)
bool setFrameCount(uint16_t newFrameCount)
ImageConversion::ResizeMethod getImageWidthResizeMethod() const
std::vector< std::byte > getImageDataAs(ImageFormat newFormat, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
void setFormat(ImageFormat newFormat, ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
static ImageFormat getDefaultCompressedFormat(ImageFormat inputFormat, uint32_t version, bool isCubeMap)
void setStartFrame(uint16_t newStartFrame)
std::vector< std::byte > getThumbnailDataAs(ImageFormat newFormat) const
static constexpr uint32_t FLAGS_MASK_V5_CSGO
bool setSliceCount(uint16_t newSliceCount)
std::span< const std::byte > getImageDataRaw(uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
sourcepp::math::Vec3f getReflectivity() const
std::vector< std::byte > getThumbnailDataAsRGBA8888() const
static constexpr auto FORMAT_UNCHANGED
This value is only valid when passed to VTF::create through CreationOptions.
std::span< const std::byte > getThumbnailDataRaw() const
bool setMipCount(uint8_t newMipCount)
void setExtendedFlagsResource(uint32_t value)
CompressionMethod getCompressionMethod() const
void setCRCResource(uint32_t value)
void setCompressionMethod(CompressionMethod newCompressionMethod)
void computeThumbnail(ImageConversion::ResizeFilter filter=ImageConversion::ResizeFilter::DEFAULT)
bool setFaceCount(bool isCubeMap)
uint8_t getThumbnailHeight() const
const Resource * getResource(Resource::Type type) const
static constexpr uint32_t FLAGS_MASK_V4
uint32_t getFlags() const
void setParticleSheetResource(const SHT &value)
void setKeyValuesDataResource(const std::string &value)
uint32_t getVersion() const
static bool create(std::span< const std::byte > imageData, ImageFormat format, uint16_t width, uint16_t height, const std::string &vtfPath, CreationOptions options)
int16_t getCompressionLevel() const
void setHotspotDataResource(const HOT &value)
void removeFlags(uint32_t flags_)
void setResourceInternal(Resource::Type type, std::span< const std::byte > data_)
std::vector< Resource > resources
std::vector< std::byte > getParticleSheetFrameDataRaw(uint16_t &spriteWidth, uint16_t &spriteHeight, uint32_t shtSequenceID, uint32_t shtFrame, uint8_t shtBounds=0, uint8_t mip=0, uint16_t frame=0, uint8_t face=0, uint16_t slice=0) const
This is a convenience function. You're best off uploading the bounds to the GPU and scaling the UV th...
void removeExtendedFlagsResource()
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)
std::optional< std::vector< std::byte > > compressValveLZMA(std::span< const std::byte > data, uint8_t compressLevel=6)
constexpr auto VALVE_LZMA_SIGNATURE
std::optional< std::vector< std::byte > > decompressValveLZMA(std::span< const std::byte > data)
std::vector< std::byte > readFileBuffer(const std::string &filepath, std::size_t startOffset=0)
bool writeFileBuffer(const std::string &filepath, std::span< const std::byte > buffer)
constexpr uint16_t paddingForAlignment(uint16_t alignment, uint64_t n)
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 > 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 > 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 > 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 > 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)
constexpr uint8_t getActualMipCountForDimsOnConsole(uint16_t width, uint16_t height)
constexpr uint8_t getRecommendedMipCountForDims(ImageFormat format, uint16_t width, uint16_t height)
constexpr uint32_t VTF_SIGNATURE
constexpr uint32_t VTFX_SIGNATURE
constexpr uint32_t VTF3_SIGNATURE
static consteval std::array< Type, 9 > getOrder()
ConvertedData convertData() const
std::variant< std::monostate, SHT, uint32_t, std::tuple< uint8_t, uint8_t, uint8_t, uint8_t >, std::string, HOT, std::span< uint32_t > > ConvertedData
@ TYPE_PARTICLE_SHEET_DATA
std::span< std::byte > data
uint16_t initialFrameCount
uint16_t initialSliceCount
bool computeTransparencyFlags
ImageConversion::ResizeFilter filter
ImageConversion::ResizeMethod heightResizeMethod
CompressionMethod compressionMethod
ImageConversion::ResizeMethod widthResizeMethod