#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2025/12/06 21:23:37 +0900> """ Open image files using the standard Windows file dialog Windows11 x64 25H2 + GIMP 3.0.4 Portable Rev 2 """ import sys import gi gi.require_version("Gimp", "3.0") from gi.repository import Gimp gi.require_version("GimpUi", "3.0") from gi.repository import GimpUi gi.require_version("Gio", "2.0") from gi.repository import Gio from gi.repository import GLib from gi.repository import Gtk from gi.repository import Gegl from gi.repository import GObject import ctypes import os from pathlib import Path WINOPEN_FILTERS = [ "All files {*.*}", "*.*", "PNG", "*.png", "JPEG", "*.jpg;*.jpeg", ] OFN_ALLOWMULTISELECT = 0x00000200 OFN_FILEMUSTEXIST = 0x00001000 OFN_PATHMUSTEXIST = 0x00000800 OFN_EXPLORER = 0x00080000 BUFFER_SIZE = 32768 class OPENFILENAME(ctypes.Structure): """Windowsの標準ファイルダイアログ用の構造体を定義""" _fields_ = [ ("lStructSize", ctypes.c_uint32), ("hwndOwner", ctypes.c_void_p), ("hInstance", ctypes.c_void_p), ("lpstrFilter", ctypes.c_wchar_p), ("lpstrCustomFilter", ctypes.c_wchar_p), ("nMaxCustFilter", ctypes.c_uint32), ("nFilterIndex", ctypes.c_uint32), ("lpstrFile", ctypes.c_wchar_p), ("nMaxFile", ctypes.c_uint32), ("lpstrFileTitle", ctypes.c_wchar_p), ("nMaxFileTitle", ctypes.c_uint32), ("lpstrInitialDir", ctypes.c_wchar_p), ("lpstrTitle", ctypes.c_wchar_p), ("Flags", ctypes.c_uint32), ("nFileOffset", ctypes.c_uint16), ("nFileExtension", ctypes.c_uint16), ("lpstrDefExt", ctypes.c_wchar_p), ("lCustData", ctypes.c_void_p), ("lpfnHook", ctypes.c_void_p), ("lpTemplateName", ctypes.c_wchar_p), ("pvReserved", ctypes.c_void_p), ("dwReserved", ctypes.c_uint32), ("FlagsEx", ctypes.c_uint32), ] class m256WindowsOpenDialogPlugin(Gimp.PlugIn): """GIMPプラグイン部分""" def do_query_procedures(self): # プロシージャブラウザで表示される名前。ユニークな名前にすること。 return ["m256-plug-in-windows-opendlg-python"] def do_set_i18n(self, name): """国際化に対応してるかどうかを返す""" return False def do_create_procedure(self, name): """プロシージャに登録するための設定""" procedure = Gimp.ImageProcedure.new( self, name, Gimp.PDBProcType.PLUGIN, self.run, None ) # 対応画像形式 procedure.set_image_types("*") # 画像を開いてなくてもメニューを有効にする procedure.set_sensitivity_mask(Gimp.ProcedureSensitivityMask.ALWAYS) # メニュー上に表示するラベル名 procedure.set_menu_label("Win Open") # メニューの場所 procedure.add_menu_path("/File/") # 説明 procedure.set_documentation( "Open standard Windows file dialog", # 簡単な説明 "Open the file using the standard Windows file dialog", # 詳細な説明 name, ) # 作成者、著作権者、作成日 procedure.set_attribution("mieki256", "mieki256", "2025/12/05") return procedure def get_openfilename(self, initial_dir=None, filters=None): """Windowsの標準ファイルダイアログを使って複数ファイルを選択""" comdlg32 = ctypes.WinDLL("comdlg32") comdlg32.GetOpenFileNameW.restype = ctypes.c_bool comdlg32.GetOpenFileNameW.argtypes = (ctypes.POINTER(OPENFILENAME),) ofn = OPENFILENAME() lenFilenameBufferInChars = BUFFER_SIZE buf = ctypes.create_unicode_buffer(lenFilenameBufferInChars) ofn.lStructSize = ctypes.sizeof(OPENFILENAME) # ファイル種類を設定 if filters: ofn.lpstrFilter = "\0".join(filters) + "\0\0" else: ofn.lpstrFilter = "All files {*.*}\0*.*\0\0" if initial_dir: ofn.lpstrInitialDir = initial_dir ofn.lpstrFile = ctypes.cast(buf, ctypes.c_wchar_p) ofn.nMaxFile = lenFilenameBufferInChars ofn.lpstrTitle = "Select file" ofn.Flags = ( OFN_ALLOWMULTISELECT | OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST ) # ファイル選択ダイアログを開く ret = comdlg32.GetOpenFileNameW(ofn) if ret: s = buf[:].rstrip("\0") path = s[: ofn.nFileOffset].rstrip("\0") filenames = s[ofn.nFileOffset :].split("\0") files = [os.path.abspath(os.path.join(path, f)) for f in filenames] # ファイルパスをリストで返す return files return [] def load_image(self, filepath: str): """画像を読み込み""" # "\\" は問題があるらしいので "/" に置換 p = Path(filepath) filepath_unix = p.as_posix() # 画像を読み込む。この段階ではGIMPウインドウに表示されていない file = Gio.File.new_for_path(filepath_unix) image = Gimp.file_load(Gimp.RunMode.NONINTERACTIVE, file) if image: # 画像バッファに元ファイル名を反映させたいが上手く行かない # if not image.set_file(file): # Gimp.message(f"Failed set_file(). {file.get_basename()}") # GIMPウインドウ上に画像を表示 display = Gimp.Display.new(image) # 何故か編集された画像として扱われてしまうのでフラグをクリア image.clean_all() # 画面を更新 Gimp.displays_flush() else: Gimp.message(f"Failed to load {file.get_basename()}") return image, display def run(self, procedure, run_mode, image, drawables, config, run_data): homedir = os.path.expanduser("~") files = self.get_openfilename(initial_dir=homedir, filters=WINOPEN_FILTERS) if files: for path in files: self.load_image(path) # else: # Gimp.message("Cancel.") # 処理を実行して成功した場合は以下を返す return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error()) class GetDialog(Gtk.Dialog): """ダイアログ関係""" # 今回は何もしていない def __init__(self, parent, plugin): Gtk.Dialog.__init__( self, title="Dialog Title", parent=parent, flags=Gtk.DialogFlags.MODAL ) # プラグインとして登録 Gimp.main(m256WindowsOpenDialogPlugin.__gtype__, sys.argv)