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

使用WIC枚举PNG帧级元数据丢失大量项,求代码排查

问题:使用WIC读取PNG帧级元数据时丢失大量信息

我用以下C++代码导出PNG的帧级元数据树,但输出的元数据远少于系统和ExifTool显示的内容,请问代码存在什么问题?

main.cc

#include <wincodec.h>

#include <cassert>
#include <iostream>

void PrintMetadata(IWICMetadataQueryReader* query_reader, int32_t indentation) {
  IEnumString* enumerator{};
  if (FAILED(query_reader->GetEnumerator(&enumerator))) {
    return;
  }

  while (true) {
    LPOLESTR name{};
    ULONG fetched{};
    if (enumerator->Next(1, &name, &fetched) == S_OK) {
      PROPVARIANT var;
      ::PropVariantInit(&var);
      if (query_reader->GetMetadataByName(name, &var) == S_OK) {
        std::wcout << std::wstring(indentation, L'\t') << name;
        if (var.vt == VT_UNKNOWN) {
          std::wcout << std::endl;
          IWICMetadataQueryReader* sub_query_reader{};
          if (SUCCEEDED(var.punkVal->QueryInterface(&sub_query_reader))) {
            PrintMetadata(sub_query_reader, indentation + 1);
            sub_query_reader->Release();
          }
        } else {
          std::wcout << " type: " << var.vt;
        }
        std::wcout << std::endl;
      }
      ::PropVariantClear(&var);
      ::CoTaskMemFree(name);
    } else {
      break;
    }
  }

  enumerator->Release();
}

void DumpMetadatas(PCWSTR path) {
  IWICImagingFactory* factory{};
  HRESULT result =
      ::CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
                         __uuidof(IWICImagingFactory), (void**)&factory);
  if (FAILED(result)) {
    return;
  }

  IWICBitmapDecoder* decoder{};
  result = factory->CreateDecoderFromFilename(
      path, nullptr, GENERIC_READ,
      WICDecodeOptions::WICDecodeMetadataCacheOnLoad, &decoder);
  if (FAILED(result)) {
    factory->Release();
    return;
  }

  IWICBitmapFrameDecode* frame{};
  result = decoder->GetFrame(0, &frame);
  if (FAILED(result)) {
    decoder->Release();
    factory->Release();
    return;
  }

  IWICMetadataQueryReader* query_reader{};
  result = frame->GetMetadataQueryReader(&query_reader);
  if (FAILED(result)) {
    frame->Release();
    decoder->Release();
    factory->Release();
    return;
  }

  PrintMetadata(query_reader, 0);
  query_reader->Release();

  frame->Release();
  decoder->Release();
  factory->Release();
}

int wmain(int /*argc*/, wchar_t** argv) {
  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  DumpMetadatas(argv[1]);

  ::CoUninitialize();

  return 0;
}

我的代码输出:

/sRGB
        /RenderingIntent type: 17
/gAMA
        /ImageGamma type: 19

ExifTool可显示的完整元数据:

ExifTool Version Number : 12.98
File Name : red.png
Directory : C:/images
File Size : 1147 bytes
File Modification Date/Time : 2024:10:08 15:06:37+08:00
File Access Date/Time : 2024:10:10 16:05:36+08:00
File Creation Date/Time : 2024:10:08 15:06:37+08:00
File Permissions : -rw-rw-rw-
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 400
Image Height : 300
Bit Depth : 8
Color Type : RGB with Alpha
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
SRGB Rendering : Perceptual
Gamma : 2.2
Pixels Per Unit X : 4724
Pixels Per Unit Y : 4724
Pixel Units : meters
Image Size : 400x300
Megapixels : 0.120

问题分析与解决方案

你的代码存在三个核心问题:

1. WIC帧元数据查询器仅暴露部分PNG元数据块

IWICBitmapFrameDecode::GetMetadataQueryReader返回的查询器,只会读取PNG中被WIC标记为"标准元数据"的块(比如sRGB、gAMA),而PNG的IHDR块(图像尺寸、位深度、颜色类型等)、**pHYs块(像素密度)**这类基础信息,WIC不会将其放入帧元数据查询器,而是通过IWICBitmapFrameDecode的专属接口提供。

2. 文件系统属性未被读取

ExifTool显示的文件名、修改时间、文件大小等属于文件系统属性,WIC的元数据查询器不包含这些内容,需要通过Windows原生API(如GetFileAttributesEx)单独获取。

3. 未枚举容器级元数据

PNG的部分元数据块属于容器级而非帧级,你需要通过IWICBitmapDecoder::GetMetadataQueryReader获取解码器的元数据查询器,才能枚举到这些块。


修改后的代码示例

以下代码补充了图像基础信息、像素密度、文件属性的读取,并同时枚举帧级和容器级元数据:

#include <wincodec.h>
#include <windows.h>
#include <cassert>
#include <iostream>
#include <iomanip>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

