mieki256's diary



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ライブラリを使えるのか試してみた次第。

必要なライブラリ :

以後のサンプルをビルドするには、freeglut、libpng、zlib(libz) が必要。

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 に変換する。
.\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 を実行すると、以下のウインドウが表示された。

02_loadpng_draw_ss.png


たしかに、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 に変換。
.\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 が得られた。実行すると以下のウインドウが表示された。

03_loadpngwithstb_ss.png


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画像を読み込むために使っているわけだから、特にデメリットは無さそうな気もする。

以上です。

過去ログ表示

Prev - 2024/01 - Next
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

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project