QColor::hue()だけじゃない!Qtで色相を扱う代替メソッド

2025-05-27

色相は、色を表現するHSV(Hue, Saturation, Value)モデルにおける主要な要素の一つです。HSVモデルでは、以下のように色が表現されます。

  • Value(明度): 色の明るさを表します。明度が高いほど色は明るくなり、明度が低いほど色は暗くなります(黒に近づきます)。
  • Saturation(彩度): 色の鮮やかさを表します。彩度が高いほど色は鮮やかになり、彩度が低いほど色はくすんで灰色に近づきます。
  • Hue(色相): 色の種類を表します。赤、オレンジ、黄、緑、青、紫といった色のスペクトルを円周状に配置した「色相環」上で、その色がどこに位置するかを示します。通常、0度から359度の範囲で表現され、赤は0度、緑は120度、青は240度といった具合です。

QColor::hue()関数が返す値は、以下のようになります。

  • 無彩色の場合: もし色が灰色や白、黒といった無彩色(彩度がない色)である場合、色相は意味を持たないため、QColor::hue()は**-1**を返します。
  • 色相(Hue)の値: 0から359の範囲の整数値を返します。これは、色相環上の角度に対応します。
#include <QColor>
#include <QDebug>

int main() {
    QColor red(255, 0, 0); // 赤
    qDebug() << "赤の色相:" << red.hue(); // 0

    QColor green(0, 255, 0); // 緑
    qDebug() << "緑の色相:" << green.hue(); // 120

    QColor blue(0, 0, 255); // 青
    qDebug() << "青の色相:" << blue.hue(); // 240

    QColor gray(128, 128, 128); // 灰色
    qDebug() << "灰色の色相:" << gray.hue(); // -1

    return 0;
}


無彩色(灰色、白、黒)の場合の-1

一般的なエラー/混乱
QColor::hue()を呼び出した際に、期待していた0〜359の範囲の値ではなく、-1が返ってくることがあります。これはエラーではなく、無彩色(彩度がない色、つまり灰色、白、黒)の場合の正常な挙動です。色相は色の種類を表すため、彩度がない色には色相の概念が存在しません。

トラブルシューティング

  • 無彩色の場合にどう処理するか(例えば、特定の色相のデフォルト値を割り当てる、エラーメッセージを表示する、特別な処理を行うなど)をロジックに組み込みます。
  • -1が返された場合、その色が意図的に無彩色であるのか、それとも計算や初期化のミスで無彩色になってしまったのかを確認します。
  • QColor::hue()の戻り値が-1であるかどうかを常にチェックするようにします。


QColor myColor = ...; // 何らかの方法でQColorを初期化
int hue = myColor.hue();

if (hue == -1) {
    qDebug() << "この色は無彩色(灰色、白、黒)です。色相はありません。";
    // 必要に応じて、無彩色の場合の処理を行う
} else {
    qDebug() << "色相:" << hue;
}

HSV/HSLモデルの理解不足

一般的なエラー/混乱
色相はHSV(Hue, Saturation, Value)またはHSL(Hue, Saturation, Lightness)モデルの一部です。RGB(Red, Green, Blue)値から直接色相を直感的に判断することは難しく、計算によって導き出されます。QColor::hue()は内部的にこれらのモデルに基づいて色相を計算しますが、ユーザーがRGB値のみを考慮していると、期待と異なる結果になることがあります。

トラブルシューティング

  • 期待する色相が得られない場合、そのQColorオブジェクトの彩度(QColor::saturation()またはQColor::saturationF())や明度/輝度(QColor::value()またはQColor::valueF()、あるいはQColor::lightness()またはQColor::lightnessF())も確認してみてください。彩度が非常に低い場合、色相はあまり意味を持ちません。
  • 色相がどのような意味を持つのか、HSV/HSLモデルの基本を理解することが重要です。特に、彩度(Saturation)が低い(色がくすんでいる、灰色に近い)場合、色相の値は不安定になりやすいという特性があります。

浮動小数点精度と整数値の範囲

一般的なエラー/混乱
QColorには、整数値(0-255や0-359)を扱う関数と、浮動小数点値(0.0-1.0)を扱う関数(例: hueF()saturationF()valueF())があります。混同して使用したり、型変換の際に丸め誤差が発生したりする可能性があります。QColor::hue()は0-359の整数値を返します。

