WIC CopyPaletteが失敗する原因とWINCODEC_ERR_PALETTEUNAVAILABLEの対処法

WIC(Windows Imaging Component)のサンプルコードをそのまま写して使っていると、ある日突然 CopyPalette だけが謎の HRESULT で失敗し、「なぜか真っ黒の画像になる」「特定の PNG だけエラーになる」といった現象に悩まされがちです。本記事では、その原因のほとんどを占める「パレットなし画像に対して CopyPalette を呼んでいる」ケースを中心に、エラーが起きる状況と、実務で使える現実的な回避策を詳しく整理します。

目次

WIC の CopyPalette が失敗する典型的なパターン

IWICBitmapDecoder や IWICBitmapFrameDecode の CopyPalette を呼び出すと、次のようなエラーコードで失敗することがあります。

  • WINCODEC_ERR_PALETTEUNAVAILABLE
  • E_INVALIDARG / WINCODEC_ERR_INVALIDPARAMETER
  • その他、デコーダー内部のエラー

もっとも多いのは WINCODEC_ERR_PALETTEUNAVAILABLE で、これは「この画像にはパレットが存在しない」という意味です。24bit / 32bit BMP、JPEG、Truecolor PNG など、現在一般的な画像形式の多くはパレットを持たないため、それらに対して CopyPalette を呼ぶと、仕様どおりこのエラーで失敗します。

状況ピクセル形式の例CopyPalette の結果
インデックスカラー画像(パレットあり)GUID_WICPixelFormat8bppIndexed などS_OK(パレット情報を取得できる)
Truecolor 画像(パレットなし)GUID_WICPixelFormat24bppBGR、GUID_WICPixelFormat32bppBGRA などWINCODEC_ERR_PALETTEUNAVAILABLE(仕様どおり失敗)
無効なポインタや未初期化 COM など―E_POINTER や WINCODEC_ERR_INVALIDPARAMETER など

つまり、WIC 側の不具合ではなく、「パレットが存在する前提でコードを書いている」ことが主な原因です。

そもそも「パレット付き画像」とは何か

CopyPalette の挙動を正しく理解するには、パレットとインデックスカラーの前提を押さえておく必要があります。

インデックスカラーとパレットの関係

インデックスカラー画像では、ピクセル値は「色そのもの」ではなく、「パレット配列のインデックス」を表します。たとえば 8bit インデックスカラー画像では、ピクセル値 0〜255 が「パレットテーブルの 0〜255 番目の色」を指す仕組みになっています。

  • パレット:最大 256 個程度の RGB(場合によってはアルファ付き)の配列
  • ピクセルデータ:0〜255 のインデックス値だけを持つ

このとき WIC では、IWICBitmapFrameDecode::CopyPalette を通じて、その「パレット配列」をアプリケーション側にコピーできます。逆に言うと、パレットを使っていない Truecolor 画像(1 ピクセルごとに RGB を直接持つ画像)には、そもそもコピーするパレットが存在しません。

WIC でインデックスカラーを表すピクセルフォーマット

WIC には多数のピクセルフォーマット GUID が存在しますが、「Indexed」が付くものはインデックスカラー形式であると考えて差し支えありません。

代表的なインデックスカラー形式ビット数備考
GUID_WICPixelFormat1bppIndexed1bpp2 色パレット
GUID_WICPixelFormat2bppIndexed2bpp最大 4 色パレット
GUID_WICPixelFormat4bppIndexed4bpp最大 16 色パレット
GUID_WICPixelFormat8bppIndexed8bpp最大 256 色パレット(もっとも一般的)

これら以外、たとえば GUID_WICPixelFormat24bppBGR や GUID_WICPixelFormat32bppBGRA のような形式は Truecolor であり、CopyPalette を呼んでもパレットは返ってきません。

CopyPalette 失敗時のエラーコードと意味・対処

