Update TGA codec for TGA 2.0 (#151)

This commit is contained in:
Chuck Walbourn
2019-10-04 13:25:44 -07:00
committed by GitHub
parent 002baf8072
commit 351ff17936
4 changed files with 238 additions and 31 deletions

View File

@@ -382,8 +382,8 @@ namespace DirectX
_In_z_ const wchar_t* szFile,
_Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image);
HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image, _Out_ Blob& blob);
HRESULT __cdecl SaveToTGAFile(_In_ const Image& image, _In_z_ const wchar_t* szFile);
HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image, _Out_ Blob& blob, _In_opt_ const TexMetadata* metadata = nullptr);
HRESULT __cdecl SaveToTGAFile(_In_ const Image& image, _In_z_ const wchar_t* szFile, _In_opt_ const TexMetadata* metadata = nullptr);
// WIC operations
HRESULT __cdecl LoadFromWICMemory(

View File

@@ -110,6 +110,7 @@
#include <vector>
#include <time.h>
#include <stdlib.h>
#include <search.h>

View File

@@ -23,6 +23,9 @@ using namespace DirectX;
namespace
{
const char g_Signature[] = "TRUEVISION-XFILE.";
// This is the official footer signature for the TGA 2.0 file format.
enum TGAImageType
{
TGA_NO_IMAGE = 0,
@@ -42,6 +45,15 @@ namespace
TGA_FLAGS_INTERLEAVED_4WAY = 0x80, // Deprecated
};
enum TGAAttributesType : uint8_t
{
TGA_ATTRIBUTE_NONE = 0, // 0: no alpha data included
TGA_ATTRIBUTE_IGNORED = 1, // 1: undefined data, can be ignored
TGA_ATTRIBUTE_UNDEFINED = 2, // 2: uedefined data, should be retained
TGA_ATTRIBUTE_ALPHA = 3, // 3: useful alpha channel data
TGA_ATTRIBUTE_PREMULTIPLIED = 4, // 4: pre-multiplied alpha
};
#pragma pack(push,1)
struct TGA_HEADER
{
@@ -892,6 +904,82 @@ namespace
}
}
}
//-------------------------------------------------------------------------------------
// TGA 2.0 Extension helpers
//-------------------------------------------------------------------------------------
void SetExtension(TGA_EXTENSION *ext, const TexMetadata& metadata)
{
memset(ext, 0, sizeof(TGA_EXTENSION));
ext->wSize = sizeof(TGA_EXTENSION);
memcpy(ext->szSoftwareId, "DirectXTex", sizeof("DirectXTex"));
ext->wVersionNumber = DIRECTX_TEX_VERSION;
ext->bVersionLetter = ' ';
if (IsSRGB(metadata.format))
{
ext->wGammaNumerator = 22;
ext->wGammaDenominator = 10;
}
switch (metadata.GetAlphaMode())
{
case TEX_ALPHA_MODE_UNKNOWN:
ext->bAttributesType = HasAlpha(metadata.format) ? TGA_ATTRIBUTE_UNDEFINED : TGA_ATTRIBUTE_NONE;
break;
case TEX_ALPHA_MODE_STRAIGHT:
ext->bAttributesType = TGA_ATTRIBUTE_ALPHA;
break;
case TEX_ALPHA_MODE_PREMULTIPLIED:
ext->bAttributesType = TGA_ATTRIBUTE_PREMULTIPLIED;
break;
case TEX_ALPHA_MODE_OPAQUE:
ext->bAttributesType = TGA_ATTRIBUTE_IGNORED;
break;
case TEX_ALPHA_MODE_CUSTOM:
ext->bAttributesType = TGA_ATTRIBUTE_UNDEFINED;
break;
}
// Set file time stamp
{
time_t now = {};
time(&now);
tm info;
if (!gmtime_s(&info, &now))
{
ext->wStampMonth = static_cast<uint16_t>(info.tm_mon + 1);
ext->wStampDay = static_cast<uint16_t>(info.tm_mday);
ext->wStampYear = static_cast<uint16_t>(info.tm_year + 1900);
ext->wStampHour = static_cast<uint16_t>(info.tm_hour);
ext->wStampMinute = static_cast<uint16_t>(info.tm_min);
ext->wStampSecond = static_cast<uint16_t>(info.tm_sec);
}
}
}
TEX_ALPHA_MODE GetAlphaModeFromExtension(const TGA_EXTENSION *ext)
{
if (ext && ext->wSize == sizeof(TGA_EXTENSION))
{
switch (ext->bAttributesType)
{
case TGA_ATTRIBUTE_IGNORED: return TEX_ALPHA_MODE_OPAQUE;
case TGA_ATTRIBUTE_UNDEFINED: return TEX_ALPHA_MODE_CUSTOM;
case TGA_ATTRIBUTE_ALPHA: return TEX_ALPHA_MODE_STRAIGHT;
case TGA_ATTRIBUTE_PREMULTIPLIED: return TEX_ALPHA_MODE_PREMULTIPLIED;
}
}
return TEX_ALPHA_MODE_UNKNOWN;
}
}
@@ -1021,6 +1109,21 @@ HRESULT DirectX::LoadFromTGAMemory(
{
metadata->SetAlphaMode(TEX_ALPHA_MODE_OPAQUE);
}
else if (size >= sizeof(TGA_FOOTER))
{
// Handle optional TGA 2.0 footer
auto footer = reinterpret_cast<const TGA_FOOTER*>(static_cast<const uint8_t*>(pSource) + size - sizeof(TGA_FOOTER));
if (memcmp(footer->Signature, g_Signature, sizeof(g_Signature)) == 0)
{
if (footer->dwExtensionOffset != 0
&& ((footer->dwExtensionOffset + sizeof(TGA_EXTENSION)) <= size))
{
auto ext = reinterpret_cast<const TGA_EXTENSION*>(static_cast<const uint8_t*>(pSource) + footer->dwExtensionOffset);
metadata->SetAlphaMode(GetAlphaModeFromExtension(ext));
}
}
}
}
return S_OK;
@@ -1312,6 +1415,46 @@ HRESULT DirectX::LoadFromTGAFile(
{
metadata->SetAlphaMode(TEX_ALPHA_MODE_OPAQUE);
}
else
{
// Handle optional TGA 2.0 footer
TGA_FOOTER footer = {};
if (SetFilePointer(hFile.get(), -static_cast<int>(sizeof(TGA_FOOTER)), 0, FILE_END) == INVALID_SET_FILE_POINTER)
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (!ReadFile(hFile.get(), &footer, sizeof(TGA_FOOTER), &bytesRead, nullptr))
{
image.Release();
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != sizeof(TGA_FOOTER))
{
image.Release();
return E_FAIL;
}
if (memcmp(footer.Signature, g_Signature, sizeof(g_Signature)) == 0)
{
if (footer.dwExtensionOffset != 0
&& ((footer.dwExtensionOffset + sizeof(TGA_EXTENSION)) <= fileInfo.EndOfFile.LowPart))
{
LARGE_INTEGER filePos = { { static_cast<DWORD>(footer.dwExtensionOffset), 0 } };
if (SetFilePointerEx(hFile.get(), filePos, nullptr, FILE_BEGIN))
{
TGA_EXTENSION ext = {};
if (ReadFile(hFile.get(), &ext, sizeof(TGA_EXTENSION), &bytesRead, nullptr)
&& bytesRead == sizeof(TGA_EXTENSION))
{
metadata->SetAlphaMode(GetAlphaModeFromExtension(&ext));
}
}
}
}
}
}
return S_OK;
@@ -1322,7 +1465,7 @@ HRESULT DirectX::LoadFromTGAFile(
// Save a TGA file to memory
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::SaveToTGAMemory(const Image& image, Blob& blob)
HRESULT DirectX::SaveToTGAMemory(const Image& image, Blob& blob, const TexMetadata* metadata)
{
if (!image.pixels)
return E_POINTER;
@@ -1349,13 +1492,18 @@ HRESULT DirectX::SaveToTGAMemory(const Image& image, Blob& blob)
return hr;
}
hr = blob.Initialize(sizeof(TGA_HEADER) + slicePitch);
hr = blob.Initialize(sizeof(TGA_HEADER)
+ slicePitch
+ (metadata ? sizeof(TGA_EXTENSION) : 0)
+ sizeof(TGA_FOOTER));
if (FAILED(hr))
return hr;
// Copy header
auto dPtr = static_cast<uint8_t*>(blob.GetBufferPointer());
assert(dPtr != nullptr);
auto destPtr = static_cast<uint8_t*>(blob.GetBufferPointer());
assert(destPtr != nullptr);
uint8_t* dPtr = destPtr;
memcpy_s(dPtr, blob.GetBufferSize(), &tga_header, sizeof(TGA_HEADER));
dPtr += sizeof(TGA_HEADER);
@@ -1382,6 +1530,24 @@ HRESULT DirectX::SaveToTGAMemory(const Image& image, Blob& blob)
pPixels += image.rowPitch;
}
uint32_t extOffset = 0;
if (metadata)
{
// metadata is only used for writing the TGA 2.0 extension header
auto ext = reinterpret_cast<TGA_EXTENSION*>(dPtr);
SetExtension(ext, *metadata);
extOffset = static_cast<uint32_t>(dPtr - destPtr);
dPtr += sizeof(TGA_EXTENSION);
}
// Copy TGA 2.0 footer
auto footer = reinterpret_cast<TGA_FOOTER*>(dPtr);
footer->dwDeveloperOffset = 0;
footer->dwExtensionOffset = extOffset;
memcpy(footer->Signature, g_Signature, sizeof(g_Signature));
return S_OK;
}
@@ -1390,7 +1556,7 @@ HRESULT DirectX::SaveToTGAMemory(const Image& image, Blob& blob)
// Save a TGA file to disk
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::SaveToTGAFile(const Image& image, const wchar_t* szFile)
HRESULT DirectX::SaveToTGAFile(const Image& image, const wchar_t* szFile, const TexMetadata* metadata)
{
if (!szFile)
return E_INVALIDARG;
@@ -1513,6 +1679,41 @@ HRESULT DirectX::SaveToTGAFile(const Image& image, const wchar_t* szFile)
if (bytesWritten != rowPitch)
return E_FAIL;
}
uint32_t extOffset = 0;
if (metadata)
{
// metadata is only used for writing the TGA 2.0 extension header
TGA_EXTENSION ext = {};
SetExtension(&ext, *metadata);
extOffset = SetFilePointer(hFile.get(), 0, nullptr, FILE_CURRENT);
if (extOffset == INVALID_SET_FILE_POINTER)
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (!WriteFile(hFile.get(), &ext, sizeof(TGA_EXTENSION), &bytesWritten, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesWritten != sizeof(TGA_EXTENSION))
return E_FAIL;
}
// Write TGA 2.0 footer
TGA_FOOTER footer = {};
footer.dwExtensionOffset = extOffset;
memcpy(footer.Signature, g_Signature, sizeof(g_Signature));
if (!WriteFile(hFile.get(), &footer, sizeof(footer), &bytesWritten, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesWritten != sizeof(footer))
return E_FAIL;
}
delonfail.clear();