You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于DirectXTex实现DDS格式转换函数的问题求助

DDS格式转换函数修正方案

原代码核心问题

  • COM资源泄漏:调用CoInitializeEx后未调用CoUninitialize,会导致COM引用计数异常
  • Alpha通道处理错误:转换到单通道格式(如DXGI_FORMAT_R8_UNORM)时,未保留Alpha通道的透明度信息,导致透明背景被填充为白色
  • 解压参数不当:使用DXGI_FORMAT_UNKNOWN作为解压目标格式,可能让DirectXTex选择丢失Alpha的中间格式
  • 过滤器标志滥用:错误组合多个默认过滤器标志,可能触发不必要的颜色空间转换或滤波逻辑
  • 冗余平面格式处理:DirectXTex的Convert/Compress函数原生支持平面格式,无需手动转单平面

修正后的完整实现

#include <vector>
#include <DirectXTex.h>
#include <stdexcept>
#include <utility>

// 假设项目中已有typedef:typedef unsigned char byte;
// 若无此定义,可替换为unsigned char

void throw_error(const std::string& err, HRESULT hr)
{
    LPWSTR errorText = nullptr;
    const DWORD messageLength = FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        nullptr, hr & 0x0000FFFF, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        reinterpret_cast<LPWSTR>(&errorText), 0, nullptr);

    if (messageLength > 0)
    {
        const std::wstring wErrorText(errorText);
        LocalFree(errorText);
        throw std::invalid_argument(err + ": " + std::string(wErrorText.begin(), wErrorText.end()));
    }
    else
    {
        throw std::invalid_argument(err + ": Unknown HRESULT error");
    }
}

std::vector<byte> ConvertDDS(const byte* src, size_t src_len, DXGI_FORMAT targetFormat)
{
    HRESULT hr = S_OK;

    // COM初始化:用RAII确保退出时自动清理
    hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    if (FAILED(hr))
        throw_error("Failed to initialize COM", hr);
    std::unique_ptr<void, decltype(&CoUninitialize)> comGuard(nullptr, [](void*) { CoUninitialize(); });

    DirectX::TexMetadata srcMeta;
    DirectX::ScratchImage srcImage;

    // 加载输入DDS
    hr = DirectX::LoadFromDDSMemory(src, src_len, DirectX::DDS_FLAGS_ALLOW_LARGE_FILES, &srcMeta, srcImage);
    if (FAILED(hr))
        throw_error("Failed to load DDS from memory", hr);

    DirectX::ScratchImage dstImage;

    // 格式已匹配,直接复用原图像
    if (srcMeta.format == targetFormat)
    {
        dstImage = std::move(srcImage);
    }
    else
    {
        DirectX::ScratchImage tempImage;
        const DirectX::TexMetadata* currentMeta = &srcMeta;
        const DirectX::Image* currentImages = srcImage.GetImages();
        size_t currentImageCount = srcImage.GetImageCount();

        // 处理压缩格式:解压到带Alpha的中间格式,保留透明度信息
        if (DirectX::IsCompressed(srcMeta.format))
        {
            DXGI_FORMAT decompressFormat = DirectX::IsSRGB(srcMeta.format) 
                ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB 
                : DXGI_FORMAT_R8G8B8A8_UNORM;

            hr = DirectX::Decompress(currentImages, currentImageCount, *currentMeta, decompressFormat, tempImage);
            if (FAILED(hr))
                throw_error("Failed to decompress DDS", hr);

            currentImages = tempImage.GetImages();
            currentImageCount = tempImage.GetImageCount();
            currentMeta = &tempImage.GetMetadata();
        }

        // 执行格式转换:针对单通道目标格式,处理Alpha通道
        DirectX::TEX_FILTER_FLAGS filterFlags = DirectX::TEX_FILTER_DEFAULT;
        // 若目标是R8_UNORM,可选择提取Alpha作为灰度值,解决透明背景变白问题
        if (targetFormat == DXGI_FORMAT_R8_UNORM)
        {
            filterFlags |= DirectX::TEX_FILTER_EXTRACT_ALPHA;
            // 若需要将RGB混合Alpha后转灰度,移除上述标志即可
        }

        hr = DirectX::Convert(currentImages, currentImageCount, *currentMeta, targetFormat, filterFlags, DirectX::TEX_THRESHOLD_DEFAULT, dstImage);
        if (FAILED(hr))
            throw_error("Failed to convert DDS format", hr);

        // 目标格式为压缩格式时,重新压缩
        if (DirectX::IsCompressed(targetFormat))
        {
            DirectX::ScratchImage compressedImage;
            hr = DirectX::Compress(dstImage.GetImages(), dstImage.GetImageCount(), dstImage.GetMetadata(), targetFormat,
                DirectX::TEX_COMPRESS_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, compressedImage);
            if (FAILED(hr))
                throw_error("Failed to compress DDS", hr);
            dstImage = std::move(compressedImage);
        }
    }

    // 保存转换后的DDS到内存
    DirectX::Blob ddsBlob;
    hr = DirectX::SaveToDDSMemory(dstImage.GetImages(), dstImage.GetImageCount(), dstImage.GetMetadata(), DirectX::DDS_FLAGS_NONE, ddsBlob);
    if (FAILED(hr))
        throw_error("Failed to save DDS to memory", hr);

    // 转换为vector返回
    std::vector<byte> result(static_cast<size_t>(ddsBlob.GetBufferSize()));
    std::memcpy(result.data(), ddsBlob.GetBufferPointer(), result.size());
    return result;
}

关键修改说明

  1. COM资源RAII管理:用std::unique_ptr做资源守卫,确保函数退出时自动调用CoUninitialize,避免资源泄漏
  2. Alpha通道处理:针对DXGI_FORMAT_R8_UNORM这类单通道格式,通过TEX_FILTER_EXTRACT_ALPHA标志直接提取Alpha通道作为灰度值,解决透明背景变白的问题;若需要将RGB混合Alpha后转灰度,移除该标志即可
  3. 明确解压格式:不再使用DXGI_FORMAT_UNKNOWN,而是指定带Alpha的中间格式,确保解压过程中保留透明度信息
  4. 简化转换流程:移除冗余的平面格式转换逻辑,DirectXTex原生支持多数平面格式的转换
  5. 优化过滤器标志:仅保留必要标志位,避免错误触发颜色空间转换或不必要的滤波
  6. 内存优化:用std::move转移ScratchImage所有权,减少内存拷贝

内容的提问来源于stack exchange,提问作者code-tester

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 03:45:04