Qtで半透明画像を操る:qUnpremultiply()を使ったピクセル操作の具体例

2025-05-27

QtにおけるqUnpremultiply()関数は、プレマルチプライされたアルファ値を持つ色(QColorまたはQRgb)のコンポーネントを、プレマルチプライされていない形式に戻すために使用されます。

もう少し詳しく説明します。

グラフィックスプログラミングにおいて、アルファブレンディング(透明度の表現)を行う際には、色の各成分(赤、緑、青)にアルファ値(透明度)を乗算する「プレマルチプライドアルファ」という手法がよく使われます。

  • プレマルチプライされていないアルファ (Unpremultiplied Alpha)
    各ピクセルの赤、緑、青の成分が、アルファ値とは独立して純粋な色の値として表現されている状態です。透明度を扱う際によく用いられる、一般的な色の表現方法です。

  • プレマルチプライドアルファ (Premultiplied Alpha)
    各ピクセルの赤、緑、青の成分が、そのピクセルのアルファ値によって既にスケーリング(乗算)されている状態の色です。 例えば、アルファ値が0.5 (50%) のピクセルで、元の色が (R, G, B) だった場合、プレマルチプライされた色はその半分の明るさの (0.5R, 0.5G, 0.5B) となります。この形式は、複数の半透明なオブジェクトを重ねて描画する際に、計算を簡略化できるという利点があります。

なぜqUnpremultiply()が必要なのか?

Qt内部、特にレンダリングパイプラインの一部では、パフォーマンス最適化のためにプレマルチプライドアルファ形式で色を扱うことがあります。しかし、アプリケーションが色の値を直接操作する場合や、他のライブラリに色を渡す場合など、プレマルチプライされていない(純粋な)アルファ形式の色が必要になることがあります。

qUnpremultiply()は、そのような場合に、プレマルチプライされた色を入力として受け取り、アルファ値を考慮して各色成分を元のプレマルチプライされていない値に戻します。

具体的な計算

qUnpremultiply(QRgb)またはqUnpremultiply(QColor)は、以下のような計算を行います。

  • rgbをそれぞれプレマルチプライされた赤、緑、青の成分とします。
  • aをアルファ値(0から255の範囲)とします。

もしaが0でない場合、

  • 新しい青成分 = b / (a / 255.0)
  • 新しい緑成分 = g / (a / 255.0)
  • 新しい赤成分 = r / (a / 255.0)

となります。aが0の場合は、R, G, B成分も0として扱われます。

使用例

#include <QColor>
#include <QRgb>
#include <QDebug>

int main() {
    // プレマルチプライされた色を作成(例:アルファ値50%の赤)
    // Qt内部では、赤(255) * 0.5 = 127 となる
    QRgb premultipliedRed = QColor(255, 0, 0, 128).premultiplied().rgba(); // アルファ128 (約50%)

    qDebug() << "Premultiplied Red (RGBA):"
             << qRed(premultipliedRed) << ","
             << qGreen(premultipliedRed) << ","
             << qBlue(premultipliedRed) << ","
             << qAlpha(premultipliedRed);

    // qUnpremultiply() を使ってプレマルチプライされていない色に戻す
    QRgb unpremultipliedRed = qUnpremultiply(premultipliedRed);

    qDebug() << "Unpremultiplied Red (RGBA):"
             << qRed(unpremultipliedRed) << ","
             << qGreen(unpremultipliedRed) << ","
             << qBlue(unpremultipliedRed) << ","
             << qAlpha(unpremultipliedRed);

    // QColorオブジェクトの場合
    QColor premultipliedColor = QColor(0, 0, 255, 64).premultiplied(); // 青(255), アルファ64 (約25%)
    qDebug() << "Premultiplied Blue (QColor):" << premultipliedColor.red() << premultipliedColor.green() << premultipliedColor.blue() << premultipliedColor.alpha();

    QColor unpremultipliedColor = qUnpremultiply(premultipliedColor);
    qDebug() << "Unpremultiplied Blue (QColor):" << unpremultipliedColor.red() << unpremultipliedColor.green() << unpremultipliedColor.blue() << unpremultipliedColor.alpha();

    return 0;
}

上記のコードを実行すると、以下のような出力が得られます。