実際に CopyPalette が失敗したときに HRESULT をきちんとログに残しておくと、原因の切り分けが非常に楽になります。代表的なエラーと、その意味・対処方法をまとめると次のようになります。

エラーコード意味よくある原因主な対処方法
WINCODEC_ERR_PALETTEUNAVAILABLEこのフレームにはパレットが存在しないTruecolor 画像に対して CopyPalette を呼んでいるGetPixelFormat で Indexed 形式かどうか事前チェック 非インデックス画像では CopyPalette を呼ばない どうしても必要なら自前のパレットを生成する
E_POINTER無効なポインタが渡されたIWICPalette* が nullptr / 解放済みComPtr 使用やスマートポインタでライフタイム管理を見直す
WINCODEC_ERR_INVALIDPARAMETER など不正なパラメータCOM の初期化漏れ・デコーダーの生成失敗など前段エラー前段の HRESULT を順に確認し、失敗を無視していないかチェック

特に WINCODEC_ERR_PALETTEUNAVAILABLE は予想される「正常系の一種」と考え、ログには出しつつも「警告」レベルにとどめる、といった扱いにしておくと運用しやすくなります。

CopyPalette を呼ぶ前に必ず行うべきチェック

もっとも重要なのは、パレットが存在しうる形式かどうかを事前に確認することです。具体的な手順は次のとおりです。

  1. IWICBitmapFrameDecode::GetPixelFormat でピクセル形式 GUID を取得する。
  2. その GUID が GUID_WICPixelFormat8bppIndexed などのインデックスカラー形式かどうか判定する。
  3. インデックスカラー形式の場合のみ CopyPalette を呼ぶ。

ピクセル形式をチェックするサンプルコード

// pFrame : IWICBitmapFrameDecode*
// pFactory : IWICImagingFactory*

GUID pixelFormat = {};
HRESULT hr = pFrame->GetPixelFormat(&pixelFormat);
if (FAILED(hr)) {
    // ログ出力など
    return hr;
}

bool isIndexed = 
    IsEqualGUID(pixelFormat, GUID_WICPixelFormat1bppIndexed) ||
    IsEqualGUID(pixelFormat, GUID_WICPixelFormat2bppIndexed) ||
    IsEqualGUID(pixelFormat, GUID_WICPixelFormat4bppIndexed) ||
    IsEqualGUID(pixelFormat, GUID_WICPixelFormat8bppIndexed);

if (!isIndexed) {
    // この画像はパレットを持たないので CopyPalette は呼ばない
    // そのまま Truecolor として処理する
    return S_FALSE; // 独自定義で「パレットなし」を表すのもあり
}

// パレットを生成
ComPtr<IWICPalette> palette;
hr = pFactory->CreatePalette(&palette);
if (FAILED(hr)) {
    return hr;
}

// 実際にパレットをコピー
hr = pFrame->CopyPalette(palette.Get());
if (FAILED(hr)) {
    // ここに来るのは、フォーマット情報と実データが矛盾している場合など、かなりレアケース
    return hr;
}

このように、「Indexed 形式でなければそもそも CopyPalette を呼ばない」という設計にしておくと、WINCODEC_ERR_PALETTEUNAVAILABLE はほぼ出なくなります。

パレットが存在しない画像をどう扱うか

とはいえ、アプリケーション側の仕様として「必ずパレットが欲しい」というケースもあります。たとえば「パレット付き画像として別形式に書き出したい」「独自の減色処理でインデックスカラー化したい」といったシーンです。この場合、取れるアプローチは大きく次の 3 つです。

  • 画像自体をパレット形式に変換してから読み込む
  • WIC 上で変換・量子化してパレット付きビットマップを作る
  • IWICPalette::InitializeCustom などで自前のパレットを作る

外部ツールであらかじめパレット形式に変換する

もっとも簡単なのは、画像編集ツールを使って 8bit インデックスカラーに変換しておく方法です。たとえば GIMP であれば、次のように設定します。

  1. 画像を GIMP で開く。
  2. メニューの「画像 > モード > インデックスカラー」を選ぶ。
  3. 256 色以下のパレットを指定して変換し、PNG などで保存する。

こうして作成した画像は GUID_WICPixelFormat8bppIndexed などのインデックスカラー形式として WIC に読み込まれるため、CopyPalette は通常どおり成功します。

WIC でインデックスカラーへ変換してからパレットを取得する

外部ツールを使えない場合や、実行時に動的に Truecolor 画像をインデックスカラーに変換したい場合は、WIC のフォーマットコンバータとパレット生成機能を組み合わせます。

// Truecolor のフレームから 8bpp インデックスカラーに変換する例

ComPtr<IWICFormatConverter> converter;
hr = pFactory->CreateFormatConverter(&converter);
if (FAILED(hr)) return hr;

// パレットを自動生成させる場合
ComPtr<IWICPalette> palette;
hr = pFactory->CreatePalette(&palette);
if (FAILED(hr)) return hr;

// WIC の既定パレットを利用する例(Web セーフカラーなど)
hr = palette->InitializePredefined(WICBitmapPaletteTypeWebPalette, FALSE);
if (FAILED(hr)) return hr;

hr = converter->Initialize(
    pFrame,                             // 元のフレーム
    GUID_WICPixelFormat8bppIndexed,     // 変換後の形式
    WICBitmapDitherTypeErrorDiffusion,  // ディザリング方法
    palette.Get(),                      // 使用するパレット
    0.0,                                // アルファしきい値(未使用なら 0)
    WICBitmapPaletteTypeCustom          // パレット種別
);
if (FAILED(hr)) return hr;

このようにしてインデックスカラーへ変換したビットマップからであれば、CopyPalette を使って変換後パレットを取得することも可能です。

自前でパレットを生成して InitializeCustom する

「画像ファイルのパレットではなく、アプリケーション側で決めた色テーブルを使いたい」という場合は、IWICPalette::InitializeCustom を使って任意のパレットを定義できます。

// 256 階調グレースケールパレットを自前で作る例

const UINT colorCount = 256;
WICColor colors[colorCount];

for (UINT i = 0; i < colorCount; ++i) {
    BYTE v = static_cast<BYTE>(i);
    colors[i] = 0xFF000000 | (v << 16) | (v << 8) | v; // ARGB (A=255, R=G=B=v)
}

ComPtr<IWICPalette> palette;
hr = pFactory->CreatePalette(&palette);
if (FAILED(hr)) return hr;

hr = palette->InitializeCustom(colors, colorCount);
if (FAILED(hr)) return hr;

// 以降、converter->Initialize(..., GUID_WICPixelFormat8bppIndexed, ..., palette.Get(), ...) のように使用

この方法であれば、元画像が Truecolor であっても、「最終的にはアプリケーション指定のパレットに量子化されたインデックスカラー画像」を生成できます。

CopyPalette 呼び出しまわりの実装パターン

実務でありがちなバグを避けるために、CopyPalette を含む WIC デコードコードは「パターン化」してしまうのがおすすめです。

典型的な安全実装の流れ

  1. COM を CoInitializeEx で初期化。
  2. IWICImagingFactory を生成。
  3. CreateDecoderFromFilename などで IWICBitmapDecoder を生成。
  4. GetFrameCount でフレーム数を取得し、想定内かチェック。
  5. GetFrame で目的のフレーム(通常は 0)を取得。
  6. GetPixelFormat でピクセル形式を確認。
  7. インデックスカラー形式ならパレットを CreatePalette + CopyPalette で取得。
  8. ピクセルデータ本体は CopyPixels で取得。

フレーム数のチェックやピクセル形式のチェックを省略してしまうと、CopyPalette に限らず「特定のファイルだけ表示できない」という落とし穴にはまりがちです。

GetFrameCount チェックの論理演算子ミスに注意

