2012年

6月

29日

続・OpenGL+DevILで画像読み込み

以前OpenGL+DevILで画像を入出力するで紹介した画像読み込み方法ですが、今頃になって8bit画像の読み込み方法に不具合があることに気が付きましたので修正を施しました。

 

修正内容:

・8bit画像の上下反転現象に対応

・複数のoriginに対応

 

OpenGLは「Tao framework」を使い、コードは以下のようになりました。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// DevIL初期化
Il.ilInit();
Ilu.iluInit();

int texBindID = 0, imageBindID = 0;
IntPtr pixel = IntPtr.Zero;

try
{
    /*
     * 画像のファイルチェック
     * 存在しないファイルをilLoadImageに指定すると以降動作がおかしくなるので
     * 予めファイルの存在チェックをやっておく
     */
    if (!File.Exists(filename))
    
        throw new Exception(
                string.Format(
                        "\"{0}\" テクスチャファイルが存在しません.",
                        filename));

    // 領域確保
    imageBindID = Il.ilGenImage();

    if (!Il.ilIsImage(imageBindID) || Il.ilGetError() != Il.IL_NO_ERROR)
        throw new Exception("テクスチャ名生成に失敗しました.");

    Il.ilBindImage(imageBindID);
    
    // 画像読み込み
    bool result = Il.ilLoadImage(filename);
    if(!result)
        throw new Exception("テクスチャ読み込みに失敗しました.");
        
    // 画像データ取得
    int width = Il.ilGetInteger(Il.IL_IMAGE_WIDTH);
    int height = Il.ilGetInteger(Il.IL_IMAGE_HEIGHT);
    int depth = Il.ilGetInteger(Il.IL_IMAGE_DEPTH);
    int bits = Il.ilGetInteger(Il.IL_IMAGE_BITS_PER_PIXEL);
    int size = Il.ilGetInteger(Il.IL_IMAGE_SIZE_OF_DATA);
    int origin = Il.ilGetInteger(Il.IL_IMAGE_ORIGIN);
    
    // ピクセルデータ格納
    pixel = Marshal.AllocCoTaskMem(width * height * 4);
    
    switch (bits)
    {
        case 32:
        case 24: // 16bit画像は24bitに修正される
            if (origin == Il.IL_ORIGIN_UPPER_LEFT)
            {
                // ビットマップにピクセルデータをコピー
                Il.ilCopyPixels(
                        0, 0, 0, // X, Y, Zのオフセット
                        width, height, depth, // X, Y, Z幅
                        Il.IL_RGBA, // ピクセルフォーマット
                        Il.IL_UNSIGNED_BYTE, // 1ピクセルのデータ形式
                        pixel); // ピクセルデータを格納するポインタ

                if (Il.ilGetError() != Il.IL_NO_ERROR)
                    throw new Exception("ピクセルデータの取得に失敗しました.");
            }
            else if (origin == Il.IL_ORIGIN_LOWER_LEFT)
            {
                unsafe
                {
                    // 直接値を書き換える
                    byte* pixels = (byte*)Il.ilGetData();
                    byte* results = (byte*)pixel;
                    int stride = Il.ilGetInteger(Il.IL_IMAGE_BYTES_PER_PIXEL);

                    // BGRA形式→RGBA形式に修正
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // ピクセルにデータ格納
                            int id1 = (y * width + x) * 4;
                            int id2 = ((height - y - 1) * width + x) * stride;

                            results[id1 + 2] = pixels[id2 + 0];
                            results[id1 + 1] = pixels[id2 + 1];
                            results[id1 + 0] = pixels[id2 + 2];
                            results[id1 + 3] = (stride == 4 ?
                                                pixels[id2 + 3]:
                                                byte.MaxValue);
                        }
                    }
                }
            }
            break;

        case 8: // 1bit、4bit画像は8bitに修正される
            unsafe
            {
                // 直接値を書き換える
                int* palette = (int*)Il.ilGetPalette();
                byte* pixels = (byte*)Il.ilGetData();
                byte* results = (byte*)pixel;

                if (origin == Il.IL_ORIGIN_UPPER_LEFT)
                {
                    // パレット色からピクセルデータを設定
                    for (int i = 0; i < size; i++)
                    {
                        // パレットから色データを取得
                        Color color = Color.FromArgb(palette[pixels[i]]);

                        // ピクセルにデータ格納
                        int index = i << 2;
                        results[index] = color.R;
                        results[index + 1] = color.G;
                        results[index + 2] = color.B;
                        results[index + 3] = 255;
                    }
                }
                else if (origin == Il.IL_ORIGIN_LOWER_LEFT)
                {
                    // 画像を上下入れ替える
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // パレットから色データを取得
                            int palette_id = x + width * (height - y - 1);
                            palette_id = pixels[palette_id]];
                            Color color = Color.FromArgb(palette[palette_id]);

                            int index = (x + width * y) << 2;
                            results[index] = color.R;
                            results[index + 1] = color.G;
                            results[index + 2] = color.B;
                            results[index + 3] = 255;
                        }
                    }
                }
            }
            break;
    }

    // 不要なので開放
    Il.ilDeleteImage(imageBindID);
    imageBindID = 0;
    
    // 領域確保
    Gl.glGenTextures(1, out texBindID);
    Gl.glBindTexture(Gl.GL_TEXTURE_2D, texBindID);
    
    if (Gl.glIsTexture(texBindID) == Gl.GL_FALSE ||
            Gl.glGetError() != Gl.GL_NO_ERROR)
            
        throw new Exception("テクスチャ名生成に失敗しました.");

    // ポリゴン色とテクスチャ色の合成方法
    Gl.glTexEnvf(Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_MODULATE);

    // ミップマップ作成
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_GENERATE_MIPMAP, Gl.GL_TRUE);

    // テクスチャフィルタ
    Gl.glTexParameteri(
            Gl.GL_TEXTURE_2D,
            Gl.GL_TEXTURE_MAG_FILTER,
            Gl.GL_LINEAR);
    
    Gl.glTexParameteri(
            Gl.GL_TEXTURE_2D,
            Gl.GL_TEXTURE_MIN_FILTER,
            Gl.GL_LINEAR_MIPMAP_LINEAR);

    // ラッピング方法
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);

    // ソースの貼り付け
    Gl.glTexImage2D(
        Gl.GL_TEXTURE_2D, // テクスチャターゲット
        0, // ミップマップレベル
        Gl.GL_RGBA, // ピクセルの格納方法
        width, height, // X, Y幅
        0, // ボーダー
        Gl.GL_RGBA, // 格納するピクセルのフォーマット
        Gl.GL_UNSIGNED_BYTE, // 格納するピクセルのデータ形式
        pixel); // 格納するピクセルデータ

    if (Gl.glGetError() != Gl.GL_NO_ERROR)
        throw new Exception("ピクセルデータの割り当てに失敗しました.");
}
catch (Exception ex)
{
    Console.WriteLine(string.Format(">>>エラー:{0}", ex.Message));

    // ピクセル領域開放
    if (pixel != null)
    {
        Marshal.FreeHGlobal(pixel);
        pixel = IntPtr.Zero;
    }

    // テクスチャ開放
    Gl.glBindTexture(Gl.GL_TEXTURE_2D, texBindID);
     
    if (Gl.glIsTexture(texBindID) != Gl.GL_FALSE)
    {
        Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
        Gl.glDeleteTextures(1, ref texBindID);
        texBindID = 0;
    }
}
finally
{
    // イメージ開放
    if (Il.ilIsImage(imageBindID))
        Il.ilDeleteImage(imageBindID);
}

