SDL2のアルファブレンディングと色調補正

 SDL2でSDL_Texture構造体のアルファブレンディングと色調補正を実行します。

 

1 SDL_Texture構造体に対するアルファブレンディングと色調補正

ここではSDL_Texture構造体に対する一括したアルファブレンディングと色調補正についてき記載します。

ピクセル単位のアルファブレンディングと色調補正を実行する場合、SDL_Surface構造体をピクセル単位でアクセスして、各ピクセルのアルファ値とカラーの値を変更してから、SDL_Texture構造体を作成する必要があります。

背景色を透過させたい場合は、背景色を透過する設定にしたPNG画像を用いてIMG_LoadPNG_RWを使うべきです。

2 SDL_SetTextureAlphaMod

SDL_SetTextureAlphaModでアルファブレンディングの値を指定します。

int SDL_SetTextureAlphaMod(SDL_Texture* texture,
                           Uint8        alpha)

計算式は アルファ値 = 元のアルファ値 * (指定したアルファ値 / 255)です。

段階的にSDL_Texture構造体を透明にするには、指定するアルファ値を255, 192, 168, …, 0と指定していけば良いことになります。

3 SDL_GetTextureAlphaMod

SDL_GetTextureAlphaModでアルファブレンディングの値を取得します。

int SDL_GetTextureAlphaMod(SDL_Texture* texture,
                           Uint8*       alpha)

SDL_Texture構造体と描画対象が1対1の場合はこちらを使用すると良いでしょう。

サンプルプログラムではSDL_Texture構造体が描画対象と1対多を想定している為(同じ内容のSDL_Texture構造体を持つのを嫌い、描画対象で共有できる余地を残す為)、本APIは使用しません。

4 SDL_SetTextureColorMod

SDL_SetTextureColorModで色調補正を実行します。

int SDL_SetTextureColorMod(SDL_Texture* texture,
                           Uint8        r,
                           Uint8        g,
                           Uint8        b)

計算式は 色の値 = 元の色の値 * (指定した色の値 / 255) となります。元の色が128で指定した色の値が128の場合、64が色の値となります。その後、さらに255を指定した場合、128が色の値となります。

段階的にSDL_Texture構造体を暗くするには、指定するr, g, bの値を(255, 255, 255,)、(192, 192, 192)、(168, 168, 168), … , (0, 0, 0)と同じ割合で指定していけば良いことになります。

5 SDL_GetTextureColorMod

SDL_GetTextureColorModで色調補正の値を取得できます。

int SDL_GetTextureColorMod(SDL_Texture* texture,
                           Uint8*       r,
                           Uint8*       g,
                           Uint8*       b)

SDL_Texture構造体と描画対象が1対1の場合はこちらを使用すると良いでしょう。

SDL_GetTextureAlphaModと同様、サンプルプログラムでは使用しません。

6 サンプルプログラム

SDL2のBMP画像とPNG画像表示に比べて描画対象(Objectクラス)と描画処理(Rendererクラス)に、アルファ値と色調の値を追加するよう変更を加えます。外部からキューにアクセスできるようにgetterも追加しています。

