diff --git a/meson.build b/meson.build
index 3e6a324e16..6b19eb89de 100644
--- a/meson.build
+++ b/meson.build
@@ -1123,6 +1123,9 @@ conf.set('HAVE_LIBGUDEV', gudev.found())
directx = platform_windows ? cc.find_library('dxguid') : no_dep
conf.set('HAVE_DX_DINPUT', directx.found())
+# Windows Image Acquistion (WIA)
+wia = platform_windows ? cc.find_library('wiaguid') : no_dep
+
cfitsio_dep = dependency('cfitsio', required: get_option('fits'))
diff --git a/plug-ins/common/meson.build b/plug-ins/common/meson.build
index 420e4d1cdd..d76525d112 100644
--- a/plug-ins/common/meson.build
+++ b/plug-ins/common/meson.build
@@ -168,6 +168,11 @@ endif
if platform_windows
common_plugins_list += { 'name': 'file-lnk', }
+ if wia.found()
+ common_plugins_list += { 'name': 'wia',
+ 'deps': [ wia ],
+ }
+ endif
endif
if platform_linux
diff --git a/plug-ins/common/wia.c b/plug-ins/common/wia.c
new file mode 100644
index 0000000000..401095e56f
--- /dev/null
+++ b/plug-ins/common/wia.c
@@ -0,0 +1,335 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * WIA Scanner plug-in
+ *
+ * Copyright (C) 2026 Alex S.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "config.h"
+
+#include /* Needed when compiling with gcc */
+#include
+#include
+
+
+#include
+#include
+#include
+
+#include "libgimp/gimp.h"
+#include "libgimp/stdplugins-intl.h"
+
+
+#define PLUG_IN_NAME "wia-acquire"
+#define PLUG_IN_DESCRIPTION _("Capture an image from a WIA datasource")
+#define PLUG_IN_HELP N_("This plug-in will capture an image from a WIA datasource")
+
+#define MAX_FOLDER_PATH 1024
+
+typedef struct _Wia Wia;
+typedef struct _WiaClass WiaClass;
+
+struct _Wia
+{
+ GimpPlugIn parent_instance;
+};
+
+struct _WiaClass
+{
+ GimpPlugInClass parent_class;
+};
+
+
+#define WIA_TYPE (wia_get_type ())
+#define WIA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), WIA_TYPE, Wia))
+
+GType wia_get_type (void) G_GNUC_CONST;
+
+static GList * wia_query_procedures (GimpPlugIn *plug_in);
+static GimpProcedure * wia_create_procedure (GimpPlugIn *plug_in,
+ const gchar *name);
+
+static GimpValueArray * wia_run (GimpProcedure *procedure,
+ GimpRunMode run_mode,
+ GimpImage *image,
+ GimpDrawable **drawables,
+ GimpProcedureConfig *config,
+ gpointer run_data);
+
+static GimpValueArray * wia_main (GimpProcedure *procedure,
+ GimpProcedureConfig *config);
+
+static HRESULT CreateWiaDeviceManager (IWiaDevMgr2 **ppWiaDevMgr);
+
+G_DEFINE_TYPE (Wia, wia, GIMP_TYPE_PLUG_IN)
+
+GIMP_MAIN (WIA_TYPE)
+DEFINE_STD_SET_I18N
+
+
+static void
+wia_class_init (WiaClass *klass)
+{
+ GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
+
+ plug_in_class->query_procedures = wia_query_procedures;
+ plug_in_class->create_procedure = wia_create_procedure;
+ plug_in_class->set_i18n = STD_SET_I18N;
+}
+
+static void
+wia_init (Wia *wia)
+{
+}
+
+static GList *
+wia_query_procedures (GimpPlugIn *plug_in)
+{
+ return g_list_append (NULL, g_strdup (PLUG_IN_NAME));
+}
+
+static GimpProcedure *
+wia_create_procedure (GimpPlugIn *plug_in,
+ const gchar *name)
+{
+ GimpProcedure *procedure = NULL;
+
+ if (! strcmp (name, PLUG_IN_NAME))
+ {
+ procedure = gimp_image_procedure_new (plug_in, name,
+ GIMP_PDB_PROC_TYPE_PLUGIN,
+ wia_run, NULL, NULL);
+
+ gimp_procedure_set_image_types (procedure, "*");
+ gimp_procedure_set_sensitivity_mask (procedure,
+ GIMP_PROCEDURE_SENSITIVE_DRAWABLE |
+ GIMP_PROCEDURE_SENSITIVE_DRAWABLES |
+ GIMP_PROCEDURE_SENSITIVE_NO_DRAWABLES |
+ GIMP_PROCEDURE_SENSITIVE_NO_IMAGE);
+
+ gimp_procedure_set_menu_label (procedure, _("_Scanner/Camera..."));
+ gimp_procedure_add_menu_path (procedure, "/File/Create");
+
+ gimp_procedure_set_documentation (procedure,
+ PLUG_IN_DESCRIPTION,
+ PLUG_IN_HELP,
+ name);
+ gimp_procedure_set_attribution (procedure,
+ "Alx Sa",
+ "Alx Sa",
+ "2026");
+
+ gimp_procedure_add_string_aux_argument (procedure, "scan-directory",
+ NULL, NULL,
+ NULL,
+ GIMP_PARAM_READWRITE);
+
+ gimp_procedure_add_core_object_array_return_value (procedure, "images",
+ "Array of acquired images",
+ "Array of acquired images",
+ GIMP_TYPE_IMAGE,
+ G_PARAM_READWRITE);
+ }
+
+ return procedure;
+}
+
+static GimpValueArray *
+wia_run (GimpProcedure *procedure,
+ GimpRunMode run_mode,
+ GimpImage *image,
+ GimpDrawable **drawables,
+ GimpProcedureConfig *config,
+ gpointer run_data)
+{
+ gegl_init (NULL, NULL);
+
+ if (run_mode == GIMP_RUN_NONINTERACTIVE)
+ /* Currently, we don't do non-interactive calls.
+ * Bail if someone tries to call us non-interactively
+ */
+ return gimp_procedure_new_return_values (procedure, GIMP_PDB_CALLING_ERROR,
+ NULL);
+
+ return wia_main (procedure, config);
+}
+
+/* Windows methods */
+static HRESULT
+CreateWiaDeviceManager (IWiaDevMgr2 **ppWiaDevMgr)
+{
+ HRESULT hr;
+
+ if (NULL == ppWiaDevMgr)
+ return E_INVALIDARG;
+
+ *ppWiaDevMgr = NULL;
+
+ hr = CoCreateInstance (&CLSID_WiaDevMgr2, NULL, CLSCTX_LOCAL_SERVER,
+ &IID_IWiaDevMgr2, (void**) ppWiaDevMgr);
+
+ return hr;
+}
+
+static GimpValueArray *
+wia_main (GimpProcedure *procedure,
+ GimpProcedureConfig *config)
+{
+ GError *error = NULL;
+ HRESULT hr = CoInitialize (NULL);
+
+ if (SUCCEEDED (hr))
+ {
+ IWiaDevMgr2 *pWiaDevMgr2 = NULL;
+
+ hr = CreateWiaDeviceManager (&pWiaDevMgr2);
+
+ if (SUCCEEDED (hr))
+ {
+ GimpImage **images = NULL;
+ GimpValueArray *return_vals = NULL;
+ HWND window_handle;
+ LONG nFiles = 0;
+ LPWSTR *rFilePaths = NULL;
+ IWiaItem2 *pItem = NULL;
+ BSTR szFolder = NULL;
+ BSTR szFileName;
+ TCHAR temp_path[MAX_FOLDER_PATH];
+ gchar *scan_directory = NULL;
+
+ szFileName = SysAllocString (L"GIMP-SCANNED-IMAGE");
+
+ /* Check if we have a saved directory, and if so, if it's still
+ * valid. */
+ g_object_get (config, "scan-directory", &scan_directory, NULL);
+ if (scan_directory != NULL)
+ {
+ GDir *directory = g_dir_open (scan_directory, 0, NULL);
+
+ if (directory)
+ {
+ szFolder =
+ SysAllocString (g_utf8_to_utf16 (scan_directory, -1, NULL,
+ NULL, NULL));
+ g_dir_close (directory);
+ }
+ g_free (scan_directory);
+ }
+
+ /* Otherwise, use Window's temp location */
+ if (szFolder == NULL)
+ {
+ GetTempPath (MAX_FOLDER_PATH, temp_path);
+ szFolder = SysAllocString (g_utf8_to_utf16 (temp_path, -1, NULL,
+ NULL, NULL));
+ }
+
+ /* Values taken from app/gui/gui-unique.h */
+ window_handle = FindWindowW (L"GimpWin32UniqueHandler", L"GimpProxy");
+
+ hr = pWiaDevMgr2->lpVtbl->GetImageDlg (pWiaDevMgr2, 0, NULL, window_handle,
+ szFolder, szFileName, &nFiles,
+ &rFilePaths, &pItem);
+ SysFreeString (szFolder);
+ SysFreeString (szFileName);
+
+ if (FAILED (hr))
+ {
+ pWiaDevMgr2->lpVtbl->Release (pWiaDevMgr2);
+ pWiaDevMgr2 = NULL;
+
+ CoUninitialize ();
+
+ g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
+ _("Unable to read scanned images."));
+
+ return gimp_procedure_new_return_values (procedure,
+ GIMP_PDB_EXECUTION_ERROR,
+ NULL);
+ }
+
+ if (nFiles > 0)
+ {
+ gboolean has_directory = FALSE;
+ images = g_new0 (GimpImage *, nFiles + 1);
+
+ for (gint i = 0; i < nFiles; i++)
+ {
+ gchar *filename = g_utf16_to_utf8 (rFilePaths[i], -1, NULL,
+ NULL, NULL);
+
+ if (filename)
+ {
+ GFile *file = g_file_new_for_path (filename);
+
+ images[i] = gimp_file_load (GIMP_RUN_NONINTERACTIVE,
+ file);
+ gimp_display_new (images[i]);
+ if (! gimp_image_undo_is_enabled (images[i]))
+ gimp_image_undo_enable (images[i]);
+
+ /* Store first valid directory for later use */
+ if (! has_directory)
+ {
+ GFile *parent = g_file_get_parent (file);
+
+ g_object_set (config,
+ "scan-directory",
+ g_file_get_parse_name (parent),
+ NULL);
+ g_object_unref (parent);
+
+ has_directory = TRUE;
+ }
+
+ g_object_unref (file);
+ g_free (filename);
+ }
+ SysFreeString (rFilePaths[i]);
+ }
+
+ CoTaskMemFree (rFilePaths);
+ }
+
+ pWiaDevMgr2->lpVtbl->Release (pWiaDevMgr2);
+ pWiaDevMgr2 = NULL;
+
+ CoUninitialize ();
+
+ return_vals = gimp_procedure_new_return_values (procedure,
+ GIMP_PDB_SUCCESS,
+ NULL);
+ GIMP_VALUES_TAKE_CORE_OBJECT_ARRAY (return_vals, 1, images);
+
+ return return_vals;
+ }
+ else
+ {
+ g_set_error (&error, GIMP_PLUG_IN_ERROR, 0,
+ _("WIA driver not found, scanning not available"));
+
+ return gimp_procedure_new_return_values (procedure,
+ GIMP_PDB_EXECUTION_ERROR,
+ NULL);
+ }
+
+ CoUninitialize ();
+ }
+
+ return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR,
+ NULL);
+}
diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in
index b1743b2070..0b521be1fb 100644
--- a/po-plug-ins/POTFILES.in
+++ b/po-plug-ins/POTFILES.in
@@ -93,6 +93,7 @@ plug-ins/common/warp.c
plug-ins/common/wavelet-decompose.c
plug-ins/common/web-browser.c
plug-ins/common/web-page.c
+plug-ins/common/wia.c
plug-ins/file-bmp/bmp-export.c
plug-ins/file-bmp/bmp-load.c
plug-ins/file-bmp/bmp.c