ところで、「ILUTを使えば読み込み処理は1行で済む」と聞き、試してみました。

 

OpenGL用画像ファイル読み込み関数

int Tao.DevIl.Ilut.ilutGLLoadImage(string FileName)

 

FileName:画像ファイル名

戻り値:テクスチャ領域番号

 

この関数にファイル名を渡すと画像を読み込んで、テクスチャ領域を確保してピクセルデータの割り当てまでやってくれます。

 

戻り値にはテクスチャ領域番号が返されますが、この値はglGenTextures()で確保した場合と同じ領域番号が渡されます。

 

コレを使えば「ファイル読み込み&領域確保&ピクセルデータ割り当て」までの処理をやってくれるらしいです。

 

で、早速使ってみました。

 

IL&ILUでファイル読み込み

 

ILUTのみでファイル読み込み

まぁ、このように残念な結果となりました。使い方が間違ってたのでしょうかね?

でも今の方法で充分に画像読み込みは出来てるし、ILUTに変える必要も無いかなぁ?

 

追記

どうやら処理が足りなかったようです。ILUTでちゃんと読み込めるようにしてみました。

ILとILUだけの処理と比べると、コードがかなり短くなってます。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
int texBindID = 0;

try
{
    // ファイルチェック
    if (!File.Exists(filename))
    
        throw new Exception(
                string.Format("\"{0}\" テクスチャファイルが存在しません.",
                filename));

    // ファイル読み込み
    texBindID = Ilut.ilutGLLoadImage(filename);

    if (Gl.glIsTexture(texBindID) == Gl.GL_FALSE ||
            Gl.glGetError() != Gl.GL_NO_ERROR)
            
        throw new Exception("テクスチャ名生成に失敗しました.");

    // ポリゴン色とテクスチャ色の合成方法
    Gl.glTexEnvf(Gl.GL_TEXTURE_ENV, Gl.GL_TEXTURE_ENV_MODE, Gl.GL_MODULATE);

    // ミップマップ作成
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_GENERATE_MIPMAP, Gl.GL_TRUE);

    // フィルタ
    Gl.glTexParameteri(
            Gl.GL_TEXTURE_2D,
            Gl.GL_TEXTURE_MAG_FILTER,
            Gl.GL_LINEAR);
            
    Gl.glTexParameteri(
            Gl.GL_TEXTURE_2D,
            Gl.GL_TEXTURE_MIN_FILTER,
            Gl.GL_LINEAR_MIPMAP_LINEAR);

    // ラッピング
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_S, Gl.GL_REPEAT);
    Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_WRAP_T, Gl.GL_REPEAT);

    // 画像データ取得
    int width = Il.ilGetInteger(Il.IL_IMAGE_WIDTH);
    int height = Il.ilGetInteger(Il.IL_IMAGE_HEIGHT);
    int bits = Il.ilGetInteger(Il.IL_IMAGE_BITS_PER_PIXEL);
    int origin = Il.ilGetInteger(Il.IL_IMAGE_ORIGIN);

    // ピクセル格納法が逆順の場合
    if (origin == Il.IL_ORIGIN_LOWER_LEFT)
    {
        // 24bit以上の画像
        if (bits >= 24)
        {
            // ピクセル反転
            Ilu.iluFlipImage();

            // 再設定
            Gl.glTexImage2D(
                Gl.GL_TEXTURE_2D,
                0,
                Gl.GL_RGBA,
                width, height,
                0,
                Il.ilGetInteger(Il.IL_IMAGE_FORMAT),
                Gl.GL_UNSIGNED_BYTE,
                Il.ilGetData());

            if (Gl.glGetError() != Gl.GL_NO_ERROR)
                throw new Exception("ピクセルデータの割り当てに失敗しました.");
        }

        // 8bit画像の場合
        else if (bits == 8)
        {
            IntPtr pixel = IntPtr.Zero;

            try
            {
                pixel = Marshal.AllocCoTaskMem(width * height * 4);

                unsafe
                {
                    // 直接値を書き換える
                    int* palette = (int*)Il.ilGetPalette();
                    byte* pixels = (byte*)Il.ilGetData();
                    byte* results = (byte*)pixel;

                    // 画像を上下入れ替える
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // パレットから色データを取得
                            int palette_id = x + width * (height - y - 1);
                            palette_id = pixels[palette_id];
                            Color color = Color.FromArgb(palette[palette_id]);

                            int index = (x + width * y) << 2;
                            results[index] = color.R;
                            results[index + 1] = color.G;
                            results[index + 2] = color.B;
                            results[index + 3] = 255;
                        }
                    }
                }

                // 再設定
                Gl.glTexImage2D(
                Gl.GL_TEXTURE_2D,
                0,
                Gl.GL_RGBA,
                width, height,
                0,
                Gl.GL_RGBA,
                Gl.GL_UNSIGNED_BYTE,
                pixel);
            }
            finally
            {
                // ピクセルデータ開放
                if (pixel != null)
                {
                    Marshal.FreeHGlobal(pixel);
                    pixel = IntPtr.Zero;
                }
            }
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine(string.Format(">>>エラー:{0}", ex.Message));

    // テクスチャ開放
    Gl.glBindTexture(Gl.GL_TEXTURE_2D, texBindID);

    if (Gl.glIsTexture(texBindID) != Gl.GL_FALSE)
    {
        Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
        Gl.glDeleteTextures(1, ref texBindID);
        texBindID = 0;
    }
}

