/* 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 #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 struct device_image *image = device_read(device); if (image) { SDL_UpdateTexture(texture, NULL, image->pixels, image->stride); 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); }