Premultiplied Red (RGBA): 128 , 0 , 0 , 128 // (255 * 0.5, 0, 0, 128)
Unpremultiplied Red (RGBA): 255 , 0 , 0 , 128 // (255, 0, 0, 128)

Premultiplied Blue (QColor): 0 0 64 64 // (0, 0, 255 * 0.25, 64)
Unpremultiplied Blue (QColor): 0 0 255 64 // (0, 0, 255, 64)


誤った前提:入力が既にプレマルチプライされていると想定していない

  • トラブルシューティング
    • 入力色の確認
      qUnpremultiply()を適用する前に、その色が本当にプレマルチプライされているかを確認してください。もし、Qtのレンダリングパイプラインから直接取得した色や、QColor::premultiplied()で生成された色でない場合、それは通常プレマルチプライされていません。
    • 不要な変換の回避
      プレマルチプライされていない色を操作したい場合は、最初からqUnpremultiply()を呼ばずに、直接その色を扱ってください。
  • 症状
    • 透明度が薄い(アルファ値が低い)ピクセルが、非常に明るく、ほとんど白っぽく表示される。
    • 色が期待通りに表示されない(特に半透明な部分)。
  • エラーの状況
    qUnpremultiply()は、入力される色が既にプレマルチプライされているという前提で動作します。もし、入力される色が元々プレマルチプライされていない(一般的なRGB形式の)色であるにもかかわらずqUnpremultiply()を適用すると、色の各成分が不適切に明るくなりすぎたり、透明度がおかしくなったりします。

アルファ値が0の場合の浮動小数点計算の課題

  • トラブルシューティング
    • QtのqUnpremultiply()関数をそのまま使用してください。これはゼロ除算を適切に処理します。
    • カスタムで同様の処理を行う場合、if (alpha != 0)のチェックを必ず行い、アルファが0の場合はR, G, Bも0にするなどの対策を取ってください。
  • 症状
    稀に、極端な浮動小数点精度誤差により、完全に透明なピクセルでごくわずかな色のアーティファクトが発生する可能性があります(Qtの組み込み関数ではあまり問題になりませんが、カスタム実装の場合)。
  • エラーの状況
    qUnpremultiply()は、色成分をアルファ値で除算して元の値に戻します。もしアルファ値が0(完全に透明)の場合、ゼロ除算が発生します。QtのqUnpremultiply()の実装はこれを適切に処理し、アルファ値が0の場合はR, G, B成分も0に設定しますが、もし自分で同様のロジックを実装する場合、この点に注意が必要です。

データ型の不一致や誤った使用法

  • トラブルシューティング
    • 正しい入力型を使用
      qUnpremultiply()に渡すデータがQRgbまたはQColorであることを確認してください。
    • データ変換
      もし他の形式のデータがある場合、先にそれをQRgbQColorに適切に変換してからqUnpremultiply()を適用してください。例えば、QImageのピクセルデータを操作する場合は、image.pixel(x, y)QRgbを取得し、qUnpremultiply()を適用した後にimage.setPixel(x, y, newRgb)で設定します。
  • 症状
    • コンパイルエラー(型変換エラー)。
    • 実行時エラー、クラッシュ、または予期しない画像の破損。
  • エラーの状況
    qUnpremultiply()QRgbまたはQColor型を受け取ります。異なる型のデータ(例: 生のバイト配列や他の画像ライブラリの型)に直接適用しようとすると、コンパイルエラーや未定義の動作が発生します。

