HRESULT DirectX::LoadFromTGAFile()

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;
}