Windows GDI+ API 呼び出しによる画像ファイル読み書き
Win32/64API の GDI では BMP ファイルの読み書きしかできませんでしたが、その後に出た GDI+ で JPG/PNG /GIF/TIFF 等が読み書きできるようになりました。しかしながら C++ のクラス利用などが前提となって GDI の時の様に素朴に C ルーチン呼び出しのインターフェースを書くだけでは利用できず、私にはお手上げでした。
その後、昔から Fortran での画像処理を手掛けていた Fortran Plaza さんが、gfortran から GDI+ を利用できることを示してソースコード も公開されました。その内容はライブラリ全体を包括するような本格的なもので、大規模で少し難しく、そのうち勉強しようと思いつつほったらかしていました。
fortran66.hatenablog.com
最近、AI に 「GDI+ での jpg 読み書き Fortran プログラム書けるか?」と聞いてみたところ、「お安い御用さ!」と言いつつ、ホイホイプログラムを吐き出し、画像サイズを出すところまではわずかな修正で行けました。そこから先は嘘だらけになって若干苦労しましたが、必要最低限の API インターフェースの用意で、JPG/PNG /GIF/TIFF 等の画像ファイルを RGB 形式の配列として読み込み、適当な処理をした後、JPG/PNG /GIF/TIFF 等の好みの形式で書き出せるようになりました。
かったるい C との interface 書きなどを AI がやってくれるので、AI 様様と言ったところです。ただ AI 様は、質問者の私がよく分からないことを、分からない人なりの要領を得ない問で聞くと、嘘と真を混ぜて答えてきて閉口させられます。こちらも真偽の判断がつかないので、すぐ騙されます。この辺は、次の世代の AI を待ちたいと思います。
AI 利用は 八合目あたりまで AI 様のケーブルカーで登って、そこから頂上までは自力登山という感じでしょうか。最近の腐りきった Google 検索などではなかなか欲しい情報にたどり着けないので 、AI 様なしではこのプログラムは書けなかったと思います。なおもっぱら Claude 無料枠に書いてもらいました。ChatGPT をクロスチェックに利用しました。
実行例
jpg ファイルを読み込んで、色を反転させて別名 jpg ファイルに保存しています。
GDI+ initialized successfully
Image loaded successfully
0 width 1280
0 height 720
Image saved successfully
Bitmap disposed successfully
GDI+ shut down successfully
GDI+ library unloaded successfully
続行するには何かキーを押してください . . .
入力ファイル
出力ファイル
VIDEO
www.youtube.com
ソース・プログラム
intel fortran ifx 2024.2.0 で実行しました。stdcall などの呼び出し規約の関係で Win64 でなければ動きません。
ファイルの読み込みの方は、拡張子で自動判別して読み込めますが、書き込みの方は jpg 決め打ちになっています。CLSID を変えることで書き出しファイル形式を変えられます。(CLSID は ID 数列を 32bit little endian , 16 bit little endian , 16 bit little endian , 8bit/4bit の並びで解釈します。AIに騙されましたw)ファイル名は末尾に C 言語式の NULL 番兵が必要です。また ASCII 文字列では駄目で、16bit の wide 文字列に変換する必要があります。(AIに騙されましたw)
プログラムの概要:
gdiplus.dll をダイナミックロード。
CreateBitmapFromFile によって 32bit BMP 形式(ARGB 形式)で読み込む。
RGB の配列をいじる。
GdipSaveImageToFile でファイルに書き出す。
module LoadDll_m
use , intrinsic :: iso_c_binding
implicit none
private
public :: LoadLibrary, GetProcAddress, FreeLibrary
interface
function LoadLibrary(lpFileName) bind (C, name= 'LoadLibraryA' )
import
integer (C_INTPTR_T ) :: LoadLibrary
character (kind = C_CHAR ), intent (in ) :: lpFileName(* )
end function LoadLibrary
function GetProcAddress(hModule, lpProcName) bind (C, name= 'GetProcAddress' )
import
type (C_FUNPTR ) :: GetProcAddress
integer (C_INTPTR_T ), value :: hModule
character (kind = C_CHAR ), intent (in ) :: lpProcName(* )
end function GetProcAddress
function FreeLibrary(hModule) bind (C, name= 'FreeLibrary' )
import
integer (C_INT ) :: FreeLibrary
integer (C_INTPTR_T ), value :: hModule
end function FreeLibrary
end interface
end module LoadDll_m
module WideString_m
use , intrinsic :: iso_c_binding
implicit none
integer (C_INT ), parameter :: CP_ACP = 0
integer (C_INT ), parameter :: MB_ERR_INVALID_CHARS = z'00000008'
interface
function MultiByteToWideChar(CodePage, dwFlags, lpMultiByteStr, cbMultiByte, &
lpWideCharStr, cchWideChar) bind (C, name= 'MultiByteToWideChar' )
import
integer (C_INT ), value :: CodePage, dwFlags
type (C_PTR ), value :: lpMultiByteStr
integer (C_INT ), value :: cbMultiByte
type (C_PTR ), value :: lpWideCharStr
integer (C_INT ), value :: cchWideChar
integer (C_INT ) :: MultiByteToWideChar
end function MultiByteToWideChar
end interface
contains
function to_wide_string(text) result (res)
character (len = * ), intent (in ), target :: text
character (kind = C_CHAR , len = :), allocatable , target :: res
integer (C_INT ) :: len_text, iret
character (kind = C_CHAR , len = :), pointer :: wide_string
len_text = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, &
c_loc (text), len_trim (text), &
C_NULL_PTR , 0 )
if (len_text == 0 ) then
print * , "to_wide_string: Error in getting length."
stop
end if
allocate (character (kind = C_CHAR , len = len_text* 2 ) :: res)
iret = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, &
c_loc (text), len_trim (text), &
c_loc (res), len_text)
if (iret == 0 ) then
print * , "to_wide_string: Error in conversion."
stop
end if
end function to_wide_string
end module WideString_m
module GdiPlus_m
use , intrinsic :: iso_c_binding
use :: LoadDll_m
use :: WideString_m
implicit none
integer (c_int ), parameter :: PixelFormat32bppARGB = 2498570
integer (c_int ), parameter :: ImageLockModeRead = 1 , ImageLockModeWrite = 2
type , bind (C) :: GdiplusStartupInput_t
integer (C_LONG ) :: GdiplusVersion
type (C_FUNPTR ) :: DebugEventCallback
integer (C_INT ) :: SuppressBackgroundThread
integer (C_INT ) :: SuppressExternalCodecs
end type GdiplusStartupInput_t
type , bind (c) :: Rect_t
integer (c_long ) :: X = 0 , Y = 0 , Width = 0 , Height = 0
end type Rect_t
type , bind (c) :: BitmapData_t
integer (c_int ) :: Width
integer (c_int ) :: Height
integer (c_int ) :: Stride
integer (c_int ) :: PixelFormat
type (c_ptr ) :: Scan0
type (c_ptr ) :: Reserved
end type BitmapData_t
abstract interface
function GdiplusStartupFunc(token, input, output) bind (C)
import
integer (C_INT ) :: GdiplusStartupFunc
integer (C_INT64_T ) :: token
type (GdiplusStartupInput_t), intent (in ) :: input
type (C_PTR ), value :: output
end function GdiplusStartupFunc
subroutine GdiplusShutdownFunc(token) bind (C)
import
integer (C_INT64_T ), value :: token
end subroutine GdiplusShutdownFunc
function GdipLoadImageFromFileFunc(filename, image) bind (C)
import
integer (C_INT ) :: GdipLoadImageFromFileFunc
character (kind = C_CHAR ), intent (in ):: filename(* )
type (C_PTR ) :: image
end function GdipLoadImageFromFileFunc
function GdipSaveImageToFileFunc(image, filename, clsidEncoder, encoderParams) bind (C)
import
integer (C_long ) :: GdipSaveImageToFileFunc
type (C_PTR ), value :: image
character (kind = C_CHAR ), intent (in ) :: filename(* )
type (C_PTR ), value :: clsidEncoder
type (C_PTR ), value :: encoderParams
end function GdipSaveImageToFileFunc
function GdipGetImageWidthFunc(image, width) bind (c)
import
integer (c_long ) :: GdipGetImageWidthFunc
type (c_ptr ), value :: image
integer (c_int ), intent (out ) :: width
end function GdipGetImageWidthFunc
function GdipGetImageHeightFunc(image, height) bind (c)
import
integer (c_long ) :: GdipGetImageHeightFunc
type (c_ptr ), value :: image
integer (c_int ), intent (out ) :: height
end function GdipGetImageHeightFunc
function GdipDisposeImageFunc(image) bind (c)
import
integer (c_long ) :: GdipDisposeImageFunc
type (c_ptr ), value :: image
end function GdipDisposeImageFunc
function GdipBitmapLockBitsFunc(bitmap, rect, flags, format , lockedBitmapData) bind (c)
import
integer (c_long ) :: GdipBitmapLockBitsFunc
type (c_ptr ), value :: bitmap
type (Rect_t), intent (in ) :: rect
integer (c_int ), value :: flags
integer (c_int ), value :: format
type (BitmapData_t) :: lockedBitmapData
end function GdipBitmapLockBitsFunc
function GdipBitmapUnlockBitsFunc(bitmap, lockedBitmapData) bind (c)
import
integer (c_long ) :: GdipBitmapUnlockBitsFunc
type (c_ptr ), value :: bitmap
type (BitmapData_t), intent (in ) :: lockedBitmapData
end function GdipBitmapUnlockBitsFunc
function GdipCreateBitmapFromFileFunc(filename, bitmap) bind (c)
import
integer (c_long ) :: GdipCreateBitmapFromFileFunc
character (kind = C_CHAR ), intent (in ):: filename(* )
type (c_ptr ):: bitmap
end function GdipCreateBitmapFromFileFunc
end interface
type (C_FUNPTR ) :: procStartup, procShutdown
type (C_FUNPTR ) :: procLoadImage, procSaveImage
type (c_funptr ) :: procGetImageWidth, procGetImageHeight, procDisposeImage
type (c_funptr ) :: procCreateBitmapFromFile
type (c_funptr ) :: procBitmapLockBits, procBitmapUnlockBits
procedure (GdiplusStartupFunc), pointer :: GdiplusStartup
procedure (GdiplusShutdownFunc), pointer :: GdiplusShutdown
procedure (GdipLoadImageFromFileFunc), pointer :: GdipLoadImageFromFile
procedure (GdipSaveImageToFileFunc), pointer :: GdipSaveImageToFile
procedure (GdipGetImageWidthFunc), pointer :: GdipGetImageWidth
procedure (GdipGetImageHeightFunc), pointer :: GdipGetImageHeight
procedure (GdipDisposeImageFunc), pointer :: GdipDisposeImage
procedure (GdipCreateBitmapFromFileFunc), pointer :: GdipCreateBitmapFromFile
procedure (GdipBitmapLockBitsFunc), pointer :: GdipBitmapLockBits
procedure (GdipBitmapUnlockBitsFunc), pointer :: GdipBitmapUnlockBits
integer (c_long ), target :: clsid_bmp_encoder(4 ) = [z'557cf400' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_jpg_encoder(4 ) = [z'557cf401' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_gif_encoder(4 ) = [z'557cf402' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_emf_encoder(4 ) = [z'557cf403' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_wmf_encoder(4 ) = [z'557cf404' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_tif_encoder(4 ) = [z'557cf405' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_png_encoder(4 ) = [z'557cf406' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (c_long ), target :: clsid_ico_encoder(4 ) = [z'557cf407' , z'11d31a04' , z'0000739a' , z'2ef31ef8' ]
integer (C_INTPTR_T ) :: hLib
integer (C_INT64_T ) :: token
type (GdiplusStartupInput_t) :: input
type (BitmapData_t), target :: bmpData
type (c_ptr ), target :: bitmap
contains
subroutine initialize_GDI()
integer (C_INT ) :: status
hLib = LoadLibrary("gdiplus.dll" // C_NULL_CHAR )
if (hLib == 0 ) then
print * , "Failed to load GDI+ library"
stop
end if
procStartup = GetProcAddress(hLib, "GdiplusStartup" // C_NULL_CHAR )
procShutdown = GetProcAddress(hLib, "GdiplusShutdown" // C_NULL_CHAR )
procLoadImage = GetProcAddress(hLib, "GdipCreateBitmapFromFile" // C_NULL_CHAR )
procSaveImage = GetProcAddress(hLib, "GdipSaveImageToFile" // C_NULL_CHAR )
procGetImageWidth = GetProcAddress(hLib, "GdipGetImageWidth" // C_NULL_CHAR )
procGetImageHeight = GetProcAddress(hLib, "GdipGetImageHeight" // C_NULL_CHAR )
procDisposeImage = GetProcAddress(hLib, "GdipDisposeImage" // C_NULL_CHAR )
procCreateBitmapFromFile = GetProcAddress(hLib, "GdipCreateBitmapFromFile" // C_NULL_CHAR )
procBitmapLockBits = GetProcAddress(hLib, "GdipBitmapLockBits" // C_NULL_CHAR )
procBitmapUnlockBits = GetProcAddress(hLib, "GdipBitmapUnlockBits" // C_NULL_CHAR )
if (.not. c_associated (procStartup) .or. &
.not. c_associated (procShutdown) .or. &
.not. c_associated (procLoadImage) .or. &
.not. c_associated (procSaveImage) .or. &
.not. c_associated (procGetImageWidth) .or. &
.not. c_associated (procGetImageHeight) .or. &
.not. c_associated (procDisposeImage) .or. &
.not. c_associated (procCreateBitmapFromFile) .or. &
.not. c_associated (procBitmapLockBits) .or. &
.not. c_associated (procBitmapUnlockBits) ) then
print * , "Failed to get one or more GDI+ function addresses"
status = FreeLibrary(hLib)
stop
end if
call c_f_procpointer (procStartup, GdiplusStartup)
call c_f_procpointer (procShutdown, GdiplusShutdown)
call c_f_procpointer (procLoadImage, GdipLoadImageFromFile)
call c_f_procpointer (procSaveImage, GdipSaveImageToFile)
call c_f_procpointer (procGetImageWidth, GdipGetImageWidth)
call c_f_procpointer (procGetImageHeight, GdipGetImageHeight)
call c_f_procpointer (procDisposeImage, GdipDisposeImage)
call c_f_procpointer (procBitmapLockBits, GdipBitmapLockBits)
call c_f_procpointer (procBitmapUnLockBits, GdipBitmapUnLockBits)
call c_f_procpointer (procCreateBitmapFromFile, GdipCreateBitmapFromFile)
input%GdiplusVersion = 1
input%DebugEventCallback = C_NULL_FUNPTR
input%SuppressBackgroundThread = 0
input%SuppressExternalCodecs = 0
status = GdiplusStartup(token, input, C_NULL_PTR )
if (status /= 0 ) then
print * , "Failed to initialize GDI+. Error code:" , status
status = FreeLibrary(hLib)
stop
else
print * , "GDI+ initialized successfully"
end if
end subroutine initialize_GDI
subroutine finialize_GDI()
integer (C_INT ) :: status
call GdiplusShutdown(token)
print * , "GDI+ shut down successfully"
status = FreeLibrary(hLib)
if (status == 0 ) then
print * , "Failed to unload GDI+ library"
else
print * , "GDI+ library unloaded successfully"
end if
end subroutine finialize_GDI
subroutine open_and_read(fn, pixels)
character (kind = c_char , len =* ), target , intent (in ) :: fn
integer (c_int ), pointer , intent (out ) :: pixels(:, :)
integer (C_INT ) :: status
integer (C_INT ) :: iwidth, iheight
type (Rect_t) :: rect_in
status = GdipCreateBitmapFromFile(to_wide_string(fn), bitmap)
if (status /= 0 ) then
print * , "Failed to load image. Error code:" , status
stop
else
print * , "Image loaded successfully"
status = GdipGetImageWidth(bitmap, iwidth)
print * , status , 'width ' , iwidth
status = GdipGetImageHeight(bitmap, iHeight)
print * , status , 'height' , iheight
end if
bmpData%Width = iwidth
bmpData%Height = iheight
bmpData%Stride = ((iwidth * 32 + 31 ) / 32 ) * 4
bmpData%PixelFormat = PixelFormat32bppARGB
rect_in%X = 0
rect_in%Y = 0
rect_in%Width = iwidth
rect_in%Height = iheight
status = GdipBitmapLockBits(bitmap, rect_in, ImageLockModeRead + ImageLockModeWrite, &
PixelFormat32bppARGB, bmpData)
if (status /= 0 ) then
print * , 'BitmapLockBits failed. status =' , status
stop
end if
call c_f_pointer (bmpData%Scan0, pixels, [iwidth, iheight])
end subroutine open_and_read
subroutine write_and_close(fn)
character (kind = c_char , len =* ), intent (in ) :: fn
integer (C_INT ) :: status
status = GdipBitmapUnLockBits(bitmap, bmpData)
if (status /= 0 ) then
print * , 'BitmapUnLockBits failed. status =' , status
stop
end if
status = GdipSaveImageToFile(bitmap, to_wide_string(fn), C_loc (clsid_jpg_encoder), C_NULL_PTR )
if (status /= 0 ) then
print * , "Failed to save image. Error code:" , status
else
print * , "Image saved successfully"
end if
status = GdipDisposeImage(bitmap)
if (status /= 0 ) then
print * , "Failed to dispose Bitmap. Error code:" , status
else
print * , "Bitmap disposed successfully"
end if
end subroutine write_and_close
end module GdiPlus_m
program test_gdiplus
use , intrinsic :: iso_c_binding
use :: LoadDll_m
use :: GdiPlus_m
implicit none
call initialize_GDI()
BMP :block
character (kind = c_char , LEN = 256 ), target :: fn
integer (c_int ), pointer :: pixels(:, :)
fn = "C:\temp\kositantan.jpg" // C_NULL_CHAR
call open_and_read(fn, pixels)
RGBA :block
integer (c_int ) :: ir, ig, ib, ia
do concurrent (integer ::ix = 1 :size (pixels, 1 ), iy = 1 :size (pixels, 2 ))
ia = iand (ishft (pixels(ix, iy), - 24 ), 255 )
ir = iand (ishft (pixels(ix, iy), - 16 ), 255 )
ig = iand (ishft (pixels(ix, iy), - 8 ), 255 )
ib = iand ( pixels(ix, iy) , 255 )
pixels(ix, iy) = (256 - ib) + 256 * (256 - ig + 256 * (256 - ir + 256 * ia))
end do
end block RGBA
fn = "C:\temp\sika.jpg" // C_NULL_CHAR
call write_and_close(fn)
end block BMP
call finialize_GDI()
end program test_gdiplus