トラブルシューティング

  • 整数値に変換する際は、丸め誤差を考慮し、必要に応じてqRound()などの適切な丸め関数を使用します。
  • 特に、色相値を別の計算に使う場合、浮動小数点値であるQColor::hueF()を使った方が、より正確な計算ができる場合があります。
  • 使用している関数が整数値を返すのか、浮動小数点値を返すのかを明確に意識します。

一般的なエラー/混乱
QColorオブジェクトが正しく初期化されていない場合、hue()を呼び出すと予期しない結果や未定義の動作を引き起こす可能性があります。例えば、無効なRGB値で初期化されたり、デフォルトコンストラクタで初期化されただけのQColorオブジェクトに対してhue()を呼び出したりした場合です。

トラブルシューティング

  • QColorを初期化する際には、有効なRGB値、HSV値、または事前定義された色名(例: Qt::red)を使用していることを確認します。
  • QColor::isValid()関数を使って、QColorオブジェクトが有効な色を表しているかを確認します。
QColor invalidColor; // 無効なQColor
if (!invalidColor.isValid()) {
    qDebug() << "この色は無効です。";
}

QColor validColor(100, 50, 200); // 有効なQColor
if (validColor.isValid()) {
    qDebug() << "この色は有効です。色相:" << validColor.hue();
}


すべての例を実行するには、Qt開発環境が設定されており、QApplicationを初期化し、必要なヘッダをインクルードする必要があります。

例1: 基本的なhue()の使用

最も基本的な例として、異なる色の色相を取得する方法を示します。

#include <QCoreApplication> // QCoreApplicationを使用する場合
#include <QDebug>           // qDebug()を使用する場合
#include <QColor>           // QColorを使用する場合

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv); // Qtアプリケーションの初期化 (GUIがない場合)

    // さまざまな色のQColorオブジェクトを作成
    QColor red(255, 0, 0);     // 純粋な赤
    QColor green(0, 255, 0);   // 純粋な緑
    QColor blue(0, 0, 255);    // 純粋な青
    QColor yellow(255, 255, 0); // 黄色 (赤と緑の混合)
    QColor cyan(0, 255, 255);   // シアン (緑と青の混合)
    QColor magenta(255, 0, 255); // マゼンタ (赤と青の混合)
    QColor gray(128, 128, 128); // 灰色
    QColor white(255, 255, 255); // 白
    QColor black(0, 0, 0);     // 黒

    // 各色の色相 (hue) を取得して表示
    qDebug() << "赤の色相:" << red.hue();      // 期待値: 0
    qDebug() << "緑の色相:" << green.hue();    // 期待値: 120
    qDebug() << "青の色相:" << blue.hue();     // 期待値: 240
    qDebug() << "黄色の色相:" << yellow.hue(); // 期待値: 60
    qDebug() << "シアンの色相:" << cyan.hue();   // 期待値: 180
    qDebug() << "マゼンタの色相:" << magenta.hue(); // 期待値: 300
    qDebug() << "灰色の色相:" << gray.hue();    // 期待値: -1 (無彩色)
    qDebug() << "白色の色相:" << white.hue();   // 期待値: -1 (無彩色)
    qDebug() << "黒色の色相:" << black.hue();   // 期待値: -1 (無彩色)

    return a.exec(); // イベントループを開始 (GUIがない場合でも必要)
}

解説

  • 灰色、白、黒は無彩色であるため、hue()-1を返します。これは、これらの色には「色相」の概念が存在しないためです。
  • 黄色、シアン、マゼンタは、RGBの原色の組み合わせで構成され、それぞれの中間の色相を持ちます。
  • 赤、緑、青はそれぞれ0, 120, 240という特定の色相を持ちます。
  • qDebug()を使って、コンソールに色相の値を表示しています。

例2: hue()の戻り値-1のハンドリング

hue()-1を返す場合の処理ロジックの例です。