期待する「元の色」とのずれ

  • トラブルシューティング
    • 視覚的な許容範囲
      多くのグラフィックスアプリケーションでは、このような微細な色のずれは視覚的に許容される範囲です。
    • 色のプロファイル
      色のプロファイル(sRGBなど)を考慮する必要がある場合は、qUnpremultiply()だけでは不十分で、別途カラーマネジメントの処理が必要になります。QtのQColorは、色のプロファイルも扱える場合がありますが、複雑な場合はQImageのカラープロファイル関連の機能も検討してください。
    • デバッグ出力
      qDebug()などを使って、変換前後のRGB値を詳細に確認し、ずれのパターンを把握してください。
  • 症状
    • qUnpremultiply()後の色が、わずかに色合いが違って見える、または期待したRGB値と若干ずれている。
  • エラーの状況
    qUnpremultiply()はあくまで「プレマルチプライされたアルファ値」を元に戻すものです。しかし、元の色がどのように定義されていたか、あるいは画像のエンコード時にどのような変換が行われたかによって、qUnpremultiply()を適用した結果が、あなたが期待する「純粋な元の色」とわずかに異なる場合があります。これは、浮動小数点計算の丸め誤差や、色のプロファイルの違いなどが原因で起こりえます。
  • トラブルシューティング
    • 必要な場合にのみ使用
      プレマルチプライされたアルファを必要とするQtの描画関数に直接色を渡す場合は、qUnpremultiply()は不要です。本当にプレマルチプライされていない形式が必要な場合にのみ変換を行ってください。
    • バッチ処理
      可能であれば、ピクセルをループで1つずつ変換するのではなく、より効率的なバッチ処理やGPUベースのシェーダーなどを検討してください。ただし、これはより高度なテクニックになります。
    • Qtの画像変換関数
      QImage::convertToFormat()など、Qtが提供する画像変換関数が内部でqUnpremultiply()に相当する処理を効率的に行っている場合があります。それらの関数が目的の変換をサポートしているか確認してください。
  • 症状
    • 画像処理が遅い。
    • アニメーションがカクつく。
  • エラーの状況 (直接的なエラーではないが注意点)
    qUnpremultiply()は内部で除算を行うため、ピクセルごとに大量に呼び出すと、パフォーマンスに影響を与える可能性があります。特に、大きな画像をピクセル単位で変換する場合に顕著になります。


例1: QColorオブジェクトのプレマルチプライドアルファ解除

最も基本的な使用例です。QColorオブジェクトがプレマルチプライドアルファ形式で格納されている場合に、その元のRGB値を取り出したいときに使います。

#include <QApplication>
#include <QColor>
#include <QDebug> // デバッグ出力用

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    // (1) プレマルチプライされていない元の色 (赤、半透明)
    QColor originalColor(255, 0, 0, 128); // 赤(255), 緑(0), 青(0), アルファ(128) - 約50%透明

    qDebug() << "--- Original Color (Unpremultiplied) ---";
    qDebug() << "Red:" << originalColor.red();
    qDebug() << "Green:" << originalColor.green();
    qDebug() << "Blue:" << originalColor.blue();
    qDebug() << "Alpha:" << originalColor.alpha();
    qDebug() << "Is premultiplied (should be false):" << originalColor.premultiplied(); // QColorはデフォルトでunpremultiplied

    // (2) プレマルチプライドアルファ形式に変換
    // QColor::premultiplied() は新しいQColorオブジェクトを返します
    QColor premultipliedColor = originalColor.premultiplied();

    qDebug() << "\n--- Premultiplied Color ---";
    // プレマルチプライされたR, G, B成分はアルファ値に応じてスケーリングされる
    // (例: 255 * (128/255) = 128)
    qDebug() << "Red:" << premultipliedColor.red();   // 約128になるはず
    qDebug() << "Green:" << premultipliedColor.green(); // 0のまま
    qDebug() << "Blue:" << premultipliedColor.blue();   // 0のまま
    qDebug() << "Alpha:" << premultipliedColor.alpha(); // 128のまま
    qDebug() << "Is premultiplied (should be true):" << premultipliedColor.premultiplied();

    // (3) qUnpremultiply() を使って元の形式に戻す
    QColor restoredColor = qUnpremultiply(premultipliedColor);

    qDebug() << "\n--- Restored Color (using qUnpremultiply()) ---";
    qDebug() << "Red:" << restoredColor.red();   // 255に戻るはず
    qDebug() << "Green:" << restoredColor.green(); // 0のまま
    qDebug() << "Blue:" << restoredColor.blue();   // 0のまま
    qDebug() << "Alpha:" << restoredColor.alpha(); // 128のまま
    qDebug() << "Is premultiplied (still true for QColor, but values are unpremultiplied):" << restoredColor.premultiplied();

    // qUnpremultiplyはQColorのpremultipliedフラグを変更しないため、この出力はtrueのままです。
    // 値がunpremultipliedに戻ったことを確認するのが重要です。

    // (4) QRgb (quint32) 値での使用例
    QRgb originalRgb = qRgba(255, 0, 0, 128);
    QRgb premultipliedRgb = qPremultiply(originalRgb);

    qDebug() << "\n--- QRgb Example ---";
    qDebug() << "Original QRgb (R,G,B,A):"
             << qRed(originalRgb) << ","
             << qGreen(originalRgb) << ","
             << qBlue(originalRgb) << ","
             << qAlpha(originalRgb);

    qDebug() << "Premultiplied QRgb (R,G,B,A):"
             << qRed(premultipliedRgb) << ","
             << qGreen(premultipliedRgb) << ","
             << qBlue(premultipliedRgb) << ","
             << qAlpha(premultipliedRgb);

    QRgb restoredRgb = qUnpremultiply(premultipliedRgb);

    qDebug() << "Restored QRgb (R,G,B,A):"
             << qRed(restoredRgb) << ","
             << qGreen(restoredRgb) << ","
             << qBlue(restoredRgb) << ","
             << qAlpha(restoredRgb);


    return app.exec();
}

