WICでEXIFを考慮したプロファイル読み込みを行う

JPEGやTIFFから画像を読み込むにあたって、プロファイルが付与されている場合の処理は、カラーマネジメントや、既存のIWICBitmapSourceからの構築のように処理すれば良い部分はあるのだが、一方で、EXIFでは、プロファイルを付与せずにカラースペース指定を行う方法があります。これは、プロファイルを付与して画像ファイルが大きくなることを回避するための仕様ではありますが、WICでは、この指定を自動的に反映してくれたりはしません。

そのため、この仕様に対応するには、自前で、EXIF ColorSpace (40961)を確認してあげる必要があります。また、このリンク先では、1=sRGBとしか書いてありませんが、これは、EXIFの仕様が古いためで、最新の規格では、2=Adobe RGBも定義されています。

で、この値を取得するには、IWICBitmapFrameDecode::GetMetadataQueryReader Methodを使って、"/app1/ifd/exif/subifd:{uint=40961}"を読めばいいらしいのだけども、TIFFの時は、"/ifd/exif/subifd:{uint=40961}"じゃないといけないので、とりあえず、両方確認するようにしておきます。

そして、WICでは、まさしく、このタグの処理用に、IWICColorContext::InitializeFromExifColorSpace Methodという関数が用意されています。なので、処理的には、EXIF ColorSpace (40961)を調べて、存在したら、IWICColorContext::InitializeFromExifColorSpaceで読み込むという処理になるでしょう。

// 適当なエラー処理
#define ToF(expr) do{HRESULT hr = expr; if(FAILED(hr)) {throw std::runtime_error(#expr);}} while(0)

// EXIFの標準プロファイルを読み込む (1,sRGB 2,Adobe RGB)
CComPtr<IWICColorContext> loadProfileFromExifColorSpace(UINT cs, IWICImagingFactory* factory)
{
  CComPtr<IWICColorContext> cc;
  ToF(factory->CreateColorContext(&cc));
  ToF(cc->InitializeFromExifColorSpace(cs));
  return cc;
}

// IWICBitmapFrameDecodeから、EXIF情報も考慮したプロファイル読み込みを行う
CComPtr<IWICColorContext> loadProfile(IWICBitmapFrameDecode* frameDecode, IWICImagingFactory* factory)
{
  CComPtr<IWICColorContext> cc;
  UINT co;
  HRESULT hr = frameDecode->GetColorContexts(1, &cc, &co);
  if(SUCCEEDED(hr) && co == 1 && cc)
    return cc; // プロファイルが埋め込まれてる
  
  CComPtr<IWICMetadataQueryReader> mqr;
  if(FAILED(frameDecode->GetMetadataQueryReader(&mqr)))
    return NULL;
  
  // EXIFタグの場所が違う可能性がある??
  PROPVARIANT v;
  PropVariantInit(&v);
  if(FAILED(mqr->GetMetadataByName(L"/app1/ifd/exif/subifd:{uint=40961}", &v)) &&
    FAILED(mqr->GetMetadataByName(L"/ifd/exif/subifd:{uint=40961}", &v)))
    return NULL;
  
  UINT cs = (v.vt == VT_UI2) ? v.uiVal : 0;
  PropVariantClear(&v);

  // 1,sRGB 2,Adobe RGB
  if(cs == 1 || cs == 2)
    return loadProfileFromExifColorSpace(cs, factory);
  return NULL;
}

// frameDecodeをdestColorContext/destPixelFormatで指定される色空間に変換する
CComPtr<IWICBitmapSource> convert(IWICBitmapFrameDecode* frameDecode, IWICColorContext* destColorContext, WICPixelFormatGUID destPixelFormat, IWICImagingFactory* factory)
{
  CComPtr<IWICColorTransform> txf;
  ToF(factory->CreateColorTransformer(&txf));
  
  ToF(txf->Initialize(
    frameDecode,
    loadProfile(frameDecode),
    destColorContext,
    destPixelFormat));
  
  CComPtr<IWICBitmapSource> bmpSrc;
  ToF(txf->QueryInterface(
    __uuidof(IWICBitmapSource), (void**)&bmpSrc));

  return bmpSrc;
}