#include <QCoreApplication>
#include <QDebug>
#include <QColor>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QColor myColor1(100, 150, 200); // 青みがかった色
    QColor myColor2(50, 50, 50);    // 濃い灰色

    int hue1 = myColor1.hue();
    int hue2 = myColor2.hue();

    // myColor1の処理
    if (hue1 == -1) {
        qDebug() << "myColor1 は無彩色です。";
        // 無彩色の場合の特別な処理(例: デフォルトの色相を設定)
        // myColor1.setHsv(0, 0, myColor1.value()); // 赤系の色相に設定するが、彩度を0に保つ
    } else {
        qDebug() << "myColor1 の色相: " << hue1 << "度";
    }

    // myColor2の処理
    if (hue2 == -1) {
        qDebug() << "myColor2 は無彩色です。";
        // ここでは、無彩色であることを利用してメッセージを表示
    } else {
        qDebug() << "myColor2 の色相: " << hue2 << "度"; // この部分は実行されない
    }

    return a.exec();
}

解説

  • 無彩色の場合に、ユーザーへの通知や、特定のデフォルト値を設定するなどの追加のロジックを実装できます。
  • if (hue == -1)の条件分岐を使って、色が有彩色か無彩色かを判断しています。

例3: 色相による色のフィルタリング/分類

色相の値を使って色を特定のカテゴリに分類する例です。

#include <QCoreApplication>
#include <QDebug>
#include <QColor>
#include <QList>

// 色相に基づいて色を分類する関数
QString classifyColorByHue(const QColor &color) {
    int hue = color.hue();

    if (hue == -1) {
        return "無彩色 (白, 黒, 灰色)";
    } else if (hue >= 0 && hue < 30) { // 0-29
        return "赤系 (Red-Orange)";
    } else if (hue >= 30 && hue < 90) { // 30-89
        return "黄系 (Orange-Yellow-Green)";
    } else if (hue >= 90 && hue < 150) { // 90-149
        return "緑系 (Green-Cyan)";
    } else if (hue >= 150 && hue < 210) { // 150-209
        return "シアン系 (Cyan-Blue)";
    } else if (hue >= 210 && hue < 270) { // 210-269
        return "青系 (Blue-Magenta)";
    } else if (hue >= 270 && hue < 330) { // 270-329
        return "マゼンタ系 (Magenta-Red)";
    } else { // 330-359 (赤の終わり)
        return "赤系 (Red)";
    }
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QList<QColor> colors;
    colors << QColor(255, 0, 0)      // 赤
           << QColor(255, 165, 0)   // オレンジ
           << QColor(255, 255, 0)   // 黄色
           << QColor(0, 128, 0)     // 緑
           << QColor(0, 0, 255)     // 青
           << QColor(128, 0, 128)   // 紫
           << QColor(200, 200, 200) // 明るい灰色
           << QColor(10, 10, 10);    // 暗い灰色

    for (const QColor &color : colors) {
        qDebug() << "色 (" << color.red() << "," << color.green() << "," << color.blue() << ") は "
                 << classifyColorByHue(color);
    }

    return a.exec();
}

解説

  • このような分類は、画像処理で特定の色範囲を検出したり、UIでテーマカラーを動的に変更したりする際に役立ちます。
  • classifyColorByHue関数は、与えられたQColorの色相に基づいて、その色がどの系統に属するかを文字列で返します。

既存の色の色相のみを変更し、彩度と明度を保つ例です。

#include <QCoreApplication>
#include <QDebug>
#include <QColor>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QColor originalColor(100, 200, 50); // 緑がかった色
    qDebug() << "元の色: H(" << originalColor.hue() << ") S(" << originalColor.saturation()
             << ") V(" << originalColor.value() << ")";

    // 元の色の彩度と明度を保持しつつ、色相を赤に変更 (0度)
    QColor newColor = originalColor; // まずコピーを作成
    newColor.setHsv(0, originalColor.saturation(), originalColor.value());
    qDebug() << "色相を赤に変更後: H(" << newColor.hue() << ") S(" << newColor.saturation()
             << ") V(" << newColor.value() << ")";

    // 元の色の彩度と明度を保持しつつ、色相を青に変更 (240度)
    QColor anotherNewColor = originalColor;
    anotherNewColor.setHsv(240, originalColor.saturation(), originalColor.value());
    qDebug() << "色相を青に変更後: H(" << anotherNewColor.hue() << ") S(" << anotherNewColor.saturation()
             << ") V(" << anotherNewColor.value() << ")";

    // 無彩色の色相変更の試み (注意: 彩度が0なので見た目の変化はない)
    QColor grayColor(128, 128, 128);
    qDebug() << "元の灰色: H(" << grayColor.hue() << ") S(" << grayColor.saturation()
             << ") V(" << grayColor.value() << ")";
    
    grayColor.setHsv(120, grayColor.saturation(), grayColor.value()); // 緑に設定しようとする
    qDebug() << "色相を緑に変更後 (灰色): H(" << grayColor.hue() << ") S(" << grayColor.saturation()
             << ") V(" << grayColor.value() << ")"; // 彩度が0なので、hue()は-1のまま

    return a.exec();
}
  • 灰色の例では、setHsv()で色相を120(緑)に設定しようとしていますが、彩度(saturation())が0であるため、結果的に色は変わらず、hue()-1を返したままになります。これは、彩度が0の色はどんな色相を設定しても無彩色のままだからです。
  • originalColor.saturation()originalColor.value()を使って、元の色の彩度と明度を保持し、色相のみを変更しています。
  • setHsv(hue, saturation, value)関数は、指定されたHSV値に基づいてQColorを設定します。