解説

  • QRgb型でも同様に、qPremultiply()qUnpremultiply()を使って変換できます。
  • qUnpremultiply(premultipliedColor); を使用すると、スケーリングされたR, G, B成分が元の値(この場合は255, 0, 0)に戻されます。
  • originalColor.premultiplied(); を呼び出すと、新しいQColorオブジェクトが返され、その中のR, G, B成分がアルファ値でスケーリングされます。これにより、赤は255 * (128/255) = 128に、緑と青は0のままになります。
  • QColor originalColor(255, 0, 0, 128); は、アルファ値128(約50%透明)の赤色を表現します。これはプレマルチプライされていない形式です。

例2: QImageのピクセルデータを操作する

画像処理において、ピクセルデータがプレマルチプライドアルファ形式で格納されている場合(例: QImage::Format_ARGB32_Premultiplied)、個々のピクセルを操作する際にqUnpremultiply()が必要になることがあります。

#include <QApplication>
#include <QImage>
#include <QPainter>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
#include <QDebug>

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    // 透明度を持つ画像を生成
    QImage originalImage(100, 100, QImage::Format_ARGB32);
    originalImage.fill(Qt::transparent); // 全体を透明で初期化

    QPainter painter(&originalImage);
    painter.setBrush(QColor(255, 0, 0, 128)); // 半透明な赤
    painter.drawEllipse(0, 0, 100, 100);
    painter.end();

    // プレマルチプライドアルファ形式の画像に変換
    QImage premultipliedImage = originalImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);

    // プレマルチプライドイメージのピクセルを読み取り、qUnpremultiply()を適用して新しいイメージを作成
    QImage unpremultipliedImage(premultipliedImage.size(), QImage::Format_ARGB32);

    for (int y = 0; y < premultipliedImage.height(); ++y) {
        for (int x = 0; x < premultipliedImage.width(); ++x) {
            QRgb premultipliedPixel = premultipliedImage.pixel(x, y);
            QRgb unpremultipliedPixel = qUnpremultiply(premultipliedPixel);
            unpremultipliedImage.setPixel(x, y, unpremultipliedPixel);
        }
    }

    // 表示用のウィジェット
    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *originalLabel = new QLabel("Original (ARGB32)");
    originalLabel->setPixmap(QPixmap::fromImage(originalImage));
    layout->addWidget(originalLabel);

    QLabel *premultipliedLabel = new QLabel("Premultiplied (ARGB32_Premultiplied)");
    premultipliedLabel->setPixmap(QPixmap::fromImage(premultipliedImage));
    layout->addWidget(premultipliedLabel);

    QLabel *unpremultipliedLabel = new QLabel("Unpremultiplied (ARGB32 via qUnpremultiply)");
    unpremultipliedLabel->setPixmap(QPixmap::fromImage(unpremultipliedImage));
    layout->addWidget(unpremultipliedLabel);

    window.setWindowTitle("qUnpremultiply() Example");
    window.show();

    return app.exec();
}

解説

  • 3つのラベルで、元の画像、プレマルチプライドされた画像、そしてqUnpremultiply()で復元された画像を表示し、視覚的に違いを確認できるようにしています。
  • その後、premultipliedImageの各ピクセルをループで取得し、qUnpremultiply()を適用して元の純粋な色に戻し、unpremultipliedImageに設定しています。
  • originalImage.convertToFormat(QImage::Format_ARGB32_Premultiplied); を使用して、画像をプレマルチプライドアルファ形式に変換します。この時点で、premultipliedImageの各ピクセルは、色成分がアルファ値で乗算された状態になっています。
  • originalImageは一般的なFormat_ARGB32で作成され、半透明な円が描画されます。

