FTR is a portable and ported library for opening a window, drawing pixels to it, and reading keyboard and mouse input. It is crippled, slow, and extremely minimal. The main goal is to write tiny portable programs that open windows and show images. It is a thin wrapper around native libraries, intended to be as thin as possible, but not thinner.
Current ports include X11 (tested on linux, freebsd and openbsd) and any platform supporting freeglut (tested on linux, should work on Windows and OSX after installing freeglut). Planned ports include native Windows in the short term and native OSX on the long term.
This is the simplest program using FTR:
#include "ftr.c" int main(void) { struct FTR f = ftr_new_window(320, 200); return ftr_loop_run(&f); }
To compile and run this program on a unix system with X, place the file "ftr.c" besides "mini.c" and run
cc mini.c -lX11 ./a.out
This should open a black window of size 320×200 that can be closed by pressing ESC.
A useful program is obtained by capturing some events on the window:
#include#include "ftr.c" void print_event(struct FTR *f, int k, int m, int x, int y) { printf("event k=%d m=%d x=%d y=%d\n", k, m, x, y); } int main(void) { struct FTR f = ftr_new_window(320, 200); ftr_set_handler(&f, "key", print_event); ftr_set_handler(&f, "button", print_event); ftr_set_handler(&f, "motion", print_event); ftr_set_handler(&f, "resize", print_event); return ftr_loop_run(&f); }
This program opens a black window and prints a line when an "event" happens on that window. Events include key-presses, mouse motions and clicks, and window resizing. The key-presses are represented by the ASCII value of the corresponding key (using the correct keyboard configuration).
The following program opens a black window with a green pixel at the position (10,10):
#include "ftr.c" int main(void) { struct FTR f = ftr_new_window(320, 200); f.rgb[1 + 3 * (f.w * 10 + 10)] = 255; f.changed = 1; return ftr_loop_run(&f); }
First, notice that the struct FTR is not an opaque data type, but it has data that can be read and written explicitly, such as the framebuffer rgb and the image size.
Second, observe the line f.changed = 1, that tells "the system" that the framebuffer contents have changed. This line is necessary because, as opposed to many other "widget" toolkits, the call to ftr_new_window actually opens the window. The call to ftr_loop_run is optional, and only needed so that the program does not exit immediately. To understand that, try comenting the line f.changed=1. The window will be opened but the green pixel will not appear immediately; it will appear only when the window is redrawn, such as when moving another window in front of this one. Another way to understand this is to run the following program:
#include#include "ftr.c" int main(void) { struct FTR f = ftr_new_window(320, 200); return sleep(3); }
This program opens a window for three seconds, and then it exists and closes the window. This observation is very important: FTR is not an event-based library, it is multi-paradigm. You can write perfectly useful programs without a main loop, or with many main loops in different places. There are API functions like ftr_wait_for_mouse_click that allow a synchronous programming style, which is impossible in other toolkits such as GLUT:
#include#include "ftr.c" int main(void) { struct FTR f = ftr_new_window(320, 200); int x, y; ftr_wait_for_mouse_click(&f, &x, &y); printf("the mouse was clicked at position %d %d\n", x, y); return 0; }
The "idle" event is called continuously. This allows, for example, to run simulations in realtime. However, it is dangerous because the CPU is used 100%. The following program draws a fire effect (compile with optimizations to obtain an acceptable framerate)
#include(Javascript fire by Pau Gargallo)#include #include "ftr.c" static void draw_fire(struct FTR *f, int x, int y, int k, int m) { int num_lines_bottom = 3; // build palette unsigned char palette[3*256]; for (int i = 0; i < 256; i++) { palette[3 * i + 0] = 4 * i; palette[3 * i + 1] = (255-2 * i)/3; palette[3 * i + 2] = 3 * i; } // build buffer (if resize, restart buffer) static float *t = NULL; static int w = 0; static int h = 0; if (!f || w != f->w || h != f->h) { w = f->w; h = f->h; if (t) free(t); t = malloc(w * h * sizeof*t); for (int i = 0; i < w*h; i++) t[i] = (unsigned char)rand(); } // draw random values at the bottom int p = 0; for (int j = 0; j < num_lines_bottom; j++) for (int i = 0; i < w; i++) { t[p] = (unsigned char)(t[p] + 15*(rand()/(1.0+RAND_MAX))); p++; } // paint pixels by combining lower rows for (int j = h-1; j >= num_lines_bottom; j--) for (int i = 0; i < w; i++) { p = j*w+i; t[p] = (t[p-w] + 2 * t[p-2*w-1] + 2 * t[p-2*w] + 2 * t[p-2*w+1]) * 9 * 4 / 256; } // render with palette for (int j = 0; j < h; j++) for (int i = 0; i < w; i++) { int idx = (unsigned char)(t[w*(h-j-1) + i]); f->rgb[3 * (w*j+i) + 0] = palette[3 * idx + 0]; f->rgb[3 * (w*j+i) + 1] = palette[3 * idx + 1]; f->rgb[3 * (w*j+i) + 2] = palette[3 * idx + 2]; } } int main(void) { struct FTR f = ftr_new_window(800, 600); ftr_set_handler(&f, "idle", draw_fire); return ftr_loop_run(&f); }
Besides the examples above, the header file ftr.h is a useful and complete source of information:
#ifndef _FTR_H #define _FTR_H // data structure to store the state of a window struct FTR { // visible state int w, h; // size of the image unsigned char *rgb; // rgb 24-bit image int changed; // variable to indicate that the image has changed void *userdata; // ignored by the library // hidden implementation details char pad[100]; }; // type of a handler function typedef void (*ftr_event_handler_t)(struct FTR*,int,int,int,int); // core API struct FTR ftr_new_window(int w, int h); void ftr_change_title(struct FTR *f, char *title); int ftr_set_handler(struct FTR *f, char *event, ftr_event_handler_t h); int ftr_loop_run(struct FTR *f); void ftr_notify_the_desire_to_stop_this_loop(struct FTR *f, int retval); // convenience functions (reducible to the core api) struct FTR ftr_new_window_with_image_uint8_rgb(unsigned char *i, int w, int h); void ftr_close(struct FTR *f); void ftr_wait_for_mouse_click(struct FTR *f, int *x, int *y); void ftr_wait_for_key_depress(struct FTR *f, int *key, int *modifiers); int ftr_num_pending(struct FTR *f); void ftr_loop_fork(struct FTR *f); void ftr_fork_window_with_image_uint8_rgb(unsigned char *i, int w, int h); ftr_event_handler_t ftr_get_handler(struct FTR *f, char *id); // example handlers void ftr_handler_exit_on_ESC (struct FTR*,int,int,int,int); void ftr_handler_exit_on_ESC_or_q(struct FTR*,int,int,int,int); void ftr_handler_toggle_idle (struct FTR*,int,int,int,int); void ftr_handler_stop_loop (struct FTR*,int,int,int,int); void ftr_handler_dummy (struct FTR*,int,int,int,int); // ascii keys with name (necessary because '\e' is not standard) #define FTR_KEY_ESC 27 #define FTR_KEY_DEL 127 // non-ascii keys (numbers inspired by glut) #define FTR_KEY_FN 1000 #define FTR_KEY_LEFT 1100 #define FTR_KEY_UP 1101 #define FTR_KEY_RIGHT 1102 #define FTR_KEY_DOWN 1103 #define FTR_KEY_PAGE_UP 1104 #define FTR_KEY_PAGE_DOWN 1105 #define FTR_KEY_HOME 1106 #define FTR_KEY_END 1107 #define FTR_KEY_INSERT 1108 // key modifiers (numbers inspired by X) #define FTR_MASK_SHIFT 1 #define FTR_MASK_LOCK 2 #define FTR_MASK_CONTROL 4 #define FTR_MASK_MOD1 8 #define FTR_MASK_MOD2 16 #define FTR_MASK_MOD3 32 #define FTR_MASK_MOD4 64 #define FTR_MASK_MOD5 128 // buttons and button modifiers (inspired by X) #define FTR_BUTTON_LEFT 256 #define FTR_BUTTON_MIDDLE 512 #define FTR_BUTTON_RIGHT 1024 #define FTR_BUTTON_UP 2048 #define FTR_BUTTON_DOWN 4096 #endif//_FTR_H
The API may change to adapt to future needs, and new functions may be added without problem. However, the non-goals are strong decisions and I accept no compromise on them. For example, FTR will never perform a contrast change. This is up to the user of the library. On the other hand, some "helper" and "convenience" functions are defined to ease common usages. For example, the "ftr_open_window_with_image" function.
The X11 interface is tested and working in linux and mac. The freeglut interface should be working in any platform that has glut, but needs some testing, specially in the keyboard handler.
Back-end | Type | Status |
---|---|---|
X11 | native | Working, tested in linux and OSX--Quartz |
cocoa | native | not written |
windows | native | not written |
glut | semi-native | not written |
glfw | semi-native | not written |
freeglut | semi-native | Working, tested in linux (some keyboard issues) |
gdk | experimental | not written |
fltk | experimental | not written |
qt | experimental | not written |
xcb | experimental | not written |
FTR is inspired and tries to imitate many other libraries. The principal sources of inspiration are CImg and GLUT.
CImg is essentially perfect. There's no real reason to not use CImg in C++. Sometimes I would like to use CImgDdisplay only, without need for CImg. For example, when I want to publish an image processing algorithm of my own (that does not use the CImg image processing capabilities), and I want to provide a small interface. CImg is perfect for that, but a bit overkill if you do not work with the CImg class. CImg.h is 46.000 lines of code. My objective is that ftr.c should never go beyond 1000.
GLUT is essentially perfect, it has a very beautiful API and provides a neat cross-platform way to open a window and show pixels on it. There are, however a few problems: you can not exit easily the main loop, the mouse wheel does not generate events, and you are forced to draw your pixels using OpenGL. The freeglut re-implementation of GLUT solves the problem of the mouse wheel (but is not native on mac osx) and it lets you exit the event loop (but it closes the window when you do that). Freeglut is about 20.000 lines of code in about 30 files, including font data and widget-drawing stuff.
Besides its limited scope (no widgets, no image processing, no opengl access), FTR gives you more flexibility in the way you write your program. The idea is that you write your whole program (e.g., an algorithm in image processing), without needing FTR at all. Then, as an afterthought, you decide to add, at some scattered places in your program, a few calls to FTR to do things like:
1. Nested deep inside your algorithm, you want to open a window
containing a temporary image, and then continue running the program.
The window is kept open until it is closed by the user or until the
program ends. This involves a single call to this function:
ftr_new_window_with_image(char *rgb, int w, int h);
and nothing else. No initialization, no boilerplate. This function
returns immediately.
2. In the main loop of your program, you want to see how the iterations progress. Thus, you open a window with the image before the loop, and inside the loop you update the pixels of the image and then call the function ftr_notify_that_the_framebuffer_has_changed.
3. Your example program uses a hard-coded pixel position. By calling ftr_wait_for_mouse_click you let the user to click on a pixel.
4. See the tutorial for further examples.
FTR is under heavy development and has not yet released a distribution. By "distribution" I mean a single file ftr.c. Right now, I have two files ftr_x11.c ftr_freeglut.c, containing the respective implementations, that help me understand the required symmetries.
You can find these files inside the "other" subdirectory of imscript
The first user who guesses correcly the meaning of the acronym "FTR" will be invited to a beer of his choice.