diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index a916d75..7ffa7db 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -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( diff --git a/DirectXTex/DirectXTexP.h b/DirectXTex/DirectXTexP.h index 59f15d0..f03bd51 100644 --- a/DirectXTex/DirectXTexP.h +++ b/DirectXTex/DirectXTexP.h @@ -110,6 +110,7 @@ #include +#include #include #include diff --git a/DirectXTex/DirectXTexTGA.cpp b/DirectXTex/DirectXTexTGA.cpp index 8d73f15..82f1d19 100644 --- a/DirectXTex/DirectXTexTGA.cpp +++ b/DirectXTex/DirectXTexTGA.cpp @@ -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(info.tm_mon + 1); + ext->wStampDay = static_cast(info.tm_mday); + ext->wStampYear = static_cast(info.tm_year + 1900); + ext->wStampHour = static_cast(info.tm_hour); + ext->wStampMinute = static_cast(info.tm_min); + ext->wStampSecond = static_cast(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(static_cast(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(static_cast(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(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(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(blob.GetBufferPointer()); - assert(dPtr != nullptr); + auto destPtr = static_cast(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(dPtr); + SetExtension(ext, *metadata); + + extOffset = static_cast(dPtr - destPtr); + dPtr += sizeof(TGA_EXTENSION); + } + + // Copy TGA 2.0 footer + auto footer = reinterpret_cast(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(); diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index bf23e8a..aace8e1 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -54,6 +54,7 @@ using Microsoft::WRL::ComPtr; enum OPTIONS { OPT_RECURSIVE = 1, + OPT_FILELIST, OPT_WIDTH, OPT_HEIGHT, OPT_MIPLEVELS, @@ -72,6 +73,10 @@ enum OPTIONS OPT_DDS_DWORD_ALIGN, OPT_DDS_BAD_DXTN_TAILS, OPT_USE_DX10, + OPT_TGA20, + OPT_WIC_QUALITY, + OPT_WIC_LOSSLESS, + OPT_WIC_MULTIFRAME, OPT_NOLOGO, OPT_TIMING, OPT_SEPALPHA, @@ -95,15 +100,11 @@ enum OPTIONS OPT_COMPRESS_MAX, OPT_COMPRESS_QUICK, OPT_COMPRESS_DITHER, - OPT_WIC_QUALITY, - OPT_WIC_LOSSLESS, - OPT_WIC_MULTIFRAME, OPT_COLORKEY, OPT_TONEMAP, OPT_X2_BIAS, OPT_PRESERVE_ALPHA_COVERAGE, OPT_INVERT_Y, - OPT_FILELIST, OPT_ROTATE_COLOR, OPT_PAPER_WHITE_NITS, OPT_MAX @@ -140,6 +141,7 @@ struct SValue const SValue g_pOptions[] = { { L"r", OPT_RECURSIVE }, + { L"flist", OPT_FILELIST }, { L"w", OPT_WIDTH }, { L"h", OPT_HEIGHT }, { L"m", OPT_MIPLEVELS }, @@ -158,9 +160,14 @@ const SValue g_pOptions[] = { L"dword", OPT_DDS_DWORD_ALIGN }, { L"badtails", OPT_DDS_BAD_DXTN_TAILS }, { L"dx10", OPT_USE_DX10 }, + { L"tga20", OPT_TGA20 }, + { L"wicq", OPT_WIC_QUALITY }, + { L"wiclossless", OPT_WIC_LOSSLESS }, + { L"wicmulti", OPT_WIC_MULTIFRAME }, { L"nologo", OPT_NOLOGO }, { L"timing", OPT_TIMING }, { L"sepalpha", OPT_SEPALPHA }, + { L"keepcoverage", OPT_PRESERVE_ALPHA_COVERAGE }, { L"nowic", OPT_NO_WIC }, { L"tu", OPT_TYPELESS_UNORM }, { L"tf", OPT_TYPELESS_FLOAT }, @@ -181,15 +188,10 @@ const SValue g_pOptions[] = { L"bcmax", OPT_COMPRESS_MAX }, { L"bcquick", OPT_COMPRESS_QUICK }, { L"bcdither", OPT_COMPRESS_DITHER }, - { L"wicq", OPT_WIC_QUALITY }, - { L"wiclossless", OPT_WIC_LOSSLESS }, - { L"wicmulti", OPT_WIC_MULTIFRAME }, { L"c", OPT_COLORKEY }, { L"tonemap", OPT_TONEMAP }, { L"x2bias", OPT_X2_BIAS }, - { L"keepcoverage", OPT_PRESERVE_ALPHA_COVERAGE }, { L"inverty", OPT_INVERT_Y }, - { L"flist", OPT_FILELIST }, { L"rotatecolor", OPT_ROTATE_COLOR }, { L"nits", OPT_PAPER_WHITE_NITS }, { nullptr, 0 } @@ -702,32 +704,34 @@ namespace wprintf(L"Usage: texconv \n\n"); wprintf(L" -r wildcard filename search is recursive\n"); - wprintf(L" -w width\n"); + wprintf(L" -flist use text file with a list of input files (one per line)\n"); + wprintf(L"\n -w width\n"); wprintf(L" -h height\n"); wprintf(L" -m miplevels\n"); wprintf(L" -f format\n"); - wprintf(L" -if image filtering\n"); + wprintf(L"\n -if image filtering\n"); wprintf(L" -srgb{i|o} sRGB {input, output}\n"); - wprintf(L" -px name prefix\n"); + wprintf(L"\n -px name prefix\n"); wprintf(L" -sx name suffix\n"); wprintf(L" -o output directory\n"); wprintf(L" -y overwrite existing output file (if any)\n"); wprintf(L" -ft output file type\n"); - wprintf(L" -hflip horizonal flip of source image\n"); + wprintf(L"\n -hflip horizonal flip of source image\n"); wprintf(L" -vflip vertical flip of source image\n"); - wprintf(L" -sepalpha resize/generate mips alpha channel separately\n"); + wprintf(L"\n -sepalpha resize/generate mips alpha channel separately\n"); wprintf(L" from color channels\n"); - wprintf(L" -nowic Force non-WIC filtering\n"); + wprintf(L" -keepcoverage Preserve alpha coverage in mips for alpha test ref\n"); + wprintf(L"\n -nowic Force non-WIC filtering\n"); wprintf(L" -wrap, -mirror texture addressing mode (wrap, mirror, or clamp)\n"); wprintf(L" -pmalpha convert final texture to use premultiplied alpha\n"); wprintf(L" -alpha convert premultiplied alpha to straight alpha\n"); + wprintf(L"\n -fl Set maximum feature level target (defaults to 11.0)\n"); wprintf(L" -pow2 resize to fit a power-of-2, respecting aspect ratio\n"); wprintf( - L" -nmap converts height-map to normal-map\n" + L"\n -nmap converts height-map to normal-map\n" L" options must be one or more of\n" L" r, g, b, a, l, m, u, v, i, o\n"); wprintf(L" -nmapamp normal map amplitude (defaults to 1.0)\n"); - wprintf(L" -fl Set maximum feature level target (defaults to 11.0)\n"); wprintf(L"\n (DDS input only)\n"); wprintf(L" -t{u|f} TYPELESS format is treated as UNORM or FLOAT\n"); wprintf(L" -dword Use DWORD instead of BYTE alignment\n"); @@ -735,6 +739,12 @@ namespace wprintf(L" -xlum expand legacy L8, L16, and A8P8 formats\n"); wprintf(L"\n (DDS output only)\n"); wprintf(L" -dx10 Force use of 'DX10' extended header\n"); + wprintf(L"\n (TGA output only)\n"); + wprintf(L" -tga20 Write file including TGA 2.0 extension area\n"); + wprintf(L"\n (BMP, PNG, JPG, TIF, WDP output only)\n"); + wprintf(L" -wicq When writing images with WIC use quality (0.0 to 1.0)\n"); + wprintf(L" -wiclossless When writing images with WIC use lossless mode\n"); + wprintf(L" -wicmulti When writing images with WIC encode multiframe images\n"); wprintf(L"\n -nologo suppress copyright message\n"); wprintf(L" -timing Display elapsed processing time\n\n"); #ifdef _OPENMP @@ -746,20 +756,15 @@ namespace wprintf(L" -bcdither Use dithering for BC1-3\n"); wprintf(L" -bcmax Use exhaustive compression (BC7 only)\n"); wprintf(L" -bcquick Use quick compression (BC7 only)\n"); - wprintf(L" -wicq When writing images with WIC use quality (0.0 to 1.0)\n"); - wprintf(L" -wiclossless When writing images with WIC use lossless mode\n"); - wprintf(L" -wicmulti When writing images with WIC encode multiframe images\n"); wprintf( L" -aw BC7 GPU compressor weighting for alpha error metric\n" L" (defaults to 1.0)\n"); - wprintf(L" -c colorkey (a.k.a. chromakey) transparency\n"); + wprintf(L"\n -c colorkey (a.k.a. chromakey) transparency\n"); wprintf(L" -rotatecolor rotates color primaries and/or applies a curve\n"); wprintf(L" -nits paper-white value in nits to use for HDR10 (def: 200.0)\n"); wprintf(L" -tonemap Apply a tonemap operator based on maximum luminance\n"); wprintf(L" -x2bias Enable *2 - 1 conversion cases for unorm/pos-only-float\n"); - wprintf(L" -keepcoverage Preserve alpha coverage in generated mips for alpha test ref\n"); wprintf(L" -inverty Invert Y (i.e. green) channel values\n"); - wprintf(L" -flist use text file with a list of input files (one per line)\n"); wprintf(L"\n : "); PrintList(13, g_pFormats); @@ -2990,7 +2995,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) break; case CODEC_TGA: - hr = SaveToTGAFile(img[0], pConv->szDest); + hr = SaveToTGAFile(img[0], pConv->szDest, (dwOptions & (DWORD64(1) << OPT_TGA20)) ? &info : nullptr); break; case CODEC_HDR: