WIC(Windows Imaging Component)のサンプルコードをそのまま写して使っていると、ある日突然 CopyPalette だけが謎の HRESULT で失敗し、「なぜか真っ黒の画像になる」「特定の PNG だけエラーになる」といった現象に悩まされがちです。本記事では、その原因のほとんどを占める「パレットなし画像に対して CopyPalette を呼んでいる」ケースを中心に、エラーが起きる状況と、実務で使える現実的な回避策を詳しく整理します。
WIC の CopyPalette が失敗する典型的なパターン
IWICBitmapDecoder や IWICBitmapFrameDecode の CopyPalette を呼び出すと、次のようなエラーコードで失敗することがあります。
WINCODEC_ERR_PALETTEUNAVAILABLEE_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_WICPixelFormat1bppIndexed | 1bpp | 2 色パレット |
GUID_WICPixelFormat2bppIndexed | 2bpp | 最大 4 色パレット |
GUID_WICPixelFormat4bppIndexed | 4bpp | 最大 16 色パレット |
GUID_WICPixelFormat8bppIndexed | 8bpp | 最大 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 を呼ぶ前に必ず行うべきチェック
もっとも重要なのは、パレットが存在しうる形式かどうかを事前に確認することです。具体的な手順は次のとおりです。
IWICBitmapFrameDecode::GetPixelFormatでピクセル形式 GUID を取得する。- その GUID が
GUID_WICPixelFormat8bppIndexedなどのインデックスカラー形式かどうか判定する。 - インデックスカラー形式の場合のみ
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 であれば、次のように設定します。
- 画像を GIMP で開く。
- メニューの「画像 > モード > インデックスカラー」を選ぶ。
- 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 デコードコードは「パターン化」してしまうのがおすすめです。
典型的な安全実装の流れ
- COM を
CoInitializeExで初期化。 IWICImagingFactoryを生成。CreateDecoderFromFilenameなどでIWICBitmapDecoderを生成。GetFrameCountでフレーム数を取得し、想定内かチェック。GetFrameで目的のフレーム(通常は 0)を取得。GetPixelFormatでピクセル形式を確認。- インデックスカラー形式ならパレットを
CreatePalette+CopyPaletteで取得。 - ピクセルデータ本体は
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 側での作業
- 元画像を GIMP で開く。
- メニューから「画像 > モード > インデックスカラー」を選択。
- 「最大色数」を 256 などに設定し、「変換」を実行する。
- 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 を安定して運用するうえでの鍵となります。

コメント