SDL2でブラー効果

SDL2で画像のブラー効果を実行します。ブラー効果は画像をボヤけさせる処理です。

 

1 ブラー効果

指定したピクセル周りのRGBA値から新たなRGBA値を計算し、画像をボヤけさせる処理です。今回は最も単純な平均値を用いたブラー効果を実行します。

2 SDL_Surface構造体とSDL_Texture構造体

画像や文字列からSDL_Surface構造体を作成し、SDL_Surface構造体からSDL_Texture構造体を作成します。SDL_Texture構造体をディスプレイに描画します。

SDL_Texture構造体はピクセルの更新手段がSDL_UpdateTexture関数とSDL_LockTexture関数経由のみである点です。SDL_UpdateTexture関数に変更内容を渡すとVRAMにその内容が送信されます。SDL_LockTexture関数は書き込む領域へのアドレスが渡され、SDL_UnlockTexture関数を呼び出したタイミングでVRAMに内容が送信されます。

両方とも書き込み専用のAPIで読み込む術はないようです。

SDL2のサンプルプログラムでは、SDL_Texture構造体を生成したらすぐにSDL_Surface構造体を破棄するようにしているものが多いですが、ピクセルにアクセスする必要がある場合はSDL_Surface構造体を保持しなければなりません(あるいは各ピクセルのRGBA値を保持する)。

3 SDL_BYTEORDERマクロ

SDL_BYTEORDERマクロでSDLコンパイル時のバイトオーダーを知ることができます。SDL_BYTEORDERがSDL_BIG_ENDIANである場合はビッグエンディアンとなります。

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
/** Code for big endian. */
#else
/** Code for little endian. */
#endif

4 SDL_ConvertSurfaceFormat

SDL_ConvertSurfaceFormatで指定したフォーマットのSDL_Surface構造体を新たに生成します。

SDL_Surface* SDL_ConvertSurfaceFormat(SDL_Surface* src,
                                      Uint32       pixel_format,
                                      Uint32       flags)

サンプルプログラムではフォーマットを全てRGBA値を扱うものにします。ビッグエンディアンの場合はSDL_PIXELFORMAT_RGBA8888を、リトルエンディアンの場合はSDL_PIXELFORMAT_ARGB8888を使用します。

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define SDL_CONVERT_SURFACE_FORMAT (SDL_PIXELFORMAT_RGBA8888)
#else
#define SDL_CONVERT_SURFACE_FORMAT (SDL_PIXELFORMAT_ABGR8888)
#endif

生成したSDL_Surface構造体のsurfaceは変換後に破棄します。

SDL_Surface *convert
  = SDL_ConvertSurfaceFormat(surface, SDL_CONVERT_SURFACE_FORMAT, 0);
SDL_FreeSurface(surface);

5 SDL_Surface構造体のメンバ変数pixels

SDL_Surface構造体のメンバ変数pixelsでピクセルにアクセスします。

/**
 * \brief A collection of pixels used in software blitting.
 *
 * \note  This structure should be treated as read-only, except for \c pixels,
 *        which, if not NULL, contains the raw pixel data for the surface.
 */
typedef struct SDL_Surface
{
    Uint32 flags;               /**< Read-only */
    SDL_PixelFormat *format;    /**< Read-only */
    int w, h;                   /**< Read-only */
    int pitch;                  /**< Read-only */
    void *pixels;               /**< Read-write */

    /** Application data associated with the surface */
    void *userdata;             /**< Read-write */

    /** information needed for surfaces requiring locks */
    int locked;                 /**< Read-only */
    void *lock_data;            /**< Read-only */

    /** clipping information */
    SDL_Rect clip_rect;         /**< Read-only */

    /** info for fast blit mapping to other surfaces */
    struct SDL_BlitMap *map;    /**< Private */

    /** Reference count -- used when freeing surface */
    int refcount;               /**< Read-mostly */
} SDL_Surface;

