blob: 7c0be69b712a73f8826b03ce3d1f94266dfcded7 [file] [log] [blame]
/*
* Copyright © 2004 Carl Worth
* Copyright © 2006 Red Hat, Inc.
* Copyright © 2008 Chris Wilson
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*
* The Initial Developer of the Original Code is Carl Worth
*
* Contributor(s):
* Carl D. Worth <cworth@cworth.org>
* Chris Wilson <chris@chris-wilson.co.uk>
*/
/* Provide definitions for standalone compilation */
#include "cairoint.h"
#include "cairo-boxes-private.h"
#include "cairo-combsort-inline.h"
#include "cairo-error-private.h"
#include "cairo-traps-private.h"
typedef struct _cairo_bo_edge cairo_bo_edge_t;
typedef struct _cairo_bo_trap cairo_bo_trap_t;
/* A deferred trapezoid of an edge */
struct _cairo_bo_trap {
cairo_bo_edge_t *right;
int32_t top;
};
struct _cairo_bo_edge {
cairo_edge_t edge;
cairo_bo_edge_t *prev;
cairo_bo_edge_t *next;
cairo_bo_trap_t deferred_trap;
};
typedef enum {
CAIRO_BO_EVENT_TYPE_START,
CAIRO_BO_EVENT_TYPE_STOP
} cairo_bo_event_type_t;
typedef struct _cairo_bo_event {
cairo_bo_event_type_t type;
cairo_point_t point;
cairo_bo_edge_t *edge;
} cairo_bo_event_t;
typedef struct _cairo_bo_sweep_line {
cairo_bo_event_t **events;
cairo_bo_edge_t *head;
cairo_bo_edge_t *stopped;
int32_t current_y;
cairo_bo_edge_t *current_edge;
} cairo_bo_sweep_line_t;
static inline int
_cairo_point_compare (const cairo_point_t *a,
const cairo_point_t *b)
{
int cmp;
cmp = a->y - b->y;
if (likely (cmp))
return cmp;
return a->x - b->x;
}
static inline int
_cairo_bo_edge_compare (const cairo_bo_edge_t *a,
const cairo_bo_edge_t *b)
{
int cmp;
cmp = a->edge.line.p1.x - b->edge.line.p1.x;
if (likely (cmp))
return cmp;
return b->edge.bottom - a->edge.bottom;
}
static inline int
cairo_bo_event_compare (const cairo_bo_event_t *a,
const cairo_bo_event_t *b)
{
int cmp;
cmp = _cairo_point_compare (&a->point, &b->point);
if (likely (cmp))
return cmp;
cmp = a->type - b->type;
if (cmp)
return cmp;
return a - b;
}
static inline cairo_bo_event_t *
_cairo_bo_event_dequeue (cairo_bo_sweep_line_t *sweep_line)
{
return *sweep_line->events++;
}
CAIRO_COMBSORT_DECLARE (_cairo_bo_event_queue_sort,
cairo_bo_event_t *,
cairo_bo_event_compare)
static void
_cairo_bo_sweep_line_init (cairo_bo_sweep_line_t *sweep_line,
cairo_bo_event_t **events,
int num_events)
{
_cairo_bo_event_queue_sort (events, num_events);
events[num_events] = NULL;
sweep_line->events = events;
sweep_line->head = NULL;
sweep_line->current_y = INT32_MIN;
sweep_line->current_edge = NULL;
}
static void
_cairo_bo_sweep_line_insert (cairo_bo_sweep_line_t *sweep_line,
cairo_bo_edge_t *edge)
{
if (sweep_line->current_edge != NULL) {
cairo_bo_edge_t *prev, *next;
int cmp;
cmp = _cairo_bo_edge_compare (sweep_line->current_edge, edge);
if (cmp < 0) {
prev = sweep_line->current_edge;
next = prev->next;
while (next != NULL && _cairo_bo_edge_compare (next, edge) < 0)
prev = next, next = prev->next;
prev->next = edge;
edge->prev = prev;
edge->next = next;
if (next != NULL)
next->prev = edge;
} else if (cmp > 0) {
next = sweep_line->current_edge;
prev = next->prev;
while (prev != NULL && _cairo_bo_edge_compare (prev, edge) > 0)
next = prev, prev = next->prev;
next->prev = edge;
edge->next = next;
edge->prev = prev;
if (prev != NULL)
prev->next = edge;
else
sweep_line->head = edge;
} else {
prev = sweep_line->current_edge;
edge->prev = prev;
edge->next = prev->next;
if (prev->next != NULL)
prev->next->prev = edge;
prev->next = edge;
}
} else {
sweep_line->head = edge;
}
sweep_line->current_edge = edge;
}
static void
_cairo_bo_sweep_line_delete (cairo_bo_sweep_line_t *sweep_line,
cairo_bo_edge_t *edge)
{
if (edge->prev != NULL)
edge->prev->next = edge->next;
else
sweep_line->head = edge->next;
if (edge->next != NULL)
edge->next->prev = edge->prev;
if (sweep_line->current_edge == edge)
sweep_line->current_edge = edge->prev ? edge->prev : edge->next;
}
static inline cairo_bool_t
edges_collinear (const cairo_bo_edge_t *a, const cairo_bo_edge_t *b)
{
return a->edge.line.p1.x == b->edge.line.p1.x;
}
static cairo_status_t
_cairo_bo_edge_end_trap (cairo_bo_edge_t *left,
int32_t bot,
cairo_bool_t do_traps,
void *container)
{
cairo_bo_trap_t *trap = &left->deferred_trap;
cairo_status_t status = CAIRO_STATUS_SUCCESS;
/* Only emit (trivial) non-degenerate trapezoids with positive height. */
if (likely (trap->top < bot)) {
if (do_traps) {
_cairo_traps_add_trap (container,
trap->top, bot,
&left->edge.line, &trap->right->edge.line);
status = _cairo_traps_status ((cairo_traps_t *) container);
} else {
cairo_box_t box;
box.p1.x = left->edge.line.p1.x;
box.p1.y = trap->top;
box.p2.x = trap->right->edge.line.p1.x;
box.p2.y = bot;
status = _cairo_boxes_add (container, CAIRO_ANTIALIAS_DEFAULT, &box);
}
}
trap->right = NULL;
return status;
}
/* Start a new trapezoid at the given top y coordinate, whose edges
* are `edge' and `edge->next'. If `edge' already has a trapezoid,
* then either add it to the traps in `traps', if the trapezoid's
* right edge differs from `edge->next', or do nothing if the new
* trapezoid would be a continuation of the existing one. */
static inline cairo_status_t
_cairo_bo_edge_start_or_continue_trap (cairo_bo_edge_t *left,
cairo_bo_edge_t *right,
int top,
cairo_bool_t do_traps,
void *container)
{
cairo_status_t status;
if (left->deferred_trap.right == right)
return CAIRO_STATUS_SUCCESS;
if (left->deferred_trap.right != NULL) {
if (right != NULL && edges_collinear (left->deferred_trap.right, right))
{
/* continuation on right, so just swap edges */
left->deferred_trap.right = right;
return CAIRO_STATUS_SUCCESS;
}
status = _cairo_bo_edge_end_trap (left, top, do_traps, container);
if (unlikely (status))
return status;
}
if (right != NULL && ! edges_collinear (left, right)) {
left->deferred_trap.top = top;
left->deferred_trap.right = right;
}
return CAIRO_STATUS_SUCCESS;
}
static inline cairo_status_t
_active_edges_to_traps (cairo_bo_edge_t *left,
int32_t top,
cairo_fill_rule_t fill_rule,
cairo_bool_t do_traps,
void *container)
{
cairo_bo_edge_t *right;
cairo_status_t status;
if (fill_rule == CAIRO_FILL_RULE_WINDING) {
while (left != NULL) {
int in_out;
/* Greedily search for the closing edge, so that we generate the
* maximal span width with the minimal number of trapezoids.
*/
in_out = left->edge.dir;
/* Check if there is a co-linear edge with an existing trap */
right = left->next;
if (left->deferred_trap.right == NULL) {
while (right != NULL && right->deferred_trap.right == NULL)
right = right->next;
if (right != NULL && edges_collinear (left, right)) {
/* continuation on left */
left->deferred_trap = right->deferred_trap;
right->deferred_trap.right = NULL;
}
}
/* End all subsumed traps */
right = left->next;
while (right != NULL) {
if (right->deferred_trap.right != NULL) {
status = _cairo_bo_edge_end_trap (right, top, do_traps, container);
if (unlikely (status))
return status;
}
in_out += right->edge.dir;
if (in_out == 0) {
/* skip co-linear edges */
if (right->next == NULL ||
! edges_collinear (right, right->next))
{
break;
}
}
right = right->next;
}
status = _cairo_bo_edge_start_or_continue_trap (left, right, top,
do_traps, container);
if (unlikely (status))
return status;
left = right;
if (left != NULL)
left = left->next;
}
} else {
while (left != NULL) {
int in_out = 0;
right = left->next;
while (right != NULL) {
if (right->deferred_trap.right != NULL) {
status = _cairo_bo_edge_end_trap (right, top, do_traps, container);
if (unlikely (status))
return status;
}
if ((in_out++ & 1) == 0) {
cairo_bo_edge_t *next;
cairo_bool_t skip = FALSE;
/* skip co-linear edges */
next = right->next;
if (next != NULL)
skip = edges_collinear (right, next);
if (! skip)
break;
}
right = right->next;
}
status = _cairo_bo_edge_start_or_continue_trap (left, right, top,
do_traps, container);
if (unlikely (status))
return status;
left = right;
if (left != NULL)
left = left->next;
}
}
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_bentley_ottmann_tessellate_rectilinear (cairo_bo_event_t **start_events,
int num_events,
cairo_fill_rule_t fill_rule,
cairo_bool_t do_traps,
void *container)
{
cairo_bo_sweep_line_t sweep_line;
cairo_bo_event_t *event;
cairo_status_t status;
_cairo_bo_sweep_line_init (&sweep_line, start_events, num_events);
while ((event = _cairo_bo_event_dequeue (&sweep_line))) {
if (event->point.y != sweep_line.current_y) {
status = _active_edges_to_traps (sweep_line.head,
sweep_line.current_y,
fill_rule, do_traps, container);
if (unlikely (status))
return status;
sweep_line.current_y = event->point.y;
}
switch (event->type) {
case CAIRO_BO_EVENT_TYPE_START:
_cairo_bo_sweep_line_insert (&sweep_line, event->edge);
break;
case CAIRO_BO_EVENT_TYPE_STOP:
_cairo_bo_sweep_line_delete (&sweep_line, event->edge);
if (event->edge->deferred_trap.right != NULL) {
status = _cairo_bo_edge_end_trap (event->edge,
sweep_line.current_y,
do_traps, container);
if (unlikely (status))
return status;
}
break;
}
}
return CAIRO_STATUS_SUCCESS;
}
cairo_status_t
_cairo_bentley_ottmann_tessellate_rectilinear_polygon_to_boxes (const cairo_polygon_t *polygon,
cairo_fill_rule_t fill_rule,
cairo_boxes_t *boxes)
{
cairo_status_t status;
cairo_bo_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_event_t)];
cairo_bo_event_t *events;
cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1];
cairo_bo_event_t **event_ptrs;
cairo_bo_edge_t stack_edges[ARRAY_LENGTH (stack_events)];
cairo_bo_edge_t *edges;
int num_events;
int i, j;
if (unlikely (polygon->num_edges == 0))
return CAIRO_STATUS_SUCCESS;
num_events = 2 * polygon->num_edges;
events = stack_events;
event_ptrs = stack_event_ptrs;
edges = stack_edges;
if (num_events > ARRAY_LENGTH (stack_events)) {
events = _cairo_malloc_ab_plus_c (num_events,
sizeof (cairo_bo_event_t) +
sizeof (cairo_bo_edge_t) +
sizeof (cairo_bo_event_t *),
sizeof (cairo_bo_event_t *));
if (unlikely (events == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
event_ptrs = (cairo_bo_event_t **) (events + num_events);
edges = (cairo_bo_edge_t *) (event_ptrs + num_events + 1);
}
for (i = j = 0; i < polygon->num_edges; i++) {
edges[i].edge = polygon->edges[i];
edges[i].deferred_trap.right = NULL;
edges[i].prev = NULL;
edges[i].next = NULL;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_START;
events[j].point.y = polygon->edges[i].top;
events[j].point.x = polygon->edges[i].line.p1.x;
events[j].edge = &edges[i];
j++;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_STOP;
events[j].point.y = polygon->edges[i].bottom;
events[j].point.x = polygon->edges[i].line.p1.x;
events[j].edge = &edges[i];
j++;
}
status = _cairo_bentley_ottmann_tessellate_rectilinear (event_ptrs, j,
fill_rule,
FALSE, boxes);
if (events != stack_events)
free (events);
return status;
}
cairo_status_t
_cairo_bentley_ottmann_tessellate_rectilinear_traps (cairo_traps_t *traps,
cairo_fill_rule_t fill_rule)
{
cairo_bo_event_t stack_events[CAIRO_STACK_ARRAY_LENGTH (cairo_bo_event_t)];
cairo_bo_event_t *events;
cairo_bo_event_t *stack_event_ptrs[ARRAY_LENGTH (stack_events) + 1];
cairo_bo_event_t **event_ptrs;
cairo_bo_edge_t stack_edges[ARRAY_LENGTH (stack_events)];
cairo_bo_edge_t *edges;
cairo_status_t status;
int i, j, k;
if (unlikely (traps->num_traps == 0))
return CAIRO_STATUS_SUCCESS;
assert (traps->is_rectilinear);
i = 4 * traps->num_traps;
events = stack_events;
event_ptrs = stack_event_ptrs;
edges = stack_edges;
if (i > ARRAY_LENGTH (stack_events)) {
events = _cairo_malloc_ab_plus_c (i,
sizeof (cairo_bo_event_t) +
sizeof (cairo_bo_edge_t) +
sizeof (cairo_bo_event_t *),
sizeof (cairo_bo_event_t *));
if (unlikely (events == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
event_ptrs = (cairo_bo_event_t **) (events + i);
edges = (cairo_bo_edge_t *) (event_ptrs + i + 1);
}
for (i = j = k = 0; i < traps->num_traps; i++) {
edges[k].edge.top = traps->traps[i].top;
edges[k].edge.bottom = traps->traps[i].bottom;
edges[k].edge.line = traps->traps[i].left;
edges[k].edge.dir = 1;
edges[k].deferred_trap.right = NULL;
edges[k].prev = NULL;
edges[k].next = NULL;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_START;
events[j].point.y = traps->traps[i].top;
events[j].point.x = traps->traps[i].left.p1.x;
events[j].edge = &edges[k];
j++;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_STOP;
events[j].point.y = traps->traps[i].bottom;
events[j].point.x = traps->traps[i].left.p1.x;
events[j].edge = &edges[k];
j++;
k++;
edges[k].edge.top = traps->traps[i].top;
edges[k].edge.bottom = traps->traps[i].bottom;
edges[k].edge.line = traps->traps[i].right;
edges[k].edge.dir = -1;
edges[k].deferred_trap.right = NULL;
edges[k].prev = NULL;
edges[k].next = NULL;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_START;
events[j].point.y = traps->traps[i].top;
events[j].point.x = traps->traps[i].right.p1.x;
events[j].edge = &edges[k];
j++;
event_ptrs[j] = &events[j];
events[j].type = CAIRO_BO_EVENT_TYPE_STOP;
events[j].point.y = traps->traps[i].bottom;
events[j].point.x = traps->traps[i].right.p1.x;
events[j].edge = &edges[k];
j++;
k++;
}
_cairo_traps_clear (traps);
status = _cairo_bentley_ottmann_tessellate_rectilinear (event_ptrs, j,
fill_rule,
TRUE, traps);
traps->is_rectilinear = TRUE;
if (events != stack_events)
free (events);
return status;
}