fortran66のブログ

fortran について書きます。Amazonのアソシエイトとして収入を得ています。

【メモ帳】STB ライブラリによる Fortran での画像ファイル I/O 【ChatGPT 5】

ChatGPT 5 におまかせ

チャッピーこと ChatGPTが 5 になるというので期待していたのですが、冗談のわからないクソ真面目AIになって失望しました。Google Gemini と話しているような感じです。字義通りに解釈しようとするのはアスペのようです。

ともあれプログラム能力が上がったというので、Fortran で画像を読み書きする方法を考えてくれと聞いてみました。過去には Windows GDI+ によって行う方法で一緒に作りましたが、Win 32/64 API 用のインターフェースはかなり手で修正する必要がありました。

fortran66.hatenablog.com

fortran66.hatenablog.com

fortran66.hatenablog.com

今回は OS 非依存にしてと頼んだので、おおかた libjpeg/libpng あたりをリンクしてくるだろうと予想しておりました。確かにそれが第一候補だったのですが、第二候補として、Sean T. Barrett 氏の軽量・簡易ライブラリも提案してくれました。これについては全く聞いたことがなかったのですが、主要機能のみを扱う簡便さを重視したルーチン集で、ヘッダーファイルの中に実装も記述してシングルファイルとするような、私にはよく分からない技法を使って簡易に使えるようにしています。

github.com