質問文でも触れられているように、GetFrameCount の結果をチェックする条件式で、誤って論理 OR(||)ではなく別の演算子を使っているケースがあります。たとえば次のようなコードです。

UINT frameCount = 0;
hr = decoder->GetFrameCount(&frameCount);
if (FAILED(hr)) return hr;

// 間違った例(ありがちなバグ)
// if (frameCount == 1 || frameCount == 0) { ... } など

// 期待しているのは「フレーム数が 1 である」ことだけなら、単純にこうでよい
if (frameCount != 1) {
    // 想定外のマルチフレーム画像(アニメ GIF など)
    return E_FAIL;
}

あるいは「フレーム数が 1 かつ、取得するフレームインデックスが 0 であること」を確認する場合は、次のように論理 AND(&&)を使います。

if (frameCount == 1 && frameIndex == 0) {
    // 安全に単一フレームとして処理してよい
}

このあたりのチェックが曖昧だと、「想定外のフレームに対して CopyPalette を呼んでいる」状況になり、結果としてエラーが返ってくる場合もあります。

デバッグ時に必ず確認したいポイント

CopyPalette の失敗で悩んだとき、最低限次の 3 つを確認すると、原因にたどり着きやすくなります。

1. すべての HRESULT をログに出しているか

CopyPalette のエラーそのものよりも、その前段で既にエラーが発生しているのに無視しているケースが多く見られます。たとえば:

  • CreateDecoderFromFilename が失敗している
  • GetFrame が失敗している(範囲外のインデックスを指定など)
  • GetPixelFormat が失敗している

これらを無視したまま CopyPalette に進んでしまうと、まったく別のエラーコードが返ってきて、原因が分かりにくくなってしまいます。すべての HRESULT をログに残し、最初に失敗した場所を特定する習慣をつけましょう。

2. ピクセル形式は本当に Indexed か

「資料では 8bit インデックスカラーと書いてあるから大丈夫だろう」と思い込み、実際には PNG 保存時の設定変更で Truecolor になっていた、というケースもあります。実際に GetPixelFormat で得られた GUID をログに出して確認するのが最も確実です。

3. 同じ画像でもツールの保存設定によって挙動が変わる

画像編集ツールによっては、見た目は同じでも、保存時のオプションによって「インデックスカラー PNG」と「Truecolor PNG」が簡単に切り替わります。同じファイル名・ほぼ同じルックでも、形式だけが違うと CopyPalette の成否が変わる点は、テスト時に注意すべきポイントです。

具体例:GIMP でパレット付き PNG に変換してから CopyPalette する

質問文にある手順を、WIC の観点からもう少し具体的に整理してみます。

GIMP 側での作業

  1. 元画像を GIMP で開く。
  2. メニューから「画像 > モード > インデックスカラー」を選択。
  3. 「最大色数」を 256 などに設定し、「変換」を実行する。
  4. PNG 形式で保存する(必要に応じて「パレットを保存」のオプションを有効にする)。

こうして保存された PNG は、「8bit インデックスカラー + パレット付き画像」として WIC に認識されます。

WIC 側での挙動

この画像を WIC で読み込むと、次のような挙動になります。

  • GetPixelFormat は GUID_WICPixelFormat8bppIndexed を返す。
  • CopyPalette は S_OK を返し、GIMP で生成されたパレットが取得できる。
  • CopyPixels で取得したピクセル値は、パレットのインデックスとして解釈される。

元の画像が Truecolor であっても、このように「事前にパレット付き形式に変換しておく」ことで、WIC 側では何も特別なことをしなくても CopyPalette を利用できるようになります。

設計レベルで見直したいポイント

最後に、「本当にその処理にパレットが必要なのか?」という視点から、設計の見直しポイントをいくつか挙げておきます。

Truecolor 画像ではパレット不要なケースが多い

最近のアプリケーションでは、内部表現を 32bit RGBA(GUID_WICPixelFormat32bppPBGRA など)に統一し、すべての画像を Truecolor として扱う方が実装がシンプルになることが多くあります。この場合、CopyPalette はそもそも必要なく、パレット情報を取得できなくても何の問題もありません。

