зеркало из https://github.com/mozilla/pjs.git
462 строки
13 KiB
C
462 строки
13 KiB
C
/* Libart_LGPL - library of basic graphic primitives
|
|
* Copyright (C) 1998-2000 Raph Levien
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/* The spiffy antialiased renderer for sorted vector paths. */
|
|
|
|
#include <math.h>
|
|
#include <string.h> /* for memmove */
|
|
#include "art_misc.h"
|
|
|
|
#include "art_rect.h"
|
|
#include "art_svp.h"
|
|
|
|
#include "art_svp_render_aa.h"
|
|
#include "stdio.h"
|
|
|
|
typedef double artfloat;
|
|
|
|
struct _ArtSVPRenderAAIter {
|
|
const ArtSVP *svp;
|
|
int x0, x1;
|
|
int y;
|
|
int seg_ix;
|
|
|
|
int *active_segs;
|
|
int n_active_segs;
|
|
int *cursor;
|
|
artfloat *seg_x;
|
|
artfloat *seg_dx;
|
|
|
|
ArtSVPRenderAAStep *steps;
|
|
};
|
|
|
|
static void
|
|
art_svp_render_insert_active (int i, int *active_segs, int n_active_segs,
|
|
artfloat *seg_x, artfloat *seg_dx)
|
|
{
|
|
int j;
|
|
artfloat x;
|
|
int tmp1, tmp2;
|
|
|
|
/* this is a cheap hack to get ^'s sorted correctly */
|
|
x = seg_x[i] + 0.001 * seg_dx[i];
|
|
for (j = 0; j < n_active_segs && seg_x[active_segs[j]] < x; j++);
|
|
|
|
tmp1 = i;
|
|
while (j < n_active_segs)
|
|
{
|
|
tmp2 = active_segs[j];
|
|
active_segs[j] = tmp1;
|
|
tmp1 = tmp2;
|
|
j++;
|
|
}
|
|
active_segs[j] = tmp1;
|
|
}
|
|
|
|
static void
|
|
art_svp_render_delete_active (int *active_segs, int j, int n_active_segs)
|
|
{
|
|
int k;
|
|
|
|
for (k = j; k < n_active_segs; k++)
|
|
active_segs[k] = active_segs[k + 1];
|
|
}
|
|
|
|
#define EPSILON 1e-6
|
|
|
|
/* Render the sorted vector path in the given rectangle, antialiased.
|
|
|
|
This interface uses a callback for the actual pixel rendering. The
|
|
callback is called y1 - y0 times (once for each scan line). The y
|
|
coordinate is given as an argument for convenience (it could be
|
|
stored in the callback's private data and incremented on each
|
|
call).
|
|
|
|
The rendered polygon is represented in a semi-runlength format: a
|
|
start value and a sequence of "steps". Each step has an x
|
|
coordinate and a value delta. The resulting value at position x is
|
|
equal to the sum of the start value and all step delta values for
|
|
which the step x coordinate is less than or equal to x. An
|
|
efficient algorithm will traverse the steps left to right, keeping
|
|
a running sum.
|
|
|
|
All x coordinates in the steps are guaranteed to be x0 <= x < x1.
|
|
(This guarantee is a change from the gfonted vpaar renderer, and is
|
|
designed to simplify the callback).
|
|
|
|
There is now a further guarantee that no two steps will have the
|
|
same x value. This may allow for further speedup and simplification
|
|
of renderers.
|
|
|
|
The value 0x8000 represents 0% coverage by the polygon, while
|
|
0xff8000 represents 100% coverage. This format is designed so that
|
|
>> 16 results in a standard 0x00..0xff value range, with nice
|
|
rounding.
|
|
|
|
Status of this routine:
|
|
|
|
Basic correctness: OK
|
|
|
|
Numerical stability: pretty good, although probably not
|
|
bulletproof.
|
|
|
|
Speed: Needs more aggressive culling of bounding boxes. Can
|
|
probably speed up the [x0,x1) clipping of step values. Can do more
|
|
of the step calculation in fixed point.
|
|
|
|
Precision: No known problems, although it should be tested
|
|
thoroughly, especially for symmetry.
|
|
|
|
*/
|
|
|
|
ArtSVPRenderAAIter *
|
|
art_svp_render_aa_iter (const ArtSVP *svp,
|
|
int x0, int y0, int x1, int y1)
|
|
{
|
|
ArtSVPRenderAAIter *iter = art_new (ArtSVPRenderAAIter, 1);
|
|
|
|
iter->svp = svp;
|
|
iter->y = y0;
|
|
iter->x0 = x0;
|
|
iter->x1 = x1;
|
|
iter->seg_ix = 0;
|
|
|
|
iter->active_segs = art_new (int, svp->n_segs);
|
|
iter->cursor = art_new (int, svp->n_segs);
|
|
iter->seg_x = art_new (artfloat, svp->n_segs);
|
|
iter->seg_dx = art_new (artfloat, svp->n_segs);
|
|
iter->steps = art_new (ArtSVPRenderAAStep, x1 - x0);
|
|
iter->n_active_segs = 0;
|
|
|
|
return iter;
|
|
}
|
|
|
|
#define ADD_STEP(xpos, xdelta) \
|
|
/* stereotype code fragment for adding a step */ \
|
|
if (n_steps == 0 || steps[n_steps - 1].x < xpos) \
|
|
{ \
|
|
sx = n_steps; \
|
|
steps[sx].x = xpos; \
|
|
steps[sx].delta = xdelta; \
|
|
n_steps++; \
|
|
} \
|
|
else \
|
|
{ \
|
|
for (sx = n_steps; sx > 0; sx--) \
|
|
{ \
|
|
if (steps[sx - 1].x == xpos) \
|
|
{ \
|
|
steps[sx - 1].delta += xdelta; \
|
|
sx = n_steps; \
|
|
break; \
|
|
} \
|
|
else if (steps[sx - 1].x < xpos) \
|
|
{ \
|
|
break; \
|
|
} \
|
|
} \
|
|
if (sx < n_steps) \
|
|
{ \
|
|
memmove (&steps[sx + 1], &steps[sx], \
|
|
(n_steps - sx) * sizeof(steps[0])); \
|
|
steps[sx].x = xpos; \
|
|
steps[sx].delta = xdelta; \
|
|
n_steps++; \
|
|
} \
|
|
}
|
|
|
|
void
|
|
art_svp_render_aa_iter_step (ArtSVPRenderAAIter *iter, int *p_start,
|
|
ArtSVPRenderAAStep **p_steps, int *p_n_steps)
|
|
{
|
|
const ArtSVP *svp = iter->svp;
|
|
int *active_segs = iter->active_segs;
|
|
int n_active_segs = iter->n_active_segs;
|
|
int *cursor = iter->cursor;
|
|
artfloat *seg_x = iter->seg_x;
|
|
artfloat *seg_dx = iter->seg_dx;
|
|
int i = iter->seg_ix;
|
|
int j;
|
|
int x0 = iter->x0;
|
|
int x1 = iter->x1;
|
|
int y = iter->y;
|
|
int seg_index;
|
|
|
|
int x;
|
|
ArtSVPRenderAAStep *steps = iter->steps;
|
|
int n_steps;
|
|
artfloat y_top, y_bot;
|
|
artfloat x_top, x_bot;
|
|
artfloat x_min, x_max;
|
|
int ix_min, ix_max;
|
|
artfloat delta; /* delta should be int too? */
|
|
int last, this;
|
|
int xdelta;
|
|
artfloat rslope, drslope;
|
|
int start;
|
|
const ArtSVPSeg *seg;
|
|
int curs;
|
|
artfloat dy;
|
|
|
|
int sx;
|
|
|
|
/* insert new active segments */
|
|
for (; i < svp->n_segs && svp->segs[i].bbox.y0 < y + 1; i++)
|
|
{
|
|
if (svp->segs[i].bbox.y1 > y &&
|
|
svp->segs[i].bbox.x0 < x1)
|
|
{
|
|
seg = &svp->segs[i];
|
|
/* move cursor to topmost vector which overlaps [y,y+1) */
|
|
for (curs = 0; seg->points[curs + 1].y < y; curs++);
|
|
cursor[i] = curs;
|
|
dy = seg->points[curs + 1].y - seg->points[curs].y;
|
|
if (fabs (dy) >= EPSILON)
|
|
seg_dx[i] = (seg->points[curs + 1].x - seg->points[curs].x) /
|
|
dy;
|
|
else
|
|
seg_dx[i] = 1e12;
|
|
seg_x[i] = seg->points[curs].x +
|
|
(y - seg->points[curs].y) * seg_dx[i];
|
|
art_svp_render_insert_active (i, active_segs, n_active_segs++,
|
|
seg_x, seg_dx);
|
|
}
|
|
}
|
|
|
|
n_steps = 0;
|
|
|
|
/* render the runlengths, advancing and deleting as we go */
|
|
start = 0x8000;
|
|
|
|
for (j = 0; j < n_active_segs; j++)
|
|
{
|
|
seg_index = active_segs[j];
|
|
seg = &svp->segs[seg_index];
|
|
curs = cursor[seg_index];
|
|
while (curs != seg->n_points - 1 &&
|
|
seg->points[curs].y < y + 1)
|
|
{
|
|
y_top = y;
|
|
if (y_top < seg->points[curs].y)
|
|
y_top = seg->points[curs].y;
|
|
y_bot = y + 1;
|
|
if (y_bot > seg->points[curs + 1].y)
|
|
y_bot = seg->points[curs + 1].y;
|
|
if (y_top != y_bot) {
|
|
delta = (seg->dir ? 16711680.0 : -16711680.0) *
|
|
(y_bot - y_top);
|
|
x_top = seg_x[seg_index] + (y_top - y) * seg_dx[seg_index];
|
|
x_bot = seg_x[seg_index] + (y_bot - y) * seg_dx[seg_index];
|
|
if (x_top < x_bot)
|
|
{
|
|
x_min = x_top;
|
|
x_max = x_bot;
|
|
}
|
|
else
|
|
{
|
|
x_min = x_bot;
|
|
x_max = x_top;
|
|
}
|
|
ix_min = floor (x_min);
|
|
ix_max = floor (x_max);
|
|
if (ix_min >= x1)
|
|
{
|
|
/* skip; it starts to the right of the render region */
|
|
}
|
|
else if (ix_max < x0)
|
|
/* it ends to the left of the render region */
|
|
start += delta;
|
|
else if (ix_min == ix_max)
|
|
{
|
|
/* case 1, antialias a single pixel */
|
|
xdelta = (ix_min + 1 - (x_min + x_max) * 0.5) * delta;
|
|
|
|
ADD_STEP(ix_min, xdelta)
|
|
|
|
if (ix_min + 1 < x1)
|
|
{
|
|
xdelta = delta - xdelta;
|
|
|
|
ADD_STEP(ix_min + 1, xdelta)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* case 2, antialias a run */
|
|
rslope = 1.0 / fabs (seg_dx[seg_index]);
|
|
drslope = delta * rslope;
|
|
last =
|
|
drslope * 0.5 *
|
|
(ix_min + 1 - x_min) * (ix_min + 1 - x_min);
|
|
xdelta = last;
|
|
if (ix_min >= x0)
|
|
{
|
|
ADD_STEP(ix_min, xdelta)
|
|
|
|
x = ix_min + 1;
|
|
}
|
|
else
|
|
{
|
|
start += last;
|
|
x = x0;
|
|
}
|
|
if (ix_max > x1)
|
|
ix_max = x1;
|
|
for (; x < ix_max; x++)
|
|
{
|
|
this = (seg->dir ? 16711680.0 : -16711680.0) * rslope *
|
|
(x + 0.5 - x_min);
|
|
xdelta = this - last;
|
|
last = this;
|
|
|
|
ADD_STEP(x, xdelta)
|
|
}
|
|
if (x < x1)
|
|
{
|
|
this =
|
|
delta * (1 - 0.5 *
|
|
(x_max - ix_max) * (x_max - ix_max) *
|
|
rslope);
|
|
xdelta = this - last;
|
|
last = this;
|
|
|
|
ADD_STEP(x, xdelta)
|
|
|
|
if (x + 1 < x1)
|
|
{
|
|
xdelta = delta - last;
|
|
|
|
ADD_STEP(x + 1, xdelta)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
curs++;
|
|
if (curs != seg->n_points - 1 &&
|
|
seg->points[curs].y < y + 1)
|
|
{
|
|
dy = seg->points[curs + 1].y - seg->points[curs].y;
|
|
if (fabs (dy) >= EPSILON)
|
|
seg_dx[seg_index] = (seg->points[curs + 1].x -
|
|
seg->points[curs].x) / dy;
|
|
else
|
|
seg_dx[seg_index] = 1e12;
|
|
seg_x[seg_index] = seg->points[curs].x +
|
|
(y - seg->points[curs].y) * seg_dx[seg_index];
|
|
}
|
|
/* break here, instead of duplicating predicate in while? */
|
|
}
|
|
if (seg->points[curs].y >= y + 1)
|
|
{
|
|
curs--;
|
|
cursor[seg_index] = curs;
|
|
seg_x[seg_index] += seg_dx[seg_index];
|
|
}
|
|
else
|
|
{
|
|
art_svp_render_delete_active (active_segs, j--,
|
|
--n_active_segs);
|
|
}
|
|
}
|
|
|
|
*p_start = start;
|
|
*p_steps = steps;
|
|
*p_n_steps = n_steps;
|
|
|
|
iter->seg_ix = i;
|
|
iter->n_active_segs = n_active_segs;
|
|
iter->y++;
|
|
}
|
|
|
|
void
|
|
art_svp_render_aa_iter_done (ArtSVPRenderAAIter *iter)
|
|
{
|
|
art_free (iter->steps);
|
|
|
|
art_free (iter->seg_dx);
|
|
art_free (iter->seg_x);
|
|
art_free (iter->cursor);
|
|
art_free (iter->active_segs);
|
|
art_free (iter);
|
|
}
|
|
|
|
/**
|
|
* art_svp_render_aa: Render SVP antialiased.
|
|
* @svp: The #ArtSVP to render.
|
|
* @x0: Left coordinate of destination rectangle.
|
|
* @y0: Top coordinate of destination rectangle.
|
|
* @x1: Right coordinate of destination rectangle.
|
|
* @y1: Bottom coordinate of destination rectangle.
|
|
* @callback: The callback which actually paints the pixels.
|
|
* @callback_data: Private data for @callback.
|
|
*
|
|
* Renders the sorted vector path in the given rectangle, antialiased.
|
|
*
|
|
* This interface uses a callback for the actual pixel rendering. The
|
|
* callback is called @y1 - @y0 times (once for each scan line). The y
|
|
* coordinate is given as an argument for convenience (it could be
|
|
* stored in the callback's private data and incremented on each
|
|
* call).
|
|
*
|
|
* The rendered polygon is represented in a semi-runlength format: a
|
|
* start value and a sequence of "steps". Each step has an x
|
|
* coordinate and a value delta. The resulting value at position x is
|
|
* equal to the sum of the start value and all step delta values for
|
|
* which the step x coordinate is less than or equal to x. An
|
|
* efficient algorithm will traverse the steps left to right, keeping
|
|
* a running sum.
|
|
*
|
|
* All x coordinates in the steps are guaranteed to be @x0 <= x < @x1.
|
|
* (This guarantee is a change from the gfonted vpaar renderer from
|
|
* which this routine is derived, and is designed to simplify the
|
|
* callback).
|
|
*
|
|
* The value 0x8000 represents 0% coverage by the polygon, while
|
|
* 0xff8000 represents 100% coverage. This format is designed so that
|
|
* >> 16 results in a standard 0x00..0xff value range, with nice
|
|
* rounding.
|
|
*
|
|
**/
|
|
void
|
|
art_svp_render_aa (const ArtSVP *svp,
|
|
int x0, int y0, int x1, int y1,
|
|
void (*callback) (void *callback_data,
|
|
int y,
|
|
int start,
|
|
ArtSVPRenderAAStep *steps, int n_steps),
|
|
void *callback_data)
|
|
{
|
|
ArtSVPRenderAAIter *iter;
|
|
int y;
|
|
int start;
|
|
ArtSVPRenderAAStep *steps;
|
|
int n_steps;
|
|
|
|
iter = art_svp_render_aa_iter (svp, x0, y0, x1, y1);
|
|
|
|
|
|
for (y = y0; y < y1; y++)
|
|
{
|
|
art_svp_render_aa_iter_step (iter, &start, &steps, &n_steps);
|
|
(*callback) (callback_data, y, start, steps, n_steps);
|
|
}
|
|
|
|
art_svp_render_aa_iter_done (iter);
|
|
}
|