mieki256's diary



2022/07/15(金) [n年前の日記]

#1 [prog] FreeImageとDevILライブラリを試用

C/C++で、pngファイルを読み込んだり書き込んだりする処理をしてみたい。

画像処理用のライブラリとして、FreeImage、DevIL というライブラリがあるらしいと知ったので試用してみた。

_C/C++ にて 画像ファイルを読み込む - Qiita


動作確認環境は、Windows10 x64 21H2 + MSYS2 (MinGW64)。g++ 12.1.0。GNU Make 4.3。

FreeImageライブラリ。 :

C/C++ から利用できる画像処理用ライブラリとして、FreeImage というライブラリがあるらしい。

_The FreeImage Project


ライセンスは、GPL、または FreeImage Public License (FIPL)。どちらかを選べる、ということなのだろう。たぶん。

_GNU General Public License | Open Source Initiative
_freeimage.sourceforge.net/freeimage-license.txt


MSYS2 なら、パッケージが既に用意されていた。pacman -Ss hoge でパッケージ検索。
$ pacman -Ss freeimage
mingw32/mingw-w64-i686-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
mingw64/mingw-w64-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
ucrt64/mingw-w64-ucrt-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
clang32/mingw-w64-clang-i686-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)
clang64/mingw-w64-clang-x86_64-freeimage 3.18.0-8
    Library project for developers who would like to  support popular graphics image formats (mingw-w64)

インストールしてみる。pacman -S hoge でインストールできる。
pacman -S mingw-w64-x86_64-freeimage
pacman -S mingw-w64-i686-freeimage

以下を参考にしてサンプルを用意した。pngファイルを読み込んで、1ドットずつグレースケール変換して、out.png として保存する、という処理をしている。

_FreeImageで画像の読み込みと保存 - Qiita
_画像変換ツールを自作する - Qiita
_FreeImage 3.13.1 documentation - FreeImage3131.pdf

_01_load_png01.cpp
// load png image with freeimage.

#include <stdio.h>
#include <iostream>
#include <FreeImage.h>

const char *outfile = "out.png";

int main(int argc, char *argv[])
{
  if (argc != 2)
  {
    printf("Usage:\n\t01_load_png01.exe IN.png\n");
    return -1;
  }

  FreeImage_Initialise(FALSE);

  printf("FreeImage ver. %s\n", FreeImage_GetVersion());
  printf("%s\n\n", FreeImage_GetCopyrightMessage());

  try
  {
    auto filename = argv[1];

    // get input image format
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    int flag = 0;

    fif = FreeImage_GetFileType(filename);
    if (fif == FIF_UNKNOWN)
      fif = FreeImage_GetFIFFromFilename(filename);

    if (fif == FIF_PNG)
      flag = PNG_DEFAULT;
    else if (fif == FIF_BMP)
      flag = BMP_DEFAULT;
    else if (fif == FIF_GIF)
      flag = GIF_DEFAULT;
    else if (fif == FIF_JPEG)
      flag = JPEG_DEFAULT;

    // load image
    FIBITMAP *image = FreeImage_Load(fif, filename, flag);
    if (image)
      printf("Load : %s\n", filename);
    else
      throw std::runtime_error("Load failed");

    // convert to 24bit image
    FIBITMAP *image24 = FreeImage_ConvertTo24Bits(image);

    // convert greyscale
    int width = FreeImage_GetWidth(image24);
    int height = FreeImage_GetHeight(image24);
    FIBITMAP *newimg = FreeImage_Allocate(width, height, 24);
    RGBQUAD col;

    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        FreeImage_GetPixelColor(image24, x, y, &col);

        double ncol = 0.299 * col.rgbRed + 0.587 * col.rgbGreen + 0.114 * col.rgbBlue;
        if (ncol < 0)
          ncol = 0;
        if (ncol > 255)
          ncol = 255;
        col.rgbRed = int(ncol);
        col.rgbGreen = int(ncol);
        col.rgbBlue = int(ncol);
        
        FreeImage_SetPixelColor(newimg, x, y, &col);
      }
    }

    // save png image
    if (FreeImage_Save(FIF_PNG, newimg, outfile, PNG_DEFAULT))
      printf("Save : %s\n", outfile);
    else
      throw std::runtime_error("Save failed");

    FreeImage_Unload(image);
    FreeImage_Unload(newimg);
  }
  catch (std::exception &e)
  {
    std::cout << "exception!\n"
              << e.what() << std::endl;
  }

  FreeImage_DeInitialise();

  return 0;
}

コンパイルは以下。
g++ 01_load_png01.cpp -o 01_load_png01.exe -lfreeimage

実行。
./01_load_png01.exe in.png

グレースケールになった out.png が得られた。

ただ、ldd 01_load_png01.exe と打って、使用している .dll を確認したところ、かなり多い。ここまで多いのでは、おそらく静的リンク(スタティックリンク)はできない予感がする。

DevILライブラリ。 :

C/C++から利用できる画像処理用ライブラリとして、DevILというライブラリがあるらしい。

_DevIL - A full featured cross-platform Image Library
_DevILを用いた画像ファイルの読み込み - 月とスカラベ
_How To Load and Display an Image with DevIL and OpenGL | Geeks3D

