cairo/util/show-events.c
Chris Wilson f8bb3617c3 Eliminate self-intersecting strokes.
We refactor the surface fallbacks to convert full strokes and fills to the
intermediate polygon representation (as opposed to before where we
returned the trapezoidal representation). This allow greater flexibility
to choose how then to rasterize the polygon. Where possible we use the
local spans rasteriser for its increased performance, but still have the
option to use the tessellator instead (for example, with the current
Render protocol which does not yet have a polygon image).

In order to accommodate this, the spans interface is tweaked to accept
whole polygons instead of a path and the tessellator is tweaked for speed.

Performance Impact
==================

...
Still measuring, expecting some severe regressions.
...
2009-08-29 08:08:28 +01:00

845 lines
19 KiB
C

#define _GNU_SOURCE
#include <gtk/gtk.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <math.h>
typedef struct _point {
gdouble x, y;
} point_t;
typedef struct _box {
point_t p1, p2;
} box_t;
typedef struct _line {
point_t p1, p2;
} line_t;
typedef struct _edge {
gulong id;
line_t line;
gdouble top, bottom;
point_t p1, p2;
int dir;
} edge_t;
typedef struct _trapezoid {
gdouble top, bottom;
const edge_t *left, *right;
} trapezoid_t;
typedef struct _traps {
int num_traps;
int size;
trapezoid_t traps[0];
} traps_t;
typedef struct _edges {
GHashTable *ht;
int num_edges;
int size;
edge_t edges[0];
} edges_t;
typedef struct _event {
enum {
START_EDGE,
END_EDGE,
INTERSECTION,
START_TRAP,
END_TRAP,
} type;
int x, y; /* (top, bottom) for trap */
long e1, e2;
} event_t;
typedef struct _events {
struct _events *prev, *next;
box_t extents;
edges_t *edges;
traps_t *prototraps;
traps_t *traps;
int current_event;
int num_events;
int size_events;
event_t *events;
} events_t;
typedef struct _EventView {
GtkWidget widget;
events_t *events_list;
events_t *current_events;
double px, py;
gint mag_x, mag_y;
gint mag_size;
gdouble mag_zoom;
gboolean in_mag_drag;
gint mag_drag_x, mag_drag_y;
} EventView;
typedef struct _EventViewClass {
GtkWidgetClass parent_class;
} EventViewClass;
G_DEFINE_TYPE (EventView, event_view, GTK_TYPE_WIDGET)
static edge_t *
edges_lookup (edges_t *edges, gulong id)
{
return &edges->edges[GPOINTER_TO_UINT(g_hash_table_lookup (edges->ht,
GUINT_TO_POINTER
(id)))];
}
static gdouble
_compute_intersection_x_for_y (const line_t *line,
gdouble y)
{
gdouble dx = line->p2.x - line->p1.x;
gdouble dy = line->p2.y - line->p1.y;
gdouble x;
if (y == line->p1.y)
return line->p1.x;
if (y == line->p2.y)
return line->p2.x;
x = line->p1.x;
if (dy != 0)
x += (y - line->p1.y)*dx/dy;
return x;
}
static void
_compute_intersection_point (const line_t *line,
gdouble y,
point_t *p)
{
p->x = _compute_intersection_x_for_y (line, p->y = y);
}
static void
_edge_path (cairo_t *cr, const cairo_matrix_t *m, const edge_t *e)
{
double x, y;
x = e->p1.x; y = e->p1.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_move_to (cr, x, y);
x = e->p2.x; y = e->p2.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_line_to (cr, x, y);
if (e->dir < 0) {
cairo_set_source_rgb (cr, 0, 0, 1);
} else {
cairo_set_source_rgb (cr, 1, 0, 0);
}
}
static void
_events_draw (events_t *events, cairo_t *cr, cairo_matrix_t *m)
{
double dash[2] = {8, 8};
point_t p;
int n;
/* first the existing and proto-traps */
cairo_save (cr); {
cairo_set_matrix (cr, m);
cairo_set_source_rgba (cr, 1, 0, 0, .15);
for (n = 0; n < events->prototraps->num_traps; n++) {
const trapezoid_t *t = &events->prototraps->traps[n];
_compute_intersection_point (&t->left->line, t->top, &p);
cairo_move_to (cr, p.x, p.y);
_compute_intersection_point (&t->right->line, t->top, &p);
cairo_line_to (cr, p.x, p.y);
_compute_intersection_point (&t->right->line, t->bottom, &p);
cairo_line_to (cr, p.x, p.y);
_compute_intersection_point (&t->left->line, t->bottom, &p);
cairo_line_to (cr, p.x, p.y);
cairo_close_path (cr);
cairo_fill (cr);
}
cairo_set_source_rgba (cr, 0, 1, 0, .2);
for (n = 0; n < events->traps->num_traps; n++) {
const trapezoid_t *t = &events->traps->traps[n];
_compute_intersection_point (&t->left->line, t->top, &p);
cairo_move_to (cr, p.x, p.y);
_compute_intersection_point (&t->right->line, t->top, &p);
cairo_line_to (cr, p.x, p.y);
_compute_intersection_point (&t->right->line, t->bottom, &p);
cairo_line_to (cr, p.x, p.y);
_compute_intersection_point (&t->left->line, t->bottom, &p);
cairo_line_to (cr, p.x, p.y);
cairo_close_path (cr);
cairo_fill (cr);
}
} cairo_restore (cr);
/* known edges */
cairo_save (cr);
cairo_set_line_width (cr, 1.);
for (n = 0; n < events->edges->num_edges; n++) {
const edge_t *e = &events->edges->edges[n];
double x, y;
x = e->p1.x; y = e->p1.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_move_to (cr, x, y);
x = e->p2.x; y = e->p2.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_line_to (cr, x, y);
if (e->dir < 0) {
cairo_set_source_rgba (cr, 0, 0, 1., .4);
cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1]) + dash[0]);
} else {
cairo_set_source_rgba (cr, 1, 0, 0., 4);
cairo_set_dash (cr, dash, 2, fmod (e->p1.x, dash[0]+dash[1]));
}
cairo_stroke (cr);
x = e->p1.x; y = e->p1.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_arc (cr, x, y, 2., 0, 2 * G_PI);
x = e->p2.x; y = e->p2.y;
cairo_matrix_transform_point (m, &x, &y);
cairo_arc (cr, x, y, 2., 0, 2 * G_PI);
cairo_fill (cr);
}
cairo_restore (cr);
/* event time */
cairo_save (cr); {
event_t *e;
double x, y;
e = &events->events[events->current_event];
cairo_set_line_width (cr, 2.);
cairo_set_matrix (cr, m);
cairo_move_to (cr,
events->extents.p1.x,
e->y);
cairo_line_to (cr,
events->extents.p2.x,
e->y);
cairo_identity_matrix (cr);
cairo_stroke (cr);
x = e->x; y = e->y;
cairo_matrix_transform_point (m, &x, &y);
switch (e->type) {
case START_EDGE:
case END_EDGE:
case INTERSECTION:
cairo_arc (cr, x, y, 4., 0, 2 * G_PI);
break;
case START_TRAP:
case END_TRAP:
break;
}
switch (e->type) {
case START_EDGE:
cairo_set_source_rgb (cr, 1, 0, 0);
break;
case END_EDGE:
cairo_set_source_rgb (cr, 0, 0, 1);
break;
case INTERSECTION:
cairo_set_source_rgb (cr, 1, 0, 1);
break;
case START_TRAP:
case END_TRAP:
break;
}
cairo_fill (cr);
cairo_set_line_width (cr, 1.);
switch (e->type) {
case START_EDGE:
_edge_path (cr, m, edges_lookup (events->edges, e->e1));
cairo_stroke (cr);
break;
case END_EDGE:
_edge_path (cr, m, edges_lookup (events->edges, e->e1));
cairo_stroke (cr);
break;
case INTERSECTION:
_edge_path (cr, m, edges_lookup (events->edges, e->e1));
cairo_stroke (cr);
_edge_path (cr, m, edges_lookup (events->edges, e->e2));
cairo_stroke (cr);
break;
}
} cairo_restore (cr);
}
static void
event_view_draw (EventView *self, cairo_t *cr)
{
events_t *events;
gdouble sf_x, sf_y, sf;
gdouble mid, dim;
gdouble x0, x1, y0, y1;
cairo_matrix_t m;
cairo_save (cr);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_paint (cr);
cairo_restore (cr);
events = self->current_events;
if (events == NULL)
return;
mid = (events->extents.p2.x + events->extents.p1.x) / 2.;
dim = (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2;
sf_x = self->widget.allocation.width / dim / 2;
mid = (events->extents.p2.y + events->extents.p1.y) / 2.;
dim = (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2;
sf_y = self->widget.allocation.height / dim / 2;
sf = MIN (sf_x, sf_y);
mid = (events->extents.p2.x + events->extents.p1.x) / 2.;
dim = sf_x / sf * (events->extents.p2.x - events->extents.p1.x) / 2. * 1.2;
x0 = mid - dim;
x1 = mid + dim;
mid = (events->extents.p2.y + events->extents.p1.y) / 2.;
dim = sf_y / sf * (events->extents.p2.y - events->extents.p1.y) / 2. * 1.2;
y0 = mid - dim;
y1 = mid + dim;
cairo_matrix_init_scale (&m, sf, sf);
cairo_matrix_translate (&m, -x0, -y0);
_events_draw (events, cr, &m);
/* draw a zoom view of the area around the mouse */
cairo_save (cr); {
double zoom = self->mag_zoom;
int size = self->mag_size;
/* bottom right */
cairo_rectangle (cr, self->mag_x, self->mag_y, size, size);
cairo_stroke_preserve (cr);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_fill_preserve (cr);
cairo_clip (cr);
/* compute roi in extents */
cairo_translate (cr, self->mag_x + size/2, self->mag_y + size/2);
cairo_matrix_init_scale (&m, zoom, zoom);
cairo_matrix_translate (&m, -(self->px / sf + x0), -(self->py /sf + y0));
_events_draw (events, cr, &m);
/* grid */
cairo_save (cr); {
int i;
cairo_translate (cr,
-zoom*fmod (self->px/sf + x0, 1.),
-zoom*fmod (self->py/sf + y0, 1.));
for (i = -size/2/zoom - 1; i <= size/2/zoom + 1; i++) {
cairo_move_to (cr, zoom*i, -size/2);
cairo_line_to (cr, zoom*i, size/2 + zoom);
cairo_move_to (cr, -size/2, zoom*i);
cairo_line_to (cr, size/2 + zoom, zoom*i);
}
cairo_set_source_rgba (cr, .7, .7, .7, .5);
cairo_set_line_width (cr, 1.);
cairo_stroke (cr);
} cairo_restore (cr);
} cairo_restore (cr);
}
static void
event_view_draw_labels (EventView *self, cairo_t *cr)
{
events_t *events;
events = self->current_events;
if (events == NULL)
return;
}
static gboolean
event_view_expose (GtkWidget *w, GdkEventExpose *ev)
{
EventView *self = (EventView *) w;
cairo_t *cr;
cr = gdk_cairo_create (w->window);
gdk_cairo_region (cr, ev->region);
cairo_clip (cr);
event_view_draw (self, cr);
event_view_draw_labels (self, cr);
cairo_destroy (cr);
return FALSE;
}
static void
traps_clear (traps_t *traps)
{
traps->num_traps = 0;
}
static traps_t *
traps_add (traps_t *traps, int top, int bot, const edge_t *e1, const edge_t *e2)
{
trapezoid_t *t;
if (traps->num_traps == traps->size) {
traps->size *= 2;
traps = g_realloc (traps,
sizeof (traps_t) + traps->size*sizeof (trapezoid_t));
}
t = &traps->traps[traps->num_traps++];
t->top = top;
if (bot > e1->bottom)
bot = e1->bottom;
if (bot > e2->bottom)
bot = e2->bottom;
t->bottom = bot;
t->left = e1;
t->right = e2;
return traps;
}
static void
traps_remove (traps_t *traps, int top, const edge_t *e1, const edge_t *e2)
{
int n;
for (n = 0; n < traps->num_traps; n++) {
trapezoid_t *t = &traps->traps[n];
if (t->top == top && t->left == e1 && t->right == e2)
break;
}
if (n < traps->num_traps) {
g_memmove (&traps->traps[n],
&traps->traps[n+1],
(traps->num_traps-n+1) * sizeof (trapezoid_t));
traps->num_traps--;
}
}
static void
event_next (EventView *self)
{
events_t *events;
event_t *e;
events = self->current_events;
if (++events->current_event == events->num_events) {
return;
} else if (events->current_event >= events->num_events) {
traps_clear (events->prototraps);
traps_clear (events->traps);
events->current_event = 0;
self->current_events = events->next;
if (self->current_events == NULL)
self->current_events = self->events_list;
events = self->current_events;
}
e = &events->events[events->current_event];
switch (e->type) {
case START_TRAP:
events->prototraps = traps_add (events->prototraps,
e->x, G_MAXINT,
edges_lookup (events->edges, e->e1),
edges_lookup (events->edges, e->e2));
break;
case END_TRAP:
traps_remove (events->prototraps,
e->x,
edges_lookup (events->edges, e->e1),
edges_lookup (events->edges, e->e2));
events->traps = traps_add (events->traps,
e->x, e->y,
edges_lookup (events->edges, e->e1),
edges_lookup (events->edges, e->e2));
break;
}
}
static gboolean
event_view_button_press (GtkWidget *w, GdkEventButton *ev)
{
EventView *self = (EventView *) w;
if (ev->x < self->mag_x ||
ev->y < self->mag_y ||
ev->x > self->mag_x + self->mag_size ||
ev->y > self->mag_y + self->mag_size)
{
if (ev->type == GDK_BUTTON_PRESS) {
event_next (self);
gtk_widget_queue_draw (w);
}
}
else
{
self->in_mag_drag = TRUE;
self->mag_drag_x = ev->x;
self->mag_drag_y = ev->y;
}
return FALSE;
}
static gboolean
event_view_button_release (GtkWidget *w, GdkEventButton *ev)
{
EventView *self = (EventView *) w;
self->in_mag_drag = FALSE;
return FALSE;
}
static gboolean
event_view_motion (GtkWidget *w, GdkEventMotion *ev)
{
EventView *self = (EventView *) w;
if (self->in_mag_drag) {
self->mag_x += ev->x - self->mag_drag_x;
self->mag_y += ev->y - self->mag_drag_y;
gtk_widget_queue_draw (&self->widget);
self->mag_drag_x = ev->x;
self->mag_drag_y = ev->y;
} else if (ev->x < self->mag_x ||
ev->y < self->mag_y ||
ev->x > self->mag_x + self->mag_size ||
ev->y > self->mag_y + self->mag_size)
{
self->px = ev->x;
self->py = ev->y;
gtk_widget_queue_draw (&self->widget);
}
return FALSE;
}
static void
event_view_realize (GtkWidget *widget)
{
GdkWindowAttr attributes;
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK |
GDK_KEY_RELEASE_MASK |
GDK_POINTER_MOTION_MASK |
GDK_BUTTON_MOTION_MASK |
GDK_EXPOSURE_MASK;
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes,
GDK_WA_X | GDK_WA_Y |
GDK_WA_VISUAL | GDK_WA_COLORMAP);
gdk_window_set_user_data (widget->window, widget);
widget->style = gtk_style_attach (widget->style, widget->window);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
}
static void
event_view_size_allocate (GtkWidget *w, GdkRectangle *r)
{
EventView *self = (EventView *) w;
GTK_WIDGET_CLASS (event_view_parent_class)->size_allocate (w, r);
self->mag_x = w->allocation.width - self->mag_size - 10;
self->mag_y = w->allocation.height - self->mag_size - 10;
}
static void
event_view_finalize (GObject *obj)
{
G_OBJECT_CLASS (event_view_parent_class)->finalize (obj);
}
static void
event_view_class_init (EventViewClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
object_class->finalize = event_view_finalize;
widget_class->realize = event_view_realize;
widget_class->size_allocate = event_view_size_allocate;
widget_class->expose_event = event_view_expose;
widget_class->button_press_event = event_view_button_press;
widget_class->button_release_event = event_view_button_release;
widget_class->motion_notify_event = event_view_motion;
}
static void
event_view_init (EventView *self)
{
self->mag_zoom = 10;
self->mag_size = 200;
}
static traps_t *
traps_new (void)
{
traps_t *t;
t = g_malloc (sizeof (traps_t) + 16 * sizeof (trapezoid_t));
t->size = 16;
t->num_traps = 0;
return t;
}
static edges_t *
_edges_add_edge (edges_t *edges, edge_t *e, box_t *extents)
{
if (e->top < extents->p1.y)
extents->p1.y = e->top;
if (e->bottom > extents->p2.y)
extents->p2.y = e->bottom;
_compute_intersection_point (&e->line, e->top, &e->p1);
_compute_intersection_point (&e->line, e->bottom, &e->p2);
if (e->p1.x < extents->p1.x)
extents->p1.x = e->p1.x;
if (e->p2.x < extents->p1.x)
extents->p1.x = e->p2.x;
if (e->p1.x > extents->p2.x)
extents->p2.x = e->p1.x;
if (e->p2.x > extents->p2.x)
extents->p2.x = e->p2.x;
if (edges->num_edges == edges->size) {
edges->size *= 2;
edges = g_realloc (edges,
sizeof (edges_t) + edges->size * sizeof (edge_t));
}
g_hash_table_insert (edges->ht,
GUINT_TO_POINTER (e->id),
GUINT_TO_POINTER (edges->num_edges));
edges->edges[edges->num_edges++] = *e;
return edges;
}
static void
_events_add_event (events_t *events,
int type,
int x, int y,
gulong e1, gulong e2)
{
event_t *e;
if (events->num_events == events->size_events) {
int newsize = 2 * events->size_events;
void *newevents;
newevents = g_renew (event_t, events->events, newsize);
events->events = newevents;
events->size_events = newsize;
}
e = &events->events[events->num_events++];
e->type = type;
e->x = x;
e->y = y;
e->e1 = e1;
e->e2 = e2;
}
static edges_t *
edges_new (void)
{
edges_t *t;
t = g_malloc (sizeof (edges_t) + 16 * sizeof (edge_t));
t->ht = g_hash_table_new (NULL, NULL);
t->size = 16;
t->num_edges = 0;
return t;
}
static events_t *
events_new (void)
{
events_t *events;
events = g_malloc (sizeof (events_t));
events->next = NULL;
events->prev = NULL;
events->events = g_new (event_t, 16);
events->size_events = 16;
events->num_events = 0;
events->current_event = 0;
events->edges = edges_new ();
events->prototraps = traps_new ();
events->traps = traps_new ();
events->extents.p1.x = G_MAXDOUBLE;
events->extents.p1.y = G_MAXDOUBLE;
events->extents.p2.x = -G_MAXDOUBLE;
events->extents.p2.y = -G_MAXDOUBLE;
return events;
}
static void
events_read (EventView *ev, const char *filename)
{
FILE *file;
file = fopen (filename, "r");
if (file != NULL) {
char *line = NULL;
size_t len = 0;
events_t *events;
events = ev->events_list = events_new ();
while (getline (&line, &len, file) != -1) {
line = g_strstrip (line);
if (*line == '\0') {
events->next = events_new ();
events->next->prev = events;
events = events->next;
} else if (g_str_has_prefix (line, "edge:")) {
edge_t edge;
sscanf (line, "edge: %lu (%lf, %lf) (%lf, %lf) (%lf, %lf) %d",
&edge.id,
&edge.line.p1.x,
&edge.line.p1.y,
&edge.line.p2.x,
&edge.line.p2.y,
&edge.top,
&edge.bottom,
&edge.dir);
events->edges = _edges_add_edge (events->edges,
&edge,
&events->extents);
} else if (g_str_has_prefix (line, "event:")) {
int type;
int x,y;
gulong e1, e2;
sscanf (line, "event: %d (%d, %d) %lu %lu",
&type, &x, &y,
&e1, &e2);
_events_add_event (events, type, x, y, e1, e2);
} else if (g_str_has_prefix (line, "begin trap:")) {
int top;
gulong e1, e2;
sscanf (line, "begin trap: %lu %lu %u", &e1, &e2, &top);
_events_add_event (events, START_TRAP, top, 0, e1, e2);
} else if (g_str_has_prefix (line, "end trap:")) {
int top, bottom;
gulong e1, e2;
sscanf (line, "end trap: %lu %lu %d %d",
&e1, &e2, &top, &bottom);
_events_add_event (events, END_TRAP, top, bottom, e1, e2);
}
}
ev->current_events = ev->events_list;
free (line);
fclose (file);
}
}
static gboolean
timeout_advance (EventView *self)
{
event_next (self);
gtk_widget_queue_draw (&self->widget);
return TRUE;
}
int
main (int argc, char **argv)
{
EventView *ev;
GtkWidget *window, *hbox;
gtk_init (&argc, &argv);
hbox = gtk_hbox_new (TRUE, 0);
ev = g_object_new (event_view_get_type (), NULL);
gtk_box_pack_start (GTK_BOX (hbox), &ev->widget, TRUE, TRUE, 0);
gtk_widget_show (&ev->widget);
events_read (ev, argv[1]);
g_timeout_add (750, (GSourceFunc) timeout_advance, ev);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request (window, 800, 800);
g_signal_connect (window, "delete-event",
G_CALLBACK (gtk_main_quit), NULL);
gtk_container_add (GTK_CONTAINER (window), hbox);
gtk_widget_show (hbox);
gtk_widget_show (window);
gtk_main ();
return 0;
}