From 85da8be1863641598aa5ec64dc262fd644a324b6 Mon Sep 17 00:00:00 2001 From: eddyem Date: Mon, 30 Mar 2020 17:21:37 +0300 Subject: [PATCH] can shot images, store them into png and display in OpenGL window --- .gitignore | 4 +- Makefile | 1 + aux.c | 24 +++ aux.h | 1 + camera_functions.c | 200 +++++++++++++++++ camera_functions.h | 49 +++++ cmdlnopts.c | 11 +- cmdlnopts.h | 6 +- events.c | 186 ++++++++++++++++ events.h | 42 ++++ grasshopper.c | 520 ++++++++++++++++++++------------------------- imageview.c | 338 +++++++++++++++++++++++++++++ imageview.h | 82 +++++++ 13 files changed, 1168 insertions(+), 296 deletions(-) create mode 100644 camera_functions.c create mode 100644 camera_functions.h create mode 100644 events.c create mode 100644 events.h create mode 100644 imageview.c create mode 100644 imageview.h diff --git a/.gitignore b/.gitignore index d12f46e..60663b5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ # qt-creator *.config -*.creator +*.cflags +*.cxxflags +*.creator* *.files *.includes diff --git a/Makefile b/Makefile index 316d9dd..7603e41 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ PROGRAM := grasshopper LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all LDFLAGS += -lusefull_macros -lflycapture-c -lflycapture -L/usr/local/lib +LDFLAGS += -lm -pthread -lglut -lGL -lX11 SRCS := $(wildcard *.c) DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 OBJDIR := mk diff --git a/aux.c b/aux.c index 8e4cbeb..8248d02 100644 --- a/aux.c +++ b/aux.c @@ -16,12 +16,17 @@ * along with this program. If not, see . */ +#include // PATH_MAX #include #include +#include +#include +#include #include "aux.h" #include "cmdlnopts.h" +// print messages for given verbose_level int verbose(verblevel levl, const char *fmt, ...){ if((unsigned)verbose_level < levl) return 0; va_list ar; int i; @@ -32,3 +37,22 @@ int verbose(verblevel levl, const char *fmt, ...){ fflush(stdout); return i; } + +/** + * @brief check_filename - find file name "outfile_xxxx.suff" NOT THREAD-SAFE! + * @param outfile - file name prefix + * @param suff - file name suffix + * @return NULL or next free file name like "outfile_0010.suff" (don't free() it!) + */ +char *check_filename(char *outfile, char *suff){ + static char buff[PATH_MAX]; + struct stat filestat; + int num; + for(num = 1; num < 10000; num++){ + if(snprintf(buff, PATH_MAX, "%s_%04d.%s", outfile, num, suff) < 1) + return NULL; + if(stat(buff, &filestat)) // OK, file not exists + return buff; + } + return NULL; +} diff --git a/aux.h b/aux.h index e61142d..704f951 100644 --- a/aux.h +++ b/aux.h @@ -27,6 +27,7 @@ typedef enum{ } verblevel; int verbose(verblevel levl, const char *fmt, ...); +char *check_filename(char *outfile, char *suff); #define VMESG(...) do{verbose(VERB_MESG, __VA_ARGS__);}while(0) #define VDBG(...) do{verbose(VERB_DEBUG, __VA_ARGS__);}while(0) diff --git a/camera_functions.c b/camera_functions.c new file mode 100644 index 0000000..71b7807 --- /dev/null +++ b/camera_functions.c @@ -0,0 +1,200 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 3 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, see . + */ + +#include +#include + +#include "aux.h" +#include "cmdlnopts.h" +#include "camera_functions.h" + +static const char *propnames[] = { + [FC2_BRIGHTNESS] = "brightness", + [FC2_AUTO_EXPOSURE] = "auto exposure", + [FC2_SHARPNESS] = "sharpness", + [FC2_WHITE_BALANCE] = "white balance", + [FC2_HUE] = "hue", + [FC2_SATURATION] = "saturation", + [FC2_GAMMA] = "gamma", + [FC2_IRIS] = "iris", + [FC2_FOCUS] = "focus", + [FC2_ZOOM] = "zoom", + [FC2_PAN] = "pan", + [FC2_TILT] = "tilt", + [FC2_SHUTTER] = "shutter", + [FC2_GAIN] = "gain", + [FC2_TRIGGER_MODE] = "trigger mode", + [FC2_TRIGGER_DELAY] = "trigger delay", + [FC2_FRAME_RATE] = "frame rate", + [FC2_TEMPERATURE] = "temperature", + [FC2_UNSPECIFIED_PROPERTY_TYPE] = "unspecified" +}; + +// return property name +const char *getPropName(fc2PropertyType t){ + if(t < FC2_BRIGHTNESS || t > FC2_UNSPECIFIED_PROPERTY_TYPE) return NULL; + return propnames[t]; +} + +static void prbl(char *s, BOOL prop){ + printf("\t%s = ", s); + if(prop) green("true"); + else red("false"); + printf("\n"); +} + +fc2Error getproperty(fc2Context context, fc2PropertyType t){ + fc2Property prop; + prop.type = t; + FC2FNW(fc2GetProperty, context, &prop); + if(!prop.present) return FC2_ERROR_NOT_FOUND; + if(t <= FC2_UNSPECIFIED_PROPERTY_TYPE) green("\nProperty \"%s\":\n", propnames[t]); + prbl("absControl", prop.absControl); // 1 - world units, 0 - camera units + prbl("onePush", prop.onePush); // "one push" + prbl("onOff", prop.onOff); + prbl("autoManualMode", prop.autoManualMode); // 1 - auto, 0 - manual + printf("\tvalueA = %u\n", prop.valueA); // values in non-absolute mode + printf("\tvalueB = %u\n", prop.valueB); + printf("\tabsValue = %g\n", prop.absValue); // value in absolute mode + return FC2_ERROR_OK; +} + +fc2Error getpropertyInfo(fc2Context context, fc2PropertyType t){ + fc2PropertyInfo i; + i.type = t; + FC2FNW(fc2GetPropertyInfo, context, &i); + if(!i.present) return FC2_ERROR_NOT_FOUND; + green("Property Info:\n"); + prbl("autoSupported", i.autoSupported); // can be auto + prbl("manualSupported", i.manualSupported); // can be manual + prbl("onOffSupported", i.onOffSupported); // can be on/off + prbl("onePushSupported", i.onePushSupported); // can be "one push" + prbl("absValSupported", i.absValSupported); // can be absolute + prbl("readOutSupported", i.readOutSupported); // could be read out + printf("\tmin = %u\n", i.min); + printf("\tmax = %u\n", i.max); + printf("\tabsMin = %g\n", i.absMin); + printf("\tabsMax = %g\n", i.absMax); + printf("\tpUnits = %s\n", i.pUnits); + printf("\tpUnitAbbr = %s\n", i.pUnitAbbr); + return FC2_ERROR_OK; +} + + +/** + * @brief setfloat - set absolute property value (float) + * @param t - type of property + * @param context - initialized context + * @param f - new value + * @return FC2_ERROR_OK if all OK + */ +fc2Error setfloat(fc2PropertyType t, fc2Context context, float f){ + fc2Property prop; + prop.type = t; + fc2PropertyInfo i; + i.type = t; + FC2FNW(fc2GetProperty, context, &prop); + FC2FNW(fc2GetPropertyInfo, context, &i); + if(!prop.present || !i.present) return FC2_ERROR_NOT_FOUND; + if(prop.autoManualMode){ + if(!i.manualSupported){ + WARNX("Can't set auto-only property"); + return FC2_ERROR_PROPERTY_FAILED; + } + prop.autoManualMode = false; + } + if(!prop.absControl){ + if(!i.absValSupported){ + WARNX("Can't set non-absolute property to absolute value"); + return FC2_ERROR_PROPERTY_FAILED; + } + prop.absControl = true; + } + if(!prop.onOff){ + if(!i.onOffSupported){ + WARNX("Can't set property ON"); + return FC2_ERROR_PROPERTY_FAILED; + } + prop.onOff = true; + } + if(prop.onePush && i.onePushSupported) prop.onePush = false; + prop.valueA = prop.valueB = 0; + prop.absValue = f; + FC2FNW(fc2SetProperty, context, &prop); + // now check + FC2FNW(fc2GetProperty, context, &prop); + if(fabsf(prop.absValue - f) > 0.02f){ + WARNX("Can't set %s! Got %g instead of %g.", propnames[t], prop.absValue, f); + return FC2_ERROR_FAILED; + } + return FC2_ERROR_OK; +} + +fc2Error propOnOff(fc2PropertyType t, fc2Context context, BOOL onOff){ + fc2Property prop; + prop.type = t; + fc2PropertyInfo i; + i.type = t; + FC2FNW(fc2GetPropertyInfo, context, &i); + FC2FNW(fc2GetProperty, context, &prop); + if(!prop.present || !i.present) return FC2_ERROR_NOT_FOUND; + if(prop.onOff == onOff) return FC2_ERROR_OK; + if(!i.onOffSupported){ + WARNX("Property %s not supported state OFF", propnames[t]); + return FC2_ERROR_PROPERTY_FAILED; + } + prop.onOff = onOff; + FC2FNW(fc2SetProperty, context, &prop); + FC2FNW(fc2GetProperty, context, &prop); + if(prop.onOff != onOff){ + WARNX("Can't change property %s OnOff state", propnames[t]); + return FC2_ERROR_FAILED; + } + return FC2_ERROR_OK; +} + +void PrintCameraInfo(fc2Context context, unsigned int n){ + fc2CameraInfo camInfo; + fc2Error error = fc2GetCameraInfo(context, &camInfo); + if(error != FC2_ERROR_OK){ + WARNX("fc2GetCameraInfo(): %s", fc2ErrorToDescription(error)); + return; + } + printf("\n\n"); + green("*** CAMERA %d INFORMATION ***\n", n); + printf("Serial number - %u\n" + "Camera model - %s\n" + "Camera vendor - %s\n" + "Sensor - %s\n" + "Resolution - %s\n" + "Firmware version - %s\n" + "Firmware build time - %s\n\n", + camInfo.serialNumber, + camInfo.modelName, + camInfo.vendorName, + camInfo.sensorInfo, + camInfo.sensorResolution, + camInfo.firmwareVersion, + camInfo.firmwareBuildTime); + if(verbose_level >= VERB_MESG){ + for(fc2PropertyType t = FC2_BRIGHTNESS; t < FC2_UNSPECIFIED_PROPERTY_TYPE; ++t){ + getproperty(context, t); + if(verbose_level >= VERB_DEBUG) getpropertyInfo(context, t); + } + } +} diff --git a/camera_functions.h b/camera_functions.h new file mode 100644 index 0000000..52ffcac --- /dev/null +++ b/camera_functions.h @@ -0,0 +1,49 @@ +/* + * This file is part of the grasshopper project. + * Copyright 2020 Edward V. Emelianov . + * + * 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 3 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, see . + */ + +#pragma once +#ifndef CAMERA_FUNCTIONS__ +#define CAMERA_FUNCTIONS__ + +#include +#include + +#define FC2FNE(fn, c, ...) do{fc2Error err = FC2_ERROR_OK; if(FC2_ERROR_OK != (err=fn(c __VA_OPT__(,) __VA_ARGS__))){ \ + fc2DestroyContext(c); ERRX(#fn "(): %s", fc2ErrorToDescription(err));}}while(0) + +#define FC2FNW(fn, c, ...) do{fc2Error err = FC2_ERROR_OK; if(FC2_ERROR_OK != (err=fn(c __VA_OPT__(,) __VA_ARGS__))){ \ + WARNX(#fn "(): %s", fc2ErrorToDescription(err)); return err;}}while(0) + +void PrintCameraInfo(fc2Context context, unsigned int n); +const char *getPropName(fc2PropertyType t); +fc2Error getproperty(fc2Context context, fc2PropertyType t); +fc2Error getpropertyInfo(fc2Context context, fc2PropertyType t); +fc2Error setfloat(fc2PropertyType t, fc2Context context, float f); +fc2Error propOnOff(fc2PropertyType t, fc2Context context, BOOL onOff); +#define autoExpOff(c) propOnOff(FC2_AUTO_EXPOSURE, c, false) +#define whiteBalOff(c) propOnOff(FC2_WHITE_BALANCE, c, false) +#define gammaOff(c) propOnOff(FC2_GAMMA, c, false) +#define trigModeOff(c) propOnOff(FC2_TRIGGER_MODE, c, false) +#define trigDelayOff(c) propOnOff(FC2_TRIGGER_DELAY, c, false) +#define frameRateOff(c) propOnOff(FC2_FRAME_RATE, c, false) +// +set: saturation, hue, sharpness +#define setbrightness(c, b) setfloat(FC2_BRIGHTNESS, c, b) +#define setexp(c, e) setfloat(FC2_SHUTTER, c, e) +#define setgain(c, g) setfloat(FC2_GAIN, c, g) + +#endif // CAMERA_FUNCTIONS__ diff --git a/cmdlnopts.c b/cmdlnopts.c index 6d12e54..921c392 100644 --- a/cmdlnopts.c +++ b/cmdlnopts.c @@ -43,6 +43,7 @@ static glob_pars const Gdefault = { .device = NULL, .pidfile = DEFAULT_PIDFILE, .exptime = NAN, + .gain = NAN }; /* @@ -57,6 +58,10 @@ static myoption cmdlnopts[] = { {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&verbose_level), _("verbose level (each 'v' increases it)")}, {"camno", NEED_ARG, NULL, 'n', arg_int, APTR(&G.camno), _("camera number (if many connected)")}, {"exptime", NEED_ARG, NULL, 'x', arg_float, APTR(&G.exptime), _("exposure time (ms)")}, + {"gain", NEED_ARG, NULL, 'g', arg_float, APTR(&G.gain), _("gain value (dB)")}, + {"display", NO_ARGS, NULL, 'D', arg_int, APTR(&G.showimage), _("display captured image")}, + {"nimages", NEED_ARG, NULL, 'N', arg_int, APTR(&G.nimages), _("number of images to capture")}, + {"png", NO_ARGS, NULL, 'p', arg_int, APTR(&G.save_png), _("save png too")}, end_option }; @@ -73,7 +78,7 @@ glob_pars *parse_args(int argc, char **argv){ ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); size_t hlen = 1024; char helpstring[1024], *hptr = helpstring; - snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); + snprintf(hptr, hlen, "Usage: %%s [args] [filename prefix]\n\n\tWhere args are:\n"); // format of help: "Usage: progname [args]\n" change_helpstring(helpstring); // parse arguments @@ -82,8 +87,10 @@ glob_pars *parse_args(int argc, char **argv){ if(argc > 0){ G.rest_pars_num = argc; G.rest_pars = MALLOC(char *, argc); - for (i = 0; i < argc; i++) + for (i = 0; i < argc; i++){ + DBG("Found free parameter %s", argv[i]); G.rest_pars[i] = strdup(argv[i]); + } } return &G; } diff --git a/cmdlnopts.h b/cmdlnopts.h index 41ee0a7..5b32ec7 100644 --- a/cmdlnopts.h +++ b/cmdlnopts.h @@ -27,9 +27,13 @@ typedef struct{ char *device; // camera device name char *pidfile; // name of PID file - int rest_pars_num; // number of rest parameters int camno; // number of camera to work with float exptime; // exposition time + float gain; // gain value + int showimage; // display last captured image in OpenGL screen + int nimages; // number of images to capture + int save_png; // save png file + int rest_pars_num; // number of rest parameters char** rest_pars; // the rest parameters: array of char* } glob_pars; diff --git a/events.c b/events.c new file mode 100644 index 0000000..e91ad20 --- /dev/null +++ b/events.c @@ -0,0 +1,186 @@ +/* + * events.c + * + * Copyright 2015 Edward V. Emelianov + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include + +#include "events.h" +#include "imageview.h" + +/** + * manage pressed keys & menu items + */ +static void processKeybrd(unsigned char key, int mod, _U_ int x, _U_ int y){ + windowData *win = getWin(); + if(!win) return; + DBG("key=%d (%c), mod=%d", key, key, mod); + if(mod == GLUT_ACTIVE_CTRL){ // 'a' == 1, 'b' == 2... + key += 'a'-1; + DBG("CTRL+%c", key); + switch(key){ + case 's': // save image + win->winevt |= WINEVT_SAVEIMAGE; + break; + case 'q': // exit case 17: + //signals(1); + killwindow(); + break; + } + }else if(mod == GLUT_ACTIVE_ALT){ + ; // ALT + }else switch(key){ + case '0': // return zoom to 1 & image to 0 + win->zoom = 1; + win->x = 0; win->y = 0; + break; + case 27: + killwindow(); + break; + case 'l': + win->flip ^= WIN_FLIP_LR; + break; + case 'u': + win->flip ^= WIN_FLIP_UD; + break; + case 'Z': + win->zoom *= 1.1f; + calc_win_props(NULL, NULL); + break; + case 'z': + win->zoom /= 1.1f; + calc_win_props(NULL, NULL); + break; + } +} + +/* + * Process keyboard + */ +void keyPressed(unsigned char key, int x, int y){ + int mod = glutGetModifiers(); + //mod: GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL, GLUT_ACTIVE_ALT; result is their sum + DBG("Key pressed. mod=%d, keycode=%d (%c), point=(%d,%d)\n", mod, key, key, x,y); + processKeybrd(key, mod, x, y); +} +/* +void keySpPressed(_U_ int key, _U_ int x, _U_ int y){ +// int mod = glutGetModifiers(); + DBG("Sp. key pressed. mod=%d, keycode=%d, point=(%d,%d)\n", glutGetModifiers(), key, x,y); +}*/ + +static int oldx, oldy; // coordinates when mouse was pressed +static int movingwin = 0; // ==1 when user moves image by middle button + +void mousePressed(int key, int state, int x, int y){ +// key: GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, GLUT_RIGHT_BUTTON +// state: GLUT_UP, GLUT_DOWN + int mod = glutGetModifiers(); + windowData *win = getWin(); + if(!win) return; + if(state == GLUT_DOWN){ + oldx = x; oldy = y; + float X,Y, Zoom = win->zoom; + conv_mouse_to_image_coords(x,y,&X,&Y,win); + DBG("press in (%d, %d) == (%f, %f) on image; mod == %d", x,y,X,Y, mod); + if(key == GLUT_LEFT_BUTTON){ + DBG("win->x=%g, win->y=%g", win->x, win->y); + win->x += Zoom * (win->w/2.f - (float)x); + win->y -= Zoom * (win->h/2.f - (float)y); + }else if(key == GLUT_MIDDLE_BUTTON) movingwin = 1; + else if(key == 3){ // wheel UP + if(mod == 0) win->y += 10.f*win->zoom; // nothing pressed - scroll up + else if(mod == GLUT_ACTIVE_SHIFT) win->x -= 10.f*Zoom; // shift pressed - scroll left + else if(mod == GLUT_ACTIVE_CTRL) win->zoom *= 1.1f; // ctrl+wheel up == zoom+ + }else if(key == 4){ // wheel DOWN + if(mod == 0) win->y -= 10.f*win->zoom; // nothing pressed - scroll down + else if(mod == GLUT_ACTIVE_SHIFT) win->x += 10.f*Zoom; // shift pressed - scroll right + else if(mod == GLUT_ACTIVE_CTRL) win->zoom /= 1.1f; // ctrl+wheel down == zoom- + } + calc_win_props(NULL, NULL); + }else{ + movingwin = 0; + } +} + +void mouseMove(int x, int y){ + windowData *win = getWin(); + if(!win) return; + if(movingwin){ + float X, Y, nx, ny, w2, h2; + float a = win->Daspect; + X = (x - oldx) * a; Y = (y - oldy) * a; + nx = win->x + X; + ny = win->y - Y; + w2 = win->image->w / 2.f * win->zoom; + h2 = win->image->h / 2.f * win->zoom; + if(nx < w2 && nx > -w2) + win->x = nx; + if(ny < h2 && ny > -h2) + win->y = ny; + oldx = x; + oldy = y; + calc_win_props(NULL, NULL); + } +} + +void menuEvents(int opt){ + DBG("opt: %d, key: %d (%c), mod: %d", opt, opt&0xff, opt&0xff, opt>>8); + // just work as shortcut pressed + processKeybrd((unsigned char)(opt&0xff), opt>>8, 0, 0); +} // GLUT_ACTIVE_CTRL + +typedef struct{ + char *name; // menu entry name + int symbol; // shortcut symbol + rolled modifier +} menuentry; + +#define CTRL_K(key) ((key-'a'+1) | (GLUT_ACTIVE_CTRL<<8)) +#define SHIFT_K(key) (key | (GLUT_ACTIVE_SHIFT<<8)) +#define ALT_K(key) (key | (GLUT_ACTIVE_ALT<<8)) +static const menuentry entries[] = { + {"Flip image LR", 'l'}, + {"Flip image UD", 'u'}, + {"Restore zoom (0)", '0'}, + {"Save image (ctrl+s)", CTRL_K('s')}, + {"Close this window (ESC)", 27}, + {"Quit (ctrl+q)", CTRL_K('q')}, + {NULL, 0} +}; +#undef CTRL_K +#undef SHIFT_K +#undef ALT_K + +void createMenu(){ + FNAME(); + windowData *win = getWin(); + if(!win) return; + DBG("menu for win ID %d", win->ID); + glutSetWindow(win->ID); + if(win->menu) glutDestroyMenu(win->menu); + win->menu = glutCreateMenu(menuEvents); + const menuentry *ptr = entries; + while(ptr->name){ + glutAddMenuEntry(ptr->name, ptr->symbol); + ++ptr; + } + DBG("created menu %d\n", win->menu); + glutAttachMenu(GLUT_RIGHT_BUTTON); +} + diff --git a/events.h b/events.h new file mode 100644 index 0000000..c5a4c72 --- /dev/null +++ b/events.h @@ -0,0 +1,42 @@ +/* + * events.h + * + * Copyright 2015 Edward V. Emelianov + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#pragma once +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#include +#include +#include +#include +#include + +extern float Z; // координата Z (zoom) + +void keyPressed(unsigned char key, int x, int y); +//void keySpPressed(int key, int x, int y); +void mousePressed(int key, int state, int x, int y); +void mouseMove(int x, int y); +void createMenu(); +void menuEvents(int opt); +//void mouseWheel(int button, int dir, int x, int y); + +#endif // __EVENTS_H__ diff --git a/grasshopper.c b/grasshopper.c index aa15e54..dab3c3b 100644 --- a/grasshopper.c +++ b/grasshopper.c @@ -16,23 +16,16 @@ * along with this program. If not, see . */ -#include -#include +#include #include #include #include #include #include "aux.h" +#include "camera_functions.h" #include "cmdlnopts.h" - -static fc2Error err; -#define FC2FNE(fn, ...) do{if(FC2_ERROR_OK != (err=fn(__VA_ARGS__))){fc2DestroyContext(context); \ - ERRX(#fn "(): %s", fc2ErrorToDescription(err));}}while(0) - -#define FC2FNW(fn, ...) do{if(FC2_ERROR_OK != (err=fn(__VA_ARGS__))){fc2DestroyContext(context); \ - WARNX(#fn "(): %s", fc2ErrorToDescription(err)); return err;}}while(0) - +#include "imageview.h" void signals(int sig){ if(sig){ @@ -45,300 +38,192 @@ void signals(int sig){ restore_console(); exit(sig); } -#if 0 -typedef struct _Property -{ - /** Property info type. */ - fc2PropertyType type; - /** Flag indicating if the property is present. */ - BOOL present; - /** - * Flag controlling absolute mode (real world units) - * or non-absolute mode (camera internal units). - */ - BOOL absControl; - /** Flag controlling one push. */ - BOOL onePush; - /** Flag controlling on/off. */ - BOOL onOff; - /** Flag controlling auto. */ - BOOL autoManualMode; - /** - * Value A (integer). - * Used to configure properties in non-absolute mode. - */ - unsigned int valueA; - /** - * Value B (integer). For white balance, value B applies to the blue value and - * value A applies to the red value. - */ - unsigned int valueB; - /** - * Floating point value. - * Used to configure properties in absolute mode. - */ - float absValue; - /** Reserved for future use. */ - unsigned int reserved[8]; - - // For convenience, trigger delay is the same structure - // used in a separate function along with trigger mode. - -} fc2Property, fc2TriggerDelay; -typedef enum _fc2PropertyType -{ - FC2_BRIGHTNESS, - FC2_AUTO_EXPOSURE, - FC2_SHARPNESS, - FC2_WHITE_BALANCE, - FC2_HUE, - FC2_SATURATION, - FC2_GAMMA, - FC2_IRIS, - FC2_FOCUS, - FC2_ZOOM, - FC2_PAN, - FC2_TILT, - FC2_SHUTTER, - FC2_GAIN, - FC2_TRIGGER_MODE, - FC2_TRIGGER_DELAY, - FC2_FRAME_RATE, - FC2_TEMPERATURE, - FC2_UNSPECIFIED_PROPERTY_TYPE, - FC2_PROPERTY_TYPE_FORCE_32BITS = FULL_32BIT_VALUE - -} fc2PropertyType; -#endif - -static const char *propnames[] = { - [FC2_BRIGHTNESS] = "brightness", - [FC2_AUTO_EXPOSURE] = "auto exposure", - [FC2_SHARPNESS] = "sharpness", - [FC2_WHITE_BALANCE] = "white balance", - [FC2_HUE] = "hue", - [FC2_SATURATION] = "saturation", - [FC2_GAMMA] = "gamma", - [FC2_IRIS] = "iris", - [FC2_FOCUS] = "focus", - [FC2_ZOOM] = "zoom", - [FC2_PAN] = "pan", - [FC2_TILT] = "tilt", - [FC2_SHUTTER] = "shutter", - [FC2_GAIN] = "gain", - [FC2_TRIGGER_MODE] = "trigger mode", - [FC2_TRIGGER_DELAY] = "trigger delay", - [FC2_FRAME_RATE] = "frame rate", - [FC2_TEMPERATURE] = "temperature", - [FC2_UNSPECIFIED_PROPERTY_TYPE] = "unspecified" -}; - -static void prbl(char *s, BOOL prop){ - printf("\t%s = ", s); - if(prop) green("true"); - else red("false"); - printf("\n"); -} - -static fc2Error getproperty(fc2Context context, fc2PropertyType t){ - fc2Property prop; - prop.type = t; - FC2FNW(fc2GetProperty, context, &prop); - if(!prop.present) return FC2_ERROR_NOT_FOUND; - if(t <= FC2_UNSPECIFIED_PROPERTY_TYPE) green("\nProperty \"%s\":\n", propnames[t]); - prbl("absControl", prop.absControl); // 1 - world units, 0 - camera units - prbl("onePush", prop.onePush); // "one push" - prbl("onOff", prop.onOff); - prbl("autoManualMode", prop.autoManualMode); // 1 - auto, 0 - manual - printf("\tvalueA = %u\n", prop.valueA); // values in non-absolute mode - printf("\tvalueB = %u\n", prop.valueB); - printf("\tabsValue = %g\n", prop.absValue); // value in absolute mode - fc2PropertyInfo i; - i.type = t; - FC2FNW(fc2GetPropertyInfo, context, &i); - if(!i.present) return FC2_ERROR_OK; - green("Property Info:\n"); - prbl("autoSupported", i.autoSupported); // can be auto - prbl("manualSupported", i.manualSupported); // can be manual - prbl("onOffSupported", i.onOffSupported); // can be on/off - prbl("onePushSupported", i.onePushSupported); // can be "one push" - prbl("absValSupported", i.absValSupported); // can be absolute - prbl("readOutSupported", i.readOutSupported); // could be read out - printf("\tmin = %u\n", i.min); - printf("\tmax = %u\n", i.max); - printf("\tabsMin = %g\n", i.absMin); - printf("\tabsMax = %g\n", i.absMax); - printf("\tpUnits = %s\n", i.pUnits); - printf("\tpUnitAbbr = %s\n", i.pUnitAbbr); - return FC2_ERROR_OK; -} - -static fc2Error setexp(fc2Context context, float e){ - fc2Property prop; - prop.type = FC2_SHUTTER; - prop.autoManualMode = false; - prop.absValue = e; - FC2FNW(fc2SetProperty, context, &prop); - // now check - FC2FNW(fc2GetProperty, context, &prop); - if(fabs(prop.absValue - e) > 0.0001){ - WARNX("Can't set exposure! Got %g instead of %g.", prop.absValue, e); - return FC2_ERROR_FAILED; - } - return FC2_ERROR_OK; -} -static void PrintCameraInfo(fc2Context context, int n){ - fc2Error error; - fc2CameraInfo camInfo; - error = fc2GetCameraInfo(context, &camInfo); - if(error != FC2_ERROR_OK){ - WARNX("fc2GetCameraInfo(): %s", fc2ErrorToDescription(error)); - return; - } - printf("\n\n"); - green("*** CAMERA %d INFORMATION ***\n", n); - printf("Serial number - %u\n" - "Camera model - %s\n" - "Camera vendor - %s\n" - "Sensor - %s\n" - "Resolution - %s\n" - "Firmware version - %s\n" - "Firmware build time - %s\n\n", - camInfo.serialNumber, - camInfo.modelName, - camInfo.vendorName, - camInfo.sensorInfo, - camInfo.sensorResolution, - camInfo.firmwareVersion, - camInfo.firmwareBuildTime); - if(verbose_level >= VERB_DEBUG){ - for(fc2PropertyType t = FC2_BRIGHTNESS; t < FC2_UNSPECIFIED_PROPERTY_TYPE; ++t) - getproperty(context, t); - } -} - -static void SetTimeStamping(fc2Context context, BOOL enableTimeStamp) -{ - fc2Error error; - fc2EmbeddedImageInfo embeddedInfo; - - error = fc2GetEmbeddedImageInfo(context, &embeddedInfo); - if (error != FC2_ERROR_OK) - { - printf("Error in fc2GetEmbeddedImageInfo: %s\n", fc2ErrorToDescription(error)); - } - - if (embeddedInfo.timestamp.available != 0) - { - embeddedInfo.timestamp.onOff = enableTimeStamp; - } - - error = fc2SetEmbeddedImageInfo(context, &embeddedInfo); - if (error != FC2_ERROR_OK) - { - printf("Error in fc2SetEmbeddedImageInfo: %s\n", fc2ErrorToDescription(error)); - } -} - -static int GrabImages(fc2Context context, int numImagesToGrab) -{ +static int GrabImage(fc2Context context, fc2Image *convertedImage){ fc2Error error; fc2Image rawImage; - fc2Image convertedImage; - fc2TimeStamp prevTimestamp = {0}; - int i; - error = fc2CreateImage(&rawImage); if (error != FC2_ERROR_OK) { printf("Error in fc2CreateImage: %s\n", fc2ErrorToDescription(error)); return -1; } - - error = fc2CreateImage(&convertedImage); + // Retrieve the image + error = fc2RetrieveBuffer(context, &rawImage); if (error != FC2_ERROR_OK) { - printf("Error in fc2CreateImage: %s\n", fc2ErrorToDescription(error)); + printf("Error in retrieveBuffer: %s\n", fc2ErrorToDescription(error)); return -1; } - - // If externally allocated memory is to be used for the converted image, - // simply assigning the pData member of the fc2Image structure is - // insufficient. fc2SetImageData() should be called in order to populate - // the fc2Image structure correctly. This can be done at this point, - // assuming that the memory has already been allocated. - - for (i = 0; i < numImagesToGrab; i++) - { - // Retrieve the image - error = fc2RetrieveBuffer(context, &rawImage); - if (error != FC2_ERROR_OK) - { - printf("Error in retrieveBuffer: %s\n", fc2ErrorToDescription(error)); - return -1; - } - else - { - // Get and print out the time stamp - fc2TimeStamp ts = fc2GetImageTimeStamp(&rawImage); - int diff = (ts.cycleSeconds - prevTimestamp.cycleSeconds) * 8000 + - (ts.cycleCount - prevTimestamp.cycleCount); - prevTimestamp = ts; - printf("timestamp [%d %d] - %d\n", - ts.cycleSeconds, - ts.cycleCount, - diff); - } - } - - if (error == FC2_ERROR_OK) - { - // Convert the final image to RGB - error = fc2ConvertImageTo(FC2_PIXEL_FORMAT_MONO8, &rawImage, &convertedImage); - if (error != FC2_ERROR_OK) - { - printf("Error in fc2ConvertImageTo: %s\n", fc2ErrorToDescription(error)); - return -1; - } - - // Save it to PNG - printf("Saving the last image to fc2TestImage.png \n"); - error = fc2SaveImage(&convertedImage, "fc2TestImage.png", FC2_PNG); - if (error != FC2_ERROR_OK) - { - printf("Error in fc2SaveImage: %s\n", fc2ErrorToDescription(error)); - printf("Please check write permissions.\n"); - return -1; - } - } - - error = fc2DestroyImage(&rawImage); + // Convert image to gray + error = fc2ConvertImageTo(FC2_PIXEL_FORMAT_MONO8, &rawImage, convertedImage); if (error != FC2_ERROR_OK) { - printf("Error in fc2DestroyImage: %s\n", fc2ErrorToDescription(error)); + printf("Error in fc2ConvertImageTo: %s\n", fc2ErrorToDescription(error)); return -1; } - - error = fc2DestroyImage(&convertedImage); - if (error != FC2_ERROR_OK) - { - printf("Error in fc2DestroyImage: %s\n", fc2ErrorToDescription(error)); - return -1; - } - + fc2DestroyImage(&rawImage); return 0; } +// main thread to deal with image +void* image_thread(_U_ void *data){ + FNAME(); + //struct timeval tv; + windowData *win = getWin(); + // int w = win->image->w, h = win->image->h, x,y, id = win->ID; + // GLubyte i; + while(1){ + pthread_mutex_lock(&win->mutex); + if(win->killthread){ + pthread_mutex_unlock(&win->mutex); + DBG("got killthread"); + pthread_exit(NULL); + } + // Do something here + //win->image->changed = 1; + pthread_mutex_unlock(&win->mutex); + usleep(10000); + } +} + +/** + * Convert gray (unsigned short) into RGB components (GLubyte) + * @argument L - gray level + * @argument rgb - rgb array (GLubyte [3]) + */ +static void gray2rgb(double gray, GLubyte *rgb){ + int i = gray * 4.; + double x = (gray - (double)i * .25) * 4.; + GLubyte r = 0, g = 0, b = 0; + //r = g = b = (gray < 1) ? gray * 256 : 255; + switch(i){ + case 0: + g = (GLubyte)(255. * x); + b = 255; + break; + case 1: + g = 255; + b = (GLubyte)(255. * (1. - x)); + break; + case 2: + r = (GLubyte)(255. * x); + g = 255; + break; + case 3: + r = 255; + g = (GLubyte)(255. * (1. - x)); + break; + default: + r = 255; + } + *rgb++ = r; + *rgb++ = g; + *rgb = b; +} + +// functions for converting grayscale value into colour +typedef enum{ + COLORFN_LINEAR, // linear + COLORFN_LOG, // ln + COLORFN_SQRT // sqrt +} colorfn_type; + +static double linfun(double arg){ return arg; } // bung for PREVIEW_LINEAR +static double logfun(double arg){ return log(1.+arg); } // for PREVIEW_LOG +static double (*colorfun)(double) = linfun; // default function to convert color + +void change_colorfun(colorfn_type f){ + switch (f){ + case COLORFN_LINEAR: + colorfun = linfun; + break; + case COLORFN_LOG: + colorfun = logfun; + break; + default: // sqrt + colorfun = sqrt; + } +} + +static void change_displayed_image(windowData *win, fc2Image *convertedImage){ + if(!win || !win->image) return; + rawimage *im = win->image; + DBG("imh=%d, imw=%d, ch=%u, cw=%u", im->h, im->w, convertedImage->rows, convertedImage->cols); + /* + if(!im->rawdata || im->h != (int)convertedImage->rows || im->w != (int)convertedImage->cols){ + DBG("[re]allocate im->rawdata"); + FREE(im->rawdata); + im->h = (int)convertedImage->rows; + im->w = (int)convertedImage->cols; + im->rawdata = MALLOC(GLubyte, 3 * im->h * im->w); + if(!im->rawdata) ERR("Can't allocate memory"); + } + printf("image data:\n"); + printf("rows=%u, cols=%u, stride=%u, datasize=%u, recds=%u\n", convertedImage->rows, + convertedImage->cols, convertedImage->stride, convertedImage->dataSize, convertedImage->receivedDataSize); + */ + pthread_mutex_lock(&win->mutex); + int x, y, w = convertedImage->cols, h = convertedImage->rows; + double avr, wd, max, min; + avr = max = min = (double)*convertedImage->pData; + for(y = 0; y < h; ++y){ + unsigned char *ptr = &convertedImage->pData[y*convertedImage->stride]; + for(x = 0; x < w; ++x, ++ptr){ + double pix = (double) *ptr; + if(pix > max) max = pix; + if(pix < min) min = pix; + avr += pix; + } + } + avr /= (double)(w*h); + wd = max - min; + if(wd > DBL_EPSILON) avr = (avr - min) / wd; // normal average by preview + if(avr < 0.6) wd *= avr + 0.2; + if(wd < DBL_EPSILON) wd = 1.; + DBG("stat: sz=(%dx%d) avr=%g wd=%g max=%g min=%g", w,h,avr, wd, max, min); + GLubyte *dst = im->rawdata; + for(y = 0; y < h; y++){ + unsigned char *ptr = &convertedImage->pData[y*convertedImage->stride]; + for(x = 0; x < w; x++, dst += 3, ++ptr){ + gray2rgb(colorfun((*ptr - min) / wd), dst); + } + } + win->image->changed = 1; + pthread_mutex_unlock(&win->mutex); +} + +static void savePng(fc2Image *convertedImage, char *name){ + VDBG("Save the image data into %s", name); + fc2Error error = fc2SaveImage(convertedImage, name, FC2_PNG); + if(error != FC2_ERROR_OK){ + fprintf(stderr, "Error in fc2SaveImage: %s\n", fc2ErrorToDescription(error)); + } +} + +static void saveImages(fc2Image *convertedImage, char *prefix){ + if(G.save_png){ + char *newname = check_filename(prefix, "png"); + if(newname) savePng(convertedImage, newname); + } + // and save FITS here +} + int main(int argc, char **argv){ int ret = 0; initial_setup(); char *self = strdup(argv[0]); parse_args(argc, argv); + char *outfprefix = NULL; + if(G.rest_pars_num){ + if(G.rest_pars_num != 1){ + WARNX("You should point only one free argument - filename prefix"); + signals(1); + }else outfprefix = G.rest_pars[0]; + } check4running(self, G.pidfile); FREE(self); - signal(SIGTERM, signals); // kill (-15) - quit signal(SIGHUP, SIG_IGN); // hup - ignore signal(SIGINT, signals); // ctrl+C - quit @@ -346,8 +231,12 @@ int main(int argc, char **argv){ signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z setup_con(); + + windowData *mainwin = NULL; + fc2Context context; fc2PGRGuid guid; + fc2Error err = FC2_ERROR_OK; unsigned int numCameras = 0; if(FC2_ERROR_OK != (err = fc2CreateContext(&context))){ @@ -363,7 +252,7 @@ int main(int argc, char **argv){ VMESG("Found %d camera[s]", numCameras); if(verbose_level >= VERB_MESG){ - for(int i = 0; i < numCameras; ++i){ + for(unsigned int i = 0; i < numCameras; ++i){ FC2FNE(fc2GetCameraFromIndex, context, i, &guid); FC2FNE(fc2Connect, context, &guid); PrintCameraInfo(context, i); @@ -373,33 +262,75 @@ int main(int argc, char **argv){ FC2FNE(fc2Connect, context, &guid); if(verbose_level >= VERB_MESG && numCameras > 1) PrintCameraInfo(context, G.camno); if(isnan(G.exptime)){ // no expose time -> return + printf("No exposure parameters given -> exit\n"); goto destr; } + // turn off all shit + autoExpOff(context); + whiteBalOff(context); + gammaOff(context); + trigModeOff(context); + trigDelayOff(context); + frameRateOff(context); if(FC2_ERROR_OK != setexp(context, G.exptime)){ ret = 1; goto destr; } VMESG("Set exposition to %gms", G.exptime); - - SetTimeStamping(context, TRUE); - - err = fc2StartCapture(context); - if (err != FC2_ERROR_OK) - { - fc2DestroyContext(context); - printf("Error in fc2StartCapture: %s\n", fc2ErrorToDescription(err)); - signals(12); + if(!isnan(G.gain)){ + if(FC2_ERROR_OK != setgain(context, G.gain)){ + ret = 1; + goto destr; + } + VMESG("Set gain value to %gdB", G.gain); } - if (GrabImages(context, 3) != 0) - { - fc2DestroyContext(context); - signals(12); + FC2FNE(fc2StartCapture, context); + + if(G.showimage){ + imageview_init(); } + // main cycle + fc2Image convertedImage; + FC2FNE(fc2CreateImage, &convertedImage); + int N = 0; + bool start = TRUE; + while(1){ + if(GrabImage(context, &convertedImage)){ + fc2DestroyContext(context); + WARNX("GrabImages()"); + signals(12); + } + VMESG("\nGrabbed image #%d", ++N); + if(outfprefix){ + saveImages(&convertedImage, outfprefix); + } + if(G.showimage){ + if(!mainwin && start){ + mainwin = createGLwin("Sample window", convertedImage.cols, convertedImage.rows, NULL); + start = FALSE; + if(!mainwin){ + WARNX("Can't open OpenGL window, image preview will be inaccessible"); + }else + pthread_create(&mainwin->thread, NULL, &image_thread, NULL); //(void*)mainwin); + } + if((mainwin = getWin())){ + if(mainwin->winevt & WINEVT_SAVEIMAGE){ // save image + DBG("Try to make screenshot"); + saveImages(&convertedImage, "ScreenShot"); + mainwin->winevt &= ~WINEVT_SAVEIMAGE; + } + DBG("change image"); + change_displayed_image(mainwin, &convertedImage); + }else break; + } + if(--G.nimages <= 0) break; + } + FC2FNE(fc2DestroyImage, &convertedImage); + err = fc2StopCapture(context); - if (err != FC2_ERROR_OK) - { + if(err != FC2_ERROR_OK){ fc2DestroyContext(context); printf("Error in fc2StopCapture: %s\n", fc2ErrorToDescription(err)); signals(12); @@ -407,6 +338,11 @@ int main(int argc, char **argv){ destr: fc2DestroyContext(context); + if(G.showimage){ + while(getWin()); + DBG("Close window"); + clear_GL_context(); + } signals(ret); return ret; } diff --git a/imageview.c b/imageview.c new file mode 100644 index 0000000..a082241 --- /dev/null +++ b/imageview.c @@ -0,0 +1,338 @@ +// bmpview.c +// +// Copyright 2010 Edward V. Emelianoff +// +// 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., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301, USA. +//-lglut + +#include // XInitThreads(); +#include // roundf(), log(), sqrt() +#include +#include + +#include "imageview.h" + +static windowData *win = NULL; // main window +static pthread_t GLUTthread; // main GLUT thread + +static int initialized = 0; // ==1 if GLUT is initialized; ==0 after clear_GL_context + +static void createWindow(); +static void RedrawWindow(); +static void *Redraw(_U_ void *arg); +static void Resize(int width, int height); + +/** + * calculate window properties on creating & resizing + */ +void calc_win_props(GLfloat *Wortho, GLfloat *Hortho){ + if(!win || ! win->image) return; + float a, A, w, h, W, H; + float Zoom = win->zoom; + w = (float)win->image->w / 2.f; + h = (float)win->image->h / 2.f; + W = (float)win->w; + H =(float) win->h; + A = W / H; + a = w / h; + if(A > a){ // now W & H are parameters for glOrtho + win->Daspect = (float)h / H * 2.f; + W = h * A; H = h; + }else{ + win->Daspect = (float)w / W * 2.f; + H = w / A; W = w; + } + if(Wortho) *Wortho = W; + if(Hortho) *Hortho = H; + // calculate coordinates of center + win->x0 = W/Zoom - w + win->x / Zoom; + win->y0 = H/Zoom + h - win->y / Zoom; +} + +/** + * create window & run main loop + */ +static void createWindow(){ + FNAME(); + DBG("ini=%d, win %s", initialized, win ? "yes" : "no"); + if(!initialized) return; + if(!win) return; + int w = win->w, h = win->h; + DBG("create window with title %s", win->title); + glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); + glutInitWindowSize(w, h); + win->ID = glutCreateWindow(win->title); + DBG("created GL_ID=%d", win->ID); + glutReshapeFunc(Resize); + glutDisplayFunc(RedrawWindow); + glutKeyboardFunc(keyPressed); + //glutSpecialFunc(keySpPressed); + //glutMouseWheelFunc(mouseWheel); + glutMouseFunc(mousePressed); + glutMotionFunc(mouseMove); + //glutIdleFunc(glutPostRedisplay); + glutIdleFunc(NULL); + DBG("init textures"); + glGenTextures(1, &(win->Tex)); + calc_win_props(NULL, NULL); + win->zoom = 1. / win->Daspect; + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, win->Tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, win->image->w, win->image->h, 0, + GL_RGB, GL_UNSIGNED_BYTE, win->image->rawdata); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); +// glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +// glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); + + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glDisable(GL_TEXTURE_2D); + createMenu(); + DBG("Window opened"); + pthread_create(&GLUTthread, NULL, &Redraw, NULL); +} + +int killwindow(){ + if(!win) return 0; + glutSetWindow(win->ID); // obviously set window (for closing from menu) + if(!win->killthread){ + pthread_mutex_lock(&win->mutex); + // say changed thread to die + win->killthread = 1; + pthread_mutex_unlock(&win->mutex); + } + DBG("wait for changed thread"); + pthread_join(win->thread, NULL); // wait while thread dies + if(win->menu) glutDestroyMenu(win->menu); + glutDestroyWindow(win->ID); + DBG("destroy texture %d", win->Tex); + glDeleteTextures(1, &(win->Tex)); + glFinish(); + DBG("free(rawdata)"); + FREE(win->image->rawdata); + DBG("free(image)"); + FREE(win->image); + DBG("free(win)"); + FREE(win); + return 1; +} + +void renderBitmapString(float x, float y, void *font, char *string, GLubyte *color){ + if(!initialized) return; + char *c; + int x1=x, W=0; + for(c = string; *c; c++){ + W += glutBitmapWidth(font,*c);// + 1; + } + x1 -= W/2; + glColor3ubv(color); + glLoadIdentity(); + glTranslatef(0.,0., -150); + //glTranslatef(x,y, -4000.); + for (c = string; *c != '\0'; c++){ + glColor3ubv(color); + glRasterPos2f(x1,y); + glutBitmapCharacter(font, *c); + //glutStrokeCharacter(GLUT_STROKE_ROMAN, *c); + x1 = x1 + glutBitmapWidth(font,*c);// + 1; + } +} + +void redisplay(int GL_ID){ + if(!initialized) return; + glutSetWindow(GL_ID); + glutPostRedisplay(); +} + +static void RedrawWindow(){ + if(!initialized || !win) return; + if(pthread_mutex_trylock(&win->mutex) != 0) return; + GLfloat w = win->image->w, h = win->image->h; + glClearColor(0.0, 0.0, 0.5, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); + glTranslatef(win->x, win->y, 0.); + glScalef(-win->zoom, -win->zoom, 1.); + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, win->Tex); + if(win->image->changed){ + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, win->image->w, win->image->h, + GL_RGB, GL_UNSIGNED_BYTE, win->image->rawdata); + win->image->changed = 0; + } + + w /= 2.f; h /= 2.f; + float lr = 1., ud = 1.; // flipping coefficients + if(win->flip & WIN_FLIP_LR) lr = -1.; + if(win->flip & WIN_FLIP_UD) ud = -1.; + glBegin(GL_QUADS); + glTexCoord2f(1.0f, 1.0f); glVertex2f( -1.f*lr*w, ud*h ); // top right + glTexCoord2f(1.0f, 0.0f); glVertex2f( -1.f*lr*w, -1.f*ud*h ); // bottom right + glTexCoord2f(0.0f, 0.0f); glVertex2f(lr*w, -1.f*ud*h ); // bottom left + glTexCoord2f(0.0f, 1.0f); glVertex2f(lr*w, ud*h ); // top left + glEnd(); + glDisable(GL_TEXTURE_2D); + glFinish(); + glutSwapBuffers(); + pthread_mutex_unlock(&win->mutex); +} + +/** + * main freeGLUT loop + * waits for global signals to create windows & make other actions + */ +static void *Redraw(_U_ void *arg){ + FNAME(); + while(1){ + if(!initialized){ + DBG("!initialized"); + pthread_exit(NULL); + } + if(win && win->ID > 0){ + redisplay(win->ID); + glutMainLoopEvent(); // process actions if there are windows + } + usleep(10000); + } + return NULL; +} + +static void Resize(int width, int height){ + if(!initialized) return; + int window = glutGetWindow(); + if(!win || !window) return; + glutReshapeWindow(width, height); + win->w = width; + win->h = height; + glViewport(0, 0, width, height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + GLfloat W, H; + calc_win_props(&W, &H); + glOrtho(-W,W, -H,H, -1., 1.); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +/** + * create new window, run thread & return pointer to its structure or NULL + * asynchroneous call from outside + * wait for window creating & return its data + * @param title - header (copyed inside this function) + * @param w,h - image size + * @param rawdata - NULL (then the memory will be allocated here with size w x h) + * or allocated outside data + */ +windowData *createGLwin(char *title, int w, int h, rawimage *rawdata){ + FNAME(); + if(!initialized) return NULL; + if(win) killwindow(); + win = MALLOC(windowData, 1); + rawimage *raw; + if(rawdata){ + raw = rawdata; + }else{ + raw = MALLOC(rawimage, 1); + if(raw){ + raw->rawdata = MALLOC(GLubyte, w*h*3); + raw->w = w; + raw->h = h; + raw->changed = 1; + // raw->protected is zero automatically + } + } + if(!raw || !raw->rawdata){ + free(raw); + return NULL; + } + win->title = strdup(title); + win->image = raw; + if(pthread_mutex_init(&win->mutex, NULL)){ + WARN(_("Can't init mutex!")); + killwindow(); + return NULL; + } + win->w = w; + win->h = h; + createWindow(); + return win; +} + +/** + * Init freeGLUT + */ +void imageview_init(){ + FNAME(); + char *v[] = {"Grasshopper window", NULL}; + int c = 1; + static int glutnotinited = 1; + if(initialized){ + WARNX(_("Already initialized!")); + return; + } + if(glutnotinited){ + XInitThreads(); // we need it for threaded windows + glutInit(&c, v); + glutnotinited = 0; + } + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); + initialized = 1; +} + +/** + * Close all opened windows and terminate main GLUT thread + */ +void clear_GL_context(){ + FNAME(); + if(!initialized) return; + initialized = 0; + DBG("kill"); + killwindow(); + DBG("join"); + pthread_join(GLUTthread, NULL); // wait while main thread exits + DBG("main GL thread cancelled"); +} + + +/* + * Coordinates transformation from CS of drawingArea into CS of picture + * x,y - pointer coordinates + * X,Y - coordinates of appropriate point at picture + */ +void conv_mouse_to_image_coords(int x, int y, + float *X, float *Y, + windowData *window){ + float a = window->Daspect / window->zoom; + *X = (float)x * a - window->x0; + *Y = window->y0 - (float)y * a; +} + +void conv_image_to_mouse_coords(float X, float Y, + int *x, int *y, + windowData *window){ + float a = window->zoom / window->Daspect; + *x = (int)roundf((X + window->x0) * a); + *y = (int)roundf((window->y0 - Y) * a); +} + + +windowData *getWin(){ + return win; +} diff --git a/imageview.h b/imageview.h new file mode 100644 index 0000000..621b4c1 --- /dev/null +++ b/imageview.h @@ -0,0 +1,82 @@ +/* + * imageview.h + * + * Copyright 2015 Edward V. Emelianov + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#pragma once +#ifndef __BMPVIEW_H__ +#define __BMPVIEW_H__ + +#include +#include +#include + +#include "events.h" + +typedef struct{ + GLubyte *rawdata; // raw image data + int w; // size of image + int h; + int changed; // == 1 if data was changed outside (to redraw) +} rawimage; + +// events from menu +#define WINEVT_PAUSE (1<<0) +#define WINEVT_RESUME (1<<1) +#define WINEVT_SAVEIMAGE (1<<2) + +// flip image +#define WIN_FLIP_LR (1<<0) +#define WIN_FLIP_UD (1<<1) + +typedef struct{ + int ID; // identificator of OpenGL window + char *title; // title of window + GLuint Tex; // texture for image inside window + rawimage *image; // raw image data + int w; int h; // window size + float x; float y; // image offset coordinates + float x0; float y0; // center of window for coords conversion + float zoom; // zoom aspect + float Daspect; // aspect ratio between image & window sizes + int menu; // window menu identifier + uint32_t winevt; // window menu events + uint8_t flip; // flipping settings + pthread_t thread; // identificator of thread that changes window data + pthread_mutex_t mutex;// mutex for operations with image + int killthread; // flag for killing data changing thread & also signal that there's no threads +} windowData; + +typedef enum{ + INNER, + OPENGL +} winIdType; + +void imageview_init(); +windowData *createGLwin(char *title, int w, int h, rawimage *rawdata); +windowData *getWin(); +int killwindow(); +void renderBitmapString(float x, float y, void *font, char *string, GLubyte *color); +void clear_GL_context(); + +void calc_win_props(GLfloat *Wortho, GLfloat *Hortho); + +void conv_mouse_to_image_coords(int x, int y, float *X, float *Y, windowData *window); +void conv_image_to_mouse_coords(float X, float Y, int *x, int *y, windowData *window); + +#endif // __BMPVIEW_H__