しかし、この関数に代わる、または補完するいくつかの方法があり、特定のユースケースでより適している場合があります。

QColor::hueF() (浮動小数点版の色相)

QColor::hue()は0-359の整数値を返しますが、QColor::hueF()は0.0-1.0の範囲のqreal(浮動小数点)値を返します。より高い精度が必要な場合や、他の浮動小数点ベースの計算と組み合わせる場合に便利です。

特徴

  • 無彩色
    無彩色の場合、-1.0を返します。
  • 範囲
    0.0から1.0までの範囲で、これを360倍することで従来の360度表現に変換できます。
  • 精度
    整数値の丸め誤差を回避し、より正確な色相値を提供します。

使用例

#include <QCoreApplication>
#include <QDebug>
#include <QColor>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QColor myColor(100, 200, 50); // 緑がかった色
    QColor grayColor(128, 128, 128); // 灰色

    qDebug() << "元の色 (RGB): " << myColor.red() << "," << myColor.green() << "," << myColor.blue();
    qDebug() << "整数色相 (hue()): " << myColor.hue();
    qDebug() << "浮動小数点色相 (hueF()): " << myColor.hueF();
    qDebug() << "浮動小数点色相 (hueF()) * 360: " << myColor.hueF() * 360.0;

    qDebug() << "\n灰色の色相:";
    qDebug() << "整数色相 (hue()): " << grayColor.hue();     // -1
    qDebug() << "浮動小数点色相 (hueF()): " << grayColor.hueF(); // -1.0

    return a.exec();
}

HSL (Hue, Saturation, Lightness) モデル

HSVモデルの代わりにHSLモデルを使用することもできます。Qtでは、QColor::hslHue()QColor::hslSaturation()QColor::hslLightness()、およびそれらの浮動小数点版(hslHueF()など)が提供されています。色相(Hue)はHSVとHSLで同じ概念ですが、彩度(Saturation)と明度(Lightness)の計算方法が異なります。

  • QColor::hslHueF(): HSLモデルの色相成分を0.0-1.0の浮動小数点数で返します。無彩色の場合は-1.0を返します。
  • QColor::hslHue(): HSLモデルの色相成分を0-359の整数で返します。無彩色の場合は-1を返します。

HSLモデルの利点

  • 知覚的な均一性
    HSLは、明度(Lightness)が独立しているため、色の明るさの調整がHSVの明度(Value)よりも直感的であるとされています。特に、彩度や色相を保ちつつ、明るさだけを調整したい場合にHSLが適しています。

使用例

#include <QCoreApplication>
#include <QDebug>
#include <QColor>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QColor myColor(255, 100, 50); // 赤みがかった色

    qDebug() << "元の色: H(" << myColor.hue() << ") S(" << myColor.saturation() << ") V(" << myColor.value() << ")";

    // HSL成分を取得
    qDebug() << "HSL 色相 (hslHue()): " << myColor.hslHue();
    qDebug() << "HSL 彩度 (hslSaturation()): " << myColor.hslSaturation();
    qDebug() << "HSL 明度 (hslLightness()): " << myColor.hslLightness();

    // HSL浮動小数点成分を取得
    qDebug() << "HSL 色相 (hslHueF()): " << myColor.hslHueF();
    qDebug() << "HSL 彩度 (hslSaturationF()): " << myColor.hslSaturationF();
    qDebug() << "HSL 明度 (hslLightnessF()): " << myColor.hslLightnessF();

    // HSLで色を設定
    QColor newHslColor;
    newHslColor.setHsl(myColor.hslHue(), 255, myColor.hslLightness()); // 彩度を最大にする
    qDebug() << "HSLで彩度を最大にした色 (HSV): H(" << newHslColor.hue() << ") S(" << newHslColor.saturation()
             << ") V(" << newHslColor.value() << ")";
    qDebug() << "HSLで彩度を最大にした色 (HSL): H(" << newHslColor.hslHue() << ") S(" << newHslColor.hslSaturation()
             << ") L(" << newHslColor.hslLightness() << ")";

    return a.exec();
}