SDL_Surface構造体のフォーマットによってpixelsへのアクセス方法が変わってきます。例えば赤が8ビットのフォーマットもあれば、5ビットのフォーマットも存在します。今回はRGBA値はそれぞれ8ビットのフォーマットを使用します。

加えてビッグエンディアンかリトルエンディアンかを意識する必要があります。

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define SDL_RMASK (0xff000000)
#define SDL_GMASK (0x00ff0000)
#define SDL_BMASK (0x0000ff00)
#define SDL_AMASK (0x000000ff)
#define SDL_RSHIFT (24)
#define SDL_GSHIFT (16)
#define SDL_BSHIFT (8)
#define SDL_ASHIFT (0)
#else
#define SDL_RMASK (0x000000ff)
#define SDL_GMASK (0x0000ff00)
#define SDL_BMASK (0x00ff0000)
#define SDL_AMASK (0xff000000)
#define SDL_RSHIFT (0)
#define SDL_GSHIFT (8)
#define SDL_BSHIFT (16)
#define SDL_ASHIFT (24)
#endif

pixelsからRGBA値を取得する処理は以下の通りです。

Uint32 *pixels = (Uint32 *) texture->getSurface()->pixels;
Uint32 value = pixels[y * rect->w + x];
r += (value & SDL_RMASK) >> SDL_RSHIFT;
g += (value & SDL_GMASK) >> SDL_GSHIFT;
b += (value & SDL_BMASK) >> SDL_BSHIFT;
a += (value & SDL_AMASK) >> SDL_ASHIFT;
count++;

6 サンプルプログラム

PNG画像と文字列にブラー効果を施します。SDL2の文字列描画処理から次の変更を加えています。フォントファイルはIPAフォントを別途ダウンロードしてお使いください。

6.1 PixelTextureクラス

TextureクラスのSDL_Surface構造体からブラー効果を施したSDL_Surface構造体を生成するクラスです。

#ifndef __MYSDL_PIXEL_TEXTURE_H
#define __MYSDL_PIXEL_TEXTURE_H

#include <MySDL.h>
#include <Texture.h>
#include <SDL.h>

namespace mysdl {

#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define SDL_RMASK (0xff000000)
#define SDL_GMASK (0x00ff0000)
#define SDL_BMASK (0x0000ff00)
#define SDL_AMASK (0x000000ff)
#define SDL_RSHIFT (24)
#define SDL_GSHIFT (16)
#define SDL_BSHIFT (8)
#define SDL_ASHIFT (0)
#else
#define SDL_RMASK (0x000000ff)
#define SDL_GMASK (0x0000ff00)
#define SDL_BMASK (0x00ff0000)
#define SDL_AMASK (0xff000000)
#define SDL_RSHIFT (0)
#define SDL_GSHIFT (8)
#define SDL_BSHIFT (16)
#define SDL_ASHIFT (24)
#endif

class PixelTexture : public Texture {
 private:
  static bool contains(SDL_Rect *rect, int x, int y)
  {
    return x >= rect->x && x < rect->x + rect->w &&
      y >= rect->y && y < rect->y + rect->h;
  }

  static Uint32 average(Texture *texture, int cx, int cy, size_t size)
  {
    Uint32 r = 0, g = 0, b = 0, a = 0;
    Uint32 *pixels = (Uint32 *) texture->getSurface()->pixels;
    SDL_Rect *rect = texture->getRect();
    Uint32 count = 0;

    for (int x = cx - size / 2; x < cx + size / 2; ++x)
      for (int y = cy - size / 2; y < cy + size / 2; ++y)
        if (contains(rect, x, y)) {
          Uint32 value = pixels[y * rect->w + x];
          r += (value & SDL_RMASK) >> SDL_RSHIFT;
          g += (value & SDL_GMASK) >> SDL_GSHIFT;
          b += (value & SDL_BMASK) >> SDL_BSHIFT;
          a += (value & SDL_AMASK) >> SDL_ASHIFT;
          count++;
        }

    if (count == 0)
      return 0;

    r = (r / count) & 0xff;
    g = (g / count) & 0xff;
    b = (b / count) & 0xff;
    a = (a / count) & 0xff;

    return (r << SDL_RSHIFT) | (g << SDL_GSHIFT) |
      (b << SDL_BSHIFT) | (a << SDL_ASHIFT);
  }