ピクセル配列方式が「IL_ORIGIN_LOWER_LEFT」の場合は反転作業が必要になります。

 

24、32bitの場合は54行目の「iluFlipImage()」で上下の反転を自動的にやってくれますが、8bitの場合はパレットから色データを取得する必要があるので結局自前で反転作業をする必要があります。

 

ちなみに前回ブログで「大きなサイズの画像にiluFlipImage()をかけると画像が崩壊する」と書きました。以下のような感じです。

どうやら古いDevILライブラリにはiluFlipImage()に不具合があるらしく、私が使用している「Tao framework 2.1.0」のDevILライブラリには古い ものが使用されていました(Tao frameworkにはDevIL1.6.5が使用されています)。

 

DevILライブラリを最新版(DevIL1.7.8)に更新したら正常に動くようになりました。

使ってみた感想としては、ILUTを使うほうがシンプルに読み込みが出来ますね。全部コチラの処理に書き換えようかな?

 

おまけ

C++で組むとこのようになりました。動かすにはGLEWが必要になります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// GLEW
#include <GL/glew.h>

// OpenGL
#include <GL/gl.h>
#include <GL/glu.h>

// OpenIL(DevIL)
#include<IL/il.h>
#include<IL/ilu.h>
#include<IL/ilut.h>