void PrintMetadata(IWICMetadataQueryReader* query_reader, int32_t indentation) {
    if (!query_reader) return;

    IEnumString* enumerator{};
    if (FAILED(query_reader->GetEnumerator(&enumerator))) {
        return;
    }

    while (true) {
        LPOLESTR name{};
        ULONG fetched{};
        if (enumerator->Next(1, &name, &fetched) != S_OK) {
            break;
        }

        PROPVARIANT var;
        ::PropVariantInit(&var);
        if (SUCCEEDED(query_reader->GetMetadataByName(name, &var))) {
            std::wcout << std::wstring(indentation, L'\t') << name;
            if (var.vt == VT_UNKNOWN) {
                std::wcout << std::endl;
                IWICMetadataQueryReader* sub_query_reader{};
                if (SUCCEEDED(var.punkVal->QueryInterface(&sub_query_reader))) {
                    PrintMetadata(sub_query_reader, indentation + 1);
                    sub_query_reader->Release();
                }
            } else {
                std::wcout << L" type: " << var.vt;
                // 打印部分常见值的实际内容
                if (var.vt == VT_R8) {
                    std::wcout << L" value: " << var.dblVal;
                } else if (var.vt == VT_UI4) {
                    std::wcout << L" value: " << var.ulVal;
                } else if (var.vt == VT_UI2) {
                    std::wcout << L" value: " << var.uiVal;
                }
            }
            std::wcout << std::endl;
        }
        ::PropVariantClear(&var);
        ::CoTaskMemFree(name);
    }

    enumerator->Release();
}

void PrintFileAttributes(PCWSTR path) {
    WIN32_FILE_ATTRIBUTE_DATA fileData{};
    if (GetFileAttributesEx(path, GetFileExInfoStandard, &fileData)) {
        std::wcout << L"File Name: " << PathFindFileName(path) << std::endl;
        ULONGLONG fileSize = (static_cast<ULONGLONG>(fileData.nFileSizeHigh) << 32) + fileData.nFileSizeLow;
        std::wcout << L"File Size: " << fileSize << L" bytes" << std::endl;

        SYSTEMTIME st{};
        FileTimeToSystemTime(&fileData.ftLastWriteTime, &st);
        std::wcout << L"File Modification Date/Time: " 
                   << std::setw(4) << st.wYear << L":" 
                   << std::setw(2) << std::setfill(L'0') << st.wMonth << L":" 
                   << std::setw(2) << st.wDay << L" " 
                   << std::setw(2) << st.wHour << L":" 
                   << std::setw(2) << st.wMinute << L":" 
                   << std::setw(2) << st.wSecond << std::endl;
        std::setfill(L' ');
    }
}

void DumpMetadatas(PCWSTR path) {
    IWICImagingFactory* factory{};
    HRESULT result = ::CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
                                        __uuidof(IWICImagingFactory), (void**)&factory);
    if (FAILED(result)) {
        return;
    }

    IWICBitmapDecoder* decoder{};
    result = factory->CreateDecoderFromFilename(
        path, nullptr, GENERIC_READ,
        WICDecodeOptions::WICDecodeMetadataCacheOnLoad, &decoder);
    if (FAILED(result)) {
        factory->Release();
        return;
    }

    // 打印容器级元数据
    std::wcout << L"=== 容器级元数据 ===" << std::endl;
    IWICMetadataQueryReader* container_query{};
    if (SUCCEEDED(decoder->GetMetadataQueryReader(&container_query))) {
        PrintMetadata(container_query, 0);
        container_query->Release();
    }

    IWICBitmapFrameDecode* frame{};
    result = decoder->GetFrame(0, &frame);
    if (FAILED(result)) {
        decoder->Release();
        factory->Release();
        return;
    }

    // 打印图像基础信息
    std::wcout << L"\n=== 图像基础信息 ===" << std::endl;
    UINT width, height;
    if (SUCCEEDED(frame->GetSize(&width, &height))) {
        std::wcout << L"Image Width: " << width << std::endl;
        std::wcout << L"Image Height: " << height << std::endl;
    }

    UINT bitDepth, channels;
    WICPixelFormatGUID format;
    if (SUCCEEDED(frame->GetPixelFormat(&format)) &&
        SUCCEEDED(WICGetPixelFormatInfo(format, &bitDepth, &channels))) {
        std::wcout << L"Bit Depth: " << bitDepth << std::endl;
        std::wcout << L"Color Channels: " << channels << std::endl;
    }

    // 打印帧级元数据
    std::wcout << L"\n=== 帧级元数据 ===" << std::endl;
    IWICMetadataQueryReader* frame_query{};
    if (SUCCEEDED(frame->GetMetadataQueryReader(&frame_query))) {
        PrintMetadata(frame_query, 0);
        frame_query->Release();
    }

    // 打印文件属性
    std::wcout << L"\n=== 文件系统属性 ===" << std::endl;
    PrintFileAttributes(path);

    frame->Release();
    decoder->Release();
    factory->Release();
}

int wmain(int argc, wchar_t** argv) {
    if (argc < 2) {
        std::wcout << L"Usage: " << argv[0] << L" <png file path>" << std::endl;
        return 1;
    }

    ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
    DumpMetadatas(argv[1]);
    ::CoUninitialize();

    return 0;
}

内容的提问来源于stack exchange,提问作者wixom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:45:55