 public:
  static PixelTexture *createBlurPixelTexture(Texture *texture,
                                              size_t size)
  {
    SDL_Rect *rect = texture->getRect();
    SDL_Surface *surface
      = SDL_CreateRGBSurface(0, rect->w, rect->h, 32, SDL_RMASK,
                             SDL_GMASK, SDL_BMASK, SDL_AMASK);
    Uint32 *pixels = (Uint32 *) surface->pixels;

    for (int x = 0; x < rect->w; ++x)
      for (int y = 0; y < rect->h; ++y)
        pixels[y * rect->w + x] = average(texture, x, y, size);

    return new PixelTexture(surface);
  }

  PixelTexture(SDL_Surface *surface)
  {
    setSurface(surface);
  }
};

};

#endif /** __MYSDL_PIXEL_TEXTURE_H */

6.2 PixelObjectクラス

PixelTextureをメンバに持つクラスです。Objectクラスを継承し、描画処理をそのまま流用します。

#ifndef __MYSDL_PIXEL_OBJECT_H
#define __MYSDL_PIXEL_OBJECT_H

#include <Object.h>
#include <PixelTexture.h>
#include <SDL.h>

namespace mysdl {

class PixelObject : public Object {
 public:
  PixelObject(PixelTexture *pixel, int x, int y, int w, int h)
    : Object(pixel, x, y, w, h) {}

  static PixelObject *createBlurPixelObject(Object *object,
                                            size_t size)
  {
    PixelTexture *pixel
      = PixelTexture::createBlurPixelTexture(object->getTexture(),
                                             size);
    SDL_Rect *rect = object->getRect();
    return new PixelObject(pixel, rect->x, rect->y, rect->w, rect->h);
  }
};

};

#endif /** __MYSDL_PIXEL_OBJECT_H */

6.3 main.ccの変更

pキーを押した場合にPNG画像とブラー効果を施した画像を、sキーを押した場合に文字列とブラー効果を施した文字列を表示します。

$ diff -uprN main.cc.org main.cc
--- main.cc.org 2015-05-17 06:39:31.000000000 +0900
+++ main.cc     2015-05-17 06:00:09.000000000 +0900
@@ -3,6 +3,7 @@
 #include <AlphaAndColorModThread.h>
 #include <SpriteObject.h>
 #include <StringObject.h>
+#include <PixelObject.h>
 #include <Font.h>
 #include <SDL.h>

@@ -32,6 +33,10 @@ static bool input()
         static int pngX = 0;
         SpriteObject *png
           = new SpriteObject("a.png", pngX, 0, 100, 100);
+        PixelObject *pixel
+          = PixelObject::createBlurPixelObject(png, 8);
+        pixel->getRect()->y = 100;
+        gRenderer->send(pixel);
         gRenderer->send(png);
         pngX += 48;
         break;
@@ -50,14 +55,15 @@ static bool input()
         StringObject *string
           = new StringObject("こんにちは世界", color,
                              FONT_TYPE_NORMAL, stringX, 100);
+        PixelObject *pixel
+          = PixelObject::createBlurPixelObject(string, 8);
+        pixel->getRect()->y = 200;
+        gRenderer->send(pixel);
         gRenderer->send(string);
         stringX += 48;
         break;
       }
-      default:
-        break;
       }
-      break;
     default:
       break;
     }

6.4 実行結果

pキーとsキーを押すと以下のように表示されます。

SampleProgram.png

ダウンロード
SDL2でブラー効果を実行するサンプルプログラム
src.tgz.tar.gz
GNU tar 7.1 KB