#include <memory>

// テクスチャ読み込み(ILUT使用版)
GLuint TextureFromFile(LPCSTR filename)
{
    GLuint bindID = 0;
    GLubyte* results = NULL;
    
    try
    {
        // ファイル読み込み
        bindID = ilutGLLoadImage((LPSTR)filename);

        if(glIsTexture(bindID) == GL_FALSE || glGetError() != GL_NO_ERROR)
            throw "テクスチャ名生成に失敗しました.";

        // ポリゴン色とテクスチャ色の合成方法
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

        // ミップマップ作成
        glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);

        // フィルタ
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

        // ラッピング
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        
        // 画像データ取得
        ILint width = ilGetInteger(IL_IMAGE_WIDTH);
        ILint height = ilGetInteger(IL_IMAGE_HEIGHT);
        ILint bits = ilGetInteger(IL_IMAGE_BITS_PER_PIXEL);
        ILint origin = ilGetInteger(IL_IMAGE_ORIGIN);

        // ピクセル格納法が逆順の場合
        if (origin == IL_ORIGIN_LOWER_LEFT)
        {
            // 24bit以上の画像
            if(bits >= 24)
            {
                // ピクセル反転
                iluFlipImage();

                // ピクセルデータ再設定
                glTexImage2D(
                    GL_TEXTURE_2D,
                    0,
                    GL_RGBA,
                    width, height,
                    0,
                    ilGetInteger(IL_IMAGE_FORMAT),
                    GL_UNSIGNED_BYTE,
                    ilGetData());

                if (glGetError() != GL_NO_ERROR)
                    throw "ピクセルデータの割り当てに失敗しました.";
            }

            // 8bit画像の場合
            else if (bits == 8)
            {
                ILubyte* palette = ilGetPalette();
                ILubyte* pixels = ilGetData();
                results = new ILubyte[width * height * 4];
                memset(results, 0, width * height * 4);

                // 画像を上下入れ替える
                for(int y = 0; y < height; y++)
                {
                    for(int x = 0; x < width; x++)
                    {
                        // パレットから色データを取得
                        int pixel_id = x + width * y;
                        pixel_id <<= 2;

                        int palette_id = x + width * (height - y - 1);
                        palette_id = pixels[palette_id] << 2;

                        results[pixel_id + 0] = palette[palette_id + 2];
                        results[pixel_id + 1] = palette[palette_id + 1];
                        results[pixel_id + 2] = palette[palette_id + 0];
                        results[pixel_id + 3] = 255;
                    }
                }

                // ピクセルデータ再設定
                glTexImage2D(
                    GL_TEXTURE_2D,
                    0,
                    GL_RGBA,
                    width, height,
                    0,
                    IL_RGBA,
                    GL_UNSIGNED_BYTE,
                    results);

                if (glGetError() != GL_NO_ERROR)
                    throw "ピクセルデータの割り当てに失敗しました.";
            }
        }
    }
    catch(...)
    {
        // テクスチャ領域開放
        glBindTexture(GL_TEXTURE_2D, bindID);
            
        if(glIsTexture(bindID))
        {
            glBindTexture(GL_TEXTURE_2D, 0);
            glDeleteTextures(1, &bindID);
        }
    }
    
    // ピクセルデータ開放
    if(results != NULL)
    {
        delete[] results;
        results = NULL;
    }

    return bindID;
}