summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Schodet2019-09-27 23:18:00 +0200
committerNicolas Schodet2019-11-14 00:41:59 +0100
commit51675360e87297cbf4423a1b12ae0c6a06a92158 (patch)
tree0524d9cd11a89218434f6939bb86a365fa01c644
parent7d16e5a24b597fe4982a0583339e869ec6e91544 (diff)
Now camicro, the microscope camera viewer
-rw-r--r--.gitignore4
-rw-r--r--Makefile16
-rw-r--r--device.c104
-rw-r--r--device.h115
-rw-r--r--image.c106
-rw-r--r--image.h30
-rw-r--r--main.c144
-rw-r--r--moticam.c595
-rw-r--r--moticam.h31
-rw-r--r--options.c150
-rw-r--r--options.h51
-rw-r--r--utils.c88
-rw-r--r--utils.h49
13 files changed, 1022 insertions, 461 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24bf1cb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.o
+camicro
+out*.png
+out
diff --git a/Makefile b/Makefile
index 7f448ff..05776d6 100644
--- a/Makefile
+++ b/Makefile
@@ -2,4 +2,18 @@ libs := libusb-1.0 libpng16 sdl2
CFLAGS := -g -Wall $(shell pkg-config $(libs) --cflags)
LDLIBS := $(shell pkg-config $(libs) --libs)
-all: moticam
+SOURCES := \
+ device.c \
+ image.c \
+ main.c \
+ moticam.c \
+ options.c \
+ utils.c
+
+all: camicro
+
+camicro: $(SOURCES:%.c=%.o)
+ $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
+
+clean:
+ rm -f camicro $(SOURCES:%.c=%.o)
diff --git a/device.c b/device.c
new file mode 100644
index 0000000..2168d01
--- /dev/null
+++ b/device.c
@@ -0,0 +1,104 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#include <stdlib.h>
+
+#include "device.h"
+#include "utils.h"
+
+#include "moticam.h"
+
+static const struct device_driver *drivers[] = {
+ &moticam_device_driver,
+ NULL
+};
+
+struct device *
+device_open(libusb_context *usb, struct options *options)
+{
+ 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)
+ utils_fatal("can not list devices: %s", libusb_strerror(cnt));
+ for (ssize_t i = 0; 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)
+ utils_fatal("can not get device descriptor: %s",
+ libusb_strerror(r));
+ for (const struct device_driver **dd = drivers; *dd; dd++) {
+ char *device_name = (*dd)->usb_poll(&desc);
+ if (device_name) {
+ if (found_device_name)
+ utils_fatal("more than one matching device, not"
+ " supported");
+ else
+ {
+ found_usb_device = usb_device;
+ found_device_name = device_name;
+ found_device_driver = *dd;
+ }
+ }
+ }
+ }
+ struct device *device = NULL;
+ if (found_usb_device) {
+ free(found_device_name);
+ device = found_device_driver->usb_open(found_usb_device, options);
+ }
+ libusb_free_device_list(list, 1);
+ return device;
+}
+
+const uint8_t *
+device_read_raw(struct device *device, size_t *size)
+{
+ return device->read_raw(device, size);
+}
+
+const uint32_t *
+device_read(struct device *device)
+{
+ return device->read(device);
+}
+
+void
+device_set_exposure(struct device *device, double exposure_ms)
+{
+ return device->set_exposure(device, exposure_ms);
+}
+
+void
+device_set_gain(struct device *device, double gain)
+{
+ return device->set_gain(device, gain);
+}
+
+void
+device_close(struct device *device)
+{
+ return device->close(device);
+}
diff --git a/device.h b/device.h
new file mode 100644
index 0000000..72b2b0a
--- /dev/null
+++ b/device.h
@@ -0,0 +1,115 @@
+#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: <nico at ni.fr.eu.org>
+ */
+#include <libusb.h>
+#include <stdint.h>
+
+#include "options.h"
+
+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_device *usb_device,
+ struct options *options);
+
+/* 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;
+};
+
+/* Return raw data if available, else, return NULL. */
+typedef const uint8_t *(*device_read_raw_f)(
+ struct device *device, size_t *size);
+
+/* Return an image if one is available, else, return NULL. */
+typedef const uint32_t *(*device_read_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);
+
+/* Close device. */
+typedef void (*device_close_f)(
+ struct device *device);
+
+/* Open device. */
+struct device
+{
+ /* Read raw data. */
+ device_read_raw_f read_raw;
+ /* Read an image. */
+ device_read_f read;
+ /* Change exposure. */
+ device_set_exposure_f set_exposure;
+ /* Change gain. */
+ device_set_gain_f set_gain;
+ /* Close device. */
+ device_close_f close;
+};
+
+/* Find and open device. */
+struct device *
+device_open(libusb_context *usb, struct options *options);
+
+/* Read raw data. */
+const uint8_t *
+device_read_raw(struct device *device, size_t *size);
+
+/* Read an image. */
+const uint32_t *
+device_read(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);
+
+/* Close device. */
+void
+device_close(struct device *device);
+
+#endif /* device_h */
diff --git a/image.c b/image.c
new file mode 100644
index 0000000..0fb684d
--- /dev/null
+++ b/image.c
@@ -0,0 +1,106 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#include "image.h"
+
+#include <string.h>
+
+void
+image_bayer2argb(const uint8_t *bayer, uint32_t *rgb, int width, int height)
+{
+ /*
+ * 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
+ */
+ const int in_stride = width;
+ const int out_stride = width * 4;
+ /* Skip first line and column. */
+ const uint8_t *in = bayer + in_stride + 1;
+ uint8_t *out = (uint8_t *) rgb + 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);
+}
diff --git a/image.h b/image.h
new file mode 100644
index 0000000..5ad222d
--- /dev/null
+++ b/image.h
@@ -0,0 +1,30 @@
+#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: <nico at ni.fr.eu.org>
+ */
+#include <stdint.h>
+
+void
+image_bayer2argb(const uint8_t *bayer, uint32_t *rgb, int width, int height);
+
+#endif /* image_h */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..7c0e5b5
--- /dev/null
+++ b/main.c
@@ -0,0 +1,144 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <png.h>
+#include <SDL.h>
+
+#include "options.h"
+#include "device.h"
+#include "utils.h"
+
+void
+run(struct device *device, struct options *options)
+{
+ if (options->raw) {
+ FILE *out = fopen(options->out, "wb");
+ if (!out)
+ utils_fatal("can not open output file `%s': %m", options->out);
+ for (int i = 0; i < options->count;) {
+ size_t data_size;
+ const uint8_t *data = device_read_raw(device, &data_size);
+ if (data) {
+ utils_info("write %d (%zu)", i, data_size);
+ int r = fwrite(data, data_size, 1, out);
+ if (r < 0)
+ utils_fatal("can not write: %m");
+ i++;
+ }
+ }
+ fclose(out);
+ } else {
+ for (int i = 0; i < options->count;) {
+ const uint32_t *pixels = device_read(device);
+ if (pixels) {
+ char *name = NULL;
+ if (asprintf(&name, options->out, i) < 0)
+ utils_fatal("can not prepare file name");
+ utils_info("write %s", name);
+ png_image image;
+ memset(&image, 0, sizeof(image));
+ image.version = PNG_IMAGE_VERSION;
+ image.width = options->width;
+ image.height = options->height;
+ image.format = PNG_FORMAT_BGRA;
+ int r = png_image_write_to_file(&image, name, 0, pixels, 0,
+ NULL);
+ if (r == 0)
+ utils_fatal("can not write image: %s", image.message);
+ free(name);
+ i++;
+ }
+ }
+ }
+}
+
+void
+run_video(struct device *device, struct options *options)
+{
+ if (SDL_Init(SDL_INIT_VIDEO))
+ utils_fatal("unable to initialize SDL: %s", SDL_GetError());
+ atexit(SDL_Quit);
+ SDL_DisableScreenSaver();
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ if (SDL_CreateWindowAndRenderer(options->width, options->height,
+ SDL_WINDOW_RESIZABLE, &window, &renderer))
+ utils_fatal("unable to create window: %s", SDL_GetError());
+ SDL_SetWindowTitle(window, "Moticam");
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
+ if (SDL_RenderSetLogicalSize(renderer, options->width, options->height))
+ utils_fatal("can not set logical size: %s", SDL_GetError());
+ SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA32,
+ SDL_TEXTUREACCESS_STREAMING, options->width, options->height);
+ if (!texture)
+ utils_fatal("can not create texture: %s", SDL_GetError());
+ bool exit = false;
+ while (1) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_QUIT)
+ exit = true;
+ if (event.type == SDL_KEYDOWN
+ && (event.key.keysym.sym == SDLK_q
+ || event.key.keysym.sym == SDLK_ESCAPE))
+ exit = true;
+ }
+ if (exit)
+ break;
+ const uint32_t *pixels = device_read(device);
+ if (pixels)
+ {
+ SDL_UpdateTexture(texture, NULL, pixels, options->width * 4);
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+ SDL_RenderClear(renderer);
+ SDL_RenderCopyEx(renderer, texture, NULL, NULL, 180.0, NULL,
+ SDL_FLIP_NONE);
+ SDL_RenderPresent(renderer);
+ }
+ }
+ SDL_DestroyTexture(texture);
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct options options;
+ options_parse(argc, argv, &options);
+ libusb_context *usb;
+ int r = libusb_init(&usb);
+ if (r)
+ utils_fatal("unable to initialize libusb: %s", libusb_strerror(r));
+ struct device *device = device_open(usb, &options);
+ if (!device)
+ utils_fatal("unable to find device");
+ if (options.count)
+ run(device, &options);
+ else
+ run_video(device, &options);
+ device_close(device);
+ libusb_exit(usb);
+ return EXIT_SUCCESS;
+}
diff --git a/moticam.c b/moticam.c
index 431d72a..2e46144 100644
--- a/moticam.c
+++ b/moticam.c
@@ -1,4 +1,6 @@
-/* Moticam 3+ viewer.
+/* Camicro - Microscope camera viewer.
+ *
+ * This files add support for direct connexion with USB Moticam 3+.
*
* Copyright (C) 2019 Nicolas Schodet
*
@@ -20,217 +22,78 @@
* Web: http://ni.fr.eu.org/
* Email: <nico at ni.fr.eu.org>
*/
-#define _GNU_SOURCE
#include <assert.h>
-#include <libusb.h>
-#include <stdio.h>
#include <stdlib.h>
-#include <errno.h>
-#include <error.h>
-#include <getopt.h>
-#include <time.h>
#include <string.h>
-#include <stdbool.h>
-#include <printf.h>
-#include <png.h>
-#include <SDL.h>
+#include "device.h"
+#include "image.h"
+#include "utils.h"
#define ID_VENDOR 0x232f
#define ID_PRODUCT 0x0100
-struct options {
+struct moticam_device {
+ struct device device;
+ libusb_device_handle *handle;
int width;
int height;
- double exposure;
- double gain;
- int count;
bool raw;
- const char *out;
+ size_t rawbuffer_size;
+ uint8_t *rawbuffer;
+ uint32_t *buffer;
};
-void
-usage(int status, const char *msg)
-{
- if (msg)
- error(0, 0, "%s", msg);
- fprintf(status == EXIT_SUCCESS ? stdout : stderr,
- "usage: %s [options] [FILE]\n"
- "\n"
- "Moticam 3+ viewer.\n"
- "\n"
- "positional arguments:\n"
- " FILE output file pattern (default: out%%02d.png"
- " or out for raw output)\n"
- "\n"
- "optional arguments:\n"
- " -h, --help show this help message and exit\n"
- " -w, --width VALUE image width (512, 1024 or 2048,"
- " default: 1024)\n"
- " -e, --exposure MS exposure value (1 to 5000,"
- " default: 100)\n"
- " -g, --gain VALUE gain value (0.33 to 42.66,"
- " default: 1)\n"
- " -n, --count N number of image to take"
- " (default: live video)\n"
- " -r, --raw save raw images\n"
- , program_invocation_name);
- exit(status);
-}
-
-void
-parse_options(int argc, char **argv, struct options *options)
-{
- options->width = 1024;
- options->height = 768;
- options->exposure = 100.0;
- options->gain = 1.0;
- options->count = 0;
- options->raw = false;
- options->out = NULL;
- char *tail;
- while (1) {
- static struct option long_options[] = {
- { "help", no_argument, 0, 'h' },
- { "width", required_argument, 0, 'w' },
- { "exposure", required_argument, 0, 'e' },
- { "gain", required_argument, 0, 'g' },
- { "count", required_argument, 0, 'n' },
- { "raw", required_argument, 0, 'r' },
- { NULL },
- };
- int option_index = 0;
- int c = getopt_long(argc, argv, "hw:e:g:n:r", long_options,
- &option_index);
- if (c == -1)
- break;
- switch (c) {
- case 'h':
- usage(EXIT_SUCCESS, NULL);
- break;
- case 'w':
- if (strcmp(optarg, "512") == 0) {
- options->width = 512;
- options->height = 384;
- } else if (strcmp(optarg, "1024") == 0) {
- options->width = 1024;
- options->height = 768;
- } else if (strcmp(optarg, "2048") == 0) {
- options->width = 2048;
- options->height = 1536;
- } else
- usage(EXIT_FAILURE, "bad width value");
- break;
- case 'e':
- errno = 0;
- options->exposure = strtod(optarg, &tail);
- if (*tail != '\0' || errno || options->exposure < 1
- || options->exposure > 5000)
- usage(EXIT_FAILURE, "bad exposure value");
- break;
- case 'g':
- errno = 0;
- options->gain = strtod(optarg, &tail);
- if (*tail != '\0' || errno || options->gain <= 0.0
- || options->gain >= 43.0)
- usage(EXIT_FAILURE, "bad gain value");
- break;
- case 'n':
- errno = 0;
- options->count = strtoul(optarg, &tail, 10);
- if (*tail != '\0' || errno)
- usage(EXIT_FAILURE, "bad count value");
- break;
- case 'r':
- options->raw = true;
- break;
- case '?':
- usage(EXIT_FAILURE, NULL);
- break;
- default:
- abort();
- }
- }
- if (optind < argc)
- options->out = argv[optind++];
- if (optind < argc)
- usage(EXIT_FAILURE, "too many arguments");
- if (!options->out)
- options->out = options->raw ? "out" : "out%02d.png";
- if (!options->raw)
- {
- int argtypes[1];
- int formats = parse_printf_format(options->out, 1, argtypes);
- if (formats != 1 || argtypes[0] != PA_INT)
- usage(EXIT_FAILURE, "bad file pattern, use one %d");
- }
-}
+static char *
+moticam_usb_poll(struct libusb_device_descriptor *desc);
+static struct device *
+moticam_usb_open(libusb_device *usb_device, struct options *options);
-libusb_device_handle *
-device_open()
-{
- libusb_device **list;
- libusb_device *found = NULL;
- int nfound = 0;
- ssize_t cnt = libusb_get_device_list(NULL, &list);
- ssize_t i = 0;
- if (cnt < 0)
- error(EXIT_FAILURE, 0, "can not list devices: %s",
- libusb_strerror(cnt));
- for (i = 0; i < cnt; i++) {
- libusb_device *device = list[i];
- struct libusb_device_descriptor desc;
- int r = libusb_get_device_descriptor(device, &desc);
- if (r)
- error(EXIT_FAILURE, 0, "can not get device descriptor: %s",
- libusb_strerror(r));
- if (desc.idVendor == ID_VENDOR && desc.idProduct == ID_PRODUCT) {
- found = device;
- nfound++;
- break;
- }
- }
- if (nfound > 1)
- error(EXIT_FAILURE, 0,
- "more than one matching device, not supported");
- libusb_device_handle *handle = NULL;
- if (found) {
- int r = libusb_open(found, &handle);
- if (r)
- error(EXIT_FAILURE, 0, "can not open device: %s",
- libusb_strerror(r));
- }
- libusb_free_device_list(list, 1);
- return handle;
-}
+const struct device_driver moticam_device_driver = {
+ "Moticam USB cameras",
+ &moticam_usb_poll,
+ &moticam_usb_open,
+};
-void
-device_control_vendor(libusb_device_handle *handle, uint16_t wValue,
+static void
+moticam_control_vendor(struct device *device, uint16_t wValue,
const uint8_t *data, int data_cnt)
{
- int r = libusb_control_transfer(handle, LIBUSB_REQUEST_TYPE_VENDOR, 240,
- wValue, 0, (uint8_t *) data, data_cnt, 0);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ int r = libusb_control_transfer(mdev->handle, LIBUSB_REQUEST_TYPE_VENDOR,
+ 240, wValue, 0, (uint8_t *) data, data_cnt, 0);
if (r < 0)
- error(EXIT_FAILURE, 0, "can not send vendor: %s", libusb_strerror(r));
+ utils_fatal("can not send vendor control: %s", libusb_strerror(r));
}
-void
-device_control_vendor_w(libusb_device_handle *handle, uint16_t wValue,
+static void
+moticam_control_vendor_w(struct device *device, uint16_t wValue,
const uint16_t data0)
{
uint8_t data[] = { (uint8_t) (data0 >> 8), (uint8_t) data0 };
- device_control_vendor(handle, wValue, data, sizeof(data));
+ moticam_control_vendor(device, wValue, data, sizeof(data));
}
-void
-device_reset(libusb_device_handle *handle)
+static void
+moticam_reset(struct device *device)
{
- device_control_vendor_w(handle, 0xba00, 0x0000);
- device_control_vendor_w(handle, 0xba00, 0x0001);
+ moticam_control_vendor_w(device, 0xba00, 0x0000);
+ moticam_control_vendor_w(device, 0xba00, 0x0001);
}
-void
-device_set_gain(libusb_device_handle *handle, double gain)
+static void
+moticam_set_exposure(struct device *device, double exposure_ms)
+{
+ int exposure_w = exposure_ms * 12.82;
+ if (exposure_w < 0x000c)
+ exposure_w = 0x000c;
+ else if (exposure_w > 0xffff)
+ exposure_w = 0xffff;
+ moticam_control_vendor_w(device, 0xba09, exposure_w);
+}
+
+static void
+moticam_set_gain(struct device *device, double gain)
{
double gmin, gmax;
int xmin, xmax;
@@ -259,29 +122,18 @@ device_set_gain(libusb_device_handle *handle, double gain)
x = xmax;
if (up)
x = (x << 8) | 0x60;
- device_control_vendor_w(handle, 0xba2d, x);
- device_control_vendor_w(handle, 0xba2b, x);
- device_control_vendor_w(handle, 0xba2e, x);
- device_control_vendor_w(handle, 0xba2c, x);
-}
-
-void
-device_set_exposure(libusb_device_handle *handle, double exposure)
-{
- int exposure_w = exposure * 12.82;
- if (exposure_w < 0x000c)
- exposure_w = 0x000c;
- else if (exposure_w > 0xffff)
- exposure_w = 0xffff;
- device_control_vendor_w(handle, 0xba09, exposure_w);
+ moticam_control_vendor_w(device, 0xba2d, x);
+ moticam_control_vendor_w(device, 0xba2b, x);
+ moticam_control_vendor_w(device, 0xba2e, x);
+ moticam_control_vendor_w(device, 0xba2c, x);
}
-void
-device_set_resolution(libusb_device_handle *handle, int width, int height)
+static void
+moticam_set_resolution(struct device *device, int width, int height)
{
static const uint8_t control_init[] = {
0x00, 0x14, 0x00, 0x20, 0x05, 0xff, 0x07, 0xff };
- device_control_vendor(handle, 0xba01, control_init,
+ moticam_control_vendor(device, 0xba01, control_init,
sizeof(control_init));
static const uint8_t control_512x384[] = { 0x00, 0x03, 0x00, 0x03 };
static const uint8_t control_1024x768[] = { 0x00, 0x11, 0x00, 0x11 };
@@ -290,17 +142,17 @@ device_set_resolution(libusb_device_handle *handle, int width, int height)
{
case 512:
assert(height == 384);
- device_control_vendor(handle, 0xba22, control_512x384,
+ moticam_control_vendor(device, 0xba22, control_512x384,
sizeof(control_512x384));
break;
case 1024:
assert(height == 768);
- device_control_vendor(handle, 0xba22, control_1024x768,
+ moticam_control_vendor(device, 0xba22, control_1024x768,
sizeof(control_1024x768));
break;
case 2048:
assert(height == 1536);
- device_control_vendor(handle, 0xba22, control_2048x1536,
+ moticam_control_vendor(device, 0xba22, control_2048x1536,
sizeof(control_2048x1536));
break;
default:
@@ -308,273 +160,96 @@ device_set_resolution(libusb_device_handle *handle, int width, int height)
}
}
-void
-delay_us(int us)
+static const uint8_t *
+moticam_read_raw(struct device *device, size_t *size)
{
- 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
- error(EXIT_FAILURE, errno, "can not sleep");
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ int image_size = mdev->width * mdev->height;
+ int transfered = 0;
+ int r = libusb_bulk_transfer(mdev->handle, 0x83, mdev->rawbuffer,
+ mdev->rawbuffer_size, &transfered, 0);
+ if (r)
+ utils_fatal("can not read data: %s", libusb_strerror(r));
+ if (transfered != image_size) {
+ utils_warning("bad image size (%d), drop", transfered);
+ return NULL;
+ } else {
+ *size = mdev->rawbuffer_size;
+ return mdev->rawbuffer;
}
}
-void
-device_init(libusb_device_handle *handle, struct options *options)
-{
- device_reset(handle);
- device_set_gain(handle, options->gain);
- device_set_exposure(handle, 30.0);
- device_set_resolution(handle, options->width, options->height);
- device_set_exposure(handle, options->exposure);
- delay_us(100000);
-}
-
-void
-device_uninit(libusb_device_handle *handle)
+static const uint32_t *
+moticam_read(struct device *device)
{
- device_set_exposure(handle, 0.0);
- device_set_exposure(handle, 0.0);
- device_set_exposure(handle, 0.0);
-}
-
-void
-bayer2argb(uint8_t *bayer, uint8_t *rgb, int width, int height)
-{
- /*
- * 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
- */
- const int in_stride = width;
- const int out_stride = width * 4;
- /* Skip first line and column. */
- uint8_t *in = bayer + in_stride + 1;
- uint8_t *out = rgb + out_stride + 4;
- /* Loop over lines. */
- for (int i = 1; i < height - 1; i += 2) {
- /* Even lines. */
- 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);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ size_t rawbuffer_size;
+ const uint8_t *rawbuffer = moticam_read_raw(device, &rawbuffer_size);
+ if (rawbuffer) {
+ image_bayer2argb(rawbuffer, mdev->buffer, mdev->width,
+ mdev->height);
+ return mdev->buffer;
+ } else {
+ return NULL;
}
- /* 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
-run(libusb_device_handle *handle, struct options *options)
+static void
+moticam_close(struct device *device)
{
- int image_size = options->width * options->height;
- int frame_size = 16384;
- // Request an extra frame to read the zero length packet.
- int data_size = (image_size + frame_size) / frame_size * frame_size;
- uint8_t *data = malloc(data_size);
- if (!data)
- error(EXIT_FAILURE, 0, "memory exhausted");
- FILE *out = NULL;
- uint8_t *rgb = NULL;
- if (options->raw) {
- out = fopen(options->out, "wb");
- if (!out)
- error(EXIT_FAILURE, errno, "can not open output file `%s'",
- options->out);
- }
- for (int i = 0; i < options->count;) {
- int transfered = 0;
- int r = libusb_bulk_transfer(handle, 0x83, data, data_size,
- &transfered, 0);
- if (r)
- error(EXIT_FAILURE, 0, "can not read data: %s",
- libusb_strerror(r));
- if (transfered != image_size)
- fprintf(stderr, "bad image size (%d), drop\n", transfered);
- else {
- if (options->raw) {
- fprintf(stderr, "write %d (%d)\n", i, transfered);
- r = fwrite(data, transfered, 1, out);
- if (r < 0)
- error(EXIT_FAILURE, errno, "can not write");
- } else {
- char *name = NULL;
- if (asprintf(&name, options->out, i) < 0)
- error(EXIT_FAILURE, 0, "can not prepare file name");
- fprintf(stderr, "write %s\n", name);
- if (!rgb) {
- rgb = malloc(image_size * 4);
- if (!rgb)
- error(EXIT_FAILURE, 0, "memory exhausted");
- }
- bayer2argb(data, rgb, options->width, options->height);
- png_image image;
- memset(&image, 0, sizeof(image));
- image.version = PNG_IMAGE_VERSION;
- image.width = options->width;
- image.height = options->height;
- image.format = PNG_FORMAT_BGRA;
- r = png_image_write_to_file(&image, name, 0, rgb, 0, NULL);
- if (r == 0)
- error(EXIT_FAILURE, 0, "can not write image: %s",
- image.message);
- }
- i++;
- }
- }
- free(data);
- if (out)
- fclose(out);
- if (rgb)
- free(rgb);
+ struct moticam_device *mdev = (struct moticam_device *) device;
+ moticam_set_exposure(device, 0.0);
+ moticam_set_exposure(device, 0.0);
+ moticam_set_exposure(device, 0.0);
+ libusb_close(mdev->handle);
+ free(mdev->rawbuffer);
+ if (mdev->buffer)
+ free(mdev->buffer);
+ free(device);
}
-void
-run_video(libusb_device_handle *handle, struct options *options)
+static char *
+moticam_usb_poll(struct libusb_device_descriptor *desc)
{
- if (SDL_Init(SDL_INIT_VIDEO))
- error(EXIT_FAILURE, 0, "unable to initialize SDL: %s",
- SDL_GetError());
- atexit(SDL_Quit);
- SDL_DisableScreenSaver();
- SDL_Window *window;
- SDL_Renderer *renderer;
- if (SDL_CreateWindowAndRenderer(options->width, options->height,
- SDL_WINDOW_RESIZABLE, &window, &renderer))
- error(EXIT_FAILURE, 0, "unable to create window: %s", SDL_GetError());
- SDL_SetWindowTitle(window, "Moticam");
- SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
- if (SDL_RenderSetLogicalSize(renderer, options->width, options->height))
- error(EXIT_FAILURE, 0, "can not set logical size: %s", SDL_GetError());
- SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_BGRA32,
- SDL_TEXTUREACCESS_STREAMING, options->width, options->height);
- if (!texture)
- error(EXIT_FAILURE, 0, "can not create texture: %s", SDL_GetError());
- int image_size = options->width * options->height;
+ if (desc->idVendor == ID_VENDOR && desc->idProduct == ID_PRODUCT)
+ return strdup("Moticam 3+");
+ else
+ return NULL;
+}
+
+static struct device *
+moticam_usb_open(libusb_device *usb_device, struct options *options)
+{
+ /* Create context. */
+ struct moticam_device *mdev =
+ utils_malloc(sizeof(struct moticam_device));
+ mdev->device.read_raw = &moticam_read_raw;
+ mdev->device.read = &moticam_read;
+ mdev->device.set_exposure = &moticam_set_exposure;
+ mdev->device.set_gain = &moticam_set_gain;
+ mdev->device.close = &moticam_close;
+ mdev->handle = NULL;
+ mdev->width = options->width;
+ mdev->height = options->height;
+ mdev->raw = options->raw;
+ int image_size = mdev->width * mdev->height;
int frame_size = 16384;
- int data_size = (image_size + frame_size) / frame_size * frame_size;
- uint8_t *data = malloc(data_size);
- if (!data)
- error(EXIT_FAILURE, 0, "memory exhausted");
- uint8_t *rgb = malloc(image_size * 4);
- if (!rgb)
- error(EXIT_FAILURE, 0, "memory exhausted");
- bool exit = false;
- while (1) {
- SDL_Event event;
- while (SDL_PollEvent(&event)) {
- if (event.type == SDL_QUIT)
- exit = true;
- if (event.type == SDL_KEYDOWN
- && (event.key.keysym.sym == SDLK_q
- || event.key.keysym.sym == SDLK_ESCAPE))
- exit = true;
- }
- if (exit)
- break;
- int transfered = 0;
- int r = libusb_bulk_transfer(handle, 0x83, data, data_size,
- &transfered, 0);
- if (r)
- error(EXIT_FAILURE, 0, "can not read data: %s",
- libusb_strerror(r));
- if (transfered != image_size)
- fprintf(stderr, "bad image size (%d), drop\n", transfered);
- else {
- bayer2argb(data, rgb, options->width, options->height);
- SDL_UpdateTexture(texture, NULL, rgb, options->width * 4);
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
- SDL_RenderClear(renderer);
- SDL_RenderCopyEx(renderer, texture, NULL, NULL, 180.0, NULL,
- SDL_FLIP_NONE);
- SDL_RenderPresent(renderer);
- }
- }
- free(rgb);
- free(data);
- SDL_DestroyTexture(texture);
- SDL_DestroyRenderer(renderer);
- SDL_DestroyWindow(window);
-}
-
-int
-main(int argc, char **argv)
-{
- struct options options;
- parse_options(argc, argv, &options);
- libusb_context *usb;
- int r = libusb_init(&usb);
+ /* Request an extra frame to read the zero length packet. */
+ mdev->rawbuffer_size = (image_size + frame_size) / frame_size * frame_size;
+ mdev->rawbuffer = utils_malloc(mdev->rawbuffer_size);
+ mdev->buffer = mdev->raw ? NULL :
+ utils_malloc(mdev->width * mdev->height * sizeof(uint32_t));
+ /* Open USB device. */
+ int r = libusb_open(usb_device, &mdev->handle);
if (r)
- error(EXIT_FAILURE, 0, "unable to initialize libusb: %s",
- libusb_strerror(r));
- libusb_device_handle *handle = device_open();
- if (!handle)
- error(EXIT_FAILURE, 0, "unable to find device");
- device_init(handle, &options);
- if (options.count)
- run(handle, &options);
- else
- run_video(handle, &options);
- device_uninit(handle);
- libusb_close(handle);
- libusb_exit(usb);
- return EXIT_SUCCESS;
+ utils_fatal("can not open device: %s", libusb_strerror(r));
+ /* Initialize camera. */
+ moticam_reset(&mdev->device);
+ moticam_set_gain(&mdev->device, options->gain);
+ moticam_set_exposure(&mdev->device, 30.0);
+ moticam_set_resolution(&mdev->device, options->width, options->height);
+ moticam_set_exposure(&mdev->device, options->exposure_ms);
+ utils_delay_us(100000);
+ /* Done. */
+ return &mdev->device;
}
diff --git a/moticam.h b/moticam.h
new file mode 100644
index 0000000..b9eed99
--- /dev/null
+++ b/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: <nico at ni.fr.eu.org>
+ */
+#include "device.h"
+
+extern const struct device_driver moticam_device_driver;
+
+#endif /* moticam_h */
diff --git a/options.c b/options.c
new file mode 100644
index 0000000..80c49ed
--- /dev/null
+++ b/options.c
@@ -0,0 +1,150 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <getopt.h>
+#include <printf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "options.h"
+
+/* Report command line usage, optionally prefixed with an error message. */
+static void
+usage(int status, const char *msg)
+{
+ if (msg)
+ fprintf(stderr, "%s: %s", program_invocation_short_name, msg);
+ fprintf(status == EXIT_SUCCESS ? stdout : stderr,
+ "usage: %s [options] [FILE]\n"
+ "\n"
+ "Microscope camera viewer.\n"
+ "\n"
+ "positional arguments:\n"
+ " FILE output file pattern (default: out%%02d.png"
+ " or out for raw output)\n"
+ "\n"
+ "optional arguments:\n"
+ " -h, --help show this help message and exit\n"
+ " -w, --width VALUE image width (512, 1024 or 2048,"
+ " default: 1024)\n"
+ " -e, --exposure MS exposure value (1 to 5000,"
+ " default: 100)\n"
+ " -g, --gain VALUE gain value (0.33 to 42.66,"
+ " default: 1)\n"
+ " -n, --count N number of image to take"
+ " (default: live video)\n"
+ " -r, --raw save raw images\n"
+ , program_invocation_name);
+ exit(status);
+}
+
+void
+options_parse(int argc, char **argv, struct options *options)
+{
+ options->width = 1024;
+ options->height = 768;
+ options->exposure_ms = 100.0;
+ options->gain = 1.0;
+ options->count = 0;
+ options->raw = false;
+ options->out = NULL;
+ char *tail;
+ while (1) {
+ static struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "width", required_argument, 0, 'w' },
+ { "exposure", required_argument, 0, 'e' },
+ { "gain", required_argument, 0, 'g' },
+ { "count", required_argument, 0, 'n' },
+ { "raw", required_argument, 0, 'r' },
+ { NULL },
+ };
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "hw:e:g:n:r", long_options,
+ &option_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ usage(EXIT_SUCCESS, NULL);
+ break;
+ case 'w':
+ if (strcmp(optarg, "512") == 0) {
+ options->width = 512;
+ options->height = 384;
+ } else if (strcmp(optarg, "1024") == 0) {
+ options->width = 1024;
+ options->height = 768;
+ } else if (strcmp(optarg, "2048") == 0) {
+ options->width = 2048;
+ options->height = 1536;
+ } else
+ usage(EXIT_FAILURE, "bad width value");
+ break;
+ case 'e':
+ errno = 0;
+ options->exposure_ms = strtod(optarg, &tail);
+ if (*tail != '\0' || errno || options->exposure_ms < 1
+ || options->exposure_ms > 5000)
+ usage(EXIT_FAILURE, "bad exposure value");
+ break;
+ case 'g':
+ errno = 0;
+ options->gain = strtod(optarg, &tail);
+ if (*tail != '\0' || errno || options->gain <= 0.0
+ || options->gain >= 43.0)
+ usage(EXIT_FAILURE, "bad gain value");
+ break;
+ case 'n':
+ errno = 0;
+ options->count = strtoul(optarg, &tail, 10);
+ if (*tail != '\0' || errno)
+ usage(EXIT_FAILURE, "bad count value");
+ break;
+ case 'r':
+ options->raw = true;
+ break;
+ case '?':
+ usage(EXIT_FAILURE, NULL);
+ break;
+ default:
+ abort();
+ }
+ }
+ if (optind < argc)
+ options->out = argv[optind++];
+ if (optind < argc)
+ usage(EXIT_FAILURE, "too many arguments");
+ if (!options->out)
+ options->out = options->raw ? "out" : "out%02d.png";
+ if (!options->raw)
+ {
+ int argtypes[1];
+ int formats = parse_printf_format(options->out, 1, argtypes);
+ if (formats != 1 || argtypes[0] != PA_INT)
+ usage(EXIT_FAILURE, "bad file pattern, use one %d");
+ }
+}
+
diff --git a/options.h b/options.h
new file mode 100644
index 0000000..5b85561
--- /dev/null
+++ b/options.h
@@ -0,0 +1,51 @@
+#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: <nico at ni.fr.eu.org>
+ */
+#include <stdbool.h>
+
+/* Runtime options. */
+struct options {
+ /* Image width. */
+ int width;
+ /* Image height. */
+ int height;
+ /* Exposure in ms. */
+ double exposure_ms;
+ /* Digital gain after image acquisition. */
+ double gain;
+ /* When zero, run interactive mode, else number of frame to dump. */
+ int count;
+ /* Dump raw image from sensor. Only used in dump mode. */
+ bool raw;
+ /* Output file name or pattern, only used in dump mode. This should
+ * include a printf like pattern (%d) used to name the files, unless raw
+ * dump is requested. Only used in dump mode. */
+ const char *out;
+};
+
+/* Parse command line options and fill provided options structure. */
+void
+options_parse(int argc, char **argv, struct options *options);
+
+#endif /* options_h */
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..2f944b5
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,88 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#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_warning(const char *fmt, ...)
+{
+ va_list ap;
+ fprintf(stderr, "%s: warning: ", program_invocation_short_name);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fputc('\n', stderr);
+}
+
+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_malloc(size_t size)
+{
+ void *m = malloc(size);
+ if (!m)
+ utils_fatal("memory exhausted");
+ return m;
+}
+
+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
+ utils_fatal("can not sleep: %m");
+ }
+}
+
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..a47cc5b
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,49 @@
+#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: <nico at ni.fr.eu.org>
+ */
+
+/* Report a fatal error. */
+void
+utils_fatal(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2), noreturn));
+
+/* Report a warning. */
+void
+utils_warning(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+
+/* Report an info. */
+void
+utils_info(const char *fmt, ...)
+ __attribute__((format(printf, 1, 2)));
+
+/* Allocate memory, exit on error. */
+void *
+utils_malloc(size_t size);
+
+/* Sleep for a given number of microseconds. */
+void
+utils_delay_us(int us);
+
+#endif /* utils_h */