in DirectXTex/DirectXTexTGA.cpp [1583:2057]
HRESULT DirectX::LoadFromTGAFile(
const wchar_t* szFile,
TGA_FLAGS flags,
TexMetadata* metadata,
ScratchImage& image) noexcept
{
if (!szFile)
return E_INVALIDARG;
image.Release();
#ifdef WIN32
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr)));
#else
ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, nullptr)));
#endif
if (!hFile)
{
return HRESULT_FROM_WIN32(GetLastError());
}
// Get the file size
FILE_STANDARD_INFO fileInfo;
if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo)))
{
return HRESULT_FROM_WIN32(GetLastError());
}
// File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid TGA file)
if (fileInfo.EndOfFile.HighPart > 0)
{
return HRESULT_E_FILE_TOO_LARGE;
}
size_t len = fileInfo.EndOfFile.LowPart;
#else // !WIN32
std::ifstream inFile(std::filesystem::path(szFile), std::ios::in | std::ios::binary | std::ios::ate);
if (!inFile)
return E_FAIL;
std::streampos fileLen = inFile.tellg();
if (!inFile)
return E_FAIL;
if (fileLen > UINT32_MAX)
return HRESULT_E_FILE_TOO_LARGE;
inFile.seekg(0, std::ios::beg);
if (!inFile)
return E_FAIL;
size_t len = fileLen;
#endif
// Need at least enough data to fill the header to be a valid TGA
if (len < sizeof(TGA_HEADER))
{
return E_FAIL;
}
// Read the header
uint8_t header[sizeof(TGA_HEADER)] = {};
#ifdef WIN32
DWORD bytesRead = 0;
if (!ReadFile(hFile.get(), header, sizeof(TGA_HEADER), &bytesRead, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
auto headerLen = static_cast<size_t>(bytesRead);
#else
inFile.read(reinterpret_cast<char*>(header), sizeof(TGA_HEADER));
if (!inFile)
return E_FAIL;
size_t headerLen = sizeof(TGA_HEADER);
#endif
size_t offset;
uint32_t convFlags = 0;
TexMetadata mdata;
HRESULT hr = DecodeTGAHeader(header, headerLen, flags, mdata, offset, &convFlags);
if (FAILED(hr))
return hr;
// Read the pixels
auto remaining = len - offset;
if (remaining == 0)
return E_FAIL;
if (offset > sizeof(TGA_HEADER))
{
#ifdef WIN32
// Skip past the id string
LARGE_INTEGER filePos = { { static_cast<DWORD>(offset), 0 } };
if (!SetFilePointerEx(hFile.get(), filePos, nullptr, FILE_BEGIN))
{
return HRESULT_FROM_WIN32(GetLastError());
}
#else
inFile.seekg(offset, std::ios::beg);
if (!inFile)
return E_FAIL;
#endif
}
hr = image.Initialize2D(mdata.format, mdata.width, mdata.height, 1, 1);
if (FAILED(hr))
return hr;
assert(image.GetPixels());
bool opaquealpha = false;
if (!(convFlags & (CONV_FLAGS_RLE | CONV_FLAGS_EXPAND | CONV_FLAGS_INVERTX)) && (convFlags & CONV_FLAGS_INVERTY))
{
// This case we can read directly into the image buffer in place
if (remaining < image.GetPixelsSize())
{
image.Release();
return HRESULT_E_HANDLE_EOF;
}
if (image.GetPixelsSize() > UINT32_MAX)
{
image.Release();
return HRESULT_E_ARITHMETIC_OVERFLOW;
}
#ifdef WIN32
if (!ReadFile(hFile.get(), image.GetPixels(), static_cast<DWORD>(image.GetPixelsSize()), &bytesRead, nullptr))
{
image.Release();
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != image.GetPixelsSize())
{
image.Release();
return E_FAIL;
}
#else
inFile.read(reinterpret_cast<char*>(image.GetPixels()), image.GetPixelsSize());
if (!inFile)
{
image.Release();
return E_FAIL;
}
#endif
switch (mdata.format)
{
case DXGI_FORMAT_R8G8B8A8_UNORM:
{
// TGA stores 32-bit data in BGRA form, need to swizzle to RGBA
assert(image.GetImageCount() == 1);
const Image* img = image.GetImage(0, 0, 0);
if (!img)
{
image.Release();
return E_POINTER;
}
uint8_t *pPixels = img->pixels;
if (!pPixels)
{
image.Release();
return E_POINTER;
}
size_t rowPitch = img->rowPitch;
// Scan for non-zero alpha channel
uint32_t minalpha = 255;
uint32_t maxalpha = 0;
for (size_t h = 0; h < img->height; ++h)
{
auto sPtr = reinterpret_cast<const uint32_t*>(pPixels);
for (size_t x = 0; x < img->width; ++x)
{
uint32_t alpha = ((*sPtr & 0xFF000000) >> 24);
minalpha = std::min(minalpha, alpha);
maxalpha = std::max(maxalpha, alpha);
++sPtr;
}
pPixels += rowPitch;
}
uint32_t tflags = TEXP_SCANLINE_NONE;
if (maxalpha == 0 && !(flags & TGA_FLAGS_ALLOW_ALL_ZERO_ALPHA))
{
opaquealpha = true;
tflags = TEXP_SCANLINE_SETALPHA;
}
else if (minalpha == 255)
{
opaquealpha = true;
}
// Swizzle scanlines
pPixels = img->pixels;
for (size_t h = 0; h < img->height; ++h)
{
_SwizzleScanline(pPixels, rowPitch, pPixels, rowPitch, mdata.format, tflags);
pPixels += rowPitch;
}
}
break;
case DXGI_FORMAT_B8G8R8A8_UNORM:
{
assert(image.GetImageCount() == 1);
const Image* img = image.GetImage(0, 0, 0);
if (!img)
{
image.Release();
return E_POINTER;
}
// Scan for non-zero alpha channel
uint32_t minalpha = 255;
uint32_t maxalpha = 0;
const uint8_t *pPixels = img->pixels;
if (!pPixels)
{
image.Release();
return E_POINTER;
}
size_t rowPitch = img->rowPitch;
for (size_t h = 0; h < img->height; ++h)
{
auto sPtr = reinterpret_cast<const uint32_t*>(pPixels);
for (size_t x = 0; x < img->width; ++x)
{
uint32_t alpha = ((*sPtr & 0xFF000000) >> 24);
minalpha = std::min(minalpha, alpha);
maxalpha = std::max(maxalpha, alpha);
++sPtr;
}
pPixels += rowPitch;
}
// If there are no non-zero alpha channel entries, we'll assume alpha is not used and force it to opaque
if (maxalpha == 0 && !(flags & TGA_FLAGS_ALLOW_ALL_ZERO_ALPHA))
{
opaquealpha = true;
hr = SetAlphaChannelToOpaque(img);
if (FAILED(hr))
{
image.Release();
return hr;
}
}
else if (minalpha == 255)
{
opaquealpha = true;
}
}
break;
case DXGI_FORMAT_B5G5R5A1_UNORM:
{
assert(image.GetImageCount() == 1);
const Image* img = image.GetImage(0, 0, 0);
if (!img)
{
image.Release();
return E_POINTER;
}
// Scan for non-zero alpha channel
uint32_t minalpha = 255;
uint32_t maxalpha = 0;
const uint8_t *pPixels = img->pixels;
if (!pPixels)
{
image.Release();
return E_POINTER;
}
size_t rowPitch = img->rowPitch;
for (size_t h = 0; h < img->height; ++h)
{
auto sPtr = reinterpret_cast<const uint16_t*>(pPixels);
for (size_t x = 0; x < img->width; ++x)
{
uint32_t alpha = (*sPtr & 0x8000) ? 255 : 0;
minalpha = std::min(minalpha, alpha);
maxalpha = std::max(maxalpha, alpha);
++sPtr;
}
pPixels += rowPitch;
}
// If there are no non-zero alpha channel entries, we'll assume alpha is not used and force it to opaque
if (maxalpha == 0 && !(flags & TGA_FLAGS_ALLOW_ALL_ZERO_ALPHA))
{
opaquealpha = true;
hr = SetAlphaChannelToOpaque(img);
if (FAILED(hr))
{
image.Release();
return hr;
}
}
else if (minalpha == 255)
{
opaquealpha = true;
}
}
break;
case DXGI_FORMAT_B8G8R8X8_UNORM:
// Should never be trying to direct-read 24bpp
return E_FAIL;
default:
break;
}
}
else // RLE || EXPAND || INVERTX || !INVERTY
{
std::unique_ptr<uint8_t[]> temp(new (std::nothrow) uint8_t[remaining]);
if (!temp)
{
image.Release();
return E_OUTOFMEMORY;
}
#ifdef WIN32
if (!ReadFile(hFile.get(), temp.get(), static_cast<DWORD>(remaining), &bytesRead, nullptr))
{
image.Release();
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesRead != remaining)
{
image.Release();
return E_FAIL;
}
#else
inFile.read(reinterpret_cast<char*>(temp.get()), remaining);
if (!inFile)
{
image.Release();
return E_FAIL;
}
#endif
if (convFlags & CONV_FLAGS_RLE)
{
hr = UncompressPixels(temp.get(), remaining, flags, image.GetImage(0, 0, 0), convFlags);
}
else
{
hr = CopyPixels(temp.get(), remaining, flags, image.GetImage(0, 0, 0), convFlags);
}
if (FAILED(hr))
{
image.Release();
return hr;
}
if (hr == S_FALSE)
opaquealpha = true;
}
// Optional TGA 2.0 footer & extension area
const TGA_EXTENSION* ext = nullptr;
TGA_EXTENSION extData = {};
{
TGA_FOOTER footer = {};
#ifdef WIN32
if (SetFilePointer(hFile.get(), -static_cast<int>(sizeof(TGA_FOOTER)), nullptr, FILE_END) != INVALID_SET_FILE_POINTER)
{
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;
}
}
#else // !WIN32
inFile.seekg(-static_cast<int>(sizeof(TGA_FOOTER)), std::ios::end);
if (inFile)
{
inFile.read(reinterpret_cast<char*>(&footer), sizeof(TGA_FOOTER));
if (!inFile)
{
image.Release();
return E_FAIL;
}
}
#endif
if (memcmp(footer.Signature, g_Signature, sizeof(g_Signature)) == 0)
{
if (footer.dwExtensionOffset != 0
&& ((footer.dwExtensionOffset + sizeof(TGA_EXTENSION)) <= len))
{
#ifdef WIN32
LARGE_INTEGER filePos = { { static_cast<DWORD>(footer.dwExtensionOffset), 0 } };
if (SetFilePointerEx(hFile.get(), filePos, nullptr, FILE_BEGIN))
{
if (ReadFile(hFile.get(), &extData, sizeof(TGA_EXTENSION), &bytesRead, nullptr)
&& bytesRead == sizeof(TGA_EXTENSION))
{
ext = &extData;
}
}
#else // !WIN32
inFile.seekg(static_cast<std::streampos>(footer.dwExtensionOffset), std::ios::beg);
if (inFile)
{
inFile.read(reinterpret_cast<char*>(&extData), sizeof(TGA_EXTENSION));
if (inFile)
{
ext = &extData;
}
}
#endif
}
}
}
if (!(flags & TGA_FLAGS_IGNORE_SRGB))
{
mdata.format = GetSRGBFromExtension(ext, mdata.format, flags, &image);
}
if (metadata)
{
memcpy(metadata, &mdata, sizeof(TexMetadata));
if (opaquealpha)
{
metadata->SetAlphaMode(TEX_ALPHA_MODE_OPAQUE);
}
else if (ext)
{
metadata->SetAlphaMode(GetAlphaModeFromExtension(ext));
}
}
return S_OK;
}