例3: カスタム描画やシェーダーでの色の調整

Qtの描画システム(QPainter)は内部的にプレマルチプライドアルファを扱うことがありますが、OpenGLシェーダーやカスタム描画ロジックで直接ピクセルを扱う場合、qUnpremultiply()が役立つことがあります。

例えば、QPainterから取得したQRgb値を、プレマルチプライされていない形式を期待する別の描画ライブラリやシェーダーに渡す前に変換するといったシナリオです。

// これは完全な実行可能な例というよりは、概念的なコードスニペットです。
// 通常、このようなカスタム描画では、QOpenGLWidgetなどを使い、
// OpenGLコンテキスト内で直接ピクセルデータを操作することになります。

void MyCustomDrawingWidget::drawPixel(int x, int y, QRgb premultipliedPixelFromQt) {
    // QtのQPainterやQImageから取得したピクセルは、
    // 使用しているフォーマットによってはプレマルチプライされている可能性があります。
    // ここでは、そのピクセルがプレマルチプライされていると仮定します。

    QRgb unpremultipliedPixel = qUnpremultiply(premultipliedPixelFromQt);

    // これで unpremultipliedPixel は、R, G, Bがアルファ値でスケーリングされていない
    // 純粋な色成分を持つことになります。

    // 例えば、この色をOpenGLシェーダーのuniform変数として渡す場合:
    // float r = qRed(unpremultipliedPixel) / 255.0f;
    // float g = qGreen(unpremultipliedPixel) / 255.0f;
    // float b = qBlue(unpremultipliedPixel) / 255.0f;
    // float a = qAlpha(unpremultipliedPixel) / 255.0f;

    // myShaderProgram.setUniformValue("pixelColor", QVector4D(r, g, b, a));

    // あるいは、別のライブラリのAPIに渡す場合:
    // someOtherGraphicsLibrary.setColor(unpremultipliedPixel);
}

// QOpenGLWidgetのpaintGL()などで、レンダリングターゲットから直接ピクセルを読み取る場合
void MyOpenGLWidget::paintGL() {
    // ... レンダリングコード ...

    // 例えば、QOpenGLFramebufferObject::toImage() などで画像を取得した場合
    // その画像がPremultiplied形式である可能性
    QImage framebufferImage = myFbo->toImage(); // これがPremultiplied形式かもしれない

    // 各ピクセルをunpremultiplyして表示したり、別の処理に使う
    QImage displayImage(framebufferImage.size(), QImage::Format_ARGB32);
    for (int y = 0; y < framebufferImage.height(); ++y) {
        for (int x = 0; x < framebufferImage.width(); ++x) {
            QRgb pixel = framebufferImage.pixel(x, y);
            displayImage.setPixel(x, y, qUnpremultiply(pixel));
        }
    }
    // displayImage を further process or save
}
  • QImage::Format_ARGB32_Premultipliedのような形式で画像データを扱う場合、そのピクセル値は既にアルファ乗算されているため、純粋なR, G, B値が必要な場合はqUnpremultiply()で逆変換する必要があります。
  • この例は、qUnpremultiply()がより低レベルのグラフィックスプログラミングで、異なる色の表現体系の間で変換を行うブリッジとして機能することを示しています。


ここでは、qUnpremultiply()の代替方法と、それぞれのシナリオでの使い分けについて説明します。

qUnpremultiply()の代替方法

手動での計算(R/G/B成分の除算)

