iooiau.net

Web2.0風の背景を描画する

Web2.0風の背景を簡単に描画する方法を紹介します。

Web2.0風の背景と言うのは、こういう感じ Web2.0風の背景 の、グラデーションになっていて上半分がテカっているものです。
Windows Vista や Windows Media Player でもおなじみですね。
わざわざ画像を用意することなく、これをプログラムで描画してみます。
こういった背景を見ると、上半分が下半分より明るいグラデーションで描画されていますね。 なので、そのような描画を行うコードを書けばいいわけです。

まず、単純な2色のグラデーションを描画する関数を書きます。
あまり知られていませんが、Windows には GradientFill というグラデーションを描画するための API 関数が用意されていますので、ここではそれを利用します。
ただ、この関数は引数が分かりにくかったり、色の指定が16ビットだったりと、ちょっと癖があります。
GradientFill 関数は Windows98 以降に用意されている msimg32.dll という DLL に入っていますので、利用する場合は msimg32.lib をリンクする必要があります。


#include <windows.h>  // いつものヘッダ

#pragma comment(lib, "msimg32.lib")  // msimg32.lib をリンクする指定です

// 2色のグラデーションを描画する関数です
BOOL TwoColorsGradient(
    HDC hdc,            // 描画先のデバイスコンテキスト・ハンドルです
    const RECT *pRect,  // 描画する範囲の矩形です
    COLORREF Color1,    // 描画する一つ目の色です
    COLORREF Color2,    // 描画する二つ目の色です
    BOOL fHorizontal    // 水平のグラデーションを描画する場合は TRUE にします
)
{
    TRIVERTEX vert[2];
    GRADIENT_RECT rect = {0, 1};

    // 描画範囲と色を設定します
    vert[0].x = pRect->left;
    vert[0].y = pRect->top;
    vert[0].Red   = GetRValue(Color1) << 8;
    vert[0].Green = GetGValue(Color1) << 8;
    vert[0].Blue  = GetBValue(Color1) << 8;
    vert[0].Alpha = 0;
    vert[1].x = pRect->right;
    vert[1].y = pRect->bottom;
    vert[1].Red   = GetRValue(Color2) << 8;
    vert[1].Green = GetGValue(Color2) << 8;
    vert[1].Blue  = GetBValue(Color2) << 8;
    vert[1].Alpha = 0;
    return GradientFill(hdc, vert, 2, &rect, 1,
                        fHorizontal ? GRADIENT_FILL_RECT_H : GRADIENT_FILL_RECT_V);
}

一見 Alpha で透明度を指定できそうですが、実際には Alpha は無視されます。
また、GetRValue(Color1) << 8 は GetRValue(Color1) * 257 の間違いと思われるかも知れませんが、色の指定は最大が 0xFF00 という変な仕様になっています。

なお、Windows 2000 以降では GdiGradientFill という、GradientFill と同様の関数が gdi32.dll にありますので、こちらを使うようにすれば msimg32.lib をリンク指定する必要が無くなります。
GradientFill (GdiGradientFill) を使用せずに、GDI 関数でペンを使ってグラデーションを描くこともできますので、そちらに挑戦してみてもいいでしょう。

さて、次は本題のテカった背景を描画するコードです。 このコードでは、縦方向と横方向のどちらでもグラデーションを描画できるようにしています。


// 二つの色を混ぜ合わせる関数です
COLORREF MixColor(COLORREF Color1, COLORREF Color2, BYTE Ratio = 128)
{
    int Alpha = 255 - Ratio;
    return RGB((GetRValue(Color1) * Ratio + GetRValue(Color2) * Alpha) / 255,
               (GetGValue(Color1) * Ratio + GetGValue(Color2) * Alpha) / 255,
               (GetBValue(Color1) * Ratio + GetBValue(Color2) * Alpha) / 255);
}

// テカったグラデーションを描画する関数です
void GlossyGradient(
    HDC hdc,                   // 描画先のデバイスコンテキスト・ハンドルです
    const RECT *pRect,         // 描画する範囲の矩形です
    COLORREF Color1,           // 描画する一つ目の色です
    COLORREF Color2,           // 描画する二つ目の色です
    BOOL fHorizontal = FALSE,  // 水平のグラデーションを描画する場合は TRUE にします
    BYTE GlossRatio1 = 96,     // 端のテカりの強さです
    BYTE GlossRatio2 = 48,     // 中央のテカりの強さです
)
{
    // 中央の色を求めます
    COLORREF CenterColor = MixColor(Color1, Color2);

    RECT rc;

    // テカり部分の範囲を計算します(描画領域の半分です)
    rc.left = pRect->left;
    rc.top  = pRect->top;
    if (fHorizontal) {
        rc.right  = (rc.left + pRect->right) / 2;
        rc.bottom = pRect->bottom;
    } else {
        rc.right  = pRect->right;
        rc.bottom = (rc.top + pRect->bottom) / 2;
    }
    // テカり部分を描画します
    TwoColorsGradient(hdc, &rc,
                      MixColor(RGB(255, 255, 255), Color1, GlossRatio1),
                      MixColor(RGB(255, 255, 255), CenterColor, GlossRatio2),
                      fHorizontal);

    // テカり以外の部分を描画します
    if (fHorizontal) {
        rc.left  = rc.right;
        rc.right = pRect->right;
    } else {
        rc.top    = rc.bottom;
        rc.bottom = pRect->bottom;
    }
    TwoColorsGradient(hdc, &rc, CenterColor, Color2, fHorizontal);
}

要するに、描画する範囲を半分ずつに二分割して、片方を明るい色で描画している訳です。
引数の GlossRatio1 と GlossRatio2 でテカりの強さを指定できます。 デフォルト値は見た目から適当に設定したものです。
この関数は以下のように呼び出せます。


GlossyGradient(hdc, &rect, RGB(80, 80, 80), RGB(0, 0, 0));
GlossyGradient(hdc, &rect, RGB(0, 80, 255), RGB(48, 255, 255));

実行するとこのように描画されます。
ブラック アクア風
どうでしょうか? なかなかそれらしい見栄えになっていると思います。

また、応用として両端から中心に向かってグラデーションを描画するようにすると、以下のようなちょっと違うパターンのものを描画できます。
アルミ風 ガラス風