2024/01/02(火) [n年前の日記]
#1 [prog] メモリ上のpngバイナリをOpenGLのテクスチャとして利用
昨日、bin2c というツールを使えば、何かしらのバイナリファイルをC言語の unsigned char 配列の形(.h)に変換できると知った。
_mieki256's diary - bin2cについて
その unsigned char 配列を .c に include すれば、実行形式 (.exe) にpngバイナリを含めてしまうこともできるはず。
そんなわけで、実行形式(.exe)に含まれたpngバイナリを OpenGL のテクスチャとして利用できるのか試してみた。もし、それができれば、.exe を1つ配布するだけで画像描画をするプログラムを配布することができるなと…。わざわざ .exe と一緒に .png も配布しなくて済む。例えばスクリーンセーバの類を作って配布する際などは、少しだけ都合が良いかもしれない。
使用言語はC言語。環境は Windows10 x64 22H2 + MinGW (gcc 6.3.0) or MSYS2 (gcc 13.2.0)。
今回、メモリ上のpngバイナリを読み込んで展開しなければいけないけれど、そこでlibpngライブラリ、もしくは stbライブラリを使えるのか試してみた次第。
_mieki256's diary - bin2cについて
その unsigned char 配列を .c に include すれば、実行形式 (.exe) にpngバイナリを含めてしまうこともできるはず。
そんなわけで、実行形式(.exe)に含まれたpngバイナリを OpenGL のテクスチャとして利用できるのか試してみた。もし、それができれば、.exe を1つ配布するだけで画像描画をするプログラムを配布することができるなと…。わざわざ .exe と一緒に .png も配布しなくて済む。例えばスクリーンセーバの類を作って配布する際などは、少しだけ都合が良いかもしれない。
使用言語はC言語。環境は Windows10 x64 22H2 + MinGW (gcc 6.3.0) or MSYS2 (gcc 13.2.0)。
今回、メモリ上のpngバイナリを読み込んで展開しなければいけないけれど、そこでlibpngライブラリ、もしくは stbライブラリを使えるのか試してみた次第。
◎ 必要なライブラリ :
以後のサンプルをビルドするには、freeglut、libpng、zlib(libz) が必要。
MinGW上での導入作業は以前のメモを参考に。
_mieki256's diary - MinGWでfreeglutを使ってみた
_mieki256's diary - MinGW上でzlibやlibpngをビルドしてみた
MSYS2 の場合はパッケージが用意してあるので、pacman -Ss xxxx で検索して、pacman -S xxxx でインストールすればいい。
余談。pacman -Ss stb で検索して気づいたけれど、MSYS2 の場合、stbライブラリもパッケージとして用意されてるっぽいなと…。
_Package: mingw-w64-x86_64-stb - MSYS2 Packages
include/stb/stb*.h という形でインストールされるらしい。ということは、「#include <stb/stb_image.h>」といった形で include して利用できるのだろうか? 試してないから分からないけど。
MinGW上での導入作業は以前のメモを参考に。
_mieki256's diary - MinGWでfreeglutを使ってみた
_mieki256's diary - MinGW上でzlibやlibpngをビルドしてみた
MSYS2 の場合はパッケージが用意してあるので、pacman -Ss xxxx で検索して、pacman -S xxxx でインストールすればいい。
pacman -S mingw-w64-x86_64-freeglut pacman -S mingw-w64-x86_64-libpng pacman -S mingw-w64-x86_64-zlib pacman -S mingw-w64-i686-freeglut pacman -S mingw-w64-i686-libpng pacman -S mingw-w64-i686-zlib
余談。pacman -Ss stb で検索して気づいたけれど、MSYS2 の場合、stbライブラリもパッケージとして用意されてるっぽいなと…。
mingw-w64-x86_64-stb mingw-w64-i686-stb
_Package: mingw-w64-x86_64-stb - MSYS2 Packages
include/stb/stb*.h という形でインストールされるらしい。ということは、「#include <stb/stb_image.h>」といった形で include して利用できるのだろうか? 試してないから分からないけど。
◎ libpngを使う事例 :
libpngを使えば、png画像を開いて画像情報を取り出すことができる。
libpng の使用サンプルは、一般的に、ストレージ上のpng画像ファイル名を渡して、ファイルポインタでファイルを読み込んで処理をしていく事例がほとんどだけど。ファイルから読み込むあたりの処理を、別途関数で書いて、その関数を libpng に設定してやることで、メモリ上に存在するpngバイナリを読み込んで処理できるらしい。その方法については、以下のページが参考になった。ありがたや。
_008 PNGテクスチャの読み込み [stepism]
_C言語 メモリ上にある、PNGデータをlibpngを介して読み込むサンプル。 - うーびのメモ
まずは、bin2c を使って、png画像を .h に変換する。bin2c は、SourceForge で公開されている版、bin2c-1.1.zip を利用させてもらった。
_bin2c download | SourceForge.net
bin2c-1.1.zip を解凍すると、中に bin2c.exe というファイルがあるので、コレを利用する。
_texture.png を、texture.h に変換する。
texture.h が得られた。
_texture.h
コレを、「#include "texture.h"」で include してやれば、exeファイルにpngバイナリを含めることができるはず…。
OpenGL でテクスチャを描画するサンプルを作成。以下のページで紹介されているサンプルを参考にさせてもらった。
_PNGファイルをOpenGLで扱う話
以下が、メモリ上(.exeファイル内)にあるpngバイナリを、libpngで処理して、OpenGL のテクスチャとして描画するサンプル。
_02_loadpng_draw.c
Makefile は以下。
_Makefile
make と打てばビルドできるけど…。要するに以下を実行すれば、gcc で .c をコンパイル&リンクして、実行形式 02_loadpng_draw.exe が得られる。
02_loadpng_draw.exe を実行すると、以下のウインドウが表示された。
たしかに、exeファイル内に含まれているpngバイナリを、libpngで読み込んで、OpenGLのテクスチャとして利用することができた。
余談。MSYS2 (gcc 13.2.0) を使う場合は、以下の Makefile になった。
_Makefile.msys2
make -f xxxx と打てば、xxxx を Makefile として利用できる。
libpng の使用サンプルは、一般的に、ストレージ上のpng画像ファイル名を渡して、ファイルポインタでファイルを読み込んで処理をしていく事例がほとんどだけど。ファイルから読み込むあたりの処理を、別途関数で書いて、その関数を libpng に設定してやることで、メモリ上に存在するpngバイナリを読み込んで処理できるらしい。その方法については、以下のページが参考になった。ありがたや。
_008 PNGテクスチャの読み込み [stepism]
_C言語 メモリ上にある、PNGデータをlibpngを介して読み込むサンプル。 - うーびのメモ
まずは、bin2c を使って、png画像を .h に変換する。bin2c は、SourceForge で公開されている版、bin2c-1.1.zip を利用させてもらった。
_bin2c download | SourceForge.net
bin2c-1.1.zip を解凍すると、中に bin2c.exe というファイルがあるので、コレを利用する。
_texture.png を、texture.h に変換する。
.\bin2c.exe -o texture.h texture.png
texture.h が得られた。
_texture.h
コレを、「#include "texture.h"」で include してやれば、exeファイルにpngバイナリを含めることができるはず…。
OpenGL でテクスチャを描画するサンプルを作成。以下のページで紹介されているサンプルを参考にさせてもらった。
_PNGファイルをOpenGLで扱う話
以下が、メモリ上(.exeファイル内)にあるpngバイナリを、libpngで処理して、OpenGL のテクスチャとして描画するサンプル。
_02_loadpng_draw.c
// OpenGL + libpng sample. PNG image is in memory #include <stdio.h> #include <stdlib.h> #include <GL/freeglut.h> #include <png.h> // png image binary #include "texture.h" GLuint texture; int offset = 0; // Callback function for reading data in libpng void ReadEndProcess(png_structp _pPng, png_bytep _buf, png_size_t _size) { unsigned char* p = (unsigned char*)png_get_io_ptr(_pPng); memcpy(_buf, p + offset, _size); offset += _size; } /** * Load texture from png image on memory * * @param[in] _pngData png binary on memory * return GLuint OpenGL texture ID. if 0, process fails */ GLuint createTextureFromPngInMemory(const unsigned char* _pngData) { GLuint texture; png_structp png_ptr = NULL; png_infop info_ptr = NULL; unsigned int width, height; int depth, colorType, interlaceType; int rowSize; unsigned char *data; // PNG file ? if (png_sig_cmp(_pngData, 0, 8) != 0) { fprintf(stderr, "createTextureFromPngInMemory() : Not png file binary"); return 0; } // create png read struct png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { return 0; } // create png information struct info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, NULL, NULL); return 0; } // set read data callback function png_set_read_fn(png_ptr, (png_voidp)_pngData, ReadEndProcess); offset = 8; // If error, jump here if (setjmp(png_jmpbuf(png_ptr))) { // Release allocated memory png_destroy_read_struct(&png_ptr, &info_ptr, NULL); return 0; } // Skip identification area (8 bytes) in png files png_set_sig_bytes(png_ptr, 8); // Load image information // get width, height, bit depth, color type, interlace mode png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &depth, &colorType, &interlaceType, NULL, NULL); // support RGB/RGBA only if (colorType != PNG_COLOR_TYPE_RGB && colorType != PNG_COLOR_TYPE_RGBA) { fprintf(stderr, "createTextureFromPngInMemory() : Supprted color type are RGB/RGBA only."); png_destroy_info_struct(png_ptr, &info_ptr); png_destroy_read_struct(&png_ptr, NULL, NULL); return 0; } // not support interlace if (interlaceType != PNG_INTERLACE_NONE) { fprintf(stderr, "createTextureFromPngInMemory() : Interlace image is not supprted."); png_destroy_info_struct(png_ptr, &info_ptr); png_destroy_read_struct(&png_ptr, NULL, NULL); return 0; } // calc memory size rowSize = png_get_rowbytes(png_ptr, info_ptr); data = malloc(rowSize * height); // read pixel for (int i = 0; i < height; i++) { png_read_row(png_ptr, &data[i * rowSize], NULL); } png_read_end(png_ptr, info_ptr); // create OpenGL texture glGenTextures(1, &texture); // select texture glBindTexture(GL_TEXTURE_2D, texture); // set texture from PNG image glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // Release allocated all memory free(data); png_destroy_info_struct(png_ptr, &info_ptr); png_destroy_read_struct(&png_ptr, NULL, NULL); return texture; } // draw OpenGL void display() { glClear(GL_COLOR_BUFFER_BIT); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // enable texture glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); // draw polygon glBegin(GL_QUADS); GLfloat v = 0.85f; glTexCoord2f(0.0, 0.0); // set texture u, v glVertex2f(-v, v); glTexCoord2f(0.0, 1.0); glVertex2f(-v, -v); glTexCoord2f(1.0, 1.0); glVertex2f(v, -v); glTexCoord2f(1.0, 0.0); glVertex2f(v, v); glEnd(); // disable texture glDisable(GL_TEXTURE_2D); glFlush(); } // Keyboard callback function void keyboard(unsigned char key, int x, int y) { switch (key) { case '\x1B': case 'q': // Exit on escape or 'q' key press glutLeaveMainLoop(); // exit(EXIT_SUCCESS); break; } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1280, 720); glutCreateWindow("OpenGL Texture Example"); // create OpenGL texture from PNG image texture = createTextureFromPngInMemory((void *)&texture_png); if (!texture) { fprintf(stderr, "Failed create texture\n"); exit(1); } glClearColor(0.2f, 0.4f, 0.8f, 1.0f); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glutKeyboardFunc(keyboard); glutDisplayFunc(display); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); glutMainLoop(); return EXIT_SUCCESS; }
- #include "texture.h" を書くことで、exeファイル内にpng画像のバイナリを含めてしまう。
- pngバイナリは、texture_png という名前の unsigned char 配列になっている。
- ReadEndProcess(png_structp, png_bytep, png_size_t) が、ファイルではなくメモリ上から読み込ませるためのコールバック関数。この関数を png_set_read_fn() に渡してやることで、「ファイルポインタじゃなくてこっちの関数を使ってデータを読み込め」と指示している。
- GLuint createTextureFromPngInMemory(const unsigned char*) が、pngバイナリの先頭アドレスを渡すとOpenGLのテクスチャとして設定してくれる関数。
Makefile は以下。
_Makefile
# MinGW 02_loadpng_draw.exe: 02_loadpng_draw.c texture.h Makefile gcc 02_loadpng_draw.c -o 02_loadpng_draw.exe -D FREEGLUT_STATIC -lfreeglut_static -lopengl32 -lwinmm -lgdi32 -lpng -lz texture.h: texture.png .\bin2c.exe -o texture.h texture.png .PHONY: clean clean: rm -f 02_loadpng_draw.exe rm -f texture.h rm -f *.o
make と打てばビルドできるけど…。要するに以下を実行すれば、gcc で .c をコンパイル&リンクして、実行形式 02_loadpng_draw.exe が得られる。
gcc 02_loadpng_draw.c -o 02_loadpng_draw.exe -D FREEGLUT_STATIC -lfreeglut_static -lopengl32 -lwinmm -lgdi32 -lpng -lz
- -o hoge.exe で出力ファイル名を指定。
- -lxxxx で、libxxxx.lib ライブラリをリンク。
- -D FREEGLUT_STATIC で、freeglut をスタティックリンクするように指示しているらしい。
02_loadpng_draw.exe を実行すると、以下のウインドウが表示された。
たしかに、exeファイル内に含まれているpngバイナリを、libpngで読み込んで、OpenGLのテクスチャとして利用することができた。
余談。MSYS2 (gcc 13.2.0) を使う場合は、以下の Makefile になった。
_Makefile.msys2
# MSYS2 02_loadpng_draw.exe: 02_loadpng_draw.c texture.h Makefile.msys2 gcc 02_loadpng_draw.c -o 02_loadpng_draw.exe -D FREEGLUT_STATIC -lfreeglut -lopengl32 -lwinmm -lgdi32 -lpng -lz -static texture.h: texture.png .\bin2c.exe -o texture.h texture.png .PHONY: clean clean: rm -f 02_loadpng_draw.exe rm -f texture.h rm -f *.o
make -f xxxx と打てば、xxxx を Makefile として利用できる。
make -f Makefile.msys2
◎ stbライブラリを使う事例 :
画像読み込みを行うライブラリは色々あるけれど、stbライブラリという、ちょっと変わったライブラリがある。ヘッダーファイル(.h)を include するだけで各機能が使えてしまうライブラリで…。ヘッダーファイルをコピーしてきて、.c と一緒に置いて include するだけなので、導入が圧倒的に楽。
この stbライブラリを利用して、メモリ上にあるpngバイナリを利用できないか試してみた。今回は、画像の読み込みだけができればいいので、stb_image.h のみを入手して、.c と同じ場所に置いた。
_nothings/stb: stb single-file public domain libraries for C/C++
_stb/stb_image.h at master - nothings/stb
_bin2c を使って、 _png画像 texture.png を _texture.h に変換。
以下が、stb(stb_image.h)を使って、exeに含まれているpngバイナリを、OpenGLのテクスチャとして利用するサンプル。
_03_loadpngwithstb.c
stb_image.h を利用するときは、ファイルの最初のほうで、以下を記述する。
メモリ上にあるpngバイナリの読み込みは、以下の行で処理している。
尚、利用し終わったら、確保したメモリを開放しないといけない。
Makefile は以下。
_Makefile (MinGW用)
_Makefile (MSYS2用)
libpng を使った場合と大体同じだけれど、libpng と zlib(libz) を使わないので、-lpng -lz を省くことができた。
make と打ってビルド。03_loadpngwithstb.exe が得られた。実行すると以下のウインドウが表示された。
stbライブラリを使うことでも、exeファイル内のpngバイナリを読み込んで、OpenGL のテクスチャとして利用できることが分かった。
stbライブラリを使った場合、以下のメリットがあると言えそう。
逆にデメリットとしては、セキュリティホール面が不安、という点だろうか…。おそらく、libpngほど厳しくチェックされてるわけではないだろう…。そのあたりについては、配布ページでも言及されている。
もっとも今回、正体不明の画像を読み込ませるわけではないし…。自分で作成したpng画像を読み込むために使っているわけだから、特にデメリットは無さそうな気もする。
この stbライブラリを利用して、メモリ上にあるpngバイナリを利用できないか試してみた。今回は、画像の読み込みだけができればいいので、stb_image.h のみを入手して、.c と同じ場所に置いた。
_nothings/stb: stb single-file public domain libraries for C/C++
_stb/stb_image.h at master - nothings/stb
_bin2c を使って、 _png画像 texture.png を _texture.h に変換。
.\bin2c.exe -o texture.h texture.png
以下が、stb(stb_image.h)を使って、exeに含まれているpngバイナリを、OpenGLのテクスチャとして利用するサンプル。
_03_loadpngwithstb.c
// OpenGL + stb sample. PNG image is in memory #include <stdio.h> #include <stdlib.h> #include <GL/freeglut.h> // use stb library #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" // include png image binary #include "texture.h" GLuint texture; /** * Load texture from png image on memory * * @param[in] _pngData png binary on memory * @param[in] _pngDataLen png binary size * return GLuint OpenGL texture ID. if 0, process fails */ GLuint createTextureFromPngInMemory(const unsigned char* _pngData, int _pngLen) { GLuint texture; int width = 0, height = 0, bpp = 0; unsigned char *data = NULL; data = stbi_load_from_memory(_pngData, _pngLen, &width, &height, &bpp, 4); // create OpenGL texture glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // Release allocated all memory stbi_image_free(data); return texture; } // draw OpenGL void display() { glClear(GL_COLOR_BUFFER_BIT); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); // enable texture glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); // draw polygon glBegin(GL_QUADS); GLfloat v = 0.85f; glTexCoord2f(0.0, 0.0); // set texture u, v glVertex2f(-v, v); glTexCoord2f(0.0, 1.0); glVertex2f(-v, -v); glTexCoord2f(1.0, 1.0); glVertex2f(v, -v); glTexCoord2f(1.0, 0.0); glVertex2f(v, v); glEnd(); // disable texture glDisable(GL_TEXTURE_2D); glFlush(); } // Keyboard callback function void keyboard(unsigned char key, int x, int y) { switch (key) { case '\x1B': case 'q': // Exit on escape or 'q' key press glutLeaveMainLoop(); // exit(EXIT_SUCCESS); break; } } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1280, 720); glutCreateWindow("OpenGL Texture Example"); // create OpenGL texture from PNG image texture = createTextureFromPngInMemory((void *)&texture_png, texture_png_size); if (!texture) { fprintf(stderr, "Failed create texture\n"); exit(1); } glClearColor(0.2f, 0.4f, 0.8f, 1.0f); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glutKeyboardFunc(keyboard); glutDisplayFunc(display); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS); glutMainLoop(); return EXIT_SUCCESS; }
stb_image.h を利用するときは、ファイルの最初のほうで、以下を記述する。
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h"
メモリ上にあるpngバイナリの読み込みは、以下の行で処理している。
data = stbi_load_from_memory(_pngData, _pngLen, &width, &height, &bpp, 4);
- _pngData : pngバイナリの先頭アドレス
- _pngLen : pngバイナリのサイズ(byte数)
- width : 画像幅を格納する変数
- height : 画像高さを格納する変数
- bpp : 何チャンネル持っているか。RGBなら3、RGBAなら4
- 4 : どんな画像フォーマットも4チャンネル(RGBA)に変換するよう指定
- stbi_load_from_memory() は、png画像を展開してベタに並んだピクセル情報のポインタを返す。
尚、利用し終わったら、確保したメモリを開放しないといけない。
stbi_image_free(data);
Makefile は以下。
_Makefile (MinGW用)
_Makefile (MSYS2用)
# MinGW 03_loadpngwithstb.exe: 03_loadpngwithstb.c texture.h Makefile gcc 03_loadpngwithstb.c -o 03_loadpngwithstb.exe -D FREEGLUT_STATIC -lfreeglut_static -lopengl32 -lwinmm -lgdi32 texture.h: texture.png .\bin2c.exe -o texture.h texture.png .PHONY: clean clean: rm -f 03_loadpngwithstb.exe rm -f texture.h rm -f *.o
libpng を使った場合と大体同じだけれど、libpng と zlib(libz) を使わないので、-lpng -lz を省くことができた。
make と打ってビルド。03_loadpngwithstb.exe が得られた。実行すると以下のウインドウが表示された。
stbライブラリを使うことでも、exeファイル内のpngバイナリを読み込んで、OpenGL のテクスチャとして利用できることが分かった。
stbライブラリを使った場合、以下のメリットがあると言えそう。
- 導入が簡単。
- libpngを使った事例と比べると、圧倒的に少ない記述で済む。
逆にデメリットとしては、セキュリティホール面が不安、という点だろうか…。おそらく、libpngほど厳しくチェックされてるわけではないだろう…。そのあたりについては、配布ページでも言及されている。
This project discusses security-relevant bugs in public in Github Issues and Pull Requests, and it may take significant time for security fixes to be implemented or merged. If this poses an unreasonable risk to your project, do not use stb libraries.「セキュリティホールの修正は遅れがちになるのでリスクを考えながら使ってね」てな感じだろうか。nothings/stb: stb single-file public domain libraries for C/C++ より
もっとも今回、正体不明の画像を読み込ませるわけではないし…。自分で作成したpng画像を読み込むために使っているわけだから、特にデメリットは無さそうな気もする。
[ ツッコむ ]
以上です。