qUnpremultiply()が内部で行っている計算を、自分で直接実装する方法です。主にQRgb型(quint32)の色値を扱う場合に有効です。

  • 欠点
    自分で実装すると、丸め誤差やゼロ除算の処理に気を使う必要があります。通常はqUnpremultiply()を使う方が安全で推奨されます。

  • 利点
    qUnpremultiply()が提供されていない古いQtバージョンを使用している場合や、特定の最適化(例えばSIMD命令の利用)のために低レベルで制御したい場合に有効です。

  • #include <QRgb>
    #include <QDebug>
    #include <algorithm> // for std::min/max
    
    QRgb unpremultiplyManual(QRgb premultipliedRgb) {
        int a = qAlpha(premultipliedRgb);
        int r_p = qRed(premultipliedRgb);
        int g_p = qGreen(premultipliedRgb);
        int b_p = qBlue(premultipliedRgb);
    
        if (a == 0) {
            return qRgba(0, 0, 0, 0); // アルファが0なら、色も0
        } else {
            // アルファ値で除算して、元の色成分を計算
            // 丸め処理のために (a / 2) を加える
            int r_u = (r_p * 255 + a / 2) / a;
            int g_u = (g_p * 255 + a / 2) / a;
            int b_u = (b_p * 255 + a / 2) / a;
    
            // 結果が0-255の範囲に収まるようにクランプ
            r_u = std::min(255, std::max(0, r_u));
            g_u = std::min(255, std::max(0, g_u));
            b_u = std::min(255, std::max(0, b_u));
    
            return qRgba(r_u, g_u, b_u, a);
        }
    }
    
    int main() {
        QRgb premultipliedColor = qRgba(128, 0, 0, 128); // 赤(255)が半透明で128になった状態
    
        qDebug() << "Premultiplied R,G,B,A:"
                 << qRed(premultipliedColor) << ","
                 << qGreen(premultipliedColor) << ","
                 << qBlue(premultipliedColor) << ","
                 << qAlpha(premultipliedColor);
    
        QRgb manuallyUnpremultiplied = unpremultiplyManual(premultipliedColor);
    
        qDebug() << "Manually Unpremultiplied R,G,B,A:"
                 << qRed(manuallyUnpremultiplied) << ","
                 << qGreen(manuallyUnpremultiplied) << ","
                 << qBlue(manuallyUnpremultiplied) << ","
                 << qAlpha(manuallyUnpremultiplied);
    
        // qUnpremultiply() との比較
        QRgb qUnpremultiplied = qUnpremultiply(premultipliedColor);
        qDebug() << "qUnpremultiply() R,G,B,A:"
                 << qRed(qUnpremultiplied) << ","
                 << qGreen(qUnpremultiplied) << ","
                 << qBlue(qUnprepremultiplied) << ","
                 << qAlpha(qUnprepremultiplied);
    
        return 0;
    }
    
  • 考え方
    プレマルチプライされた色 (R_p, G_p, B_p, A) が与えられた場合、元の(プレマルチプライされていない)色 (R_u, G_u, B_u, A) を得るには、各色成分をアルファ値で除算します。ただし、アルファ値がゼロの場合は特別な処理が必要です(ゼロ除算を避けるため)。

    • A を0-255の範囲のアルファ値とします。
    • R_p, G_p, B_p を0-255の範囲のプレマルチプライされた色成分とします。

    もし A > 0 ならば:

    • R_u = (R_p * 255 + A / 2) / A
    • G_u = (G_p * 255 + A / 2) / A
    • B_u = (B_p * 255 + A / 2) / A

    もし A == 0 ならば:

    • R_u = 0
    • G_u = 0
    • B_u = 0

    + A / 2 は丸め処理のためのオフセットです。Aで除算する際に最も近い整数に丸めるためです。)

QImage::convertToFormat()を使用する

