Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,20 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
<td rowspan="1"><code>vtfpp</code></td>
<td rowspan="3"><code>vtfpp</code></td>
<td><a href="https://developer.valvesoftware.com/wiki/PPL">PPL</a> v0</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td rowspan="3" align="center"></td>
</tr>
<tr><!-- empty row to disable github striped bg color --></tr>
<tr>
<td>
<a href="https://developer.valvesoftware.com/wiki/VTF_(Valve_Texture_Format)">VTF</a> v7.0-7.6
<br> &bull; <a href="https://stratasource.org">Strata Source</a> modifications
</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td rowspan="1" align="center"></td>
</tr>
</table>

Expand Down
9 changes: 7 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,19 @@ Several modern C++20 libraries for sanely parsing Valve formats, rolled into one
<td align="center">✅</td>
</tr>
<tr>
<td rowspan="1"><code>vtfpp</code></td>
<td rowspan="2"><code>vtfpp</code></td>
<td><a href="https://developer.valvesoftware.com/wiki/PPL">PPL</a> v0</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td rowspan="2" align="center"></td>
</tr>
<tr>
<td>
<a href="https://developer.valvesoftware.com/wiki/VTF_(Valve_Texture_Format)">VTF</a> v7.0-7.6
<br> &bull; <a href="https://stratasource.org">Strata Source</a> modifications
</td>
<td align="center">✅</td>
<td align="center">✅</td>
<td rowspan="1" align="center"></td>
</tr>
</table>
\endhtmlonly
Expand Down
2 changes: 2 additions & 0 deletions include/vtfpp/ImageConversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ namespace ImageConversion {
/// Converts image data to a PNG or HDR file. HDR output will be used for floating-point formats.
[[nodiscard]] std::vector<std::byte> convertImageDataToFile(std::span<const std::byte> imageData, ImageFormat format, uint16_t width, uint16_t height);

[[nodiscard]] std::vector<std::byte> convertFileToImageData(std::span<const std::byte> fileData, ImageFormat& format, int& width, int& height, int& frameCount);

enum class ResizeEdge {
// Matches stbir_edge
CLAMP = 0,
Expand Down
78 changes: 78 additions & 0 deletions include/vtfpp/PPL.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

#include <cstddef>
#include <optional>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>

#include <sourcepp/math/Integer.h>

#include "ImageConversion.h"

namespace vtfpp {

class PPL {
public:
struct Image {
uint32_t width;
uint32_t height;
std::vector<std::byte> data;
};

explicit PPL(uint32_t checksum_, ImageFormat format_ = ImageFormat::RGB888, uint32_t version_ = 0);

explicit PPL(std::span<const std::byte> pplData);

explicit PPL(const std::string& pplPath);

[[nodiscard]] explicit operator bool() const;

[[nodiscard]] uint32_t getVersion() const;

void setVersion(uint32_t newVersion);

[[nodiscard]] uint32_t getChecksum() const;

void setChecksum(uint32_t newChecksum);

[[nodiscard]] ImageFormat getFormat() const;

void setFormat(ImageFormat newFormat);

[[nodiscard]] bool hasImageForLOD(uint32_t lod) const;

[[nodiscard]] std::vector<uint32_t> getImageLODs() const;

[[nodiscard]] const Image* getImageRaw(uint32_t lod = 0) const;

[[nodiscard]] std::optional<Image> getImageAs(ImageFormat newFormat, uint32_t lod = 0) const;

[[nodiscard]] std::optional<Image> getImageAsRGB888(uint32_t lod = 0) const;

bool setImage(std::span<const std::byte> imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t lod = 0);

bool setImage(std::span<const std::byte> imageData, ImageFormat format_, uint32_t width, uint32_t height, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);

bool setImage(const std::string& imagePath, uint32_t lod);

bool setImage(const std::string& imagePath, uint32_t resizedWidth, uint32_t resizedHeight, uint32_t lod = 0, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR);

[[nodiscard]] std::vector<std::byte> saveImageToFile(uint32_t lod = 0) const;

bool saveImageToFile(const std::string& imagePath, uint32_t lod = 0) const; // NOLINT(*-use-nodiscard)

[[nodiscard]] std::vector<std::byte> bake();

bool bake(const std::string& pplPath);

protected:
uint32_t version{};
uint32_t checksum{};
ImageFormat format{};

std::unordered_map<uint32_t, Image> images;
};

} // namespace vtfpp
8 changes: 4 additions & 4 deletions include/vtfpp/VTF.h
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,9 @@ class VTF {

bool setImage(const std::string& imagePath, ImageConversion::ResizeFilter filter = ImageConversion::ResizeFilter::BILINEAR, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0);

[[nodiscard]] std::vector<std::byte> convertAndSaveImageDataToFile(uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;
[[nodiscard]] std::vector<std::byte> saveImageToFile(uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;

void convertAndSaveImageDataToFile(const std::string& imagePath, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const;
bool saveImageToFile(const std::string& imagePath, uint8_t mip = 0, uint16_t frame = 0, uint8_t face = 0, uint16_t slice = 0) const; // NOLINT(*-use-nodiscard)

[[nodiscard]] bool hasThumbnailData() const;

Expand All @@ -334,9 +334,9 @@ class VTF {

void removeThumbnail();

[[nodiscard]] std::vector<std::byte> convertAndSaveThumbnailDataToFile() const;
[[nodiscard]] std::vector<std::byte> saveThumbnailToFile() const;

void convertAndSaveThumbnailDataToFile(const std::string& imagePath) const;
bool saveThumbnailToFile(const std::string& imagePath) const; // NOLINT(*-use-nodiscard)

[[nodiscard]] std::vector<std::byte> bake();

Expand Down
1 change: 1 addition & 0 deletions include/vtfpp/vtfpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

#include "ImageConversion.h"
#include "ImageFormats.h"
#include "PPL.h"
#include "VTF.h"
83 changes: 83 additions & 0 deletions src/vtfpp/ImageConversion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include <bit>
#include <cstring>
#include <memory>
#include <span>

#ifdef SOURCEPP_BUILD_WITH_TBB
Expand All @@ -11,6 +12,7 @@

#include <Compressonator.h>
#include <sourcepp/math/Float.h>
#include <stb_image.h>
#include <stb_image_resize2.h>
#include <stb_image_write.h>

Expand Down Expand Up @@ -864,6 +866,87 @@ std::vector<std::byte> ImageConversion::convertImageDataToFile(std::span<const s
return out;
}

std::vector<std::byte> ImageConversion::convertFileToImageData(std::span<const std::byte> fileData, ImageFormat& format, int& width, int& height, int& frameCount) {
stbi_convert_iphone_png_to_rgb(true);

format = ImageFormat::EMPTY;
width = 0;
height = 0;
int channels = 0;
frameCount = 1;

if (stbi_is_hdr_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()))) {
std::unique_ptr<float, void(*)(void*)> stbImage{
stbi_loadf_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
&stbi_image_free,
};
if (!stbImage) {
return {};
}

switch (channels) {
case 1: format = ImageFormat::R32F; break;
case 3: format = ImageFormat::RGB323232F; break;
case 4: format = ImageFormat::RGBA32323232F; break;
default: return {};
}

return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
} else if (stbi_is_16_bit_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()))) {
std::unique_ptr<stbi_us, void(*)(void*)> stbImage{
stbi_load_16_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
&stbi_image_free,
};
if (!stbImage) {
return {};
}

if (channels == 4) {
format = ImageFormat::RGBA16161616;
} else {
return {};
}

return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
} else if (fileData.size() >= 3 && static_cast<char>(fileData[0]) == 'G' && static_cast<char>(fileData[1]) == 'I' && static_cast<char>(fileData[2]) == 'F') {
std::unique_ptr<stbi_uc, void(*)(void*)> stbImage{
stbi_load_gif_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), nullptr, &width, &height, &frameCount, &channels, 0),
&stbi_image_free,
};
if (!stbImage || !frameCount) {
return {};
}

switch (channels) {
case 1: format = ImageFormat::I8; break;
case 2: format = ImageFormat::UV88; break;
case 3: format = ImageFormat::RGB888; break;
case 4: format = ImageFormat::RGBA8888; break;
default: return {};
}

return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get() + (ImageFormatDetails::getDataLength(format, width, height) * frameCount))};
} else {
std::unique_ptr<stbi_uc, void(*)(void*)> stbImage{
stbi_load_from_memory(reinterpret_cast<const stbi_uc*>(fileData.data()), static_cast<int>(fileData.size()), &width, &height, &channels, 0),
&stbi_image_free,
};
if (!stbImage) {
return {};
}

switch (channels) {
case 1: format = ImageFormat::I8; break;
case 2: format = ImageFormat::UV88; break;
case 3: format = ImageFormat::RGB888; break;
case 4: format = ImageFormat::RGBA8888; break;
default: return {};
}

return {reinterpret_cast<std::byte*>(stbImage.get()), reinterpret_cast<std::byte*>(stbImage.get()) + ImageFormatDetails::getDataLength(format, width, height)};
}
}

uint16_t ImageConversion::getResizedDim(uint16_t n, ResizeMethod method) {
switch (method) {
case ResizeMethod::NONE: break;
Expand Down
Loading