MSYS2では、FreeImage と同様にパッケージが用意されていた。
$ pacman -Ss devil
mingw32/mingw-w64-i686-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
mingw64/mingw-w64-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
ucrt64/mingw-w64-ucrt-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
clang32/mingw-w64-clang-i686-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)
clang64/mingw-w64-clang-x86_64-devil 1.8.0-8
    Library for reading several different image formats  (mingw-w64)

インストール。
pacman -S mingw-w64-x86_64-devil
pacman -S mingw-w64-i686-devil

あちこちからコピペしてサンプルを書いてみた。画像を読み込んで画像サイズを出力するだけのスクリプト。

_01_load_image_devil.cpp
// DevIL sample.

#include <IL/ilu.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#undef USE_WCHAR_T

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("Usage:\n\thoge.exe IN.png\n");
        return 0;
    }

    char *filename = argv[1];

    ILuint img;
    int width, height;

    // DevIL init
    ilInit();
    iluInit();

    // generate image ID
    ilGenImages(1, &img);

    ilBindImage(img);

#ifdef USE_WCHAR_T
    // char to wchar_t
    size_t newsize = strlen(filename) + 1;
    wchar_t *wcfilename = new wchar_t[newsize];
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcfilename, newsize, filename, _TRUNCATE);
#endif

    ILboolean fg;

    if (1)
    {
        // not use ilLoadImage()

        ILubyte *lump;
        ILuint size;
        FILE *fp;

        fp = fopen(filename, "rb");
        fseek(fp, 0, SEEK_END);
        size = ftell(fp);

        lump = (ILubyte *)malloc(size);
        fseek(fp, 0, SEEK_SET);
        fread(lump, 1, size, fp);
        fclose(fp);

        fg = ilLoadL(IL_PNG, lump, size);

        free(lump);
    }
    else
    {
        // use ilLoadImage()

        fg = ilLoadImage(filename);
    }

    if (fg)
    {
        // load success
        printf("Load : %s\n", filename);

        width = ilGetInteger(IL_IMAGE_WIDTH);
        height = ilGetInteger(IL_IMAGE_HEIGHT);
        printf("image size : %d x %d\n", width, height);

        if (ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT)
        {
            iluFlipImage();
        }
    }
    else
    {
        // load failure
        ILenum err = ilGetError();
        std::cerr << iluErrorString(err) << std::endl;
    }

    ilDeleteImages(1, &img);

#ifdef USE_WCHAR_T
    delete wcfilename;
#endif

    return 0;
}

以下でコンパイル。
g++ 01_load_image_devil.cpp -o 01_load_image_devil.exe -lIL -lILU

一応動いたものの、ldd 01_load_image_devil.exe で使用DLLを確認したら、これまたかなりの .dllを利用すると分かった。

試しに -static をつけて静的リンクを試みたところ、-lIL -ILU が見つからないとエラーが出てしまった。ググった感じでは、静的リンクをするなら DevILライブラリ自体をビルドし直さないといけないらしい。

wchar_t と char の変換。 :

Visual Studio Code 上で、DevIL のサンプルを書いていた際、ilLoadImage() に char* を渡す書き方をしたところ、「この関数は wchar_t* を要求している」と警告が表示されてしまった。

色々ググって、char* を wchar_t* に変換するやり方を記述したのだけど…。MSYS2 + MinGW64 でビルドしてみたら、今度は「これは char* を要求する関数だぞ」と言われてエラーが出てしまった。

どうやら、Visual Studio Code 上の警告は無視して、そのまま char* を渡せば良かったらしい。

ちなみに、char* から wchar_t* に変換する処理は以下。wcfilename に wchar_t の文字列が入る。はず。また、使い終わったら delete で消去(解放)しておかないといけない。
    // char to wchar_t
    size_t newsize = strlen(filename) + 1;
    wchar_t *wcfilename = new wchar_t[newsize];
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcfilename, newsize, filename, _TRUNCATE);
...
    delete wcfilename;

さておき。前述の問題の解決策が分かった。#define _UNICODE を無効にすれば、wchar_t* ではなく char* が使われる状態になるらしい。そのあたり、以下の韓国語ページで解説されていた。

_c++ devil library char または wchar_t の問題

vscode(Visual Studio Code)上で、この定義、_UNICODE を無効にするためには、以下の解説に従って作業する。

_Visual Studio Code C++ remove 'define' in c_cpp_properties.json - Stack Overflow

まず、vscode-preinclude.h というファイルをワークスペースフォルダ内に作って、以下の一文を書いておく。
#undef _UNICODE

vscode上で、Ctrl + Shift + P → C/C++:構成の編集(UI) C/C++: Edit Configurations (UI)。設定画面が出てくるので下のほうまでスクロールして「詳細設定」をクリック。「強制インクルード」の欄に以下を記述。
${workspaceFolder}/vscode-preinclude.h

これで、vscode に vscode-preinclude.h が読み込まれて、そこに _UNICODE の定義を削除する一文が書いてあるから、DevIL の各関数が、wchar_t* ではなく char* を要求するものとして vscode に認識されて、おかしな警告が表示されなくなる。

あるいは、以下で紹介されているように、C++ の標準機能でファイルを開いてメモリに格納してから、そのメモリ領域を ilLoadL() に渡すのもアリかもしれない。

_The Developer's Image Library (DevIL) Tutorials

以上です。

過去ログ表示

Prev - 2022/07 - 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