summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Schodet2019-09-28 22:17:40 +0200
committerNicolas Schodet2019-11-14 00:41:59 +0100
commit4832457478133cd9610a17070b07d1abbdd45443 (patch)
treed4d2dbd1b9cd520673e0a3cb073ca10fd7f1db97
parent51675360e87297cbf4423a1b12ae0c6a06a92158 (diff)
Add keyboard commands
-rw-r--r--Makefile3
-rw-r--r--live.c329
-rw-r--r--live.h31
-rw-r--r--main.c55
4 files changed, 366 insertions, 52 deletions
diff --git a/Makefile b/Makefile
index 05776d6..792b835 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,11 @@
-libs := libusb-1.0 libpng16 sdl2
+libs := libusb-1.0 libpng16 sdl2 SDL2_ttf
CFLAGS := -g -Wall $(shell pkg-config $(libs) --cflags)
LDLIBS := $(shell pkg-config $(libs) --libs)
SOURCES := \
device.c \
image.c \
+ live.c \
main.c \
moticam.c \
options.c \
diff --git a/live.c b/live.c
new file mode 100644
index 0000000..c473b58
--- /dev/null
+++ b/live.c
@@ -0,0 +1,329 @@
+/* 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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <SDL.h>
+#include <SDL_ttf.h>
+
+#include "live.h"
+#include "utils.h"
+
+#define FONT "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf"
+#define PTSIZE 18
+
+#define HELP \
+ "←,→ - change exposure\n" \
+ "↑,↓ - change gain\n" \
+ "z - reset exposure & gain\n" \
+ "f - toggle full screen\n" \
+ "q - exit\n" \
+ "h - help screen"
+
+#define LOGICAL_WIDTH 1000
+
+struct live
+{
+ struct device *device;
+ struct options *options;
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_Rect logical_rect;
+ SDL_Texture *msg_texture;
+ SDL_Rect msg_shaded_rect;
+ SDL_Rect msg_rect;
+ uint32_t msg_expire;
+ TTF_Font *font;
+ bool fullscreen;
+ double exposure_ms;
+ double gain;
+};
+
+static void
+live_init(struct live *live, struct device *device, struct options *options)
+{
+ live->device = device;
+ live->options = options;
+ /* Initialise SDL. */
+ if (SDL_Init(SDL_INIT_VIDEO))
+ utils_fatal("unable to initialize SDL: %s", SDL_GetError());
+ atexit(SDL_Quit);
+ /* Compute window size. */
+ int num_video_displays = SDL_GetNumVideoDisplays();
+ int width_max = INT_MAX;
+ int height_max = INT_MAX;
+ for (int i = 0; i < num_video_displays; i++) {
+ SDL_DisplayMode mode;
+ if (SDL_GetCurrentDisplayMode(i, &mode) == 0) {
+ if (mode.w < width_max)
+ width_max = mode.w;
+ if (mode.h < height_max)
+ height_max = mode.h;
+ }
+ }
+ width_max = width_max * 9 / 10;
+ height_max = height_max * 9 / 10;
+ int win_width = options->width;
+ int win_height = options->height;
+ if (win_width > width_max) {
+ win_height = win_height * width_max / win_width;
+ win_width = width_max;
+ }
+ if (win_height > height_max) {
+ win_width = win_width * height_max / win_height;
+ win_height = height_max;
+ }
+ /* Create window and renderer. */
+ if (SDL_CreateWindowAndRenderer(win_width, win_height,
+ SDL_WINDOW_RESIZABLE, &live->window, &live->renderer))
+ utils_fatal("unable to create window: %s", SDL_GetError());
+ SDL_SetWindowTitle(live->window, "Moticam");
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
+ SDL_Rect logical_rect = { 0, 0, LOGICAL_WIDTH,
+ LOGICAL_WIDTH * options->height / options->width };
+ live->logical_rect = logical_rect;
+ if (SDL_RenderSetLogicalSize(live->renderer, logical_rect.w,
+ logical_rect.h))
+ utils_fatal("can not set logical size: %s", SDL_GetError());
+ /* Prepare message rendering. */
+ if (TTF_Init())
+ utils_fatal("can not initialize TTF: %s", SDL_GetError());
+ live->msg_texture = NULL;
+ live->font = TTF_OpenFont(FONT, PTSIZE);
+ if (!live->font)
+ utils_fatal("can not open font: %s", SDL_GetError());
+ /* Disable screensaver. */
+ SDL_DisableScreenSaver();
+ /* Miscellaneous. */
+ live->fullscreen = false;
+ live->exposure_ms = options->exposure_ms;
+ live->gain = options->gain;
+}
+
+static void
+live_uninit(struct live *live)
+{
+ SDL_DestroyRenderer(live->renderer);
+ SDL_DestroyWindow(live->window);
+ TTF_Quit();
+}
+
+static void
+live_message_cancel(struct live *live)
+{
+ /* Release previous message. */
+ if (live->msg_texture)
+ SDL_DestroyTexture(live->msg_texture);
+ live->msg_texture = NULL;
+}
+
+static void
+live_message(struct live *live, int duration_ms, const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+static void
+live_message(struct live *live, int duration_ms, const char *fmt, ...)
+{
+ live_message_cancel(live);
+ /* Format text. */
+ va_list ap;
+ va_start(ap, fmt);
+ char *str = NULL;
+ vasprintf(&str, fmt, ap);
+ if (!str)
+ utils_fatal("can not format message");
+ va_end(ap);
+ /* Compute text width. */
+ int maxw = 0;
+ char *line = str;
+ char *end = strchr(line, '\n');
+ int w;
+ while (end)
+ {
+ *end = '\0';
+ TTF_SizeUTF8(live->font, line, &w, NULL);
+ if (w > maxw)
+ maxw = w;
+ *end = '\n';
+ line = end + 1;
+ end = strchr(line, '\n');
+ }
+ TTF_SizeUTF8(live->font, line, &w, NULL);
+ if (w > maxw)
+ maxw = w;
+ /* Render new message. */
+ SDL_Color color = { 255, 255, 255, 255 };
+ SDL_Surface *surf = TTF_RenderUTF8_Blended_Wrapped(live->font, str, color,
+ maxw);
+ free(str);
+ if (!surf)
+ utils_fatal("can not render text: %s", SDL_GetError());
+ /* Prepare rectangles and texture. */
+ live->msg_shaded_rect = (SDL_Rect) { 0, 0, surf->w + 20, surf->h + 10 };
+ live->msg_rect = (SDL_Rect) { 10, 5, surf->w, surf->h };
+ live->msg_texture = SDL_CreateTextureFromSurface(live->renderer, surf);
+ SDL_FreeSurface(surf);
+ if (!live->msg_texture)
+ utils_fatal("can not create text texture: %s", SDL_GetError());
+ /* Update expiration. */
+ if (duration_ms <= 0)
+ live->msg_expire = 0;
+ else
+ live->msg_expire = SDL_GetTicks() + duration_ms;
+}
+
+static void
+live_render_message(struct live *live)
+{
+ if (live->msg_texture) {
+ if (SDL_TICKS_PASSED(SDL_GetTicks(), live->msg_expire)) {
+ SDL_DestroyTexture(live->msg_texture);
+ live->msg_texture = NULL;
+ } else {
+ SDL_SetRenderDrawBlendMode(live->renderer, SDL_BLENDMODE_BLEND);
+ SDL_SetRenderDrawColor(live->renderer, 0, 0, 0, 128);
+ SDL_RenderFillRect(live->renderer, &live->msg_shaded_rect);
+ SDL_RenderCopy(live->renderer, live->msg_texture, NULL,
+ &live->msg_rect);
+ }
+ }
+}
+
+static void
+live_change_exposure(struct live *live, int direction)
+{
+ double now = live->exposure_ms + direction;
+ double step = direction * (now < 100.0 ? 33.0
+ : now < 1000.0 ? 100.0 : 500.0);
+ double new_exposure_ms = live->exposure_ms + step;
+ if (new_exposure_ms >= 33.0 && new_exposure_ms <= 5000.0) {
+ live->exposure_ms = new_exposure_ms;
+ device_set_exposure(live->device, new_exposure_ms);
+ live_message(live, 5000, "exposure: %d ms", (int) new_exposure_ms);
+ }
+}
+
+static void
+live_change_gain(struct live *live, int direction)
+{
+ double now = live->gain + direction / 1000.0;
+ double step = direction * (now < 1.0 ? 0.1 : 1.0);
+ double new_gain = live->gain + step;
+ if (new_gain >= 0.33 && new_gain <= 42.66) {
+ live->gain = new_gain;
+ device_set_gain(live->device, new_gain);
+ live_message(live, 5000, "gain: %.2f", new_gain);
+ }
+}
+
+static void
+live_reset(struct live *live)
+{
+ if (live->exposure_ms != live->options->exposure_ms)
+ device_set_exposure(live->device, live->options->exposure_ms);
+ live->exposure_ms = live->options->exposure_ms;
+ if (live->gain != live->options->gain)
+ device_set_gain(live->device, live->options->gain);
+ live->gain = live->options->gain;
+ live_message(live, 5000, "reset exposure & gain");
+}
+
+static bool
+live_handle_event(struct live *live, SDL_Event *event)
+{
+ if (event->type == SDL_QUIT)
+ return true;
+ if (event->type == SDL_KEYDOWN) {
+ switch (event->key.keysym.sym) {
+ case SDLK_q:
+ case SDLK_ESCAPE:
+ return true;
+ case SDLK_f:
+ live->fullscreen = !live->fullscreen;
+ SDL_SetWindowFullscreen(live->window,
+ live->fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
+ break;
+ case SDLK_QUESTION:
+ case SDLK_h:
+ live_message(live, 60000, HELP);
+ break;
+ case SDLK_LEFT:
+ live_change_exposure(live, -1);
+ break;
+ case SDLK_RIGHT:
+ live_change_exposure(live, +1);
+ break;
+ case SDLK_DOWN:
+ live_change_gain(live, -1);
+ break;
+ case SDLK_UP:
+ live_change_gain(live, +1);
+ break;
+ case SDLK_z:
+ live_reset(live);
+ break;
+ default:
+ live_message_cancel(live);
+ break;
+ }
+ }
+ return false;
+}
+
+void
+live_run(struct device *device, struct options *options)
+{
+ struct live live;
+ live_init(&live, device, options);
+ live_message(&live, 5000, "Press h for help");
+ SDL_Texture *texture = SDL_CreateTexture(live.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 (live_handle_event(&live, &event))
+ exit = true;
+ }
+ if (exit)
+ break;
+ const uint32_t *pixels = device_read(device);
+ if (pixels) {
+ SDL_UpdateTexture(texture, NULL, pixels, options->width * 4);
+ SDL_SetRenderDrawColor(live.renderer, 0, 0, 0, 0);
+ SDL_RenderClear(live.renderer);
+ SDL_RenderCopyEx(live.renderer, texture, NULL,
+ &live.logical_rect, 180.0, NULL, SDL_FLIP_NONE);
+ live_render_message(&live);
+ SDL_RenderPresent(live.renderer);
+ }
+ }
+ SDL_DestroyTexture(texture);
+ live_uninit(&live);
+}
+
diff --git a/live.h b/live.h
new file mode 100644
index 0000000..b3b641c
--- /dev/null
+++ b/live.h
@@ -0,0 +1,31 @@
+#ifndef live_h
+#define live_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 "device.h"
+#include "options.h"
+
+void
+live_run(struct device *device, struct options *options);
+
+#endif /* live_h */
diff --git a/main.c b/main.c
index 7c0e5b5..ad3d1f0 100644
--- a/main.c
+++ b/main.c
@@ -22,11 +22,13 @@
*/
#define _GNU_SOURCE
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
#include <png.h>
-#include <SDL.h>
#include "options.h"
#include "device.h"
+#include "live.h"
#include "utils.h"
void
@@ -73,55 +75,6 @@ run(struct device *device, struct options *options)
}
}
-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)
{
@@ -137,7 +90,7 @@ main(int argc, char **argv)
if (options.count)
run(device, &options);
else
- run_video(device, &options);
+ live_run(device, &options);
device_close(device);
libusb_exit(usb);
return EXIT_SUCCESS;