summaryrefslogtreecommitdiff
path: root/live.c
diff options
context:
space:
mode:
authorNicolas Schodet2019-09-28 22:17:40 +0200
committerNicolas Schodet2019-11-14 00:41:59 +0100
commit4832457478133cd9610a17070b07d1abbdd45443 (patch)
treed4d2dbd1b9cd520673e0a3cb073ca10fd7f1db97 /live.c
parent51675360e87297cbf4423a1b12ae0c6a06a92158 (diff)
Add keyboard commands
Diffstat (limited to 'live.c')
-rw-r--r--live.c329
1 files changed, 329 insertions, 0 deletions
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);
+}
+