diff -uprN src.org/Object.h src.org2/Object.h
--- src.org/Object.h  2015-05-14 00:29:34.000000000 +0900
+++ src.org2/Object.h 2015-05-14 03:16:51.000000000 +0900
@@ -6,10 +6,18 @@

 namespace mysdl {

+#define SDL_COLOR_MAX_VALUE (Uint8) (254)
+
 class Object {
  private:
   Texture *mTexture;
   SDL_Rect mRect;
+  SDL_Color mColor;
+
+  Uint8 min(Uint8 a, Uint8 b)
+  {
+    return a < b ? a : b;
+  }

  public:
   Object(int x, int y, int w, int h) : mTexture(nullptr)
@@ -44,6 +52,29 @@ class Object {
     return &mRect;
   }

+  Uint8 getRed() const { return mColor.r; }
+  Uint8 getGreen() const { return mColor.g; }
+  Uint8 getBlue() const { return mColor.b; }
+  Uint8 getAlpha() const { return mColor.a; }
+
+  void setRed(Uint8 r) { mColor.r = min(r, SDL_COLOR_MAX_VALUE); }
+  void setGreen(Uint8 g) { mColor.g = min(g, SDL_COLOR_MAX_VALUE); }
+  void setBlue(Uint8 b) { mColor.b = min(b, SDL_COLOR_MAX_VALUE); }
+  void setAlpha(Uint8 a) { mColor.a = min(a, SDL_COLOR_MAX_VALUE); }
+
+  SDL_Color getAlphaAndColor()
+  {
+    return mColor;
+  }
+
+  void setAlphaAndColor(const SDL_Color &color)
+  {
+    mColor.r = color.r;
+    mColor.g = color.g;
+    mColor.b = color.b;
+    mColor.a = color.a;
+  }
+
   void setRect(int x, int y, int w, int h)
   {
     mRect.x = x;
diff -uprN src.org/Renderer.h src.org2/Renderer.h
--- src.org/Renderer.h  2015-05-13 23:20:50.000000000 +0900
+++ src.org2/Renderer.h 2015-05-14 03:19:33.000000000 +0900
@@ -67,6 +67,9 @@ class Renderer {
         continue;
       SDL_Rect *src = texture->getRect();
       SDL_Rect *dst = object->getRect();
+      SDL_SetTextureColorMod(sdlTexture, object->getRed(),
+                             object->getGreen(), object->getBlue());
+      SDL_SetTextureAlphaMod(sdlTexture, object->getAlpha());
       SDL_RenderCopy(mRenderer, sdlTexture, src, dst);
     }

@@ -78,6 +81,21 @@ class Renderer {
     /** NOTE: Sleep 10 msec. */
     SDL_Delay(10);
   }
+
+  bool lock()
+  {
+    return mMutex.lock();
+  }
+
+  bool unlock()
+  {
+    return mMutex.unlock();
+  }
+
+  std::deque<Object *> &getQueue()
+  {
+    return mQueue;
+  }
 };

 };

以下のスレッドを新たに追加します。本スレッドは100ミリ秒毎にRendererのキューに格納されたオブジェクトのアルファブレンディングと色調補正を実行します。

#ifndef __MYSDL_ALPHA_AND_COLOR_MOD_THREAD_H
#define __MYSDL_ALPHA_AND_COLOR_MOD_THREAD_H

#include <Renderer.h>
#include <Object.h>
#include <SDL.h>
#include <deque>

namespace mysdl {

#define ROTATE_COLOR_VALUE (8)

extern int runAlphaAndColorModThread(void *);

class AlphaAndColorModThread {
 private:
  SDL_Thread *mThread;
  bool mStop;
  Renderer *mRenderer;

  Uint8 rotate(Uint8 value)
  {
    return (value - ROTATE_COLOR_VALUE + SDL_COLOR_MAX_VALUE) %
      SDL_COLOR_MAX_VALUE;
  }

  SDL_Color rotateAlphaAndColor(const SDL_Color &color)
  {
    SDL_Color ret;
    ret.r = rotate(color.r);
    ret.g = rotate(color.g);
    ret.b = rotate(color.b);
    ret.a = rotate(color.a);
    return ret;
  }

 public:
  AlphaAndColorModThread(Renderer *renderer)
    : mStop(false), mRenderer(renderer)
  {
    mThread = SDL_CreateThread(runAlphaAndColorModThread,
                               "AlphaAndColorMod", (void *) this);
  }

  ~AlphaAndColorModThread()
  {
    if (!mStop)
      stop();
  }

  void run()
  {
    while (!mStop) {
      mRenderer->lock();
      for (Object *object : mRenderer->getQueue()) {
        SDL_Color color
          = rotateAlphaAndColor(object->getAlphaAndColor());
        object->setAlphaAndColor(color);
      }
      mRenderer->unlock();
      SDL_Delay(100);
    }
  }

  void stop()
  {
    int ret;
    mStop = true;
    SDL_WaitThread(mThread, &ret);
  }
};

};

#endif /** __MYSDL_ALPHA_AND_COLOR_MOD_THREAD_H */
#include <AlphaAndColorModThread.h>

namespace mysdl {

int runAlphaAndColorModThread(void *data)
{
  AlphaAndColorModThread *thread = (AlphaAndColorModThread *) data;
  thread->run();
  return 0;
}

};

main.ccにて、PNG画像とBMP画像が重なるように座標を調整し、上記のスレッドを呼び出すようにします。

diff -uprN src.org2/main.cc src/main.cc
--- src.org2/main.cc  2015-05-13 22:34:26.000000000 +0900
+++ src/main.cc 2015-05-14 03:14:18.000000000 +0900
@@ -1,5 +1,6 @@
 #include <Window.h>
 #include <Renderer.h>
+#include <AlphaAndColorModThread.h>
 #include <SpriteObject.h>
 #include <SDL.h>

@@ -36,7 +37,7 @@ static bool input()
       case SDLK_b: {
         static int bmpX = 200;
         SpriteObject *bmp
-          = new SpriteObject("a.bmp", bmpX, 200, 100, 100);
+          = new SpriteObject("a.bmp", bmpX, 50, 100, 100);
         gRenderer->send(bmp);
         bmpX += 48;
         break;
@@ -57,10 +58,12 @@ int main(int argc, char *argv[])
 {
   Window window(SDL_WINDOW_TITLE, SDL_WINDOW_WIDTH, SDL_WINDOW_HEIGHT);
   gRenderer = window.getRenderer();
+  AlphaAndColorModThread thread(gRenderer);

   while (1)
     if (input())
       break;

+  thread.stop();
   return 0;
 }

実行すると以下のようになります。色調補正によって、BMP画像とPNG画像は明るくなったり暗くなったりしますが、アルファブレンディングはPNG画像のみ有効です。

SampleProgram.png

6.1 BMP画像が透過されない

本ソースコードで用いているPNG画像のフォーマットがアルファ値を扱うSDL_PIXELFORMAT_ARGB8888であるのに対し、BMP画像のフォーマットはアルファ値を扱わないSDL_PIXELFORMAT_INDEX8となります。

アルファ値を扱う為に、SDL_Surface構造体を作成したのち、SDL_PIXELFORMAT_ARGB8888へ変換する変換する必要があります。加えて背景色を指定する必要もあります。 こちらのサイトのtransparentSdlSurface関数の処理がそれにあたります。

BMP画像をサポートする必要が特にない場合は、画像ファイルをすべてPNG画像に変換しておくと良いでしょう。

6.2 SDL_COLOR_MAX_VALUEで最大値を254にする理由

SDL_RENDERER_SOFTWAREが有効な際、SDL_SetTextureAlphaModでアルファ値を255から255でない値に切り替える、あるいはSDL_SetTextureColorModで色の値のすべてが255の場合とすべてが255でない場合に切り替えると、SDL_Texture構造体のキャッシュがクリアされ、freeとmallocが呼ばれてしまうようです(SDL_RENDERER_SOFTWAREが有効な場合、SDL_Texture構造体は内部にSDL_Surface構造体を保持し、そのSDL_Surface構造体の持つキャッシュがfreeとmallocされる)。StackOverflowでも議論されています。

OpenGLを叩けている場合は特に問題にならないとは思うのですが、SDL_RENDERER_SOFTWAREが必要な環境の為にSDL_COLOR_MAX_VALUEで最大値を254に抑えています。アルファ値が最大254でも、色調の値が(254, 254, 254)でも、人の目からは判断つかないと思います(機械的に判断する場合は255にした方が良い)。

ダウンロード
アルファブレンディングと色調補正のサンプルコード
src.tgz.tar.gz
GNU tar 5.4 KB