FTR

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.

Examples

mini.c

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.

events.c

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).

greenpixel.c

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;
}

fire.c

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 
#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);
}
caca (Javascript fire by Pau Gargallo)

ftr.h

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

Principles

Goals of FTR

Non-goals of FTR

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.

Status of ports

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

Motivation

FTR is inspired and tries to imitate many other libraries. The principal sources of inspiration are CImg and GLUT.

Why not use CImg?

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.

Why not use GLUT?

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.

How is FTR different?

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.

Download, etc.

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

Prize

The first user who guesses correcly the meaning of the acronym "FTR" will be invited to a beer of his choice.

Last updated: 4 March 2016, Enric Meinhardt-Llopis