stb_image などの stbライブラリ群 は、けっこう昔から存在しています。 • 作者:Sean T. Barrett • 初期公開:だいたい 2005年ごろ に最初のstbシリーズ(stb_truetypeなど)が登場 • stb_image:2006〜2007年ごろには既に公開されていました • GitHub移行:2010年代前半(作者の個人サイトでの配布から、現在の GitHub https://github.com/nothings/stb に) • 現在:GitHubのスター数は40,000以上、ゲームエンジンやツール、研究コードでもかなり使われている

歴史的な背景 • 元々はゲーム開発者向けの「依存を減らすための1ファイルCライブラリ集」として始まった • 商用ゲーム・インディーゲームの多くが画像読み込みやフォント描画の簡易実装にstbを採用 • 仕様は最小限で、libpngやlibjpegほどの完全性はないが、試作や軽量ツールでは事実上の定番になっている  

そうして、チャッピーの言う通りにやって、ほぼ問題なくそのままに近い形で動きました。唯一 iso_fortran_env を引用せずに、 real64 常数を使っていたので use, intrinsic :: iso_fortran_env の一行を加える必要がありました。これくらいは、無問題と言っていいでしょう。real(8) とか芋臭い書き方してますが。

確かにチャッピーのプログラミング能力は一層進化しているようです。 もう interface 書きのような下等な仕事は AI にやって貰えばいい時代ですね。

ChatGPT による指示

icx /c /O2 img_shim.c
ifx /O2 demo.f90 img_shim.obj /Fe:demo_img.exe
demo_img.exe

in.jpg が out.jpg と out.png に出力される。

絵もチャッピーに描いてもらいました。EU が中世ローマ教会よろしく CO2 排出権免罪符を売りつけて不正にボロ儲けいるところです。

! ---- 例の加工:簡単なガンマっぽい明るさ補正 & 対角線に白線 ----

斯様な加工がなされるようです。

  • in.jpg

  • out.jpg

  • out.png

stb より2ファイル

stb_image.h

stb_image_write.h

img_shim.c

// img_shim.c
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
#include "stb_image_write.h"

#include <stdint.h>
#include <stdlib.h>

#ifdef _MSC_VER
#define EXP __declspec(dllexport)
#else
#define EXP
#endif

// 読み込み:path → RGBA8 配列
EXP int load_image_rgba8(const char* path, int* w, int* h, int* stride, uint8_t** out) {
    int n;
    unsigned char* data = stbi_load(path, w, h, &n, 4); // つねにRGBA(8bpc)で返す
    if (!data) return -1;
    *stride = (*w) * 4;
    *out = data;
    return 0;
}

// 書き出し:RGBA8 → PNG
EXP int save_png_rgba8(const char* path, int w, int h, int stride, const uint8_t* rgba) {
    // stride は 4*w を推奨。stbは任意strideにも対応
    return stbi_write_png(path, w, h, 4, rgba, stride) ? 0 : -1;
}

// 書き出し:RGBA8 → JPEG(quality 1〜100)
EXP int save_jpeg_rgba8(const char* path, int w, int h, int stride, const uint8_t* rgba, int quality) {
    return stbi_write_jpg(path, w, h, 4, rgba, quality) ? 0 : -1;
}

// バッファ解放
EXP void free_image_buffer(uint8_t* p) {
    stbi_image_free(p);
}

demo.f90

module imgshim
  use iso_c_binding
  implicit none
  interface
    function load_image_rgba8(path, w, h, stride, out) bind(C, name="load_image_rgba8")
      import :: c_char, c_int, c_ptr
      character(kind=c_char), intent(in) :: path(*)
      integer(c_int) :: w, h, stride
      type(c_ptr) :: out
      integer(c_int) :: load_image_rgba8
    end function
    function save_png_rgba8(path, w, h, stride, rgba) bind(C, name="save_png_rgba8")
      import :: c_char, c_int, c_ptr
      character(kind=c_char), intent(in) :: path(*)
      integer(c_int), value :: w, h, stride
      type(c_ptr), value :: rgba
      integer(c_int) :: save_png_rgba8
    end function
    function save_jpeg_rgba8(path, w, h, stride, rgba, quality) bind(C, name="save_jpeg_rgba8")
      import :: c_char, c_int, c_ptr
      character(kind=c_char), intent(in) :: path(*)
      integer(c_int), value :: w, h, stride, quality
      type(c_ptr), value :: rgba
      integer(c_int) :: save_jpeg_rgba8
    end function
    subroutine free_image_buffer(p) bind(C, name="free_image_buffer")
      import :: c_ptr
      type(c_ptr), value :: p
    end subroutine
  end interface
contains
  pure function cstr(s) result(cs)
    character(*), intent(in) :: s
    character(kind=c_char), allocatable :: cs(:)
    integer :: n
    n = len_trim(s)
    allocate(cs(0:n))
    if (n > 0) cs(0:n-1) = transfer(s(1:n), cs(0:n-1))
    cs(n) = c_null_char
  end function
end module

program demo
  use iso_c_binding
  use imgshim
  implicit none

  ! 入力ファイル名は拡張子自由(jpg/png/bmp等):
  character(*), parameter :: infile  = "in.jpg"
  character(*), parameter :: outpng  = "out.png"
  character(*), parameter :: outjpeg = "out.jpg"

  integer(c_int) :: w, h, stride, ierr
  type(c_ptr)    :: pbuf
  integer(c_int8_t), pointer :: img(:)   ! バイト配列ビュー(RGBAの生配列)

  ierr = load_image_rgba8(cstr(infile), w, h, stride, pbuf)
  if (ierr /= 0) stop "load failed"
  call c_f_pointer(pbuf, img, [stride*h])

  ! ---- 例の加工:簡単なガンマっぽい明るさ補正 & 対角線に白線 ----
  call brighten(img, stride*h, 1.2_real64)
  call draw_diagonal_white(img, w, h, stride)

  ierr = save_png_rgba8(cstr(outpng),  w, h, stride, pbuf);  if (ierr /= 0) stop "save png failed"
  ierr = save_jpeg_rgba8(cstr(outjpeg), w, h, stride, pbuf, 90_c_int); if (ierr /= 0) stop "save jpeg failed"

  call free_image_buffer(pbuf)
  print *, "done: ", trim(infile), " -> ", trim(outpng), " / ", trim(outjpeg)
contains
  subroutine brighten(b, n, gain)
    integer(c_int8_t), intent(inout) :: b(:)
    integer, intent(in) :: n
    real(8), intent(in) :: gain
    integer :: i
    real(8) :: x
    do i = 1, n, 4
      x = min(255.0d0, real(b(i  ),8)*gain); b(i  ) = int(x, c_int8_t) ! R
      x = min(255.0d0, real(b(i+1),8)*gain); b(i+1) = int(x, c_int8_t) ! G
      x = min(255.0d0, real(b(i+2),8)*gain); b(i+2) = int(x, c_int8_t) ! B
      ! A(=b(i+3))はそのまま
    end do
  end subroutine

  subroutine draw_diagonal_white(b, w, h, stride)
    integer(c_int8_t), intent(inout) :: b(:)
    integer, intent(in) :: w, h, stride
    integer :: y, x, idx
    do y = 0, min(w-1, h-1)
      x = y
      idx = y*stride + x*4 + 1
      if (idx+3 <= size(b)) then
        b(idx  ) = 255_c_int8_t
        b(idx+1) = 255_c_int8_t
        b(idx+2) = 255_c_int8_t
      end if
    end do
  end subroutine
end program