From 7a80b3a140ced43f79f8c9c62aa2f286234342ff Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Fri, 22 Nov 2019 23:52:08 +0100 Subject: Build with meson, add icon and desktop file --- .gitignore | 5 +- Makefile | 34 -- cli.c | 108 ----- cli.h | 31 -- .../hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg | 230 +++++++++ data/meson.build | 22 + data/org.eu.fr.ni.Camicro.desktop | 8 + data/org.eu.fr.ni.Camicro.gresource.xml | 6 + data/window.ui | 209 +++++++++ device.c | 134 ------ device.h | 188 -------- gui_app.c | 127 ----- gui_app.gresource.xml | 6 - gui_app.h | 33 -- gui_app_window.c | 513 --------------------- gui_app_window.h | 40 -- image.c | 417 ----------------- image.h | 121 ----- main.c | 36 -- meson.build | 26 ++ meson_post_install.py | 10 + moticam.c | 482 ------------------- moticam.h | 31 -- options.c | 102 ---- options.h | 55 --- src/cli.c | 108 +++++ src/cli.h | 31 ++ src/device.c | 134 ++++++ src/device.h | 188 ++++++++ src/gui_app.c | 127 +++++ src/gui_app.h | 33 ++ src/gui_app_window.c | 513 +++++++++++++++++++++ src/gui_app_window.h | 40 ++ src/image.c | 417 +++++++++++++++++ src/image.h | 121 +++++ src/main.c | 36 ++ src/meson.build | 29 ++ src/moticam.c | 482 +++++++++++++++++++ src/moticam.h | 31 ++ src/options.c | 102 ++++ src/options.h | 55 +++ src/usb_source.c | 136 ++++++ src/usb_source.h | 31 ++ src/utils.c | 80 ++++ src/utils.h | 46 ++ usb_source.c | 136 ------ usb_source.h | 31 -- utils.c | 80 ---- utils.h | 46 -- window.ui | 209 --------- 50 files changed, 3252 insertions(+), 2964 deletions(-) delete mode 100644 Makefile delete mode 100644 cli.c delete mode 100644 cli.h create mode 100644 data/icons/hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg create mode 100644 data/meson.build create mode 100644 data/org.eu.fr.ni.Camicro.desktop create mode 100644 data/org.eu.fr.ni.Camicro.gresource.xml create mode 100644 data/window.ui delete mode 100644 device.c delete mode 100644 device.h delete mode 100644 gui_app.c delete mode 100644 gui_app.gresource.xml delete mode 100644 gui_app.h delete mode 100644 gui_app_window.c delete mode 100644 gui_app_window.h delete mode 100644 image.c delete mode 100644 image.h delete mode 100644 main.c create mode 100644 meson.build create mode 100644 meson_post_install.py delete mode 100644 moticam.c delete mode 100644 moticam.h delete mode 100644 options.c delete mode 100644 options.h create mode 100644 src/cli.c create mode 100644 src/cli.h create mode 100644 src/device.c create mode 100644 src/device.h create mode 100644 src/gui_app.c create mode 100644 src/gui_app.h create mode 100644 src/gui_app_window.c create mode 100644 src/gui_app_window.h create mode 100644 src/image.c create mode 100644 src/image.h create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/moticam.c create mode 100644 src/moticam.h create mode 100644 src/options.c create mode 100644 src/options.h create mode 100644 src/usb_source.c create mode 100644 src/usb_source.h create mode 100644 src/utils.c create mode 100644 src/utils.h delete mode 100644 usb_source.c delete mode 100644 usb_source.h delete mode 100644 utils.c delete mode 100644 utils.h delete mode 100644 window.ui diff --git a/.gitignore b/.gitignore index 6d2da66..5c5b2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -gui_resources.c -*.o -*.d -camicro +build out*.png out diff --git a/Makefile b/Makefile deleted file mode 100644 index 49edf25..0000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -libs := libusb-1.0 libpng16 gtk+-3.0 -CFLAGS := -O3 -g -Wall \ - -flax-vector-conversions \ - $(shell pkg-config $(libs) --cflags) -CPPFLAGS := -MMD -LDLIBS := $(shell pkg-config $(libs) --libs) -GLIB_COMPILE_RESOURCES = \ - $(shell pkg-config --variable=glib_compile_resources gio-2.0) - -SOURCES := \ - cli.c \ - device.c \ - image.c \ - gui_app.c \ - gui_app_window.c \ - gui_resources.c \ - main.c \ - moticam.c \ - options.c \ - usb_source.c \ - utils.c - -all: camicro - -gui_resources.c: gui_app.gresource.xml window.ui - $(GLIB_COMPILE_RESOURCES) $< --target=$@ --sourcedir=. --generate-source - -camicro: $(SOURCES:%.c=%.o) - $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ - -clean: - rm -f camicro $(SOURCES:%.c=%.o) $(SOURCES:%.c=%.d) gui_resources.c - --include $(SOURCES:%.c=%.d) diff --git a/cli.c b/cli.c deleted file mode 100644 index a5c20c7..0000000 --- a/cli.c +++ /dev/null @@ -1,108 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -#include "cli.h" -#include "device.h" -#include "utils.h" - -static bool -cli_read_images(libusb_context *usb, struct device *device, int count, - bool raw, const char *out, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - if (!device_start(device, error)) - return false; - bool ret = true; - for (int i = 0; ret && i < count;) { - /* Read image. */ - GError *image_error = NULL; - struct image *image = device_read(device, &image_error); - if (!image && image_error) { - g_propagate_error(error, image_error); - ret = false; - } else if (!image) { - /* No image yet, handle events. */ - int r = libusb_handle_events(usb); - if (r) - utils_fatal("unable to handle libusb events: %s", - libusb_strerror(r)); - } else { - /* Image received. */ - if (!raw && image->format != IMAGE_FORMAT_XBGR32) { - struct image *cimage = image_new(image->width, image->height, - image->width * 4, IMAGE_FORMAT_XBGR32); - image_convert(cimage, image); - image_unref(image); - image = cimage; - } - char *name = g_strdup_printf(out, i); - utils_info("write %s", name); - if (!image_save(image, name, error)) - ret = false; - g_free(name); - image_unref(image); - i++; - } - } - if (!device_stop(device, ret ? error : NULL)) - return false; - return ret; -} - -int -cli_run(struct options *options) -{ - libusb_context *usb; - int r = libusb_init(&usb); - if (r) - utils_fatal("unable to initialize libusb: %s", libusb_strerror(r)); - GError *error = NULL; - struct device *device = device_open(usb, &error); - if (!device) - utils_fatal("unable to find device: %s", error->message); - const struct device_info *info = device_get_info(device); - if (options->exposure_ms > 0.0) - device_set_exposure(device, options->exposure_ms); - if (options->gain > 0.0) - device_set_gain(device, options->gain); - int width = -1, height = -1; - for (int i = 0; width == -1 && i < info->resolutions; i++) { - const struct device_info_resolution *ir = &info->resolution[i]; - if ((options->width == -1 || options->width == ir->width) - && (options->height == -1 || options->height == ir->height)) { - width = ir->width; - height = ir->height; - } - } - if (width == -1) - utils_fatal("no matching resolution"); - device_set_resolution(device, width, height, 0); - if (!cli_read_images(usb, device, options->count, options->raw, - options->out, &error)) - utils_fatal("unable to read images: %s", error->message); - if (!device_close(device, &error)) - utils_fatal("unable to close device: %s", error->message); - libusb_exit(usb); - return EXIT_SUCCESS; -} diff --git a/cli.h b/cli.h deleted file mode 100644 index 01f2d8a..0000000 --- a/cli.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef cli_h -#define cli_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include "options.h" - -/* Run command line application, return status code. */ -int -cli_run(struct options *options); - -#endif /* cli_h */ diff --git a/data/icons/hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg b/data/icons/hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg new file mode 100644 index 0000000..d9c986f --- /dev/null +++ b/data/icons/hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg @@ -0,0 +1,230 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..83019b4 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,22 @@ +resources_data = files( + 'window.ui', +) + +gresource = 'org.eu.fr.ni.Camicro' + +resources = gnome.compile_resources( + gresource, + gresource + '.gresource.xml', + c_name: 'resources', + dependencies: resources_data, +) + +install_data( + 'org.eu.fr.ni.Camicro.desktop', + install_dir: join_paths(get_option('datadir'), 'applications'), +) + +install_data( + 'icons/hicolor/scalable/apps/org.eu.fr.ni.Camicro.svg', + install_dir: join_paths(get_option('datadir'), 'icons', 'hicolor', 'scalable', 'apps'), +) diff --git a/data/org.eu.fr.ni.Camicro.desktop b/data/org.eu.fr.ni.Camicro.desktop new file mode 100644 index 0000000..77222bf --- /dev/null +++ b/data/org.eu.fr.ni.Camicro.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Camicro +Comment=Microscope camera viewer +Exec=camicro +Icon=org.eu.fr.ni.Camicro +StartupNotify=true +Categories=Science; diff --git a/data/org.eu.fr.ni.Camicro.gresource.xml b/data/org.eu.fr.ni.Camicro.gresource.xml new file mode 100644 index 0000000..8f237d2 --- /dev/null +++ b/data/org.eu.fr.ni.Camicro.gresource.xml @@ -0,0 +1,6 @@ + + + + window.ui + + diff --git a/data/window.ui b/data/window.ui new file mode 100644 index 0000000..9eefa36 --- /dev/null +++ b/data/window.ui @@ -0,0 +1,209 @@ + + + + + + 1 + 1000 + 100 + 25 + 100 + + + + 10 + 1 + 0.25 + 1 + + + + diff --git a/device.c b/device.c deleted file mode 100644 index 09a94e3..0000000 --- a/device.c +++ /dev/null @@ -1,134 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include "device.h" - -#include "moticam.h" - -static const struct device_driver *drivers[] = { - &moticam_device_driver, - NULL -}; - -GQuark -device_error_quark(void) -{ - return g_quark_from_static_string("device-error-quark"); -} - -struct device * -device_open(libusb_context *usb, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, NULL); - libusb_device **list; - libusb_device *found_usb_device = NULL; - char *found_device_name = NULL; - const struct device_driver *found_device_driver = NULL; - ssize_t cnt = libusb_get_device_list(usb, &list); - if (cnt < 0) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_LIST, - "can not list devices: %s", libusb_strerror(cnt)); - return NULL; - } - for (ssize_t i = 0; !found_usb_device && i < cnt; i++) { - libusb_device *usb_device = list[i]; - struct libusb_device_descriptor desc; - int r = libusb_get_device_descriptor(usb_device, &desc); - if (r) { - g_warning("can not get device descriptor: %s", - libusb_strerror(r)); - } else { - for (const struct device_driver **dd = drivers; - !found_usb_device && *dd; dd++) { - char *device_name = (*dd)->usb_poll(&desc); - if (device_name) { - found_usb_device = usb_device; - found_device_name = device_name; - found_device_driver = *dd; - } - } - } - } - struct device *device = NULL; - if (found_usb_device) { - g_free(found_device_name); - device = found_device_driver->usb_open(usb, found_usb_device, error); - } else { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_LIST, - "no device found"); - } - libusb_free_device_list(list, 1); - return device; -} - -const struct device_info * -device_get_info(struct device *device) -{ - return device->get_info(device); -} - -void -device_set_exposure(struct device *device, double exposure_ms) -{ - device->set_exposure(device, exposure_ms); -} - -void -device_set_gain(struct device *device, double gain) -{ - device->set_gain(device, gain); -} - -void -device_set_resolution(struct device *device, int width, int height, - int stride) -{ - device->set_resolution(device, width, height, stride); -} - -bool -device_start(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - return device->start(device, error); -} - -struct image * -device_read(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - return device->read(device, error); -} - -bool -device_stop(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - return device->stop(device, error); -} - -bool -device_close(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - return device->close(device, error); -} diff --git a/device.h b/device.h deleted file mode 100644 index 7028ebb..0000000 --- a/device.h +++ /dev/null @@ -1,188 +0,0 @@ -#ifndef device_h -#define device_h -/* Camicro - Microscope camera viewer. - * - * Device interface. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include -#include - -#include "image.h" - -/* Error domain for device related errors. */ -#define DEVICE_ERROR device_error_quark() - -struct device; - -/* USB poll function. If the device is handled by this driver, it should - * return a dynamically allocated string naming this device. */ -typedef char *(*device_driver_usb_poll_f)( - struct libusb_device_descriptor *desc); - -/* USB open function. The device driver previously declared that it handles - * this device. */ -typedef struct device *(*device_driver_usb_open_f)( - libusb_context *usb, libusb_device *usb_device, GError **error); - -/* Information on a device driver. */ -struct device_driver { - /* Driver name. */ - const char *name; - /* Poll this driver for support for an USB device. */ - device_driver_usb_poll_f usb_poll; - /* Open an USB device. */ - device_driver_usb_open_f usb_open; -}; - -/* Information on a supported resolution. */ -struct device_info_resolution { - /* Image width. */ - int width; - /* Image height. */ - int height; -}; - -/* Informations on an open device. */ -struct device_info { - /* Exposure minimum and maximum values. */ - double exposure_min_ms, exposure_max_ms; - /* Exposure default value. */ - double exposure_default_ms; - /* Exposure increment between two settable values. */ - double exposure_step_ms; - /* Gain minimum and maximum values. */ - double gain_min, gain_max; - /* Gain default value. */ - double gain_default; - /* Gain increment between two settable values. */ - double gain_step; - /* Number of supported resolutions. */ - int resolutions; - /* Table of supported resolutions. */ - const struct device_info_resolution *resolution; -}; - -/* Retrieve informations. */ -typedef const struct device_info *(*device_get_info_f)( - struct device *device); - -/* Set new exposure value. */ -typedef void (*device_set_exposure_f)( - struct device *device, double exposure_ms); - -/* Set new gain value. */ -typedef void (*device_set_gain_f)( - struct device *device, double gain); - -/* Set resolution, must be called before first image read. If stride is zero, - * let the device decide. */ -typedef void (*device_set_resolution_f)( - struct device *device, int width, int height, int stride); - -/* Start streaming. */ -typedef bool (*device_start_f)( - struct device *device, GError **error); - -/* Return an image if one is available, else, return NULL. */ -typedef struct image *(*device_read_f)( - struct device *device, GError **error); - -/* Stop streaming. */ -typedef bool (*device_stop_f)( - struct device *device, GError **error); - -/* Close device. */ -typedef bool (*device_close_f)( - struct device *device, GError **error); - -/* Open device. */ -struct device { - /* Retrieve informations. */ - device_get_info_f get_info; - /* Change exposure. */ - device_set_exposure_f set_exposure; - /* Change gain. */ - device_set_gain_f set_gain; - /* Change resolution. */ - device_set_resolution_f set_resolution; - /* Start streaming. */ - device_start_f start; - /* Read an image. */ - device_read_f read; - /* Stop streaming. */ - device_stop_f stop; - /* Close device. */ - device_close_f close; -}; - -/* Error codes. */ -enum DeviceError { - /* Error listing devices. */ - DEVICE_ERROR_LIST, - /* Generic USB error. */ - DEVICE_ERROR_USB, -}; - -/* Return quark of error domain for device related errors. */ -GQuark -device_error_quark(void); - -/* Find and open device. */ -struct device * -device_open(libusb_context *usb, GError **error); - -/* Retrieve informations. */ -const struct device_info * -device_get_info(struct device *device); - -/* Set new exposure value. */ -void -device_set_exposure(struct device *device, double exposure_ms); - -/* Set new gain value. */ -void -device_set_gain(struct device *device, double gain); - -/* Set resolution. */ -void -device_set_resolution(struct device *device, int width, int height, - int stride); - -/* Start streaming. */ -bool -device_start(struct device *device, GError **error); - -/* Read an image. */ -struct image * -device_read(struct device *device, GError **error); - -/* Stop streaming. */ -bool -device_stop(struct device *device, GError **error); - -/* Close device. */ -bool -device_close(struct device *device, GError **error); - -#endif /* device_h */ diff --git a/gui_app.c b/gui_app.c deleted file mode 100644 index e58e4ae..0000000 --- a/gui_app.c +++ /dev/null @@ -1,127 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include "gui_app.h" -#include "gui_app_window.h" -#include "options.h" -#include "usb_source.h" -#include "utils.h" - -struct _GuiApp { - GtkApplication parent; -}; - -typedef struct _GuiAppPrivate GuiAppPrivate; - -struct _GuiAppPrivate { - libusb_context *usb; - GSource *usb_source; -}; - -G_DEFINE_TYPE_WITH_PRIVATE(GuiApp, gui_app, GTK_TYPE_APPLICATION); - -static gboolean -gui_app_usb_source_cb(gpointer user_data) -{ - GuiApp *app = user_data; - GuiAppPrivate *priv = gui_app_get_instance_private(app); - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 0; - int r = libusb_handle_events_timeout(priv->usb, &tv); - if (r) - g_error("unable to handle libusb events: %s", libusb_strerror(r)); - g_signal_emit_by_name(app, "video-ready"); - return G_SOURCE_CONTINUE; -} - -static void -gui_app_init(GuiApp *app) -{ - GuiAppPrivate *priv = gui_app_get_instance_private(app); - priv->usb = NULL; - priv->usb_source = NULL; - options_add(G_APPLICATION(app)); -} - -static void -gui_app_startup(GApplication *app) -{ - G_APPLICATION_CLASS(gui_app_parent_class)->startup(app); - GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); - int r = libusb_init(&priv->usb); - if (r) - g_error("unable to initialize libusb: %s", libusb_strerror(r)); - priv->usb_source = usb_source_new(priv->usb); - g_source_set_callback(priv->usb_source, gui_app_usb_source_cb, app, - NULL); - g_source_attach(priv->usb_source, NULL); -} - -static void -gui_app_activate(GApplication *app) -{ - GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); - GError *error = NULL; - struct device *device = device_open(priv->usb, &error); - if (!device) { - utils_dialog_error(NULL, "unable to find device: %s", error->message); - g_error_free(error); - } else { - GuiAppWindow *window; - window = gui_app_window_new(GUI_APP(app)); - gui_app_window_open(window, device); - gtk_window_present(GTK_WINDOW(window)); - } -} - -static void -gui_app_shutdown(GApplication *app) -{ - GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); - if (priv->usb_source) { - g_source_destroy(priv->usb_source); - g_source_unref(priv->usb_source); - } - if (priv->usb) - libusb_exit(priv->usb); - G_APPLICATION_CLASS(gui_app_parent_class)->shutdown(app); -} - -static void -gui_app_class_init(GuiAppClass *class) -{ - G_APPLICATION_CLASS(class)->startup = gui_app_startup; - G_APPLICATION_CLASS(class)->activate = gui_app_activate; - G_APPLICATION_CLASS(class)->shutdown = gui_app_shutdown; - G_APPLICATION_CLASS(class)->handle_local_options = options_handle; - g_signal_new("video-ready", GUI_APP_TYPE, G_SIGNAL_RUN_FIRST, 0, NULL, - NULL, NULL, G_TYPE_NONE, 0); -} - -GuiApp * -gui_app_new(void) -{ - return g_object_new(GUI_APP_TYPE, - "application-id", "org.eu.fr.ni.camicro", - NULL); -} diff --git a/gui_app.gresource.xml b/gui_app.gresource.xml deleted file mode 100644 index f79e22f..0000000 --- a/gui_app.gresource.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - window.ui - - diff --git a/gui_app.h b/gui_app.h deleted file mode 100644 index 8eeeff1..0000000 --- a/gui_app.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef gui_app_h -#define gui_app_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -#define GUI_APP_TYPE (gui_app_get_type()) -G_DECLARE_FINAL_TYPE(GuiApp, gui_app, GUI, APP, GtkApplication) - -GuiApp * -gui_app_new(void); - -#endif /* gui_app_h */ diff --git a/gui_app_window.c b/gui_app_window.c deleted file mode 100644 index b7c0d84..0000000 --- a/gui_app_window.c +++ /dev/null @@ -1,513 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include - -#include "gui_app_window.h" -#include "utils.h" - -struct _GuiAppWindow { - GtkApplicationWindow parent; -}; - -typedef struct _GuiAppWindowPrivate GuiAppWindowPrivate; - -struct _GuiAppWindowPrivate { - GuiAppWindow *win; - GuiApp *app; - GtkDrawingArea *video; - GtkButton *start_stop_button; - GtkComboBoxText *resolution_combo_box; - GtkAdjustment *exposure_adj; - GtkAdjustment *gain_adj; - GtkCheckButton *wb_button; - GtkButton *wb_cal_button; - GtkCheckButton *rotate_button; - GtkButton *save_button; - GdkRectangle allocation; - cairo_matrix_t transform; - cairo_surface_t *surface; - struct image *image; - struct image *image_converted; - bool white_balance; - bool white_balance_calibrated; - struct image_white_balance_reference white_balance_reference; - bool rotate; - int width; - int height; - struct device *device; - bool started; - gulong video_ready_handler_id; -}; - -G_DEFINE_TYPE_WITH_PRIVATE(GuiAppWindow, gui_app_window, - GTK_TYPE_APPLICATION_WINDOW); - -/* When input or output size change, recompute transformation matrix. */ -static void -video_size_update(GuiAppWindowPrivate *priv) -{ - int source_width = priv->width; - int source_height = priv->height; - int dest_width = priv->allocation.width; - int dest_height = priv->allocation.height; - if (source_width > 0 && dest_width > 0) { - double scale_x = (double) source_width / dest_width; - double scale_y = (double) source_height / dest_height; - double scale; - if (scale_x > scale_y) - scale = scale_x; - else - scale = scale_y; - if (scale < 1.0) - scale = 1.0; - double offset_x = -(dest_width - source_width / scale) / 2.0; - double offset_y = -(dest_height - source_height / scale) / 2.0; - if (priv->rotate) { - cairo_matrix_init_translate(&priv->transform, - source_width / 2, source_height / 2); - cairo_matrix_rotate(&priv->transform, M_PI); - cairo_matrix_translate(&priv->transform, - -source_width / 2, -source_height / 2); - } else { - cairo_matrix_init_identity(&priv->transform); - } - cairo_matrix_scale(&priv->transform, scale, scale); - cairo_matrix_translate(&priv->transform, offset_x, offset_y); - } -} - -/* A new image could be available. */ -static void -video_ready_cb(GuiApp *app, gpointer user_data) -{ - GuiAppWindowPrivate *priv = user_data; - if (priv->started) { - GError *error = NULL; - struct image *image = device_read(priv->device, &error); - if (!image) { - if (error) { - utils_dialog_error(GTK_WINDOW(priv->win), - "error reading image: %s", - error->message); - g_error_free(error); - gtk_widget_destroy(GTK_WIDGET(priv->win)); - } - } else { - /* Release old data. */ - if (priv->surface) { - cairo_surface_destroy(priv->surface); - priv->surface = NULL; - } - if (priv->image) { - image_unref(priv->image); - priv->image = NULL; - } - /* White balance. */ - if (priv->white_balance && !priv->white_balance_calibrated) { - image_white_balance_calibrate(image, - &priv->white_balance_reference); - priv->white_balance_calibrated = true; - } - if (priv->white_balance) - image_white_balance(image, &priv->white_balance_reference); - /* Different format? Convert. */ - if (image->format != IMAGE_FORMAT_XBGR32) { - if (priv->image_converted && - (priv->image_converted->width != image->width - || priv->image_converted->height != image->height)) { - image_unref(priv->image_converted); - priv->image_converted = NULL; - } - if (!priv->image_converted) { - int stride = - cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, - image->width); - priv->image_converted = image_new(image->width, - image->height, stride, IMAGE_FORMAT_XBGR32); - } - image_convert(priv->image_converted, image); - image_unref(image); - image = priv->image_converted; - } else { - if (priv->image_converted) { - image_unref(priv->image_converted); - priv->image_converted = NULL; - } - priv->image = image; - } - /* Make a surface. */ - priv->surface = cairo_image_surface_create_for_data( - (unsigned char *) image->pixels, CAIRO_FORMAT_RGB24, image->width, - image->height, image->stride); - gtk_widget_queue_draw(GTK_WIDGET(priv->video)); - gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), TRUE); - } - } -} - -/* Start video. */ -static void -video_start(GuiAppWindowPrivate *priv) -{ - g_assert(!priv->started); - int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, - priv->width); - device_set_resolution(priv->device, priv->width, priv->height, stride); - video_size_update(priv); - GError *error = NULL; - if (!device_start(priv->device, &error)) { - utils_dialog_error(GTK_WINDOW(priv->win), "can not start: %s", - error->message); - g_error_free(error); - } else { - priv->video_ready_handler_id = g_signal_connect(priv->app, "video-ready", - G_CALLBACK(video_ready_cb), priv); - gtk_button_set_label(priv->start_stop_button, "Stop"); - priv->started = true; - } -} - -/* Stop video. */ -static void -video_stop(GuiAppWindowPrivate *priv) -{ - g_assert(priv->started); - if (priv->surface) { - cairo_surface_destroy(priv->surface); - priv->surface = NULL; - } - if (priv->image) { - image_unref(priv->image); - priv->image = NULL; - } - if (priv->image_converted) { - image_unref(priv->image_converted); - priv->image_converted = NULL; - } - g_signal_handler_disconnect(priv->app, priv->video_ready_handler_id); - GError *error = NULL; - if (!device_stop(priv->device, &error)) { - g_warning("device_stop: %s", error->message); - g_error_free(error); - } - priv->started = false; - gtk_widget_queue_draw(GTK_WIDGET(priv->video)); - gtk_button_set_label(priv->start_stop_button, "Start"); - gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), FALSE); -} - -static gboolean -video_draw_cb(GtkWidget *widget, cairo_t *cr, gpointer user_data) -{ - GuiAppWindow *win = GUI_APP_WINDOW(gtk_widget_get_toplevel(widget)); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_paint(cr); - if (priv->surface) { - cairo_pattern_t *pattern = - cairo_pattern_create_for_surface(priv->surface); - cairo_pattern_set_matrix(pattern, &priv->transform); - cairo_set_source(cr, pattern); - cairo_paint(cr); - cairo_pattern_destroy(pattern); - } - return FALSE; -} - -static void -video_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation, - gpointer user_data) -{ - GuiAppWindow *win = GUI_APP_WINDOW(gtk_widget_get_toplevel(widget)); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - priv->allocation = *allocation; - video_size_update(priv); -} - -static void -destroy_cb(GtkWidget *widget, gpointer user_data) -{ - GuiAppWindow *win = GUI_APP_WINDOW(widget); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - if (priv->started) - video_stop(priv); - if (priv->device) { - GError *error = NULL; - if (!device_close(priv->device, &error)) { - g_warning("device_close: %s", error->message); - g_error_free(error); - } - priv->device = NULL; - } -} - -static void -start_stop_button_clicked_cb(GtkButton *button, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - if (!priv->started) - video_start(priv); - else - video_stop(priv); -} - -static void -resolution_combo_box_changed_cb(GtkComboBox *combo, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(combo))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - int sel = gtk_combo_box_get_active(combo); - const struct device_info *info = device_get_info(priv->device); - g_assert(sel >= 0 && sel < info->resolutions); - priv->width = info->resolution[sel].width; - priv->height = info->resolution[sel].height; - if (priv->started) { - video_stop(priv); - video_start(priv); - } -} - -static void -exposure_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data) -{ - GuiAppWindow *win = GUI_APP_WINDOW(user_data); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - double val = gtk_adjustment_get_value(adj); - device_set_exposure(priv->device, val); -} - -static void -gain_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data) -{ - GuiAppWindow *win = GUI_APP_WINDOW(user_data); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - double val = gtk_adjustment_get_value(adj); - device_set_gain(priv->device, val); -} - -static void -wb_button_toggled_cb(GtkToggleButton *button, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - priv->white_balance = gtk_toggle_button_get_active(button); - gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), - priv->white_balance); -} - -static void -wb_cal_button_clicked_cb(GtkButton *button, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - priv->white_balance_calibrated = false; -} - -static void -rotate_button_toggled_cb(GtkToggleButton *button, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - priv->rotate = gtk_toggle_button_get_active(button); - video_size_update(priv); -} - -static void -save_button_clicked_cb(GtkButton *button, gpointer user_data) -{ - GuiAppWindow *win = - GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - if (priv->surface) { - GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(priv->surface, 0, 0, - priv->width, priv->height); - if (priv->rotate && pixbuf) { - GdkPixbuf *rotated = gdk_pixbuf_rotate_simple(pixbuf, 180); - g_object_unref(pixbuf); - pixbuf = rotated; - } - if (pixbuf) { - GtkWidget *dialog = gtk_file_chooser_dialog_new("Save image", - GTK_WINDOW(win), - GTK_FILE_CHOOSER_ACTION_SAVE, - "_Cancel", GTK_RESPONSE_CANCEL, - "_Save", GTK_RESPONSE_ACCEPT, - NULL); - GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); - gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); - GtkFileFilter *png_filter = gtk_file_filter_new(); - gtk_file_filter_set_name(png_filter, "PNG image"); - gtk_file_filter_add_mime_type(png_filter, "image/png"); - gtk_file_filter_add_pattern(png_filter, "*.png"); - gtk_file_chooser_add_filter(chooser, png_filter); - GtkFileFilter *jpg_filter = gtk_file_filter_new(); - gtk_file_filter_set_name(jpg_filter, "JPEG image"); - gtk_file_filter_add_mime_type(jpg_filter, "image/jpeg"); - gtk_file_filter_add_pattern(jpg_filter, "*.jpg"); - gtk_file_filter_add_pattern(jpg_filter, "*.jpeg"); - gtk_file_chooser_add_filter(chooser, jpg_filter); - gint res = gtk_dialog_run(GTK_DIALOG(dialog)); - if (res == GTK_RESPONSE_ACCEPT) { - char *filename = gtk_file_chooser_get_filename(chooser); - if (filename) { - GtkFileFilter *filter = - gtk_file_chooser_get_filter(chooser); - const char *type = NULL; - if (filter == png_filter) - type = "png"; - else if (filter == jpg_filter) - type = "jpeg"; - if (type) { - GError *error = NULL; - if (!gdk_pixbuf_save(pixbuf, filename, type, &error, - NULL)) { - utils_dialog_error(GTK_WINDOW(win), - "can not save image: %s", - error->message); - g_error_free(error); - } - } - g_free(filename); - } - } - gtk_widget_destroy(dialog); - g_object_unref(pixbuf); - } - } -} - -static void -gui_app_window_init(GuiAppWindow *win) -{ - gtk_widget_init_template(GTK_WIDGET(win)); - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - priv->win = win; - priv->app = NULL; - priv->allocation.width = -1; - cairo_matrix_init_identity(&priv->transform); - priv->surface = NULL; - priv->image = NULL; - priv->image_converted = NULL; - priv->white_balance = false; - priv->white_balance_calibrated = false; - priv->rotate = true; - priv->width = -1; - priv->height = -1; - priv->device = NULL; - priv->started = false; - priv->video_ready_handler_id = 0; -} - -static void -gui_app_window_class_init(GuiAppWindowClass *class) -{ - gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), - "/org/eu/fr/ni/camicro/window.ui"); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, video); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, start_stop_button); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, resolution_combo_box); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, exposure_adj); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, gain_adj); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, wb_button); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, wb_cal_button); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, rotate_button); - gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), - GuiAppWindow, save_button); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - destroy_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - video_draw_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - video_size_allocate_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - start_stop_button_clicked_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - resolution_combo_box_changed_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - exposure_adj_value_changed_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - gain_adj_value_changed_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - wb_button_toggled_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - wb_cal_button_clicked_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - rotate_button_toggled_cb); - gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), - save_button_clicked_cb); -} - -GuiAppWindow * -gui_app_window_new(GuiApp *app) -{ - return g_object_new(GUI_APP_WINDOW_TYPE, "application", app, NULL); -} - -void -gui_app_window_open(GuiAppWindow *win, struct device *device) -{ - GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); - g_assert(!priv->device); - priv->app = GUI_APP(gtk_window_get_application(GTK_WINDOW(win))); - const struct device_info *info = device_get_info(device); - priv->width = info->resolution[0].width; - priv->height = info->resolution[0].height; - priv->device = device; - gtk_adjustment_set_lower(priv->exposure_adj, info->exposure_min_ms); - gtk_adjustment_set_upper(priv->exposure_adj, info->exposure_max_ms); - gtk_adjustment_set_value(priv->exposure_adj, info->exposure_default_ms); - gtk_adjustment_set_lower(priv->gain_adj, info->gain_min); - gtk_adjustment_set_upper(priv->gain_adj, info->gain_max); - gtk_adjustment_set_value(priv->gain_adj, info->gain_default); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->wb_button), - priv->white_balance); - gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), - priv->white_balance); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->rotate_button), - priv->rotate); - gtk_combo_box_text_remove_all(priv->resolution_combo_box); - for (int i = 0; i < info->resolutions; i++) { - const struct device_info_resolution *ir = &info->resolution[i]; - char buf[32]; - int r = snprintf(buf, sizeof(buf), "%dx%d", ir->width, ir->height); - g_assert(r < sizeof(buf)); - gtk_combo_box_text_append_text(priv->resolution_combo_box, buf); - } - gtk_combo_box_set_active(GTK_COMBO_BOX(priv->resolution_combo_box), 0); - video_start(priv); -} diff --git a/gui_app_window.h b/gui_app_window.h deleted file mode 100644 index 2f886db..0000000 --- a/gui_app_window.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef gui_app_window_h -#define gui_app_window_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -#include "gui_app.h" -#include "device.h" - -#define GUI_APP_WINDOW_TYPE (gui_app_window_get_type()) -G_DECLARE_FINAL_TYPE(GuiAppWindow, gui_app_window, GUI, APP_WINDOW, - GtkApplicationWindow) - -GuiAppWindow * -gui_app_window_new(GuiApp *app); - -void -gui_app_window_open(GuiAppWindow *win, struct device *device); - -#endif /* gui_app_window_h */ diff --git a/image.c b/image.c deleted file mode 100644 index ec29751..0000000 --- a/image.c +++ /dev/null @@ -1,417 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include -#include - -#include "image.h" -#include "utils.h" - -typedef uint8_t u8x16 __attribute__((vector_size(16))); -typedef uint16_t u16x8 __attribute__((vector_size(16))); - -GQuark -image_error_quark(void) -{ - return g_quark_from_static_string("image-error-quark"); -} - -static void -image_free(struct image *image, void *user_data) -{ - free(image); -} - -struct image * -image_new(int width, int height, int stride, enum image_format format) -{ - size_t struct_size_aligned = (sizeof(struct image) + 15) / 16 * 16; - size_t image_size_aligned = ((height * stride) + 15) / 16 * 16; - uint8_t *m = aligned_alloc(16, - struct_size_aligned + image_size_aligned); - if (!m) - g_error("aligned_alloc: failed to allocate"); - struct image *image = (struct image *) m; - image->width = width; - image->height = height; - image->stride = stride; - image->format = format; - image->pixels = m + struct_size_aligned; - image->refs = 1; - image->release = image_free; - image->release_user_data = NULL; - return image; -} - -void -image_ref(struct image *image) -{ - image->refs++; -} - -void -image_unref(struct image *image) -{ - image->refs--; - if (image->refs == 0 && image->release) - image->release(image, image->release_user_data); -} - -static void -image_histogram_sgrbg8(const struct image *image, uint32_t histogram[3][256]) -{ - g_assert(image->width % 2 == 0); - g_assert(image->height % 2 == 0); - g_assert(image->stride == image->width); - memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); - const uint8_t *p = image->pixels; - for (int i = 0; i < image->height; i += 2) { - for (int j = 0; j < image->width; j += 2) { - uint8_t g = *p++; - histogram[1][g] += 2; - uint8_t r = *p++; - histogram[0][r] += 4; - } - for (int j = 0; j < image->width; j += 2) { - uint8_t b = *p++; - histogram[2][b] += 4; - uint8_t g = *p++; - histogram[1][g] += 2; - } - } -} - -static void -image_histogram_xrgb32(const struct image *image, uint32_t histogram[3][256]) -{ - g_assert(image->stride == image->width * 4); - memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); - const uint8_t *p = image->pixels; - const uint8_t *pend = image->pixels + image->height * image->stride; - while (p != pend) { - uint8_t b = *p++; - uint8_t g = *p++; - uint8_t r = *p++; - p++; - histogram[0][r]++; - histogram[1][g]++; - histogram[2][b]++; - } -} - -void -image_histogram(const struct image *image, uint32_t histogram[3][256]) -{ - switch (image->format) { - case IMAGE_FORMAT_SGRBG8: - image_histogram_sgrbg8(image, histogram); - break; - case IMAGE_FORMAT_XBGR32: - image_histogram_xrgb32(image, histogram); - break; - default: - g_assert_not_reached(); - } -} - -void -image_white_balance_calibrate(const struct image *image, - struct image_white_balance_reference *reference) -{ - uint32_t histogram[3][256]; - image_histogram(image, histogram); - const uint32_t top_ignore = image->width * image->height / 1000; - uint8_t top[3]; - for (int i = 0; i < 3; i++) { - uint32_t acc = 0; - uint8_t j; - for (j = 255; acc < top_ignore && j > 0; j--) { - acc += histogram[i][j]; - } - top[i] = j; - } - reference->r = top[0]; - reference->g = top[1]; - reference->b = top[2]; -} - -static void -image_white_balance_factors( - const struct image_white_balance_reference *reference, - uint16_t *fr, uint16_t *fg, uint16_t *fb) -{ - uint8_t wr = reference->r; - uint8_t wg = reference->g; - uint8_t wb = reference->b; - if (wr > wg && wr > wb) { - *fr = 256; - *fg = 256 * wr / wg; - *fb = 256 * wr / wb; - } else if (wg > wr && wg > wb) { - *fr = 256 * wg / wr; - *fg = 256; - *fb = 256 * wg / wb; - } else { - *fr = 256 * wb / wr; - *fg = 256 * wb / wg; - *fb = 256; - } -} - -static void -image_white_balance_sgrbg8(struct image *image, - const struct image_white_balance_reference *reference) -{ - g_assert(image->width % 16 == 0); - g_assert(image->height % 2 == 0); - g_assert(image->stride == image->width); - uint16_t fr, fg, fb; - image_white_balance_factors(reference, &fr, &fg, &fb); - u8x16 *p = (u8x16 *) image->pixels; - const u8x16 zero = { 0 }; - const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, - 4, 20, 5, 21, 6, 22, 7, 23 }; - const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, - 12, 28, 13, 29, 14, 30, 15, 31 }; - const u16x8 mulo = { fg, fr, fg, fr, fg, fr, fg, fr }; - const u16x8 mule = { fb, fg, fb, fg, fb, fg, fb, fg }; - const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, - 16, 18, 20, 22, 24, 26, 28, 30 }; - for (int i = 0; i < image->height; i += 2) { - for (int j = 0; j < image->width; j += 16) { - u8x16 pix4 = *p; - u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); - u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mulo) - != (u16x8) zero; - pix2_0 = ((pix2_0 * mulo) | pix2_0_ovf) / 256; - u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); - u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mulo) - != (u16x8) zero; - pix2_1 = ((pix2_1 * mulo) | pix2_1_ovf) / 256; - pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); - *p++ = pix4; - } - for (int j = 0; j < image->width; j += 16) { - u8x16 pix4 = *p; - u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); - u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mule) - != (u16x8) zero; - pix2_0 = ((pix2_0 * mule) | pix2_0_ovf) / 256; - u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); - u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mule) - != (u16x8) zero; - pix2_1 = ((pix2_1 * mule) | pix2_1_ovf) / 256; - pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); - *p++ = pix4; - } - } -} - -static void -image_white_balance_xbgr32(struct image *image, - const struct image_white_balance_reference *reference) -{ - g_assert(image->width % 4 == 0); - g_assert(image->stride == image->width * 4); - uint16_t fr, fg, fb; - image_white_balance_factors(reference, &fr, &fg, &fb); - u8x16 *p = (u8x16 *) image->pixels; - u8x16 *pend = (u8x16 *) (image->pixels + image->height * image->stride); - const u8x16 zero = { 0 }; - const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, - 4, 20, 5, 21, 6, 22, 7, 23 }; - const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, - 12, 28, 13, 29, 14, 30, 15, 31 }; - const u16x8 mul = { fb, fg, fr, 255, fb, fg, fr, 255 }; - const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, - 16, 18, 20, 22, 24, 26, 28, 30 }; - while (p != pend) { - u8x16 pix4 = *p; - u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); - u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mul) - != (u16x8) zero; - pix2_0 = ((pix2_0 * mul) | pix2_0_ovf) / 256; - u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); - u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mul) - != (u16x8) zero; - pix2_1 = ((pix2_1 * mul) | pix2_1_ovf) / 256; - pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); - *p++ = pix4; - } -} - -void -image_white_balance(struct image *image, - const struct image_white_balance_reference *reference) -{ - switch (image->format) { - case IMAGE_FORMAT_SGRBG8: - image_white_balance_sgrbg8(image, reference); - break; - case IMAGE_FORMAT_XBGR32: - image_white_balance_xbgr32(image, reference); - break; - default: - g_assert_not_reached(); - } -} - -static void -image_convert_to_xbgr32_from_sgrbg8(struct image *dst, - const struct image *src) -{ - /* - * Compute missing value using an average of its neighbours. - * Input pattern: - * G R G R G R - * B G B G B G - * G R G R G R - * B G B G B G - */ - g_assert(dst->width == src->width - && dst->height == src->height); - const int in_stride = src->stride; - const int out_stride = dst->stride; - int width = src->width; - int height = src->height; - /* Skip first line and column. */ - const uint8_t *in = src->pixels + in_stride + 1; - uint8_t *out = dst->pixels + out_stride + 4; - /* Loop over lines. */ - for (int i = 1; i < height - 1; i += 2) { - /* Even lines. */ - const uint8_t *in_stop = in + (width - 2); - while (in != in_stop) { - *out++ = (in[-1] + in[+1] + 1) >> 1; /* B */ - *out++ = in[0]; /* G */ - *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* R */ - *out++ = 255; /* A */ - in++; - *out++ = in[0]; /* B */ - *out++ = (in[-in_stride] + in[+in_stride] /* G */ - + in[-1] + in[+1] + 2) >> 2; - *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* R */ - + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; - *out++ = 255; /* A */ - in++; - } - /* Fill first and last pixels. */ - out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; - out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; - out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; - out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; - out[0] = out[-4]; - out[1] = out[-3]; - out[2] = out[-2]; - out[3] = out[-1]; - out += out_stride - (width - 2) * 4; - in += in_stride - (width - 2); - /* Odd lines. */ - in_stop = in + (width - 2); - while (in != in_stop) { - *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* B */ - + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; - *out++ = (in[-in_stride] + in[+in_stride] /* G */ - + in[-1] + in[+1] + 2) >> 2; - *out++ = in[0]; /* R */ - *out++ = 255; /* A */ - in++; - *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* B */ - *out++ = in[0]; /* G */ - *out++ = (in[-1] + in[+1] + 1) >> 1; /* R */ - *out++ = 255; /* A */ - in++; - } - /* Fill first and last pixels. */ - out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; - out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; - out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; - out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; - out[0] = out[-4]; - out[1] = out[-3]; - out[2] = out[-2]; - out[3] = out[-1]; - out += out_stride - (width - 2) * 4; - in += in_stride - (width - 2); - } - /* Last line. */ - out -= 4; - memcpy(out, out - out_stride, width * 4); - /* First line. */ - out -= (height - 1) * out_stride; - memcpy(out, out + out_stride, width * 4); -} - -void -image_convert(struct image *dst, const struct image *src) -{ - g_assert(dst->width == src->width - && dst->height == src->height); - if (dst->format == src->format) { - g_assert(dst->stride == src->stride); - memcpy(dst->pixels, src->pixels, src->stride * src->height); - } else { - switch (src->format) { - case IMAGE_FORMAT_SGRBG8: - switch (dst->format) { - case IMAGE_FORMAT_XBGR32: - image_convert_to_xbgr32_from_sgrbg8(dst, src); - break; - default: - g_assert_not_reached(); - } - break; - default: - g_assert_not_reached(); - } - } -} - -bool -image_save(const struct image *image, const char *name, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - png_image pimage; - memset(&pimage, 0, sizeof(pimage)); - pimage.version = PNG_IMAGE_VERSION; - pimage.width = image->width; - pimage.height = image->height; - switch(image->format) { - case IMAGE_FORMAT_SGRBG8: - pimage.format = PNG_FORMAT_GRAY; - break; - case IMAGE_FORMAT_XBGR32: - pimage.format = PNG_FORMAT_BGRA; - break; - default: - g_assert_not_reached(); - } - int r = png_image_write_to_file(&pimage, name, 0, - image->pixels, image->stride, NULL); - if (r == 0) { - g_set_error(error, IMAGE_ERROR, IMAGE_ERROR_SAVE, - "can not write image: %s", pimage.message); - return false; - } - return true; -} diff --git a/image.h b/image.h deleted file mode 100644 index 0e36c02..0000000 --- a/image.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef image_h -#define image_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include -#include - -/* Error domain for image related errors. */ -#define IMAGE_ERROR image_error_quark() - -/* Image format. */ -enum image_format -{ -#define IMAGE_FORMAT_FOURCC(a, b, c, d) \ - (((uint32_t)(a)) | ((uint32_t)(b) << 8) \ - | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) - /* Bayer image format: - * G R G R - * B G B G */ - IMAGE_FORMAT_SGRBG8 = IMAGE_FORMAT_FOURCC('B', 'R', 'B', 'G'), - /* RGB 32 bits, B in LSB. */ - IMAGE_FORMAT_XBGR32 = IMAGE_FORMAT_FOURCC('X', 'R', '2', '4'), -}; - -struct image; - -/* Image release callback. */ -typedef void (*image_release_f)(struct image *image, void *user_data); - -/* Image with attached information. */ -struct image { - /* Image width. */ - int width; - /* Image height. */ - int height; - /* Byte offset between a line and the next line. */ - int stride; - /* Image format. */ - enum image_format format; - /* Frame buffer. */ - uint8_t *pixels; - /* Number of reference to this image. */ - int refs; - /* Release callback. */ - image_release_f release; - /* Release callback user data. */ - void *release_user_data; -}; - -/* Reference for white balance. */ -struct image_white_balance_reference { - /* White (maximum of each component). */ - uint8_t r, g, b; -}; - -/* Error codes. */ -enum ImageError { - /* Error writing an image to disk. */ - IMAGE_ERROR_SAVE, -}; - -/* Return quark of error domain for image related errors. */ -GQuark -image_error_quark(void); - -/* Allocate an image using system allocator. */ -struct image * -image_new(int width, int height, int stride, enum image_format format); - -/* Add a reference to an image. */ -void -image_ref(struct image *image); - -/* Release a reference to an image. */ -void -image_unref(struct image *image); - -/* Compute histogram(s), should provide an uint32_t[3][256] array. */ -void -image_histogram(const struct image *image, uint32_t histogram[3][256]); - -/* Calibrate for white balance. */ -void -image_white_balance_calibrate(const struct image *image, - struct image_white_balance_reference *reference); - -/* Apply the given white balance. */ -void -image_white_balance(struct image *image, - const struct image_white_balance_reference *reference); - -/* Copy an image to another one, converting format. */ -void -image_convert(struct image *dst, const struct image *src); - -/* Save image to PNG. */ -bool -image_save(const struct image *image, const char *name, GError **error); - -#endif /* image_h */ diff --git a/main.c b/main.c deleted file mode 100644 index 2abeeda..0000000 --- a/main.c +++ /dev/null @@ -1,36 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -#include "gui_app.h" - -int -main(int argc, char **argv) -{ - int status; - GuiApp *app; - app = gui_app_new(); - status = g_application_run(G_APPLICATION(app), argc, argv); - g_object_unref(app); - return status; -} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..8b2bfbc --- /dev/null +++ b/meson.build @@ -0,0 +1,26 @@ +project( + 'camicro', 'c', + version : '0.1', + license: 'GPLv3+', + default_options: [ + 'optimization=3', + 'werror=true', + ], +) + +cc = meson.get_compiler('c') +add_project_arguments( + cc.get_supported_arguments(['-Wall']), + language : 'c', +) + +gnome = import('gnome') + +libusb = dependency('libusb-1.0') +libpng = dependency('libpng16') +gtk = dependency('gtk+-3.0') + +subdir('data') +subdir('src') + +meson.add_install_script('meson_post_install.py') diff --git a/meson_post_install.py b/meson_post_install.py new file mode 100644 index 0000000..81b3fb0 --- /dev/null +++ b/meson_post_install.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +import os +import subprocess + +install_prefix = os.environ['MESON_INSTALL_PREFIX'] +icondir = os.path.join(install_prefix, 'share', 'icons', 'hicolor') + +if not os.environ.get('DESTDIR'): + print('Update icon cache...') + subprocess.call(['gtk-update-icon-cache', '-f', '-t', icondir]) diff --git a/moticam.c b/moticam.c deleted file mode 100644 index 232fb77..0000000 --- a/moticam.c +++ /dev/null @@ -1,482 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * This files add support for direct connexion with USB Moticam 3+. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include "moticam.h" -#include "utils.h" - -#define ID_VENDOR 0x232f -#define ID_PRODUCT 0x0100 -#define ID_PRODUCT_USB3 0x0101 - -struct moticam_device { - struct device device; - libusb_context *usb; - libusb_device_handle *handle; - double exposure_ms; - int width; - int height; - bool exposure_pending; - double gain; - bool gain_pending; - size_t rawbuffer_size; - struct image image[2]; - struct image *current_image; - struct image *other_image; - struct libusb_transfer *usb_transfer; - bool usb_transfer_done; - int drop; -}; - -static char * -moticam_usb_poll(struct libusb_device_descriptor *desc); - -static struct device * -moticam_usb_open(libusb_context *usb, libusb_device *usb_device, - GError **error); - -static bool -moticam_stop(struct device *device, GError **error); - -const struct device_driver moticam_device_driver = { - "Moticam USB cameras", - &moticam_usb_poll, - &moticam_usb_open, -}; - -static const struct device_info_resolution moticam_device_info_resolution[] = { - { 2048, 1536 }, - { 1024, 768 }, - { 512, 384 }, -}; - -static const struct device_info moticam_device_info = { - .exposure_min_ms = 1, - .exposure_max_ms = 5000, - .exposure_default_ms = 100, - .exposure_step_ms = 1, - .gain_min = 0.33, - .gain_max = 42.66, - .gain_default = 1, - .gain_step = 1.0 / 24, - .resolutions = 3, - .resolution = moticam_device_info_resolution, -}; - -static bool -moticam_control_vendor(struct moticam_device *mdev, uint16_t wValue, - const uint8_t *data, int data_cnt, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - int r = libusb_control_transfer(mdev->handle, LIBUSB_REQUEST_TYPE_VENDOR, - 240, wValue, 0, (uint8_t *) data, data_cnt, 0); - if (r < 0) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "can not send vendor control: %s", libusb_strerror(r)); - return false; - } - return true; -} - -static bool -moticam_control_vendor_w(struct moticam_device *mdev, uint16_t wValue, - const uint16_t data0, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - uint8_t data[] = { (uint8_t) (data0 >> 8), (uint8_t) data0 }; - return moticam_control_vendor(mdev, wValue, data, sizeof(data), error); -} - -static bool -moticam_control_reset(struct moticam_device *mdev, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - if (!moticam_control_vendor_w(mdev, 0xba00, 0x0000, error)) - return false; - if (!moticam_control_vendor_w(mdev, 0xba00, 0x0001, error)) - return false; - return true; -} - -static bool -moticam_control_exposure(struct moticam_device *mdev, double exposure_ms, - GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - int exposure_w = exposure_ms * 12.82; - if (exposure_w < 0x000c) - exposure_w = 0x000c; - else if (exposure_w > 0xffff) - exposure_w = 0xffff; - return moticam_control_vendor_w(mdev, 0xba09, exposure_w, error); -} - -static bool -moticam_control_gain(struct moticam_device *mdev, double gain, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - double gmin, gmax; - int xmin, xmax; - bool up = false; - if (gain <= 1.34) { - gmin = 0.33; - gmax = 1.33; - xmin = 0x08; - xmax = 0x20; - } else if (gain <= 2.68) { - gmin = 1.42; - gmax = 2.67; - xmin = 0x51; - xmax = 0x60; - } else { - gmin = 3; - gmax = 42.67; - xmin = 0x1; - xmax = 0x78; - up = true; - } - int x = (gain - gmin) / (gmax - gmin) * (xmax - xmin) + xmin; - if (x < xmin) - x = xmin; - else if (x > xmax) - x = xmax; - if (up) - x = (x << 8) | 0x60; - if (!moticam_control_vendor_w(mdev, 0xba2d, x, error)) - return false; - if (!moticam_control_vendor_w(mdev, 0xba2b, x, error)) - return false; - if (!moticam_control_vendor_w(mdev, 0xba2e, x, error)) - return false; - if (!moticam_control_vendor_w(mdev, 0xba2c, x, error)) - return false; - return true; -} - -static bool -moticam_control_resolution(struct moticam_device *mdev, int width, int height, - GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - static const uint8_t control_init[] = { - 0x00, 0x14, 0x00, 0x20, 0x05, 0xff, 0x07, 0xff }; - if (!moticam_control_vendor(mdev, 0xba01, control_init, - sizeof(control_init), error)) - return false; - static const uint8_t control_512x384[] = { 0x00, 0x03, 0x00, 0x03 }; - static const uint8_t control_1024x768[] = { 0x00, 0x11, 0x00, 0x11 }; - static const uint8_t control_2048x1536[] = { 0x00, 0x00, 0x00, 0x00 }; - bool ret; - switch (width) - { - case 512: - g_assert(height == 384); - ret = moticam_control_vendor(mdev, 0xba22, control_512x384, - sizeof(control_512x384), error); - break; - case 1024: - g_assert(height == 768); - ret = moticam_control_vendor(mdev, 0xba22, control_1024x768, - sizeof(control_1024x768), error); - break; - case 2048: - g_assert(height == 1536); - ret = moticam_control_vendor(mdev, 0xba22, control_2048x1536, - sizeof(control_2048x1536), error); - break; - default: - g_assert_not_reached(); - } - return ret; -} - -static const struct device_info * -moticam_get_info(struct device *device) -{ - return &moticam_device_info; -} - -static void -moticam_set_exposure(struct device *device, double exposure_ms) -{ - struct moticam_device *mdev = (struct moticam_device *) device; - mdev->exposure_ms = exposure_ms; - mdev->exposure_pending = true; -} - -static void -moticam_set_gain(struct device *device, double gain) -{ - struct moticam_device *mdev = (struct moticam_device *) device; - mdev->gain = gain; - mdev->gain_pending = true; -} - -static void -moticam_set_resolution(struct device *device, int width, int height, - int stride) -{ - struct moticam_device *mdev = (struct moticam_device *) device; - g_assert(!mdev->current_image); - /* Ignore stride, store parameters. */ - mdev->width = width; - mdev->height = height; -} - -static void -moticam_transfer_cb(struct libusb_transfer *transfer) -{ - struct moticam_device *mdev = transfer->user_data; - mdev->usb_transfer_done = true; -} - -static bool -moticam_start(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - struct moticam_device *mdev = (struct moticam_device *) device; - g_assert(!mdev->current_image); - /* Configure camera. */ - if (!moticam_control_exposure(mdev, 30.0, error)) - return false; - if (!moticam_control_gain(mdev, mdev->gain, error)) - return false; - if (!moticam_control_resolution(mdev, mdev->width, mdev->height, error)) - return false; - if (!moticam_control_exposure(mdev, mdev->exposure_ms, error)) - return false; - mdev->exposure_pending = false; - mdev->gain_pending = false; - utils_delay_us(100000); - /* Prepare sizes and buffers, request an extra frame to read the zero - * length packet. */ - int image_size = mdev->width * mdev->height; - int frame_size = 16384; - mdev->rawbuffer_size = (image_size + frame_size) - / frame_size * frame_size; - for (int i = 0; i < 2; i++) { - struct image *image = &mdev->image[i]; - image->width = mdev->width; - image->height = mdev->height; - image->stride = mdev->width; - image->format = IMAGE_FORMAT_SGRBG8; - image->pixels = libusb_dev_mem_alloc(mdev->handle, - mdev->rawbuffer_size); - if (!image->pixels) - g_error("can not allocate raw buffer"); - g_assert(image->refs == 0); - image->release = NULL; - image->release_user_data = NULL; - } - mdev->current_image = &mdev->image[0]; - mdev->other_image = &mdev->image[1]; - /* Drop first frame. */ - mdev->drop = 1; - /* Prepare and submit transfer. */ - mdev->usb_transfer = libusb_alloc_transfer(0); - libusb_fill_bulk_transfer(mdev->usb_transfer, mdev->handle, 0x83, - mdev->current_image->pixels, mdev->rawbuffer_size, - moticam_transfer_cb, mdev, 30000); - int r = libusb_submit_transfer(mdev->usb_transfer); - if (r) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "can not submit transfer: %s", libusb_strerror(r)); - /* Cancel start. */ - moticam_stop(device, NULL); - return false; - } - return true; -} - -static struct image * -moticam_read(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, NULL); - struct moticam_device *mdev = (struct moticam_device *) device; - g_assert(mdev->current_image); - if (mdev->usb_transfer_done) { - if (mdev->usb_transfer->status == LIBUSB_TRANSFER_COMPLETED) { - const int image_size = mdev->width * mdev->height; - struct image *image = NULL; - bool error_set = false; - /* Image received? Return it, else drop. */ - if (!mdev->drop - && mdev->usb_transfer->actual_length == image_size) { - image = mdev->current_image; - image->refs = 1; - mdev->current_image = mdev->other_image; - mdev->other_image = image; - } - if (mdev->drop) - mdev->drop--; - /* Pending updates? */ - if (image && !error_set && mdev->exposure_pending) { - if (!moticam_control_exposure(mdev, mdev->exposure_ms, error)) - error_set = true; - else - mdev->exposure_pending = false; - } - if (image && !error_set && mdev->gain_pending) { - if (!moticam_control_gain(mdev, mdev->gain, error)) - error_set = true; - else - mdev->gain_pending = false; - } - /* Start a new transfer. */ - if (!error_set) { - g_assert(mdev->current_image->refs == 0); - mdev->usb_transfer_done = false; - mdev->usb_transfer->buffer = mdev->current_image->pixels; - int r = libusb_submit_transfer(mdev->usb_transfer); - if (r) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "can not submit transfer: %s", libusb_strerror(r)); - error_set = true; - } - } - /* Return image or NULL. */ - if (error_set && image) { - image_unref(image); - image = NULL; - } - return image; - } else { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "transfer stopped"); - return NULL; - } - } else - return NULL; -} - -static bool -moticam_stop(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - struct moticam_device *mdev = (struct moticam_device *) device; - g_assert(mdev->current_image); - bool ret = true; - /* Cancel transfer if running. */ - if (!mdev->usb_transfer_done) { - int r = libusb_cancel_transfer(mdev->usb_transfer); - if (r && r != LIBUSB_ERROR_NOT_FOUND) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "can not cancel transfer: %s", libusb_strerror(r)); - ret = false; - } - while (!mdev->usb_transfer_done) { - int r = libusb_handle_events(mdev->usb); - if (r) { - g_error("unable to handle libusb events: %s", - libusb_strerror(r)); - } - } - } - /* Release. */ - libusb_free_transfer(mdev->usb_transfer); - mdev->usb_transfer = NULL; - for (int i = 0; i < 2; i++) { - libusb_dev_mem_free(mdev->handle, mdev->image[i].pixels, - mdev->rawbuffer_size); - mdev->image[i].pixels = NULL; - } - mdev->current_image = NULL; - mdev->other_image = NULL; - /* Stop camera. */ - for (int i = 0; ret && i < 3; i++) - ret = moticam_control_exposure(mdev, 0.0, error); - return ret; -} - -static bool -moticam_close(struct device *device, GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, false); - struct moticam_device *mdev = (struct moticam_device *) device; - bool ret = true; - if (mdev->current_image) - ret = moticam_stop(device, error); - libusb_close(mdev->handle); - free(device); - return ret; -} - -static char * -moticam_usb_poll(struct libusb_device_descriptor *desc) -{ - if (desc->idVendor == ID_VENDOR - && (desc->idProduct == ID_PRODUCT - || desc->idProduct == ID_PRODUCT_USB3)) - return g_strdup("Moticam 3+"); - else - return NULL; -} - -static struct device * -moticam_usb_open(libusb_context *usb, libusb_device *usb_device, - GError **error) -{ - g_return_val_if_fail(error == NULL || *error == NULL, NULL); - /* Create context. */ - struct moticam_device *mdev = g_new(struct moticam_device, 1); - mdev->device.get_info = &moticam_get_info; - mdev->device.set_exposure = &moticam_set_exposure; - mdev->device.set_gain = &moticam_set_gain; - mdev->device.set_resolution = &moticam_set_resolution; - mdev->device.start = &moticam_start; - mdev->device.read = &moticam_read; - mdev->device.stop = &moticam_stop; - mdev->device.close = &moticam_close; - mdev->usb = usb; - mdev->handle = NULL; - mdev->width = 0; - mdev->height = 0; - mdev->exposure_ms = moticam_device_info.exposure_default_ms; - mdev->exposure_pending = false; - mdev->gain = moticam_device_info.gain_default; - mdev->gain_pending = false; - mdev->rawbuffer_size = 0; - for (int i = 0; i < 2; i++) { - mdev->image[i].refs = 0; - } - mdev->current_image = NULL; - mdev->other_image = NULL; - mdev->usb_transfer = NULL; - mdev->usb_transfer_done = false; - mdev->drop = 1; - /* Open USB device. */ - int r = libusb_open(usb_device, &mdev->handle); - if (r) { - g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, - "can not open device: %s", libusb_strerror(r)); - g_free(mdev); - return NULL; - } - /* Reset camera. */ - if (!moticam_control_reset(mdev, error)) { - g_free(mdev); - return NULL; - } - /* Done. */ - return &mdev->device; -} diff --git a/moticam.h b/moticam.h deleted file mode 100644 index b9eed99..0000000 --- a/moticam.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef moticam_h -#define moticam_h -/* Camicro - Microscope camera viewer. - * - * This files add support for direct connexion with USB Moticam 3+. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include "device.h" - -extern const struct device_driver moticam_device_driver; - -#endif /* moticam_h */ diff --git a/options.c b/options.c deleted file mode 100644 index cbdaf3c..0000000 --- a/options.c +++ /dev/null @@ -1,102 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#define _GNU_SOURCE -#include - -#include "options.h" -#include "cli.h" -#include "utils.h" - -void -options_add(GApplication *app) -{ - g_application_set_option_context_summary(app, "Microscope camera viewer"); - g_application_set_option_context_description(app, - "Without options, run the GUI, else switch to batch mode to dump" - " image from the camera."); - g_application_add_main_option(app, "width", 'w', 0, - G_OPTION_ARG_INT, "image width", "VALUE"); - g_application_add_main_option(app, "exposure", 'e', 0, - G_OPTION_ARG_DOUBLE, "exposure value", "MS"); - g_application_add_main_option(app, "gain", 'g', 0, - G_OPTION_ARG_DOUBLE, "gain value", "VALUE"); - g_application_add_main_option(app, "count", 'n', 0, - G_OPTION_ARG_INT, "number of image to take", "N"); - g_application_add_main_option(app, "raw", 'r', 0, - G_OPTION_ARG_NONE, "do not convert image", NULL); - g_application_add_main_option(app, "output", 'o', 0, - G_OPTION_ARG_FILENAME, - "output file pattern (default: out%02d.png)", "PATTERN"); -} - -gint -options_handle(GApplication *app, GVariantDict *options_dict) -{ - struct options options; - options.width = -1; - options.height = -1; - options.exposure_ms = -1.0; - options.gain = -1.0; - options.count = 1; - options.raw = false; - options.out = NULL; - bool option_set = false; - if (g_variant_dict_lookup(options_dict, "width", "i", &options.width)) { - option_set = true; - if (options.width <= 0) - utils_fatal("invalid width"); - } - if (g_variant_dict_lookup(options_dict, "exposure", "d", - &options.exposure_ms)) { - option_set = true; - if (options.exposure_ms < 1.0) - utils_fatal("invalid exposure"); - } - if (g_variant_dict_lookup(options_dict, "gain", "d", &options.gain)) { - option_set = true; - if (options.gain <= 0.0) - utils_fatal("invalid gain"); - } - if (g_variant_dict_lookup(options_dict, "count", "i", &options.count)) { - option_set = true; - if (options.count <= 0) - utils_fatal("invalid count"); - } - if (g_variant_dict_contains(options_dict, "raw")) { - option_set = true; - options.raw = true; - } - if (g_variant_dict_lookup(options_dict, "out", "^&ay", &options.out)) { - option_set = true; - int argtypes[1]; - int formats = parse_printf_format(options.out, 1, argtypes); - if (formats != 1 || argtypes[0] != PA_INT) - utils_fatal("bad file pattern, use one %%d"); - } else { - options.out = "out%02d.png"; - } - if (option_set) - return cli_run(&options); - else - return -1; -} diff --git a/options.h b/options.h deleted file mode 100644 index 7a08619..0000000 --- a/options.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef options_h -#define options_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include - -/* Runtime options. */ -struct options { - /* Image width, -1 for default. */ - int width; - /* Image height, -1 for default. */ - int height; - /* Exposure in ms, -1.0 for default. */ - double exposure_ms; - /* Digital gain after image acquisition, -1.0 for default. */ - double gain; - /* Number of frame to dump. */ - int count; - /* Do not convert image. */ - bool raw; - /* Output file name or pattern. This should include a printf like pattern - * (%d) used to name the files. */ - const char *out; -}; - -/* Add options to application. */ -void -options_add(GApplication *app); - -/* Handle options. */ -gint -options_handle(GApplication *app, GVariantDict *options_dict); - -#endif /* options_h */ diff --git a/src/cli.c b/src/cli.c new file mode 100644 index 0000000..a5c20c7 --- /dev/null +++ b/src/cli.c @@ -0,0 +1,108 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +#include "cli.h" +#include "device.h" +#include "utils.h" + +static bool +cli_read_images(libusb_context *usb, struct device *device, int count, + bool raw, const char *out, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + if (!device_start(device, error)) + return false; + bool ret = true; + for (int i = 0; ret && i < count;) { + /* Read image. */ + GError *image_error = NULL; + struct image *image = device_read(device, &image_error); + if (!image && image_error) { + g_propagate_error(error, image_error); + ret = false; + } else if (!image) { + /* No image yet, handle events. */ + int r = libusb_handle_events(usb); + if (r) + utils_fatal("unable to handle libusb events: %s", + libusb_strerror(r)); + } else { + /* Image received. */ + if (!raw && image->format != IMAGE_FORMAT_XBGR32) { + struct image *cimage = image_new(image->width, image->height, + image->width * 4, IMAGE_FORMAT_XBGR32); + image_convert(cimage, image); + image_unref(image); + image = cimage; + } + char *name = g_strdup_printf(out, i); + utils_info("write %s", name); + if (!image_save(image, name, error)) + ret = false; + g_free(name); + image_unref(image); + i++; + } + } + if (!device_stop(device, ret ? error : NULL)) + return false; + return ret; +} + +int +cli_run(struct options *options) +{ + libusb_context *usb; + int r = libusb_init(&usb); + if (r) + utils_fatal("unable to initialize libusb: %s", libusb_strerror(r)); + GError *error = NULL; + struct device *device = device_open(usb, &error); + if (!device) + utils_fatal("unable to find device: %s", error->message); + const struct device_info *info = device_get_info(device); + if (options->exposure_ms > 0.0) + device_set_exposure(device, options->exposure_ms); + if (options->gain > 0.0) + device_set_gain(device, options->gain); + int width = -1, height = -1; + for (int i = 0; width == -1 && i < info->resolutions; i++) { + const struct device_info_resolution *ir = &info->resolution[i]; + if ((options->width == -1 || options->width == ir->width) + && (options->height == -1 || options->height == ir->height)) { + width = ir->width; + height = ir->height; + } + } + if (width == -1) + utils_fatal("no matching resolution"); + device_set_resolution(device, width, height, 0); + if (!cli_read_images(usb, device, options->count, options->raw, + options->out, &error)) + utils_fatal("unable to read images: %s", error->message); + if (!device_close(device, &error)) + utils_fatal("unable to close device: %s", error->message); + libusb_exit(usb); + return EXIT_SUCCESS; +} diff --git a/src/cli.h b/src/cli.h new file mode 100644 index 0000000..01f2d8a --- /dev/null +++ b/src/cli.h @@ -0,0 +1,31 @@ +#ifndef cli_h +#define cli_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include "options.h" + +/* Run command line application, return status code. */ +int +cli_run(struct options *options); + +#endif /* cli_h */ diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..09a94e3 --- /dev/null +++ b/src/device.c @@ -0,0 +1,134 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include "device.h" + +#include "moticam.h" + +static const struct device_driver *drivers[] = { + &moticam_device_driver, + NULL +}; + +GQuark +device_error_quark(void) +{ + return g_quark_from_static_string("device-error-quark"); +} + +struct device * +device_open(libusb_context *usb, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + libusb_device **list; + libusb_device *found_usb_device = NULL; + char *found_device_name = NULL; + const struct device_driver *found_device_driver = NULL; + ssize_t cnt = libusb_get_device_list(usb, &list); + if (cnt < 0) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_LIST, + "can not list devices: %s", libusb_strerror(cnt)); + return NULL; + } + for (ssize_t i = 0; !found_usb_device && i < cnt; i++) { + libusb_device *usb_device = list[i]; + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(usb_device, &desc); + if (r) { + g_warning("can not get device descriptor: %s", + libusb_strerror(r)); + } else { + for (const struct device_driver **dd = drivers; + !found_usb_device && *dd; dd++) { + char *device_name = (*dd)->usb_poll(&desc); + if (device_name) { + found_usb_device = usb_device; + found_device_name = device_name; + found_device_driver = *dd; + } + } + } + } + struct device *device = NULL; + if (found_usb_device) { + g_free(found_device_name); + device = found_device_driver->usb_open(usb, found_usb_device, error); + } else { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_LIST, + "no device found"); + } + libusb_free_device_list(list, 1); + return device; +} + +const struct device_info * +device_get_info(struct device *device) +{ + return device->get_info(device); +} + +void +device_set_exposure(struct device *device, double exposure_ms) +{ + device->set_exposure(device, exposure_ms); +} + +void +device_set_gain(struct device *device, double gain) +{ + device->set_gain(device, gain); +} + +void +device_set_resolution(struct device *device, int width, int height, + int stride) +{ + device->set_resolution(device, width, height, stride); +} + +bool +device_start(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + return device->start(device, error); +} + +struct image * +device_read(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + return device->read(device, error); +} + +bool +device_stop(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + return device->stop(device, error); +} + +bool +device_close(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + return device->close(device, error); +} diff --git a/src/device.h b/src/device.h new file mode 100644 index 0000000..7028ebb --- /dev/null +++ b/src/device.h @@ -0,0 +1,188 @@ +#ifndef device_h +#define device_h +/* Camicro - Microscope camera viewer. + * + * Device interface. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include +#include + +#include "image.h" + +/* Error domain for device related errors. */ +#define DEVICE_ERROR device_error_quark() + +struct device; + +/* USB poll function. If the device is handled by this driver, it should + * return a dynamically allocated string naming this device. */ +typedef char *(*device_driver_usb_poll_f)( + struct libusb_device_descriptor *desc); + +/* USB open function. The device driver previously declared that it handles + * this device. */ +typedef struct device *(*device_driver_usb_open_f)( + libusb_context *usb, libusb_device *usb_device, GError **error); + +/* Information on a device driver. */ +struct device_driver { + /* Driver name. */ + const char *name; + /* Poll this driver for support for an USB device. */ + device_driver_usb_poll_f usb_poll; + /* Open an USB device. */ + device_driver_usb_open_f usb_open; +}; + +/* Information on a supported resolution. */ +struct device_info_resolution { + /* Image width. */ + int width; + /* Image height. */ + int height; +}; + +/* Informations on an open device. */ +struct device_info { + /* Exposure minimum and maximum values. */ + double exposure_min_ms, exposure_max_ms; + /* Exposure default value. */ + double exposure_default_ms; + /* Exposure increment between two settable values. */ + double exposure_step_ms; + /* Gain minimum and maximum values. */ + double gain_min, gain_max; + /* Gain default value. */ + double gain_default; + /* Gain increment between two settable values. */ + double gain_step; + /* Number of supported resolutions. */ + int resolutions; + /* Table of supported resolutions. */ + const struct device_info_resolution *resolution; +}; + +/* Retrieve informations. */ +typedef const struct device_info *(*device_get_info_f)( + struct device *device); + +/* Set new exposure value. */ +typedef void (*device_set_exposure_f)( + struct device *device, double exposure_ms); + +/* Set new gain value. */ +typedef void (*device_set_gain_f)( + struct device *device, double gain); + +/* Set resolution, must be called before first image read. If stride is zero, + * let the device decide. */ +typedef void (*device_set_resolution_f)( + struct device *device, int width, int height, int stride); + +/* Start streaming. */ +typedef bool (*device_start_f)( + struct device *device, GError **error); + +/* Return an image if one is available, else, return NULL. */ +typedef struct image *(*device_read_f)( + struct device *device, GError **error); + +/* Stop streaming. */ +typedef bool (*device_stop_f)( + struct device *device, GError **error); + +/* Close device. */ +typedef bool (*device_close_f)( + struct device *device, GError **error); + +/* Open device. */ +struct device { + /* Retrieve informations. */ + device_get_info_f get_info; + /* Change exposure. */ + device_set_exposure_f set_exposure; + /* Change gain. */ + device_set_gain_f set_gain; + /* Change resolution. */ + device_set_resolution_f set_resolution; + /* Start streaming. */ + device_start_f start; + /* Read an image. */ + device_read_f read; + /* Stop streaming. */ + device_stop_f stop; + /* Close device. */ + device_close_f close; +}; + +/* Error codes. */ +enum DeviceError { + /* Error listing devices. */ + DEVICE_ERROR_LIST, + /* Generic USB error. */ + DEVICE_ERROR_USB, +}; + +/* Return quark of error domain for device related errors. */ +GQuark +device_error_quark(void); + +/* Find and open device. */ +struct device * +device_open(libusb_context *usb, GError **error); + +/* Retrieve informations. */ +const struct device_info * +device_get_info(struct device *device); + +/* Set new exposure value. */ +void +device_set_exposure(struct device *device, double exposure_ms); + +/* Set new gain value. */ +void +device_set_gain(struct device *device, double gain); + +/* Set resolution. */ +void +device_set_resolution(struct device *device, int width, int height, + int stride); + +/* Start streaming. */ +bool +device_start(struct device *device, GError **error); + +/* Read an image. */ +struct image * +device_read(struct device *device, GError **error); + +/* Stop streaming. */ +bool +device_stop(struct device *device, GError **error); + +/* Close device. */ +bool +device_close(struct device *device, GError **error); + +#endif /* device_h */ diff --git a/src/gui_app.c b/src/gui_app.c new file mode 100644 index 0000000..e7cc862 --- /dev/null +++ b/src/gui_app.c @@ -0,0 +1,127 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include "gui_app.h" +#include "gui_app_window.h" +#include "options.h" +#include "usb_source.h" +#include "utils.h" + +struct _GuiApp { + GtkApplication parent; +}; + +typedef struct _GuiAppPrivate GuiAppPrivate; + +struct _GuiAppPrivate { + libusb_context *usb; + GSource *usb_source; +}; + +G_DEFINE_TYPE_WITH_PRIVATE(GuiApp, gui_app, GTK_TYPE_APPLICATION) + +static gboolean +gui_app_usb_source_cb(gpointer user_data) +{ + GuiApp *app = user_data; + GuiAppPrivate *priv = gui_app_get_instance_private(app); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + int r = libusb_handle_events_timeout(priv->usb, &tv); + if (r) + g_error("unable to handle libusb events: %s", libusb_strerror(r)); + g_signal_emit_by_name(app, "video-ready"); + return G_SOURCE_CONTINUE; +} + +static void +gui_app_init(GuiApp *app) +{ + GuiAppPrivate *priv = gui_app_get_instance_private(app); + priv->usb = NULL; + priv->usb_source = NULL; + options_add(G_APPLICATION(app)); +} + +static void +gui_app_startup(GApplication *app) +{ + G_APPLICATION_CLASS(gui_app_parent_class)->startup(app); + GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); + int r = libusb_init(&priv->usb); + if (r) + g_error("unable to initialize libusb: %s", libusb_strerror(r)); + priv->usb_source = usb_source_new(priv->usb); + g_source_set_callback(priv->usb_source, gui_app_usb_source_cb, app, + NULL); + g_source_attach(priv->usb_source, NULL); +} + +static void +gui_app_activate(GApplication *app) +{ + GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); + GError *error = NULL; + struct device *device = device_open(priv->usb, &error); + if (!device) { + utils_dialog_error(NULL, "unable to find device: %s", error->message); + g_error_free(error); + } else { + GuiAppWindow *window; + window = gui_app_window_new(GUI_APP(app)); + gui_app_window_open(window, device); + gtk_window_present(GTK_WINDOW(window)); + } +} + +static void +gui_app_shutdown(GApplication *app) +{ + GuiAppPrivate *priv = gui_app_get_instance_private(GUI_APP(app)); + if (priv->usb_source) { + g_source_destroy(priv->usb_source); + g_source_unref(priv->usb_source); + } + if (priv->usb) + libusb_exit(priv->usb); + G_APPLICATION_CLASS(gui_app_parent_class)->shutdown(app); +} + +static void +gui_app_class_init(GuiAppClass *class) +{ + G_APPLICATION_CLASS(class)->startup = gui_app_startup; + G_APPLICATION_CLASS(class)->activate = gui_app_activate; + G_APPLICATION_CLASS(class)->shutdown = gui_app_shutdown; + G_APPLICATION_CLASS(class)->handle_local_options = options_handle; + g_signal_new("video-ready", GUI_APP_TYPE, G_SIGNAL_RUN_FIRST, 0, NULL, + NULL, NULL, G_TYPE_NONE, 0); +} + +GuiApp * +gui_app_new(void) +{ + return g_object_new(GUI_APP_TYPE, + "application-id", "org.eu.fr.ni.Camicro", + NULL); +} diff --git a/src/gui_app.h b/src/gui_app.h new file mode 100644 index 0000000..8eeeff1 --- /dev/null +++ b/src/gui_app.h @@ -0,0 +1,33 @@ +#ifndef gui_app_h +#define gui_app_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +#define GUI_APP_TYPE (gui_app_get_type()) +G_DECLARE_FINAL_TYPE(GuiApp, gui_app, GUI, APP, GtkApplication) + +GuiApp * +gui_app_new(void); + +#endif /* gui_app_h */ diff --git a/src/gui_app_window.c b/src/gui_app_window.c new file mode 100644 index 0000000..980daaa --- /dev/null +++ b/src/gui_app_window.c @@ -0,0 +1,513 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include + +#include "gui_app_window.h" +#include "utils.h" + +struct _GuiAppWindow { + GtkApplicationWindow parent; +}; + +typedef struct _GuiAppWindowPrivate GuiAppWindowPrivate; + +struct _GuiAppWindowPrivate { + GuiAppWindow *win; + GuiApp *app; + GtkDrawingArea *video; + GtkButton *start_stop_button; + GtkComboBoxText *resolution_combo_box; + GtkAdjustment *exposure_adj; + GtkAdjustment *gain_adj; + GtkCheckButton *wb_button; + GtkButton *wb_cal_button; + GtkCheckButton *rotate_button; + GtkButton *save_button; + GdkRectangle allocation; + cairo_matrix_t transform; + cairo_surface_t *surface; + struct image *image; + struct image *image_converted; + bool white_balance; + bool white_balance_calibrated; + struct image_white_balance_reference white_balance_reference; + bool rotate; + int width; + int height; + struct device *device; + bool started; + gulong video_ready_handler_id; +}; + +G_DEFINE_TYPE_WITH_PRIVATE(GuiAppWindow, gui_app_window, + GTK_TYPE_APPLICATION_WINDOW) + +/* When input or output size change, recompute transformation matrix. */ +static void +video_size_update(GuiAppWindowPrivate *priv) +{ + int source_width = priv->width; + int source_height = priv->height; + int dest_width = priv->allocation.width; + int dest_height = priv->allocation.height; + if (source_width > 0 && dest_width > 0) { + double scale_x = (double) source_width / dest_width; + double scale_y = (double) source_height / dest_height; + double scale; + if (scale_x > scale_y) + scale = scale_x; + else + scale = scale_y; + if (scale < 1.0) + scale = 1.0; + double offset_x = -(dest_width - source_width / scale) / 2.0; + double offset_y = -(dest_height - source_height / scale) / 2.0; + if (priv->rotate) { + cairo_matrix_init_translate(&priv->transform, + source_width / 2, source_height / 2); + cairo_matrix_rotate(&priv->transform, M_PI); + cairo_matrix_translate(&priv->transform, + -source_width / 2, -source_height / 2); + } else { + cairo_matrix_init_identity(&priv->transform); + } + cairo_matrix_scale(&priv->transform, scale, scale); + cairo_matrix_translate(&priv->transform, offset_x, offset_y); + } +} + +/* A new image could be available. */ +static void +video_ready_cb(GuiApp *app, gpointer user_data) +{ + GuiAppWindowPrivate *priv = user_data; + if (priv->started) { + GError *error = NULL; + struct image *image = device_read(priv->device, &error); + if (!image) { + if (error) { + utils_dialog_error(GTK_WINDOW(priv->win), + "error reading image: %s", + error->message); + g_error_free(error); + gtk_widget_destroy(GTK_WIDGET(priv->win)); + } + } else { + /* Release old data. */ + if (priv->surface) { + cairo_surface_destroy(priv->surface); + priv->surface = NULL; + } + if (priv->image) { + image_unref(priv->image); + priv->image = NULL; + } + /* White balance. */ + if (priv->white_balance && !priv->white_balance_calibrated) { + image_white_balance_calibrate(image, + &priv->white_balance_reference); + priv->white_balance_calibrated = true; + } + if (priv->white_balance) + image_white_balance(image, &priv->white_balance_reference); + /* Different format? Convert. */ + if (image->format != IMAGE_FORMAT_XBGR32) { + if (priv->image_converted && + (priv->image_converted->width != image->width + || priv->image_converted->height != image->height)) { + image_unref(priv->image_converted); + priv->image_converted = NULL; + } + if (!priv->image_converted) { + int stride = + cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, + image->width); + priv->image_converted = image_new(image->width, + image->height, stride, IMAGE_FORMAT_XBGR32); + } + image_convert(priv->image_converted, image); + image_unref(image); + image = priv->image_converted; + } else { + if (priv->image_converted) { + image_unref(priv->image_converted); + priv->image_converted = NULL; + } + priv->image = image; + } + /* Make a surface. */ + priv->surface = cairo_image_surface_create_for_data( + (unsigned char *) image->pixels, CAIRO_FORMAT_RGB24, image->width, + image->height, image->stride); + gtk_widget_queue_draw(GTK_WIDGET(priv->video)); + gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), TRUE); + } + } +} + +/* Start video. */ +static void +video_start(GuiAppWindowPrivate *priv) +{ + g_assert(!priv->started); + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, + priv->width); + device_set_resolution(priv->device, priv->width, priv->height, stride); + video_size_update(priv); + GError *error = NULL; + if (!device_start(priv->device, &error)) { + utils_dialog_error(GTK_WINDOW(priv->win), "can not start: %s", + error->message); + g_error_free(error); + } else { + priv->video_ready_handler_id = g_signal_connect(priv->app, "video-ready", + G_CALLBACK(video_ready_cb), priv); + gtk_button_set_label(priv->start_stop_button, "Stop"); + priv->started = true; + } +} + +/* Stop video. */ +static void +video_stop(GuiAppWindowPrivate *priv) +{ + g_assert(priv->started); + if (priv->surface) { + cairo_surface_destroy(priv->surface); + priv->surface = NULL; + } + if (priv->image) { + image_unref(priv->image); + priv->image = NULL; + } + if (priv->image_converted) { + image_unref(priv->image_converted); + priv->image_converted = NULL; + } + g_signal_handler_disconnect(priv->app, priv->video_ready_handler_id); + GError *error = NULL; + if (!device_stop(priv->device, &error)) { + g_warning("device_stop: %s", error->message); + g_error_free(error); + } + priv->started = false; + gtk_widget_queue_draw(GTK_WIDGET(priv->video)); + gtk_button_set_label(priv->start_stop_button, "Start"); + gtk_widget_set_sensitive(GTK_WIDGET(priv->save_button), FALSE); +} + +static gboolean +video_draw_cb(GtkWidget *widget, cairo_t *cr, gpointer user_data) +{ + GuiAppWindow *win = GUI_APP_WINDOW(gtk_widget_get_toplevel(widget)); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_paint(cr); + if (priv->surface) { + cairo_pattern_t *pattern = + cairo_pattern_create_for_surface(priv->surface); + cairo_pattern_set_matrix(pattern, &priv->transform); + cairo_set_source(cr, pattern); + cairo_paint(cr); + cairo_pattern_destroy(pattern); + } + return FALSE; +} + +static void +video_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation, + gpointer user_data) +{ + GuiAppWindow *win = GUI_APP_WINDOW(gtk_widget_get_toplevel(widget)); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->allocation = *allocation; + video_size_update(priv); +} + +static void +destroy_cb(GtkWidget *widget, gpointer user_data) +{ + GuiAppWindow *win = GUI_APP_WINDOW(widget); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + if (priv->started) + video_stop(priv); + if (priv->device) { + GError *error = NULL; + if (!device_close(priv->device, &error)) { + g_warning("device_close: %s", error->message); + g_error_free(error); + } + priv->device = NULL; + } +} + +static void +start_stop_button_clicked_cb(GtkButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + if (!priv->started) + video_start(priv); + else + video_stop(priv); +} + +static void +resolution_combo_box_changed_cb(GtkComboBox *combo, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(combo))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + int sel = gtk_combo_box_get_active(combo); + const struct device_info *info = device_get_info(priv->device); + g_assert(sel >= 0 && sel < info->resolutions); + priv->width = info->resolution[sel].width; + priv->height = info->resolution[sel].height; + if (priv->started) { + video_stop(priv); + video_start(priv); + } +} + +static void +exposure_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data) +{ + GuiAppWindow *win = GUI_APP_WINDOW(user_data); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + double val = gtk_adjustment_get_value(adj); + device_set_exposure(priv->device, val); +} + +static void +gain_adj_value_changed_cb(GtkAdjustment *adj, gpointer user_data) +{ + GuiAppWindow *win = GUI_APP_WINDOW(user_data); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + double val = gtk_adjustment_get_value(adj); + device_set_gain(priv->device, val); +} + +static void +wb_button_toggled_cb(GtkToggleButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->white_balance = gtk_toggle_button_get_active(button); + gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), + priv->white_balance); +} + +static void +wb_cal_button_clicked_cb(GtkButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->white_balance_calibrated = false; +} + +static void +rotate_button_toggled_cb(GtkToggleButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->rotate = gtk_toggle_button_get_active(button); + video_size_update(priv); +} + +static void +save_button_clicked_cb(GtkButton *button, gpointer user_data) +{ + GuiAppWindow *win = + GUI_APP_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(button))); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + if (priv->surface) { + GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(priv->surface, 0, 0, + priv->width, priv->height); + if (priv->rotate && pixbuf) { + GdkPixbuf *rotated = gdk_pixbuf_rotate_simple(pixbuf, 180); + g_object_unref(pixbuf); + pixbuf = rotated; + } + if (pixbuf) { + GtkWidget *dialog = gtk_file_chooser_dialog_new("Save image", + GTK_WINDOW(win), + GTK_FILE_CHOOSER_ACTION_SAVE, + "_Cancel", GTK_RESPONSE_CANCEL, + "_Save", GTK_RESPONSE_ACCEPT, + NULL); + GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); + GtkFileFilter *png_filter = gtk_file_filter_new(); + gtk_file_filter_set_name(png_filter, "PNG image"); + gtk_file_filter_add_mime_type(png_filter, "image/png"); + gtk_file_filter_add_pattern(png_filter, "*.png"); + gtk_file_chooser_add_filter(chooser, png_filter); + GtkFileFilter *jpg_filter = gtk_file_filter_new(); + gtk_file_filter_set_name(jpg_filter, "JPEG image"); + gtk_file_filter_add_mime_type(jpg_filter, "image/jpeg"); + gtk_file_filter_add_pattern(jpg_filter, "*.jpg"); + gtk_file_filter_add_pattern(jpg_filter, "*.jpeg"); + gtk_file_chooser_add_filter(chooser, jpg_filter); + gint res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename = gtk_file_chooser_get_filename(chooser); + if (filename) { + GtkFileFilter *filter = + gtk_file_chooser_get_filter(chooser); + const char *type = NULL; + if (filter == png_filter) + type = "png"; + else if (filter == jpg_filter) + type = "jpeg"; + if (type) { + GError *error = NULL; + if (!gdk_pixbuf_save(pixbuf, filename, type, &error, + NULL)) { + utils_dialog_error(GTK_WINDOW(win), + "can not save image: %s", + error->message); + g_error_free(error); + } + } + g_free(filename); + } + } + gtk_widget_destroy(dialog); + g_object_unref(pixbuf); + } + } +} + +static void +gui_app_window_init(GuiAppWindow *win) +{ + gtk_widget_init_template(GTK_WIDGET(win)); + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + priv->win = win; + priv->app = NULL; + priv->allocation.width = -1; + cairo_matrix_init_identity(&priv->transform); + priv->surface = NULL; + priv->image = NULL; + priv->image_converted = NULL; + priv->white_balance = false; + priv->white_balance_calibrated = false; + priv->rotate = true; + priv->width = -1; + priv->height = -1; + priv->device = NULL; + priv->started = false; + priv->video_ready_handler_id = 0; +} + +static void +gui_app_window_class_init(GuiAppWindowClass *class) +{ + gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(class), + "/org/eu/fr/ni/Camicro/window.ui"); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, video); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, start_stop_button); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, resolution_combo_box); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, exposure_adj); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, gain_adj); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, wb_button); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, wb_cal_button); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, rotate_button); + gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(class), + GuiAppWindow, save_button); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + destroy_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + video_draw_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + video_size_allocate_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + start_stop_button_clicked_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + resolution_combo_box_changed_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + exposure_adj_value_changed_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + gain_adj_value_changed_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + wb_button_toggled_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + wb_cal_button_clicked_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + rotate_button_toggled_cb); + gtk_widget_class_bind_template_callback(GTK_WIDGET_CLASS(class), + save_button_clicked_cb); +} + +GuiAppWindow * +gui_app_window_new(GuiApp *app) +{ + return g_object_new(GUI_APP_WINDOW_TYPE, "application", app, NULL); +} + +void +gui_app_window_open(GuiAppWindow *win, struct device *device) +{ + GuiAppWindowPrivate *priv = gui_app_window_get_instance_private(win); + g_assert(!priv->device); + priv->app = GUI_APP(gtk_window_get_application(GTK_WINDOW(win))); + const struct device_info *info = device_get_info(device); + priv->width = info->resolution[0].width; + priv->height = info->resolution[0].height; + priv->device = device; + gtk_adjustment_set_lower(priv->exposure_adj, info->exposure_min_ms); + gtk_adjustment_set_upper(priv->exposure_adj, info->exposure_max_ms); + gtk_adjustment_set_value(priv->exposure_adj, info->exposure_default_ms); + gtk_adjustment_set_lower(priv->gain_adj, info->gain_min); + gtk_adjustment_set_upper(priv->gain_adj, info->gain_max); + gtk_adjustment_set_value(priv->gain_adj, info->gain_default); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->wb_button), + priv->white_balance); + gtk_widget_set_sensitive(GTK_WIDGET(priv->wb_cal_button), + priv->white_balance); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->rotate_button), + priv->rotate); + gtk_combo_box_text_remove_all(priv->resolution_combo_box); + for (int i = 0; i < info->resolutions; i++) { + const struct device_info_resolution *ir = &info->resolution[i]; + char buf[32]; + int r = snprintf(buf, sizeof(buf), "%dx%d", ir->width, ir->height); + g_assert(r < sizeof(buf)); + gtk_combo_box_text_append_text(priv->resolution_combo_box, buf); + } + gtk_combo_box_set_active(GTK_COMBO_BOX(priv->resolution_combo_box), 0); + video_start(priv); +} diff --git a/src/gui_app_window.h b/src/gui_app_window.h new file mode 100644 index 0000000..2f886db --- /dev/null +++ b/src/gui_app_window.h @@ -0,0 +1,40 @@ +#ifndef gui_app_window_h +#define gui_app_window_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +#include "gui_app.h" +#include "device.h" + +#define GUI_APP_WINDOW_TYPE (gui_app_window_get_type()) +G_DECLARE_FINAL_TYPE(GuiAppWindow, gui_app_window, GUI, APP_WINDOW, + GtkApplicationWindow) + +GuiAppWindow * +gui_app_window_new(GuiApp *app); + +void +gui_app_window_open(GuiAppWindow *win, struct device *device); + +#endif /* gui_app_window_h */ diff --git a/src/image.c b/src/image.c new file mode 100644 index 0000000..ec29751 --- /dev/null +++ b/src/image.c @@ -0,0 +1,417 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include +#include + +#include "image.h" +#include "utils.h" + +typedef uint8_t u8x16 __attribute__((vector_size(16))); +typedef uint16_t u16x8 __attribute__((vector_size(16))); + +GQuark +image_error_quark(void) +{ + return g_quark_from_static_string("image-error-quark"); +} + +static void +image_free(struct image *image, void *user_data) +{ + free(image); +} + +struct image * +image_new(int width, int height, int stride, enum image_format format) +{ + size_t struct_size_aligned = (sizeof(struct image) + 15) / 16 * 16; + size_t image_size_aligned = ((height * stride) + 15) / 16 * 16; + uint8_t *m = aligned_alloc(16, + struct_size_aligned + image_size_aligned); + if (!m) + g_error("aligned_alloc: failed to allocate"); + struct image *image = (struct image *) m; + image->width = width; + image->height = height; + image->stride = stride; + image->format = format; + image->pixels = m + struct_size_aligned; + image->refs = 1; + image->release = image_free; + image->release_user_data = NULL; + return image; +} + +void +image_ref(struct image *image) +{ + image->refs++; +} + +void +image_unref(struct image *image) +{ + image->refs--; + if (image->refs == 0 && image->release) + image->release(image, image->release_user_data); +} + +static void +image_histogram_sgrbg8(const struct image *image, uint32_t histogram[3][256]) +{ + g_assert(image->width % 2 == 0); + g_assert(image->height % 2 == 0); + g_assert(image->stride == image->width); + memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); + const uint8_t *p = image->pixels; + for (int i = 0; i < image->height; i += 2) { + for (int j = 0; j < image->width; j += 2) { + uint8_t g = *p++; + histogram[1][g] += 2; + uint8_t r = *p++; + histogram[0][r] += 4; + } + for (int j = 0; j < image->width; j += 2) { + uint8_t b = *p++; + histogram[2][b] += 4; + uint8_t g = *p++; + histogram[1][g] += 2; + } + } +} + +static void +image_histogram_xrgb32(const struct image *image, uint32_t histogram[3][256]) +{ + g_assert(image->stride == image->width * 4); + memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); + const uint8_t *p = image->pixels; + const uint8_t *pend = image->pixels + image->height * image->stride; + while (p != pend) { + uint8_t b = *p++; + uint8_t g = *p++; + uint8_t r = *p++; + p++; + histogram[0][r]++; + histogram[1][g]++; + histogram[2][b]++; + } +} + +void +image_histogram(const struct image *image, uint32_t histogram[3][256]) +{ + switch (image->format) { + case IMAGE_FORMAT_SGRBG8: + image_histogram_sgrbg8(image, histogram); + break; + case IMAGE_FORMAT_XBGR32: + image_histogram_xrgb32(image, histogram); + break; + default: + g_assert_not_reached(); + } +} + +void +image_white_balance_calibrate(const struct image *image, + struct image_white_balance_reference *reference) +{ + uint32_t histogram[3][256]; + image_histogram(image, histogram); + const uint32_t top_ignore = image->width * image->height / 1000; + uint8_t top[3]; + for (int i = 0; i < 3; i++) { + uint32_t acc = 0; + uint8_t j; + for (j = 255; acc < top_ignore && j > 0; j--) { + acc += histogram[i][j]; + } + top[i] = j; + } + reference->r = top[0]; + reference->g = top[1]; + reference->b = top[2]; +} + +static void +image_white_balance_factors( + const struct image_white_balance_reference *reference, + uint16_t *fr, uint16_t *fg, uint16_t *fb) +{ + uint8_t wr = reference->r; + uint8_t wg = reference->g; + uint8_t wb = reference->b; + if (wr > wg && wr > wb) { + *fr = 256; + *fg = 256 * wr / wg; + *fb = 256 * wr / wb; + } else if (wg > wr && wg > wb) { + *fr = 256 * wg / wr; + *fg = 256; + *fb = 256 * wg / wb; + } else { + *fr = 256 * wb / wr; + *fg = 256 * wb / wg; + *fb = 256; + } +} + +static void +image_white_balance_sgrbg8(struct image *image, + const struct image_white_balance_reference *reference) +{ + g_assert(image->width % 16 == 0); + g_assert(image->height % 2 == 0); + g_assert(image->stride == image->width); + uint16_t fr, fg, fb; + image_white_balance_factors(reference, &fr, &fg, &fb); + u8x16 *p = (u8x16 *) image->pixels; + const u8x16 zero = { 0 }; + const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, + 4, 20, 5, 21, 6, 22, 7, 23 }; + const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, + 12, 28, 13, 29, 14, 30, 15, 31 }; + const u16x8 mulo = { fg, fr, fg, fr, fg, fr, fg, fr }; + const u16x8 mule = { fb, fg, fb, fg, fb, fg, fb, fg }; + const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30 }; + for (int i = 0; i < image->height; i += 2) { + for (int j = 0; j < image->width; j += 16) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mulo) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mulo) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mulo) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mulo) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } + for (int j = 0; j < image->width; j += 16) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mule) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mule) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mule) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mule) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } + } +} + +static void +image_white_balance_xbgr32(struct image *image, + const struct image_white_balance_reference *reference) +{ + g_assert(image->width % 4 == 0); + g_assert(image->stride == image->width * 4); + uint16_t fr, fg, fb; + image_white_balance_factors(reference, &fr, &fg, &fb); + u8x16 *p = (u8x16 *) image->pixels; + u8x16 *pend = (u8x16 *) (image->pixels + image->height * image->stride); + const u8x16 zero = { 0 }; + const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, + 4, 20, 5, 21, 6, 22, 7, 23 }; + const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, + 12, 28, 13, 29, 14, 30, 15, 31 }; + const u16x8 mul = { fb, fg, fr, 255, fb, fg, fr, 255 }; + const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, + 16, 18, 20, 22, 24, 26, 28, 30 }; + while (p != pend) { + u8x16 pix4 = *p; + u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); + u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mul) + != (u16x8) zero; + pix2_0 = ((pix2_0 * mul) | pix2_0_ovf) / 256; + u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); + u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mul) + != (u16x8) zero; + pix2_1 = ((pix2_1 * mul) | pix2_1_ovf) / 256; + pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); + *p++ = pix4; + } +} + +void +image_white_balance(struct image *image, + const struct image_white_balance_reference *reference) +{ + switch (image->format) { + case IMAGE_FORMAT_SGRBG8: + image_white_balance_sgrbg8(image, reference); + break; + case IMAGE_FORMAT_XBGR32: + image_white_balance_xbgr32(image, reference); + break; + default: + g_assert_not_reached(); + } +} + +static void +image_convert_to_xbgr32_from_sgrbg8(struct image *dst, + const struct image *src) +{ + /* + * Compute missing value using an average of its neighbours. + * Input pattern: + * G R G R G R + * B G B G B G + * G R G R G R + * B G B G B G + */ + g_assert(dst->width == src->width + && dst->height == src->height); + const int in_stride = src->stride; + const int out_stride = dst->stride; + int width = src->width; + int height = src->height; + /* Skip first line and column. */ + const uint8_t *in = src->pixels + in_stride + 1; + uint8_t *out = dst->pixels + out_stride + 4; + /* Loop over lines. */ + for (int i = 1; i < height - 1; i += 2) { + /* Even lines. */ + const uint8_t *in_stop = in + (width - 2); + while (in != in_stop) { + *out++ = (in[-1] + in[+1] + 1) >> 1; /* B */ + *out++ = in[0]; /* G */ + *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* R */ + *out++ = 255; /* A */ + in++; + *out++ = in[0]; /* B */ + *out++ = (in[-in_stride] + in[+in_stride] /* G */ + + in[-1] + in[+1] + 2) >> 2; + *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* R */ + + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; + *out++ = 255; /* A */ + in++; + } + /* Fill first and last pixels. */ + out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; + out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; + out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; + out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; + out[0] = out[-4]; + out[1] = out[-3]; + out[2] = out[-2]; + out[3] = out[-1]; + out += out_stride - (width - 2) * 4; + in += in_stride - (width - 2); + /* Odd lines. */ + in_stop = in + (width - 2); + while (in != in_stop) { + *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* B */ + + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; + *out++ = (in[-in_stride] + in[+in_stride] /* G */ + + in[-1] + in[+1] + 2) >> 2; + *out++ = in[0]; /* R */ + *out++ = 255; /* A */ + in++; + *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* B */ + *out++ = in[0]; /* G */ + *out++ = (in[-1] + in[+1] + 1) >> 1; /* R */ + *out++ = 255; /* A */ + in++; + } + /* Fill first and last pixels. */ + out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; + out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; + out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; + out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; + out[0] = out[-4]; + out[1] = out[-3]; + out[2] = out[-2]; + out[3] = out[-1]; + out += out_stride - (width - 2) * 4; + in += in_stride - (width - 2); + } + /* Last line. */ + out -= 4; + memcpy(out, out - out_stride, width * 4); + /* First line. */ + out -= (height - 1) * out_stride; + memcpy(out, out + out_stride, width * 4); +} + +void +image_convert(struct image *dst, const struct image *src) +{ + g_assert(dst->width == src->width + && dst->height == src->height); + if (dst->format == src->format) { + g_assert(dst->stride == src->stride); + memcpy(dst->pixels, src->pixels, src->stride * src->height); + } else { + switch (src->format) { + case IMAGE_FORMAT_SGRBG8: + switch (dst->format) { + case IMAGE_FORMAT_XBGR32: + image_convert_to_xbgr32_from_sgrbg8(dst, src); + break; + default: + g_assert_not_reached(); + } + break; + default: + g_assert_not_reached(); + } + } +} + +bool +image_save(const struct image *image, const char *name, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + png_image pimage; + memset(&pimage, 0, sizeof(pimage)); + pimage.version = PNG_IMAGE_VERSION; + pimage.width = image->width; + pimage.height = image->height; + switch(image->format) { + case IMAGE_FORMAT_SGRBG8: + pimage.format = PNG_FORMAT_GRAY; + break; + case IMAGE_FORMAT_XBGR32: + pimage.format = PNG_FORMAT_BGRA; + break; + default: + g_assert_not_reached(); + } + int r = png_image_write_to_file(&pimage, name, 0, + image->pixels, image->stride, NULL); + if (r == 0) { + g_set_error(error, IMAGE_ERROR, IMAGE_ERROR_SAVE, + "can not write image: %s", pimage.message); + return false; + } + return true; +} diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..0e36c02 --- /dev/null +++ b/src/image.h @@ -0,0 +1,121 @@ +#ifndef image_h +#define image_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include +#include + +/* Error domain for image related errors. */ +#define IMAGE_ERROR image_error_quark() + +/* Image format. */ +enum image_format +{ +#define IMAGE_FORMAT_FOURCC(a, b, c, d) \ + (((uint32_t)(a)) | ((uint32_t)(b) << 8) \ + | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) + /* Bayer image format: + * G R G R + * B G B G */ + IMAGE_FORMAT_SGRBG8 = IMAGE_FORMAT_FOURCC('B', 'R', 'B', 'G'), + /* RGB 32 bits, B in LSB. */ + IMAGE_FORMAT_XBGR32 = IMAGE_FORMAT_FOURCC('X', 'R', '2', '4'), +}; + +struct image; + +/* Image release callback. */ +typedef void (*image_release_f)(struct image *image, void *user_data); + +/* Image with attached information. */ +struct image { + /* Image width. */ + int width; + /* Image height. */ + int height; + /* Byte offset between a line and the next line. */ + int stride; + /* Image format. */ + enum image_format format; + /* Frame buffer. */ + uint8_t *pixels; + /* Number of reference to this image. */ + int refs; + /* Release callback. */ + image_release_f release; + /* Release callback user data. */ + void *release_user_data; +}; + +/* Reference for white balance. */ +struct image_white_balance_reference { + /* White (maximum of each component). */ + uint8_t r, g, b; +}; + +/* Error codes. */ +enum ImageError { + /* Error writing an image to disk. */ + IMAGE_ERROR_SAVE, +}; + +/* Return quark of error domain for image related errors. */ +GQuark +image_error_quark(void); + +/* Allocate an image using system allocator. */ +struct image * +image_new(int width, int height, int stride, enum image_format format); + +/* Add a reference to an image. */ +void +image_ref(struct image *image); + +/* Release a reference to an image. */ +void +image_unref(struct image *image); + +/* Compute histogram(s), should provide an uint32_t[3][256] array. */ +void +image_histogram(const struct image *image, uint32_t histogram[3][256]); + +/* Calibrate for white balance. */ +void +image_white_balance_calibrate(const struct image *image, + struct image_white_balance_reference *reference); + +/* Apply the given white balance. */ +void +image_white_balance(struct image *image, + const struct image_white_balance_reference *reference); + +/* Copy an image to another one, converting format. */ +void +image_convert(struct image *dst, const struct image *src); + +/* Save image to PNG. */ +bool +image_save(const struct image *image, const char *name, GError **error); + +#endif /* image_h */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..2abeeda --- /dev/null +++ b/src/main.c @@ -0,0 +1,36 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +#include "gui_app.h" + +int +main(int argc, char **argv) +{ + int status; + GuiApp *app; + app = gui_app_new(); + status = g_application_run(G_APPLICATION(app), argc, argv); + g_object_unref(app); + return status; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..cf832e1 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,29 @@ +sources = [ + 'device.c', + 'cli.c', + 'image.c', + 'options.c', + 'gui_app_window.c', + 'usb_source.c', + 'moticam.c', + 'main.c', + 'gui_app.c', + 'utils.c', +] + +deps = [ + libusb, + libpng, + gtk, +] + +c_args = [ + '-flax-vector-conversions', +] + +executable('camicro', + sources + resources, + dependencies : deps, + c_args: c_args, + install : true, +) diff --git a/src/moticam.c b/src/moticam.c new file mode 100644 index 0000000..232fb77 --- /dev/null +++ b/src/moticam.c @@ -0,0 +1,482 @@ +/* Camicro - Microscope camera viewer. + * + * This files add support for direct connexion with USB Moticam 3+. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include "moticam.h" +#include "utils.h" + +#define ID_VENDOR 0x232f +#define ID_PRODUCT 0x0100 +#define ID_PRODUCT_USB3 0x0101 + +struct moticam_device { + struct device device; + libusb_context *usb; + libusb_device_handle *handle; + double exposure_ms; + int width; + int height; + bool exposure_pending; + double gain; + bool gain_pending; + size_t rawbuffer_size; + struct image image[2]; + struct image *current_image; + struct image *other_image; + struct libusb_transfer *usb_transfer; + bool usb_transfer_done; + int drop; +}; + +static char * +moticam_usb_poll(struct libusb_device_descriptor *desc); + +static struct device * +moticam_usb_open(libusb_context *usb, libusb_device *usb_device, + GError **error); + +static bool +moticam_stop(struct device *device, GError **error); + +const struct device_driver moticam_device_driver = { + "Moticam USB cameras", + &moticam_usb_poll, + &moticam_usb_open, +}; + +static const struct device_info_resolution moticam_device_info_resolution[] = { + { 2048, 1536 }, + { 1024, 768 }, + { 512, 384 }, +}; + +static const struct device_info moticam_device_info = { + .exposure_min_ms = 1, + .exposure_max_ms = 5000, + .exposure_default_ms = 100, + .exposure_step_ms = 1, + .gain_min = 0.33, + .gain_max = 42.66, + .gain_default = 1, + .gain_step = 1.0 / 24, + .resolutions = 3, + .resolution = moticam_device_info_resolution, +}; + +static bool +moticam_control_vendor(struct moticam_device *mdev, uint16_t wValue, + const uint8_t *data, int data_cnt, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + int r = libusb_control_transfer(mdev->handle, LIBUSB_REQUEST_TYPE_VENDOR, + 240, wValue, 0, (uint8_t *) data, data_cnt, 0); + if (r < 0) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "can not send vendor control: %s", libusb_strerror(r)); + return false; + } + return true; +} + +static bool +moticam_control_vendor_w(struct moticam_device *mdev, uint16_t wValue, + const uint16_t data0, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + uint8_t data[] = { (uint8_t) (data0 >> 8), (uint8_t) data0 }; + return moticam_control_vendor(mdev, wValue, data, sizeof(data), error); +} + +static bool +moticam_control_reset(struct moticam_device *mdev, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + if (!moticam_control_vendor_w(mdev, 0xba00, 0x0000, error)) + return false; + if (!moticam_control_vendor_w(mdev, 0xba00, 0x0001, error)) + return false; + return true; +} + +static bool +moticam_control_exposure(struct moticam_device *mdev, double exposure_ms, + GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + int exposure_w = exposure_ms * 12.82; + if (exposure_w < 0x000c) + exposure_w = 0x000c; + else if (exposure_w > 0xffff) + exposure_w = 0xffff; + return moticam_control_vendor_w(mdev, 0xba09, exposure_w, error); +} + +static bool +moticam_control_gain(struct moticam_device *mdev, double gain, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + double gmin, gmax; + int xmin, xmax; + bool up = false; + if (gain <= 1.34) { + gmin = 0.33; + gmax = 1.33; + xmin = 0x08; + xmax = 0x20; + } else if (gain <= 2.68) { + gmin = 1.42; + gmax = 2.67; + xmin = 0x51; + xmax = 0x60; + } else { + gmin = 3; + gmax = 42.67; + xmin = 0x1; + xmax = 0x78; + up = true; + } + int x = (gain - gmin) / (gmax - gmin) * (xmax - xmin) + xmin; + if (x < xmin) + x = xmin; + else if (x > xmax) + x = xmax; + if (up) + x = (x << 8) | 0x60; + if (!moticam_control_vendor_w(mdev, 0xba2d, x, error)) + return false; + if (!moticam_control_vendor_w(mdev, 0xba2b, x, error)) + return false; + if (!moticam_control_vendor_w(mdev, 0xba2e, x, error)) + return false; + if (!moticam_control_vendor_w(mdev, 0xba2c, x, error)) + return false; + return true; +} + +static bool +moticam_control_resolution(struct moticam_device *mdev, int width, int height, + GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + static const uint8_t control_init[] = { + 0x00, 0x14, 0x00, 0x20, 0x05, 0xff, 0x07, 0xff }; + if (!moticam_control_vendor(mdev, 0xba01, control_init, + sizeof(control_init), error)) + return false; + static const uint8_t control_512x384[] = { 0x00, 0x03, 0x00, 0x03 }; + static const uint8_t control_1024x768[] = { 0x00, 0x11, 0x00, 0x11 }; + static const uint8_t control_2048x1536[] = { 0x00, 0x00, 0x00, 0x00 }; + bool ret; + switch (width) + { + case 512: + g_assert(height == 384); + ret = moticam_control_vendor(mdev, 0xba22, control_512x384, + sizeof(control_512x384), error); + break; + case 1024: + g_assert(height == 768); + ret = moticam_control_vendor(mdev, 0xba22, control_1024x768, + sizeof(control_1024x768), error); + break; + case 2048: + g_assert(height == 1536); + ret = moticam_control_vendor(mdev, 0xba22, control_2048x1536, + sizeof(control_2048x1536), error); + break; + default: + g_assert_not_reached(); + } + return ret; +} + +static const struct device_info * +moticam_get_info(struct device *device) +{ + return &moticam_device_info; +} + +static void +moticam_set_exposure(struct device *device, double exposure_ms) +{ + struct moticam_device *mdev = (struct moticam_device *) device; + mdev->exposure_ms = exposure_ms; + mdev->exposure_pending = true; +} + +static void +moticam_set_gain(struct device *device, double gain) +{ + struct moticam_device *mdev = (struct moticam_device *) device; + mdev->gain = gain; + mdev->gain_pending = true; +} + +static void +moticam_set_resolution(struct device *device, int width, int height, + int stride) +{ + struct moticam_device *mdev = (struct moticam_device *) device; + g_assert(!mdev->current_image); + /* Ignore stride, store parameters. */ + mdev->width = width; + mdev->height = height; +} + +static void +moticam_transfer_cb(struct libusb_transfer *transfer) +{ + struct moticam_device *mdev = transfer->user_data; + mdev->usb_transfer_done = true; +} + +static bool +moticam_start(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + struct moticam_device *mdev = (struct moticam_device *) device; + g_assert(!mdev->current_image); + /* Configure camera. */ + if (!moticam_control_exposure(mdev, 30.0, error)) + return false; + if (!moticam_control_gain(mdev, mdev->gain, error)) + return false; + if (!moticam_control_resolution(mdev, mdev->width, mdev->height, error)) + return false; + if (!moticam_control_exposure(mdev, mdev->exposure_ms, error)) + return false; + mdev->exposure_pending = false; + mdev->gain_pending = false; + utils_delay_us(100000); + /* Prepare sizes and buffers, request an extra frame to read the zero + * length packet. */ + int image_size = mdev->width * mdev->height; + int frame_size = 16384; + mdev->rawbuffer_size = (image_size + frame_size) + / frame_size * frame_size; + for (int i = 0; i < 2; i++) { + struct image *image = &mdev->image[i]; + image->width = mdev->width; + image->height = mdev->height; + image->stride = mdev->width; + image->format = IMAGE_FORMAT_SGRBG8; + image->pixels = libusb_dev_mem_alloc(mdev->handle, + mdev->rawbuffer_size); + if (!image->pixels) + g_error("can not allocate raw buffer"); + g_assert(image->refs == 0); + image->release = NULL; + image->release_user_data = NULL; + } + mdev->current_image = &mdev->image[0]; + mdev->other_image = &mdev->image[1]; + /* Drop first frame. */ + mdev->drop = 1; + /* Prepare and submit transfer. */ + mdev->usb_transfer = libusb_alloc_transfer(0); + libusb_fill_bulk_transfer(mdev->usb_transfer, mdev->handle, 0x83, + mdev->current_image->pixels, mdev->rawbuffer_size, + moticam_transfer_cb, mdev, 30000); + int r = libusb_submit_transfer(mdev->usb_transfer); + if (r) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "can not submit transfer: %s", libusb_strerror(r)); + /* Cancel start. */ + moticam_stop(device, NULL); + return false; + } + return true; +} + +static struct image * +moticam_read(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + struct moticam_device *mdev = (struct moticam_device *) device; + g_assert(mdev->current_image); + if (mdev->usb_transfer_done) { + if (mdev->usb_transfer->status == LIBUSB_TRANSFER_COMPLETED) { + const int image_size = mdev->width * mdev->height; + struct image *image = NULL; + bool error_set = false; + /* Image received? Return it, else drop. */ + if (!mdev->drop + && mdev->usb_transfer->actual_length == image_size) { + image = mdev->current_image; + image->refs = 1; + mdev->current_image = mdev->other_image; + mdev->other_image = image; + } + if (mdev->drop) + mdev->drop--; + /* Pending updates? */ + if (image && !error_set && mdev->exposure_pending) { + if (!moticam_control_exposure(mdev, mdev->exposure_ms, error)) + error_set = true; + else + mdev->exposure_pending = false; + } + if (image && !error_set && mdev->gain_pending) { + if (!moticam_control_gain(mdev, mdev->gain, error)) + error_set = true; + else + mdev->gain_pending = false; + } + /* Start a new transfer. */ + if (!error_set) { + g_assert(mdev->current_image->refs == 0); + mdev->usb_transfer_done = false; + mdev->usb_transfer->buffer = mdev->current_image->pixels; + int r = libusb_submit_transfer(mdev->usb_transfer); + if (r) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "can not submit transfer: %s", libusb_strerror(r)); + error_set = true; + } + } + /* Return image or NULL. */ + if (error_set && image) { + image_unref(image); + image = NULL; + } + return image; + } else { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "transfer stopped"); + return NULL; + } + } else + return NULL; +} + +static bool +moticam_stop(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + struct moticam_device *mdev = (struct moticam_device *) device; + g_assert(mdev->current_image); + bool ret = true; + /* Cancel transfer if running. */ + if (!mdev->usb_transfer_done) { + int r = libusb_cancel_transfer(mdev->usb_transfer); + if (r && r != LIBUSB_ERROR_NOT_FOUND) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "can not cancel transfer: %s", libusb_strerror(r)); + ret = false; + } + while (!mdev->usb_transfer_done) { + int r = libusb_handle_events(mdev->usb); + if (r) { + g_error("unable to handle libusb events: %s", + libusb_strerror(r)); + } + } + } + /* Release. */ + libusb_free_transfer(mdev->usb_transfer); + mdev->usb_transfer = NULL; + for (int i = 0; i < 2; i++) { + libusb_dev_mem_free(mdev->handle, mdev->image[i].pixels, + mdev->rawbuffer_size); + mdev->image[i].pixels = NULL; + } + mdev->current_image = NULL; + mdev->other_image = NULL; + /* Stop camera. */ + for (int i = 0; ret && i < 3; i++) + ret = moticam_control_exposure(mdev, 0.0, error); + return ret; +} + +static bool +moticam_close(struct device *device, GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, false); + struct moticam_device *mdev = (struct moticam_device *) device; + bool ret = true; + if (mdev->current_image) + ret = moticam_stop(device, error); + libusb_close(mdev->handle); + free(device); + return ret; +} + +static char * +moticam_usb_poll(struct libusb_device_descriptor *desc) +{ + if (desc->idVendor == ID_VENDOR + && (desc->idProduct == ID_PRODUCT + || desc->idProduct == ID_PRODUCT_USB3)) + return g_strdup("Moticam 3+"); + else + return NULL; +} + +static struct device * +moticam_usb_open(libusb_context *usb, libusb_device *usb_device, + GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, NULL); + /* Create context. */ + struct moticam_device *mdev = g_new(struct moticam_device, 1); + mdev->device.get_info = &moticam_get_info; + mdev->device.set_exposure = &moticam_set_exposure; + mdev->device.set_gain = &moticam_set_gain; + mdev->device.set_resolution = &moticam_set_resolution; + mdev->device.start = &moticam_start; + mdev->device.read = &moticam_read; + mdev->device.stop = &moticam_stop; + mdev->device.close = &moticam_close; + mdev->usb = usb; + mdev->handle = NULL; + mdev->width = 0; + mdev->height = 0; + mdev->exposure_ms = moticam_device_info.exposure_default_ms; + mdev->exposure_pending = false; + mdev->gain = moticam_device_info.gain_default; + mdev->gain_pending = false; + mdev->rawbuffer_size = 0; + for (int i = 0; i < 2; i++) { + mdev->image[i].refs = 0; + } + mdev->current_image = NULL; + mdev->other_image = NULL; + mdev->usb_transfer = NULL; + mdev->usb_transfer_done = false; + mdev->drop = 1; + /* Open USB device. */ + int r = libusb_open(usb_device, &mdev->handle); + if (r) { + g_set_error(error, DEVICE_ERROR, DEVICE_ERROR_USB, + "can not open device: %s", libusb_strerror(r)); + g_free(mdev); + return NULL; + } + /* Reset camera. */ + if (!moticam_control_reset(mdev, error)) { + g_free(mdev); + return NULL; + } + /* Done. */ + return &mdev->device; +} diff --git a/src/moticam.h b/src/moticam.h new file mode 100644 index 0000000..b9eed99 --- /dev/null +++ b/src/moticam.h @@ -0,0 +1,31 @@ +#ifndef moticam_h +#define moticam_h +/* Camicro - Microscope camera viewer. + * + * This files add support for direct connexion with USB Moticam 3+. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include "device.h" + +extern const struct device_driver moticam_device_driver; + +#endif /* moticam_h */ diff --git a/src/options.c b/src/options.c new file mode 100644 index 0000000..cbdaf3c --- /dev/null +++ b/src/options.c @@ -0,0 +1,102 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#define _GNU_SOURCE +#include + +#include "options.h" +#include "cli.h" +#include "utils.h" + +void +options_add(GApplication *app) +{ + g_application_set_option_context_summary(app, "Microscope camera viewer"); + g_application_set_option_context_description(app, + "Without options, run the GUI, else switch to batch mode to dump" + " image from the camera."); + g_application_add_main_option(app, "width", 'w', 0, + G_OPTION_ARG_INT, "image width", "VALUE"); + g_application_add_main_option(app, "exposure", 'e', 0, + G_OPTION_ARG_DOUBLE, "exposure value", "MS"); + g_application_add_main_option(app, "gain", 'g', 0, + G_OPTION_ARG_DOUBLE, "gain value", "VALUE"); + g_application_add_main_option(app, "count", 'n', 0, + G_OPTION_ARG_INT, "number of image to take", "N"); + g_application_add_main_option(app, "raw", 'r', 0, + G_OPTION_ARG_NONE, "do not convert image", NULL); + g_application_add_main_option(app, "output", 'o', 0, + G_OPTION_ARG_FILENAME, + "output file pattern (default: out%02d.png)", "PATTERN"); +} + +gint +options_handle(GApplication *app, GVariantDict *options_dict) +{ + struct options options; + options.width = -1; + options.height = -1; + options.exposure_ms = -1.0; + options.gain = -1.0; + options.count = 1; + options.raw = false; + options.out = NULL; + bool option_set = false; + if (g_variant_dict_lookup(options_dict, "width", "i", &options.width)) { + option_set = true; + if (options.width <= 0) + utils_fatal("invalid width"); + } + if (g_variant_dict_lookup(options_dict, "exposure", "d", + &options.exposure_ms)) { + option_set = true; + if (options.exposure_ms < 1.0) + utils_fatal("invalid exposure"); + } + if (g_variant_dict_lookup(options_dict, "gain", "d", &options.gain)) { + option_set = true; + if (options.gain <= 0.0) + utils_fatal("invalid gain"); + } + if (g_variant_dict_lookup(options_dict, "count", "i", &options.count)) { + option_set = true; + if (options.count <= 0) + utils_fatal("invalid count"); + } + if (g_variant_dict_contains(options_dict, "raw")) { + option_set = true; + options.raw = true; + } + if (g_variant_dict_lookup(options_dict, "out", "^&ay", &options.out)) { + option_set = true; + int argtypes[1]; + int formats = parse_printf_format(options.out, 1, argtypes); + if (formats != 1 || argtypes[0] != PA_INT) + utils_fatal("bad file pattern, use one %%d"); + } else { + options.out = "out%02d.png"; + } + if (option_set) + return cli_run(&options); + else + return -1; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 0000000..7a08619 --- /dev/null +++ b/src/options.h @@ -0,0 +1,55 @@ +#ifndef options_h +#define options_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include + +/* Runtime options. */ +struct options { + /* Image width, -1 for default. */ + int width; + /* Image height, -1 for default. */ + int height; + /* Exposure in ms, -1.0 for default. */ + double exposure_ms; + /* Digital gain after image acquisition, -1.0 for default. */ + double gain; + /* Number of frame to dump. */ + int count; + /* Do not convert image. */ + bool raw; + /* Output file name or pattern. This should include a printf like pattern + * (%d) used to name the files. */ + const char *out; +}; + +/* Add options to application. */ +void +options_add(GApplication *app); + +/* Handle options. */ +gint +options_handle(GApplication *app, GVariantDict *options_dict); + +#endif /* options_h */ diff --git a/src/usb_source.c b/src/usb_source.c new file mode 100644 index 0000000..761d103 --- /dev/null +++ b/src/usb_source.c @@ -0,0 +1,136 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +#include "usb_source.h" + +struct fd_tag { + int fd; + gpointer tag; +}; + +struct usb_source { + GSource parent; + libusb_context *usb; + GArray *fd_tag; +}; + +static gboolean +usb_source_prepare(GSource *source, int *timeout) +{ + return FALSE; +} + +static gboolean +usb_source_check(GSource *source) +{ + GIOCondition revents = 0; + struct usb_source *usb_source = (struct usb_source *)source; + for (guint i = 0; i < usb_source->fd_tag->len; i++) { + struct fd_tag e = g_array_index(usb_source->fd_tag, struct fd_tag, i); + revents |= g_source_query_unix_fd(source, e.tag); + } + return revents != 0; +} + +static gboolean +usb_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + if (!callback) + return G_SOURCE_CONTINUE; + else + return callback(user_data); +} + +static void +usb_source_finalize(GSource *source) +{ + struct usb_source *usb_source = (struct usb_source *)source; + libusb_set_pollfd_notifiers(usb_source->usb, NULL, NULL, NULL); + for (guint i = 0; i < usb_source->fd_tag->len; i++) { + struct fd_tag e = g_array_index(usb_source->fd_tag, struct fd_tag, i); + g_source_remove_unix_fd(source, e.tag); + } + g_array_unref(usb_source->fd_tag); +} + +static void +usb_source_pollfd_added(int fd, short events, void *user_data) +{ + struct usb_source *usb_source = user_data; + if (!g_source_is_destroyed(&usb_source->parent)) { + gpointer tag = g_source_add_unix_fd(&usb_source->parent, fd, events); + struct fd_tag fd_tag = { fd, tag }; + g_array_append_val(usb_source->fd_tag, fd_tag); + } +} + +static void +usb_source_pollfd_removed(int fd, void *user_data) +{ + struct usb_source *usb_source = user_data; + if (!g_source_is_destroyed(&usb_source->parent)) { + bool found = false; + for (guint i = 0; !found && i < usb_source->fd_tag->len; i++) { + struct fd_tag e = + g_array_index(usb_source->fd_tag, struct fd_tag, i); + if (e.fd == fd) { + g_source_remove_unix_fd(&usb_source->parent, e.tag); + g_array_remove_index_fast(usb_source->fd_tag, i); + found = true; + } + } + g_assert(found); + } +} + +GSource * +usb_source_new(libusb_context *usb) +{ + /* Get file descriptors. */ + const struct libusb_pollfd **pollfds = libusb_get_pollfds(usb); + if (!pollfds) + g_error("unable to get libusb file descriptors"); + /* Create source. */ + static GSourceFuncs funcs = { + .prepare = usb_source_prepare, + .check = usb_source_check, + .dispatch = usb_source_dispatch, + .finalize = usb_source_finalize, + }; + GSource *source = g_source_new(&funcs, sizeof(struct usb_source)); + struct usb_source *usb_source = (struct usb_source *)source; + g_source_set_name(source, "usb"); + usb_source->usb = usb; + usb_source->fd_tag = g_array_new(FALSE, FALSE, sizeof(struct fd_tag)); + g_assert(libusb_pollfds_handle_timeouts(usb)); + /* Add existing file descriptors. */ + for (const struct libusb_pollfd **pollfd = pollfds; *pollfd; pollfd++) + usb_source_pollfd_added((*pollfd)->fd, (*pollfd)->events, usb_source); + libusb_free_pollfds(pollfds); + /* Connect notifiers. */ + libusb_set_pollfd_notifiers(usb, usb_source_pollfd_added, + usb_source_pollfd_removed, usb_source); + /* Done. */ + return &usb_source->parent; +} diff --git a/src/usb_source.h b/src/usb_source.h new file mode 100644 index 0000000..0b76ce8 --- /dev/null +++ b/src/usb_source.h @@ -0,0 +1,31 @@ +#ifndef usb_source_h +#define usb_source_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include +#include + +GSource * +usb_source_new(libusb_context *usb); + +#endif /* usb_source_h */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..ddd044f --- /dev/null +++ b/src/utils.c @@ -0,0 +1,80 @@ +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "utils.h" + +void +utils_fatal(const char *fmt, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", program_invocation_short_name); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + exit(EXIT_FAILURE); +} + +void +utils_info(const char *fmt, ...) +{ + va_list ap; + fprintf(stderr, "%s: ", program_invocation_short_name); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} + +void +utils_delay_us(int us) +{ + struct timespec delay, remaining; + delay.tv_sec = us / 1000000; + delay.tv_nsec = us % 1000000 * 1000; + while (nanosleep(&delay, &remaining) == -1) { + if (errno == EINTR) + delay = remaining; + else + g_error("can not sleep: %m"); + } +} + +void +utils_dialog_error(GtkWindow *parent, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char *msg = g_strdup_vprintf(fmt, ap); + va_end(ap); + GtkWidget *dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", msg); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(msg); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..ef495e5 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,46 @@ +#ifndef utils_h +#define utils_h +/* Camicro - Microscope camera viewer. + * + * Copyright (C) 2019 Nicolas Schodet + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact : + * Web: http://ni.fr.eu.org/ + * Email: + */ +#include + +/* Report a fatal error. */ +void +utils_fatal(const char *fmt, ...) + __attribute__((format(printf, 1, 2), noreturn)); + +/* Report an info. */ +void +utils_info(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + +/* Sleep for a given number of microseconds. */ +void +utils_delay_us(int us); + +/* Display an error message. */ +void +utils_dialog_error(GtkWindow *parent, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +#endif /* utils_h */ diff --git a/usb_source.c b/usb_source.c deleted file mode 100644 index 761d103..0000000 --- a/usb_source.c +++ /dev/null @@ -1,136 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -#include "usb_source.h" - -struct fd_tag { - int fd; - gpointer tag; -}; - -struct usb_source { - GSource parent; - libusb_context *usb; - GArray *fd_tag; -}; - -static gboolean -usb_source_prepare(GSource *source, int *timeout) -{ - return FALSE; -} - -static gboolean -usb_source_check(GSource *source) -{ - GIOCondition revents = 0; - struct usb_source *usb_source = (struct usb_source *)source; - for (guint i = 0; i < usb_source->fd_tag->len; i++) { - struct fd_tag e = g_array_index(usb_source->fd_tag, struct fd_tag, i); - revents |= g_source_query_unix_fd(source, e.tag); - } - return revents != 0; -} - -static gboolean -usb_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) -{ - if (!callback) - return G_SOURCE_CONTINUE; - else - return callback(user_data); -} - -static void -usb_source_finalize(GSource *source) -{ - struct usb_source *usb_source = (struct usb_source *)source; - libusb_set_pollfd_notifiers(usb_source->usb, NULL, NULL, NULL); - for (guint i = 0; i < usb_source->fd_tag->len; i++) { - struct fd_tag e = g_array_index(usb_source->fd_tag, struct fd_tag, i); - g_source_remove_unix_fd(source, e.tag); - } - g_array_unref(usb_source->fd_tag); -} - -static void -usb_source_pollfd_added(int fd, short events, void *user_data) -{ - struct usb_source *usb_source = user_data; - if (!g_source_is_destroyed(&usb_source->parent)) { - gpointer tag = g_source_add_unix_fd(&usb_source->parent, fd, events); - struct fd_tag fd_tag = { fd, tag }; - g_array_append_val(usb_source->fd_tag, fd_tag); - } -} - -static void -usb_source_pollfd_removed(int fd, void *user_data) -{ - struct usb_source *usb_source = user_data; - if (!g_source_is_destroyed(&usb_source->parent)) { - bool found = false; - for (guint i = 0; !found && i < usb_source->fd_tag->len; i++) { - struct fd_tag e = - g_array_index(usb_source->fd_tag, struct fd_tag, i); - if (e.fd == fd) { - g_source_remove_unix_fd(&usb_source->parent, e.tag); - g_array_remove_index_fast(usb_source->fd_tag, i); - found = true; - } - } - g_assert(found); - } -} - -GSource * -usb_source_new(libusb_context *usb) -{ - /* Get file descriptors. */ - const struct libusb_pollfd **pollfds = libusb_get_pollfds(usb); - if (!pollfds) - g_error("unable to get libusb file descriptors"); - /* Create source. */ - static GSourceFuncs funcs = { - .prepare = usb_source_prepare, - .check = usb_source_check, - .dispatch = usb_source_dispatch, - .finalize = usb_source_finalize, - }; - GSource *source = g_source_new(&funcs, sizeof(struct usb_source)); - struct usb_source *usb_source = (struct usb_source *)source; - g_source_set_name(source, "usb"); - usb_source->usb = usb; - usb_source->fd_tag = g_array_new(FALSE, FALSE, sizeof(struct fd_tag)); - g_assert(libusb_pollfds_handle_timeouts(usb)); - /* Add existing file descriptors. */ - for (const struct libusb_pollfd **pollfd = pollfds; *pollfd; pollfd++) - usb_source_pollfd_added((*pollfd)->fd, (*pollfd)->events, usb_source); - libusb_free_pollfds(pollfds); - /* Connect notifiers. */ - libusb_set_pollfd_notifiers(usb, usb_source_pollfd_added, - usb_source_pollfd_removed, usb_source); - /* Done. */ - return &usb_source->parent; -} diff --git a/usb_source.h b/usb_source.h deleted file mode 100644 index 0b76ce8..0000000 --- a/usb_source.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef usb_source_h -#define usb_source_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include -#include - -GSource * -usb_source_new(libusb_context *usb); - -#endif /* usb_source_h */ diff --git a/utils.c b/utils.c deleted file mode 100644 index ddd044f..0000000 --- a/utils.c +++ /dev/null @@ -1,80 +0,0 @@ -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#define _GNU_SOURCE -#include -#include -#include -#include - -#include "utils.h" - -void -utils_fatal(const char *fmt, ...) -{ - va_list ap; - fprintf(stderr, "%s: ", program_invocation_short_name); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fputc('\n', stderr); - exit(EXIT_FAILURE); -} - -void -utils_info(const char *fmt, ...) -{ - va_list ap; - fprintf(stderr, "%s: ", program_invocation_short_name); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fputc('\n', stderr); -} - -void -utils_delay_us(int us) -{ - struct timespec delay, remaining; - delay.tv_sec = us / 1000000; - delay.tv_nsec = us % 1000000 * 1000; - while (nanosleep(&delay, &remaining) == -1) { - if (errno == EINTR) - delay = remaining; - else - g_error("can not sleep: %m"); - } -} - -void -utils_dialog_error(GtkWindow *parent, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - char *msg = g_strdup_vprintf(fmt, ap); - va_end(ap); - GtkWidget *dialog = gtk_message_dialog_new(parent, GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", msg); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_free(msg); -} diff --git a/utils.h b/utils.h deleted file mode 100644 index ef495e5..0000000 --- a/utils.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef utils_h -#define utils_h -/* Camicro - Microscope camera viewer. - * - * Copyright (C) 2019 Nicolas Schodet - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * Contact : - * Web: http://ni.fr.eu.org/ - * Email: - */ -#include - -/* Report a fatal error. */ -void -utils_fatal(const char *fmt, ...) - __attribute__((format(printf, 1, 2), noreturn)); - -/* Report an info. */ -void -utils_info(const char *fmt, ...) - __attribute__((format(printf, 1, 2))); - -/* Sleep for a given number of microseconds. */ -void -utils_delay_us(int us); - -/* Display an error message. */ -void -utils_dialog_error(GtkWindow *parent, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); - -#endif /* utils_h */ diff --git a/window.ui b/window.ui deleted file mode 100644 index 9eefa36..0000000 --- a/window.ui +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - 1 - 1000 - 100 - 25 - 100 - - - - 10 - 1 - 0.25 - 1 - - - - -- cgit v1.2.3