「なんとなくパレットも取らないといけない気がして CopyPalette を呼んでいる」というレベルであれば、その呼び出し自体を削除してしまうことも選択肢になります。

パレットがあれば使う、なければ素直に諦める設計

どうしても「パレットがあればベストだが、なくても動作させたい」場合は、次のような設計にしておくと柔軟です。

bool TryGetPalette(IWICBitmapFrameDecode* frame,
                   IWICImagingFactory* factory,
                   ComPtr<IWICPalette>& outPalette)
{
    GUID pixelFormat = {};
    HRESULT hr = frame->GetPixelFormat(&pixelFormat);
    if (FAILED(hr)) return false;

    bool isIndexed =
        IsEqualGUID(pixelFormat, GUID_WICPixelFormat1bppIndexed) ||
        IsEqualGUID(pixelFormat, GUID_WICPixelFormat2bppIndexed) ||
        IsEqualGUID(pixelFormat, GUID_WICPixelFormat4bppIndexed) ||
        IsEqualGUID(pixelFormat, GUID_WICPixelFormat8bppIndexed);

    if (!isIndexed) {
        return false; // そもそもパレットを持たない形式
    }

    ComPtr<IWICPalette> palette;
    hr = factory->CreatePalette(&palette);
    if (FAILED(hr)) return false;

    hr = frame->CopyPalette(palette.Get());
    if (FAILED(hr)) return false;

    outPalette = palette;
    return true;
}

呼び出し側では、TryGetPalette の戻り値を確認し、取得できた場合だけパレットを使い、そうでなければ通常の Truecolor 処理にフォールバックする、といった構成にしておくと、Truecolor/インデックスカラーの両方に自然に対応できます。

エラーレベルの整理:本当に「エラー」か?

WINCODEC_ERR_PALETTEUNAVAILABLE を「障害」としてログに記録してしまうと、Truecolor 画像を大量に読み込むアプリケーションではログがエラーで埋まってしまい、本当に問題のあるエラーが埋もれてしまいます。多くの場合、これは「仕様どおりの正常な結果」であり、ログ出力レベルは次のように整理するのがおすすめです。

  • S_OK:情報(パレット取得成功)
  • WINCODEC_ERR_PALETTEUNAVAILABLE:デバッグまたは詳細ログ(想定内)
  • それ以外の失敗:警告またはエラー(想定外)

このようにログのレベル分けをしておくと、運用時に「本当に調査が必要なエラー」だけをすばやく発見できます。

まとめ

WIC の CopyPalette が失敗する原因のほとんどは、元画像が Truecolor でパレットを持たないにもかかわらず、パレットがある前提で処理していることにあります。Truecolor 画像に対しては CopyPalette を呼んでも必ず失敗する、という仕様を理解しておくことが第一歩です。

そのうえで、実装としては次のポイントを押さえておくと、トラブルを大きく減らせます。

  • GetPixelFormat でインデックスカラーかどうかを必ずチェックする。
  • Indexed 形式以外では CopyPalette を呼ばない、あるいは失敗をエラーとはみなさない。
  • パレットが必要なら、外部ツールや WIC のフォーマットコンバータ、自前パレット生成でインデックスカラー画像を用意する。
  • すべての HRESULT をログに残し、最初に失敗した地点からデバッグする。
  • Truecolor 画像中心のワークフローでは、そもそもパレット API を使わない設計も検討する。

これらを踏まえてコードを整理すれば、「特定の画像だけ CopyPalette が失敗する」「WINCODEC_ERR_PALETTEUNAVAILABLE が大量に出て困る」といった悩みはほぼ解消できます。パレットは「インデックスカラー画像だけが持つメタデータ」であることを意識し、Truecolor とインデックスカラーをきちんと区別した設計を行うことが、WIC を安定して運用するうえでの鍵となります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次