画像全体を変換したい場合、qUnpremultiply()をピクセルごとに適用するよりも、QImage::convertToFormat()を使う方が効率的で推奨されます。

  • 欠点
    ピクセル単位での個別変換ではなく、画像全体を対象とします。

  • 利点

    • 効率性
      Qtの内部実装が最適化されており、手動でのピクセルループよりもはるかに高速です。
    • 簡潔性
      コードがシンプルになり、読みやすくなります。
    • 信頼性
      Qtが提供する堅牢な変換ロジックを使用できます。
  • コード例

    #include <QApplication>
    #include <QImage>
    #include <QPainter>
    #include <QLabel>
    #include <QVBoxLayout>
    #include <QWidget>
    
    int main(int argc, char *argv[]) {
        QApplication app(argc, argv);
    
        // 半透明な円が描かれた元画像 (Format_ARGB32)
        QImage originalImage(100, 100, QImage::Format_ARGB32);
        originalImage.fill(Qt::transparent);
        QPainter painter(&originalImage);
        painter.setBrush(QColor(255, 0, 0, 128)); // 半透明な赤
        painter.drawEllipse(0, 0, 100, 100);
        painter.end();
    
        // プレマルチプライドアルファ形式に変換
        QImage premultipliedImage = originalImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
    
        // convertToFormat() で直接 unpremultiplied 形式に変換
        // これが qUnpremultiply() の代替手段として非常に強力
        QImage restoredImage = premultipliedImage.convertToFormat(QImage::Format_ARGB32);
    
        // 表示用のウィジェット
        QWidget window;
        QVBoxLayout *layout = new QVBoxLayout(&window);
    
        QLabel *originalLabel = new QLabel("Original (ARGB32)");
        originalLabel->setPixmap(QPixmap::fromImage(originalImage));
        layout->addWidget(originalLabel);
    
        QLabel *premultipliedLabel = new QLabel("Premultiplied (ARGB32_Premultiplied)");
        premultipliedLabel->setPixmap(QPixmap::fromImage(premultipliedImage));
        layout->addWidget(premultipliedLabel);
    
        QLabel *restoredLabel = new QLabel("Restored via convertToFormat(ARGB32)");
        restoredLabel->setPixmap(QPixmap::fromImage(restoredImage));
        layout->addWidget(restoredLabel);
    
        window.setWindowTitle("QImage::convertToFormat() Example");
        window.show();
    
        return app.exec();
    }
    
  • 考え方
    QImage::Format_ARGB32_Premultiplied形式の画像を、一般的なQImage::Format_ARGB32形式に変換すると、Qtが内部で自動的にプレマルチプライドアルファを解除してくれます。

QPainter::CompositionModeの理解と利用

厳密にはqUnpremultiply()の代替というよりは、プレマルチプライドアルファを意識せずに描画を制御する方法です。QPainterを使用する際に、描画モードを適切に設定することで、ソースとデスティネーションのピクセルがどのように合成されるかを制御できます。

  • 欠点
    色のR/G/B値を個別に取得して操作したい場合には、この方法は使えません。あくまで「描画」の文脈での代替です。

  • 利点
    ほとんどの描画シナリオで、開発者がプレマルチプライドアルファの詳細を意識する必要がなくなります。Qtの描画パイプラインが自動的に最適な方法で処理してくれます。

  • コード例 (概念的)

    void MyWidget::paintEvent(QPaintEvent *event) {
        QPainter painter(this);
    
        // 背景を描画
        painter.fillRect(rect(), Qt::white);
    
        // プレマルチプライドアルファ形式の画像を読み込む/準備する
        // (例えば、QImage::Format_ARGB32_Premultiplied で読み込んだ画像)
        QImage premultipliedImage("path/to/premultiplied_image.png");
    
        // ComposotionMode_SourceOver を設定
        // これにより、painter は premultipliedImage のアルファ値を考慮して、
        // 適切に背景に重ねて描画します。
        painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
    
        // プレマルチプライドアルファの画像をそのまま描画
        // qUnpremultiply() を呼ぶ必要がないことが多い
        painter.drawImage(QPoint(0, 0), premultipliedImage);
    
        // ...他の描画...
    }
    
  • 考え方
    QPainterは、描画時にQPainter::CompositionModeに基づいて色を合成します。もし入力の画像がプレマルチプライドアルファ形式であっても、適切なCompositionMode(例えばQPainter::CompositionMode_SourceOver)を使えば、その内部処理でアルファブレンディングが正しく行われるため、開発者が明示的にプレマルチプライドアルファを解除する必要がない場合があります。

  • 単に画像を適切に描画したいだけの場合
    QPainter::CompositionModeを理解し、適切なモードを使用すれば、qUnpremultiply()を明示的に呼ぶ必要がないことがほとんどです。Qtは通常、内部的にプレマルチプライドアルファを処理します。
  • 低レベルでピクセル操作が必要な場合(かつ、Qtの組み込み関数が使えない場合)
    手動での計算を検討できますが、丸め誤差やゼロ除算に注意が必要です。
  • 画像全体を変換したい場合
    QImage::convertToFormat(QImage::Format_ARGB32)が最も推奨されます。パフォーマンスと簡潔さの点で優れています。