getHsv() / getHsl() メソッド

色相だけでなく、彩度や明度/輝度のすべてのHSV/HSL成分を一度に取得したい場合は、getHsv()getHsl()メソッドを使用できます。これらはポインタ引数を取り、それぞれの成分を書き込みます。

特徴

  • ポインタ引数
    結果を格納する変数のアドレスを渡す必要があります。
  • 一括取得
    複数の成分を一度に取得できるため、コードが簡潔になります。

使用例

#include <QCoreApplication>
#include <QDebug>
#include <QColor>

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QColor myColor(50, 150, 250); // 青みがかった色

    int h, s, v, a_hsv;
    myColor.getHsv(&h, &s, &v, &a_hsv);
    qDebug() << "HSV成分: H=" << h << ", S=" << s << ", V=" << v << ", Alpha=" << a_hsv;

    qreal hf, sf, vf, af_hsv;
    myColor.getHsvF(&hf, &sf, &vf, &af_hsv);
    qDebug() << "HSV成分 (浮動小数点): H=" << hf << ", S=" << sf << ", V=" << vf << ", Alpha=" << af_hsv;


    int hsl_h, hsl_s, hsl_l, a_hsl;
    myColor.getHsl(&hsl_h, &hsl_s, &hsl_l, &a_hsl);
    qDebug() << "HSL成分: H=" << hsl_h << ", S=" << hsl_s << ", L=" << hsl_l << ", Alpha=" << a_hsl;

    qreal hsl_hf, hsl_sf, hsl_lf, af_hsl;
    myColor.getHslF(&hsl_hf, &hsl_sf, &hsl_lf, &af_hsl);
    qDebug() << "HSL成分 (浮動小数点): H=" << hsl_hf << ", S=" << hsl_sf << ", L=" << hsl_lf << ", Alpha=" << af_hsl;

    return a.exec();
}

QtのQColorクラスが内部的にHSV/HSL変換を処理してくれるため、通常はRGB値から色相を手動で計算する必要はありません。しかし、Qt以外の環境で色相を扱ったり、特定のアルゴリズムを理解するために、RGBからHSVへの変換ロジックを知っておくことは有用です。

一般的なRGB to HSV変換の擬似コードは以下のようになります。

// R, G, B は 0-255 の範囲
// H は 0-359 の範囲、S, V は 0-255 の範囲
float r_norm = R / 255.0f;
float g_norm = G / 255.0f;
float b_norm = B / 255.0f;

float max_val = max(r_norm, g_norm, b_norm);
float min_val = min(r_norm, g_norm, b_norm);
float delta = max_val - min_val;

int H;
int S;
int V = round(max_val * 255.0f); // ValueはRGBの最大値に比例

if (delta == 0) {
    // 無彩色の場合
    H = -1; // または、任意のデフォルト値 (例: 0)
    S = 0;
} else {
    S = round((delta / max_val) * 255.0f); // Saturation

    if (max_val == r_norm) {
        H = round(60 * fmod(((g_norm - b_norm) / delta), 6));
    } else if (max_val == g_norm) {
        H = round(60 * (((b_norm - r_norm) / delta) + 2));
    } else { // max_val == b_norm
        H = round(60 * (((r_norm - g_norm) / delta) + 4));
    }
    if (H < 0) H += 360; // 負の値になった場合は360を足して正の範囲に
}

注意点
この手動計算は教育的な目的やQtが利用できない特殊な環境向けです。Qtを使用している場合は、必ずQColorの組み込み関数を使用してください。Qtの関数は最適化されており、バグも少ないです。