diff --git a/demos/src/ftgrays2.c b/demos/src/ftgrays2.c new file mode 100644 index 000000000..073e65770 --- /dev/null +++ b/demos/src/ftgrays2.c @@ -0,0 +1,1705 @@ +/*****************************************************************************/ +/* */ +/* ftgrays2.c - a new version of the standard FreeType anti-aliaser */ +/* */ +/* (c) 2000 David Turner - */ +/* */ +/* Beware, this code is still in heavy beta.. */ +/* */ +/* After writing a "perfect" anti-aliaser (see ftgrays.c), it is clear */ +/* that the standard FreeType renderer is better at generating glyph images */ +/* because it uses an approximation that simply produced more contrasted */ +/* edges, making its output more legible.. */ +/* */ +/* This code is an attempt to rewrite the standard renderer in order to */ +/* support the following: */ +/* */ +/* - get rid of al rendering artifacts produced by the original algorithm */ +/* - allow direct composition, by generating the output image as a "list" */ +/* of span in successive scan-lines (the standard code is forced to use */ +/* an intermediate buffer, and this is just _bad_ :-) */ +/* */ + +#include + +#define _STANDALONE_ + +#define xxxDEBUG_GRAYS +#define SPECIAL +#define HORZ + +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Overflow -2 + +#include "ftgrays2.h" + +/* include the FreeType main header if necessary */ +#ifndef _STANDALONE_ +#include "freetype.h" /* for FT_MulDiv & FT_Outline_Decompose */ +#endif + +#ifdef DEBUG_GRAYS +#include +#endif + +#ifndef FT_STATIC_RASTER + + #define RAS_ARG PRaster raster + #define RAS_ARG_ PRaster raster, + + #define RAS_VAR raster + #define RAS_VAR_ raster, + + #define ras (*raster) + +#else + + #define RAS_ARG + #define RAS_ARG_ + #define RAS_VAR + #define RAS_VAR_ + + static TRaster ras; + +#endif + +#define FMulDiv(a,b,c) ((long)(a)*(b)/(c)) + +#ifdef _STANDALONE_ +#define SMulDiv(a,b,c) FMulDiv(a,b,c) /* XXXX - TO BE CHANGED LATER */ +#else +#define SMulDiv(a,b,c) FT_MulDiv(a,b,c) +#endif + +/* note: PIXEL_BITS must not be less than 6 !! */ +#define PIXEL_BITS 6 + +#define ONE_PIXEL (1L << PIXEL_BITS) +#define ONE_HALF (ONE_PIXEL/2) +#define PIXEL_MASK (-1L << PIXEL_BITS) +#define TRUNC(x) ((x) >> PIXEL_BITS) +#define FRAC(x) ((x) & (ONE_PIXEL-1)) +#define SUBPIXELS(x) ((x) << PIXEL_BITS) +#define FLOOR(x) ((x) & -ONE_PIXEL) +#define CEILING(x) (((x)+ONE_PIXEL-1) & -ONE_PIXEL) +#define ROUND(x) (((x)+ONE_HALF) & -ONE_PIXEL) + +#define UPSCALE(x) ((x) << (PIXEL_BITS-6)) +#define DOWNSCALE(x) ((x) >> (PIXEL_BITS-6)) + +#define WRITE_CELL(top,u,v,dir) write_cell( RAS_VAR_ top, u, v, dir ) + +/****************************************************************************/ +/* */ +/* INITIALIZE THE CELLS TABLE */ +/* */ +static +void init_cells( RAS_ARG_ void* buffer, long byte_size ) +{ + ras.cells = (PCell)buffer; + ras.max_cells = byte_size / sizeof(TCell); + ras.cell_limit = ras.cells + ras.max_cells; + ras.num_cells = 0; +} + + +/****************************************************************************/ +/* */ +/* WRITE ONE CELL IN THE RENDER POOL */ +/* */ +static +int write_cell( RAS_ARG_ PCell cell, TPos u, TPos v, TDir dir ) +{ +#ifdef DEBUG_GRAYS + static const char dirs[5] = "udrl?"; +#endif + if (dir & dir_horizontal) + { + /* only keep horizontal cells within our clipping box */ + if ( u < ras.min_y || u >= ras.max_y || + v < ras.min_x || v >= ras.max_x ) goto Nope; + + /* get rid of horizontal cells with pos == 0, they're irrelevant */ + if ( FRAC(u) == 0 ) goto Nope; + + cell->y = TRUNC( u - ras.min_y ); + cell->x = TRUNC( v - ras.min_x ); + } + else + { + /* get rid of vertical cells that are below or above our clipping */ + /* box. Also discard all cells that are on the right of the clipping */ + /* box.. */ + if (u >= ras.max_x || v < ras.min_y || v >= ras.max_y) goto Nope; + u -= ras.min_x; + v -= ras.min_y; + + /* all cells that are on the left of the clipping box are located */ + /* on the same virtual "border" cell.. */ + if (u < 0) u = -1; + cell->x = TRUNC( u ); + cell->y = TRUNC( v ); + } + cell->dir = dir; + cell->pos = FRAC(u); + +#ifdef DEBUG_GRAYS + fprintf( stderr, "[%d,%d,%c,%d]\n", + (int)cell->y, + (int)cell->x, + dirs[dir], + cell->pos ); +#endif + return 1; +Nope: + return 0; +} + +/****************************************************************************/ +/* */ +/* COMPUTE THE OUTLINE BOUNDING BOX */ +/* */ +static +void compute_cbox( RAS_ARG_ FT_Outline* outline ) +{ + FT_Vector* vec = outline->points; + FT_Vector* limit = vec + outline->n_points; + + if ( outline->n_points <= 0 ) + { + ras.min_x = ras.max_x = 0; + ras.min_y = ras.max_y = 0; + goto Exit; + } + + ras.min_x = ras.max_x = vec->x; + ras.min_y = ras.max_y = vec->y; + vec++; + + for ( ; vec < limit; vec++ ) + { + TPos x = vec->x; + TPos y = vec->y; + + if ( x < ras.min_x ) ras.min_x = x; + if ( x > ras.max_x ) ras.max_x = x; + if ( y < ras.min_y ) ras.min_y = y; + if ( y > ras.max_y ) ras.max_y = y; + } + + /* grid-fit the bounding box to integer pixels */ + ras.min_x &= -64; + ras.min_y &= -64; + ras.max_x = (ras.max_x+63) & -64; + ras.max_y = (ras.max_y+63) & -64; + +Exit: + ras.min_ex = ras.min_x >> 6; + ras.max_ex = ras.max_x >> 6; + ras.min_ey = ras.min_y >> 6; + ras.max_ey = ras.max_y >> 6; +} + + /*************************************************************************/ + /* */ + /* */ + /* compute_intersects */ + /* */ + /* */ + /* Computes the scan-line intersections of a given line and store */ + /* the corresonding cells in the render pool.. */ + /* */ + /* */ + /* u1 :: The start u coordinate. */ + /* v1 :: The start v coordinate. */ + /* u2 :: The end u coordinate. */ + /* v2 :: The end v coordinate. */ + /* minv :: The minimum vertical grid coordinate. */ + /* maxv :: The maximum vertical grid coordinate. */ + /* dir :: The line direction.. */ + /* */ + /* */ + /* error code. 0 means success.. */ + /* */ + static + int compute_intersects( RAS_ARG_ TPos u1, TPos v1, + TPos u2, TPos v2, + TPos minv, TPos maxv, + TDir dir ) + { + TPos du, dv, u, v, iu, iv, ru, nu; + TScan e1, e2, size; + PCell top; + int reverse; + + /* exit if dv == 0 */ + if ( v1 == v2 ) goto Exit; + + /* adjust to scanline center */ + v1 -= ONE_HALF; + v2 -= ONE_HALF; + maxv -= ONE_PIXEL; + + /* reverse direction in order to get dv > 0 */ + reverse = 0; + if ( v2 < v1 ) + { + TPos tmp; + v1 = -v1; v2 = -v2; + tmp = minv; minv = -maxv; maxv = -tmp; + reverse = 1; + } + + /* check that we have an intersection */ + if ( v2 < minv || v1 > maxv ) + goto Exit; + + du = u2 - u1; + dv = v2 - v1; + + /* compute the first scanline in "e1" */ + e1 = CEILING(v1); + if (e1 == v1 && ras.joint) + e1 += ONE_PIXEL; + + /* compute the last scanline in "e2" */ + if (v2 <= maxv) + { + e2 = FLOOR(v2); + ras.joint = (v2 == e2); + } + else + { + e2 = maxv; + ras.joint = 0; + } + + size = TRUNC(e2-e1) + 1; + if (size <= 0) goto Exit; + + /* check that there is enough space in the render pool */ + if ( ras.cursor + size > ras.cell_limit ) + { + ras.error = ErrRaster_Overflow; + goto Fail; + } + + if (e1-v1 > 0) + u1 += SMulDiv( e1-v1, du, dv ); + + u = u1; + v = e1; if (reverse) v = -e1; + v += ONE_HALF; + iv = (1-2*reverse)*ONE_PIXEL; + + /* compute decision variables */ + if (du) + { + du <<= PIXEL_BITS; + iu = du / dv; + ru = du % dv; + if (ru < 0) + { + iu --; + ru += dv; + } + + nu = -dv; + ru <<= 1; + dv <<= 1; + } + else + { + iu = 0; + ru = 0; + nu = -dv; + } + + top = ras.cursor; + for ( ; size > 0; size-- ) + { + if (WRITE_CELL( top, u, v, dir )) + top++; + + u += iu; + nu += ru; + if (nu >= 0) + { + nu -= dv; + u++; + } + v += iv; + } + ras.cursor = top; + + Exit: + return 0; + + Fail: + return 1; + } + + + + /*************************************************************************/ + /* */ + /* */ + /* render_line */ + /* */ + /* */ + /* This function injects a new line segment in the render pool. */ + /* */ + /* */ + /* x :: target x coordinate (scaled subpixels) */ + /* y :: target y coordinate (scaled subpixels) */ + /* raster :: A pointer to the current raster object. */ + /* */ + /* */ + /* Error code. 0 means success. */ + /* */ + static + int render_line( RAS_ARG_ TPos x, TPos y ) + { + TPos minv, maxv; + TDir new_dir; + + minv = ras.min_y; + maxv = ras.max_y; + if (ras.horizontal) + { + minv = ras.min_x; + maxv = ras.max_x; + } + + new_dir = ras.dir; + + /* first of all, detect a change of direction */ + if ( y != ras.y ) + { + new_dir = ( y > ras.y ) ? dir_up : dir_down; + if (ras.horizontal) new_dir |= dir_horizontal; + + if ( new_dir != ras.dir ) + { + ras.joint = 0; + ras.dir = new_dir; + } + } + + /* then compute line intersections */ + if ( compute_intersects( RAS_VAR_ ras.x, ras.y, x, y, + minv, maxv, new_dir ) ) + goto Fail; + + ras.x = x; + ras.y = y; + + return 0; + + Fail: + return 1; + } + + + + + +static +void split_conic( FT_Vector* base ) +{ + TPos a, b; + + base[4].x = base[2].x; + b = base[1].x; + a = base[3].x = ( base[2].x + b )/2; + b = base[1].x = ( base[0].x + b )/2; + base[2].x = ( a + b ) / 2; + + base[4].y = base[2].y; + b = base[1].y; + a = base[3].y = ( base[2].y + b )/2; + b = base[1].y = ( base[0].y + b )/2; + base[2].y = ( a + b ) / 2; +} + + +static +int render_conic( RAS_ARG_ TPos x1, TPos y1, TPos x2, TPos y2 ) +{ + TPos x0, y0; + TPos dx, dy; + int top, level; + int* levels; + FT_Vector* arc; + + x0 = ras.x; + y0 = ras.y; + + dx = x0 + x2 - 2*x1; if (dx < 0) dx = -dx; + dy = y0 + y2 - 2*y1; if (dy < 0) dy = -dy; + if (dx < dy) dx = dy; + level = 1; + dx = DOWNSCALE(dx)/32; + while ( dx > 0 ) + { + dx >>= 1; + level++; + } + + if (level <= 1) + return render_line( RAS_VAR_ x2, y2 ); + + arc = ras.bez_stack; + arc[0].x = x2; + arc[0].y = y2; + arc[1].x = x1; + arc[1].y = y1; + arc[2].x = x0; + arc[2].y = y0; + + levels = ras.lev_stack; + top = 0; + levels[0] = level; + + for (;;) + { + level = levels[top]; + if (level > 1) + { + split_conic(arc); + arc += 2; + top ++; + levels[top] = levels[top-1] = level-1; + } + else + { + if (render_line( RAS_VAR_ arc[0].x, arc[0].y )) return 1; + top--; + arc-=2; + if (top < 0) + return 0; + } + } +} + + +static +void split_cubic( FT_Vector* base ) +{ + TPos a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = ( base[0].x + c ) / 2; + base[5].x = b = ( base[3].x + d ) / 2; + c = ( c + d ) / 2; + base[2].x = a = ( a + c ) / 2; + base[4].x = b = ( b + c ) / 2; + base[3].x = ( a + b ) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = ( base[0].y + c ) / 2; + base[5].y = b = ( base[3].y + d ) / 2; + c = ( c + d ) / 2; + base[2].y = a = ( a + c ) / 2; + base[4].y = b = ( b + c ) / 2; + base[3].y = ( a + b ) / 2; +} + +static +int render_cubic( RAS_ARG_ TPos x1, TPos y1, + TPos x2, TPos y2, + TPos x3, TPos y3 ) +{ + TPos x0, y0; + TPos dx, dy, da, db; + int top, level; + int* levels; + FT_Vector* arc; + + x0 = ras.x; + y0 = ras.y; + + dx = x0 + x3 - 2*x1; if (dx < 0) dx = -dx; + dy = y0 + y3 - 2*y1; if (dy < 0) dy = -dy; + da = dy; if (da < dx) da = dx; + + dx = x0 + x3 - 3*(x1+x2); if (dx < 0) dx = -dx; + dy = y0 + y3 - 3*(y1+y2); if (dy < 0) dy = -dy; + db = dy; if (db < dx) db = dx; + + da = DOWNSCALE(da); + db = DOWNSCALE(db); + + level = 1; + da = da/64; + db = db/128; + while ( da > 0 || db > 0 ) + { + da >>= 1; + db >>= 2; + level++; + } + + if (level <= 1) + return render_line( RAS_VAR_ x3, y3 ); + + arc = ras.bez_stack; + arc[0].x = x3; + arc[0].y = y3; + arc[1].x = x2; + arc[1].y = y2; + arc[2].x = x1; + arc[2].y = y1; + arc[3].x = x0; + arc[3].y = y0; + + levels = ras.lev_stack; + top = 0; + levels[0] = level; + + for (;;) + { + level = levels[top]; + if (level > 1) + { + split_cubic(arc); + arc += 3; + top ++; + levels[top] = levels[top-1] = level-1; + } + else + { + if (render_line( RAS_VAR_ arc[0].x, arc[0].y )) return 1; + top --; + arc -= 3; + if (top < 0) + return 0; + } + } +} + + +static +int is_less_than( PCell a, PCell b ) +{ + if (a->y < b->y) goto Yes; + if (a->y == b->y) + { + if (a->x < b->x) goto Yes; + if (a->x == b->x) + { + TDir ad = a->dir & dir_horizontal; + TDir bd = b->dir & dir_horizontal; + if ( ad < bd ) goto Yes; + if ( ad == bd && a->pos < b->pos) goto Yes; + } + } + return 0; +Yes: + return 1; +} + +/* a macro comparing two cell pointers. returns true if a <= b */ +#define LESS_THAN(a,b) is_less_than( (PCell)(a), (PCell)(b) ) +#define SWAP_CELLS(a,b,temp) { temp = *(a); *(a) = *(b); *(b) = temp; } +#define DEBUG_SORT + +#define QUICK_SORT + +#ifdef SHELL_SORT +/* A simple shell sort algorithm that works directly on our */ +/* cells table.. */ +static +void shell_sort ( PCell cells, + int count ) +{ + PCell i, j, limit = cells + count; + TCell temp; + int gap; + + /* compute initial gap */ + for (gap = 0; ++gap < count; gap *=3 ); + while ( gap /= 3 ) + { + for ( i = cells+gap; i < limit; i++ ) + { + for ( j = i-gap; ; j -= gap ) + { + PCell k = j+gap; + + if ( LESS_THAN(j,k) ) + break; + + SWAP_CELLS(j,k,temp); + + if ( j < cells+gap ) + break; + } + } + } + +} +#endif + +#ifdef QUICK_SORT +/* this is a non-recursive quicksort that directly process our cells array */ +/* it should be faster than calling the stdlib qsort(), and we can even */ +/* tailor our insertion threshold... */ + +#define QSORT_THRESHOLD 4 /* below this size, a sub-array will be sorted */ + /* through a normal insertion sort.. */ + +static +void quick_sort( PCell cells, + int count ) +{ + PCell stack[40]; /* should be enough ;-) */ + PCell* top; /* top of stack */ + PCell base, limit; + TCell temp; + + limit = cells + count; + base = cells; + top = stack; + for (;;) + { + int len = limit-base; + PCell i, j; + + if ( len > QSORT_THRESHOLD) + { + /* we use base+len/2 as the pivot */ + SWAP_CELLS( base, base+len/2, temp ); + i = base+1; + j = limit-1; + + /* now ensure that *i <= *base <= *j */ + if (LESS_THAN(j,i)) + SWAP_CELLS( i, j, temp ); + + if (LESS_THAN(base,i)) + SWAP_CELLS( base, i, temp ); + + if (LESS_THAN(j,base)) + SWAP_CELLS( base, j, temp ); + + for (;;) + { + do i++; while (LESS_THAN(i,base)); + do j--; while (LESS_THAN(base,j)); + if (i > j) + break; + + SWAP_CELLS( i,j, temp ); + } + /* move pivot to correct place */ + SWAP_CELLS( base, j, temp ); + + /* now, push the largest sub-array */ + if ( j - base > limit -i ) + { + top[0] = base; + top[1] = j; + base = i; + } + else + { + top[0] = i; + top[1] = limit; + limit = j; + } + top += 2; + } + else + { + /* the sub-array is small, perform insertion sort */ + j = base; + i = j+1; + for ( ; i < limit; j = i, i++ ) + { + for ( ; LESS_THAN(j+1,j); j-- ) + { + SWAP_CELLS( j+1, j, temp ); + if (j == base) + break; + } + } + if (top > stack) + { + top -= 2; + base = top[0]; + limit = top[1]; + } else + break; + } + } +} +#endif + + +#ifdef DEBUG_GRAYS +#ifdef DEBUG_SORT +static +int check_sort( PCell cells, int count ) +{ + PCell p, q; + + for ( p = cells + count-2; p >= cells; p-- ) + { + q = p+1; + if (!LESS_THAN(p,q)) + return 0; + } + return 1; +} +#endif +#endif + +#ifdef _STANDALONE_ +#if 1 + static + int FT_Outline_Decompose( FT_Outline* outline, + FT_Outline_Funcs* interface, + void* user ) + { + typedef enum _phases + { + phase_point, + phase_conic, + phase_cubic, + phase_cubic2 + + } TPhase; + + FT_Vector v_first; + FT_Vector v_last; + FT_Vector v_control; + FT_Vector v_start; + + FT_Vector* point; + FT_Vector* limit; + char* tags; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + int error; + char tag; /* current point's state */ + + + first = 0; + + for ( n = 0; n < outline->n_contours; n++ ) + { + int last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + v_first = outline->points[first]; + v_last = outline->points[last]; + + v_start = v_control = v_first; + + point = outline->points + first; + tags = outline->tags + first; + tag = FT_CURVE_TAG( tags[0] ); + + /* A contour cannot start with a cubic control point! */ + if ( tag == FT_Curve_Tag_Cubic ) + goto Invalid_Outline; + + /* check first point to determine origin */ + if ( tag == FT_Curve_Tag_Conic ) + { + /* first point is conic control. Yes, this happens. */ + if ( FT_CURVE_TAG( outline->tags[last] ) == FT_Curve_Tag_On ) + { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } + else + { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = ( v_start.x + v_last.x ) / 2; + v_start.y = ( v_start.y + v_last.y ) / 2; + + v_last = v_start; + } + point--; + tags--; + } + + error = interface->move_to( &v_start, user ); + if (error) goto Exit; + + while (point < limit) + { + point++; + tags++; + + tag = FT_CURVE_TAG( tags[0] ); + switch (tag) + { + case FT_Curve_Tag_On: /* emit a single line_to */ + { + error = interface->line_to( point, user ); + if (error) goto Exit; + continue; + } + + + case FT_Curve_Tag_Conic: /* consume conic arcs */ + { + v_control = point[0]; + + Do_Conic: + if (point < limit) + { + FT_Vector v_middle; + + point++; + tags++; + tag = FT_CURVE_TAG( tags[0] ); + + if (tag == FT_Curve_Tag_On) + { + error = interface->conic_to( &v_control, point, user ); + if (error) goto Exit; + continue; + } + + if (tag != FT_Curve_Tag_Conic) + goto Invalid_Outline; + + v_middle.x = (v_control.x + point->x)/2; + v_middle.y = (v_control.y + point->y)/2; + + error = interface->conic_to( &v_control, &v_middle, user ); + if (error) goto Exit; + + v_control = point[0]; + goto Do_Conic; + } + + error = interface->conic_to( &v_control, &v_start, user ); + goto Close; + } + + default: /* FT_Curve_Tag_Cubic */ + { + if ( point+1 > limit || + FT_CURVE_TAG( tags[1] ) != FT_Curve_Tag_Cubic ) + goto Invalid_Outline; + + point += 2; + tags += 2; + + if (point <= limit) + { + error = interface->cubic_to( point-2, point-1, point, user ); + if (error) goto Exit; + continue; + } + + error = interface->cubic_to( point-2, point-1, &v_start, user ); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = interface->line_to( &v_start, user ); + + Close: + if (error) goto Exit; + first = last+1; + } + + return 0; + Exit: + return error; + + Invalid_Outline: + return -1; + } +#else + static + int FT_Outline_Decompose( FT_Outline* outline, + FT_Outline_Funcs* interface, + void* user ) + { + typedef enum _phases + { + phase_point, + phase_conic, + phase_cubic, + phase_cubic2 + + } TPhase; + + FT_Vector v_last; + FT_Vector v_control; + FT_Vector v_control2; + FT_Vector v_start; + + FT_Vector* point; + char* tags; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + int index; /* current point's index */ + + int error; + + char tag; /* current point's state */ + TPhase phase; + + + first = 0; + + for ( n = 0; n < outline->n_contours; n++ ) + { + int last; /* index of last point in contour */ + + + last = outline->contours[n]; + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_control = v_start; + + tag = FT_CURVE_TAG( outline->tags[first] ); + index = first; + + /* A contour cannot start with a cubic control point! */ + + if ( tag == FT_Curve_Tag_Cubic ) + return ErrRaster_Invalid_Outline; + + + /* check first point to determine origin */ + + if ( tag == FT_Curve_Tag_Conic ) + { + /* first point is conic control. Yes, this happens. */ + if ( FT_CURVE_TAG( outline->tags[last] ) == FT_Curve_Tag_On ) + { + /* start at last point if it is on the curve */ + v_start = v_last; + } + else + { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = ( v_start.x + v_last.x ) / 2; + v_start.y = ( v_start.y + v_last.y ) / 2; + + v_last = v_start; + } + phase = phase_conic; + } + else + phase = phase_point; + + + /* Begin a new contour with MOVE_TO */ + + error = interface->move_to( &v_start, user ); + if ( error ) + return error; + + point = outline->points + first; + tags = outline->tags + first; + + /* now process each contour point individually */ + + while ( index < last ) + { + index++; + point++; + tags++; + + tag = FT_CURVE_TAG( tags[0] ); + + switch ( phase ) + { + case phase_point: /* the previous point was on the curve */ + + switch ( tag ) + { + /* two succesive on points -> emit segment */ + case FT_Curve_Tag_On: + error = interface->line_to( point, user ); + break; + + /* on point + conic control -> remember control point */ + case FT_Curve_Tag_Conic: + v_control = point[0]; + phase = phase_conic; + break; + + /* on point + cubic control -> remember first control */ + default: + v_control = point[0]; + phase = phase_cubic; + break; + } + break; + + case phase_conic: /* the previous point was a conic control */ + + switch ( tag ) + { + /* conic control + on point -> emit conic arc */ + case FT_Curve_Tag_On: + error = interface->conic_to( &v_control, point, user ); + phase = phase_point; + break; + + /* two successive conics -> emit conic arc `in between' */ + case FT_Curve_Tag_Conic: + { + FT_Vector v_middle; + + + v_middle.x = (v_control.x + point->x)/2; + v_middle.y = (v_control.y + point->y)/2; + + error = interface->conic_to( &v_control, + &v_middle, user ); + v_control = point[0]; + } + break; + + default: + error = ErrRaster_Invalid_Outline; + } + break; + + case phase_cubic: /* the previous point was a cubic control */ + + /* this point _must_ be a cubic control too */ + if ( tag != FT_Curve_Tag_Cubic ) + return ErrRaster_Invalid_Outline; + + v_control2 = point[0]; + phase = phase_cubic2; + break; + + + case phase_cubic2: /* the two previous points were cubics */ + + /* this point _must_ be an on point */ + if ( tag != FT_Curve_Tag_On ) + error = ErrRaster_Invalid_Outline; + else + error = interface->cubic_to( &v_control, &v_control2, + point, user ); + phase = phase_point; + break; + } + + /* lazy error testing */ + if ( error ) + return error; + } + + /* end of contour, close curve cleanly */ + error = 0; + + tag = FT_CURVE_TAG( outline->tags[first] ); + + switch ( phase ) + { + case phase_point: + if ( tag == FT_Curve_Tag_On ) + error = interface->line_to( &v_start, user ); + break; + + case phase_conic: + error = interface->conic_to( &v_control, &v_start, user ); + break; + + case phase_cubic2: + if ( tag == FT_Curve_Tag_On ) + error = interface->cubic_to( &v_control, &v_control2, + &v_start, user ); + else + error = ErrRaster_Invalid_Outline; + break; + + default: + error = ErrRaster_Invalid_Outline; + break; + } + + if ( error ) + return error; + + first = last + 1; + } + + return 0; + } + +#endif +#endif /* _STANDALONE_ */ + + + static + int Move_To2( FT_Vector* to, + FT_Raster raster ) + { + PRaster rast = (PRaster)raster; + FT_Pos* to_x; + FT_Pos* to_y; + + to_x = &to->x; + to_y = &to->y; + if (rast->horizontal) + { + to_x = &to->y; + to_y = &to->x; + } + + rast->starter.x = UPSCALE(*to_x); + rast->starter.y = UPSCALE(*to_y); + + rast->joint = 0; + rast->dir = dir_unknown; + rast->last = 0; + rast->start = 0; + + if ((*to_x & 63) == 32) + { + rast->starter.x |= 1; + rast->start = to; + } + if ((*to_y & 63) == 32) + { + rast->starter.y |= 1; + rast->start = to; + } + + rast->x = rast->starter.x; + rast->y = rast->starter.y; + return 0; + } + + + static + int Line_To2( FT_Vector* to, + FT_Raster raster ) + { + TPos x, y; + PRaster rast = (PRaster)raster; + + if ( to == rast->start ) + { + x = rast->starter.x; + y = rast->starter.y; + } + else + { + if ( rast->horizontal ) + { + x = to->y; + y = to->x; + } + else + { + x = to->x; + y = to->y; + } + x = UPSCALE(x); + y = UPSCALE(y); + } + + return render_line( rast, x, y ); + } + + + static + int Conic_To2( FT_Vector* control, + FT_Vector* to, + FT_Raster raster ) + { + PRaster rast = (PRaster)raster; + FT_Vector ctr, to2; + + ctr = *control; + to2 = *to; + if (rast->horizontal) + { + ctr.x = control->y; + ctr.y = control->x; + to2.x = to->y; + to2.y = to->x; + } + + if ( to == rast->start ) + to2 = rast->starter; + else + { + to2.x = UPSCALE(to2.x); + to2.y = UPSCALE(to2.y); + } + + return render_conic( rast, UPSCALE(ctr.x), UPSCALE(ctr.y), to2.x, to2.y ); + } + + + static + int Cubic_To2( FT_Vector* control1, + FT_Vector* control2, + FT_Vector* to, + FT_Raster raster ) + { + PRaster rast = (PRaster)raster; + FT_Vector ctr1, ctr2, to2; + + ctr1 = *control1; + ctr2 = *control2; + to2 = *to; + if (rast->horizontal) + { + ctr1.x = control1->y; ctr1.y = control1->x; + ctr2.x = control2->y; ctr2.y = control2->x; + to2.x = to->y; to2.y = to->x; + } + + if ( to == rast->start ) + to2 = rast->starter; + else + { + to2.x = UPSCALE(to2.x); + to2.y = UPSCALE(to2.y); + } + + return render_cubic( rast, UPSCALE(ctr1.x), UPSCALE(ctr1.y), + UPSCALE(ctr2.x), UPSCALE(ctr2.y), + to2.x, to2.y ); + } + + + static + void grays_render_span( int y, int count, FT_GraySpan* spans, PRaster raster ) + { + unsigned char *p, *q, *limit; + FT_Bitmap* map = &raster->target; + /* first of all, compute the scanline offset */ + p = (unsigned char*)map->buffer - y*map->pitch; + if (map->pitch >= 0) + p += (map->rows-1)*map->pitch; + + for ( ; count > 0; count--, spans++ ) + { + if (spans->coverage) + { + q = p + spans->x; + limit = q + spans->len; + for ( ; q < limit; q++ ) + q[0] = (spans->coverage+1) >> 1; + } + } + } + +#ifdef DEBUG_GRAYS +#include + + static + void dump_cells( RAS_ARG ) + { + static const char dirs[5] = "udrl?"; + PCell cell, limit; + int y = -1; + + cell = ras.cells; + limit = cell + ras.num_cells; + for ( ; cell < limit; cell++ ) + { + if ( cell->y != y ) + { + fprintf( stderr, "\n%2d: ", (int)cell->y ); + y = cell->y; + } + fprintf( stderr, "[%d %c %d]", + (int)cell->x, + dirs[cell->dir & 3], + cell->pos ); + } + fprintf(stderr, "\n" ); + } +#endif + + static + void grays_hline( RAS_ARG_ TScan y, TScan x, int coverage, int acount ) + { + FT_GraySpan* span; + int count; + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule.. */ + /* */ + /* The coverage percentage is area/ONE_PIXEL */ + /* */ + + coverage <<= 1; + coverage >>= (PIXEL_BITS-6); + + if (coverage < 0) + coverage = -coverage; + + if (coverage >= 256) + coverage = 255; + + if (coverage) + { + /* see if we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count-1; + if (count > 0 && ras.span_y == y && (int)span->x + span->len == (int)x && + span->coverage == coverage) + { + span->len += acount; + return; + } + + if ( ras.span_y != y || count >= FT_MAX_GRAY_SPANS) + { + if (ras.render_span) + ras.render_span( ras.span_y, count, ras.gray_spans, ras.render_span_closure ); + /* ras.render_span( span->y, ras.gray_spans, count ); */ + +#ifdef DEBUG_GRAYS + if (ras.span_y >= 0) + { + int n; + fprintf( stderr, "y=%3d ", ras.span_y ); + span = ras.gray_spans; + for (n = 0; n < count; n++, span++) + { + if (span->len > 1) + fprintf( stderr, "[%d..%d]:%02x ", span->x, span->x + span->len-1, span->coverage ); + else + fprintf( stderr, "[%d]:%02x ", span->x, span->coverage ); + } + fprintf( stderr, "\n" ); + } +#endif + + ras.num_gray_spans = 0; + ras.span_y = y; + + count = 0; + span = ras.gray_spans; + } + else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->len = (unsigned char)acount; + span->coverage = (unsigned char)coverage; + ras.num_gray_spans++; + } + } + + + + static + void grays_sweep( RAS_ARG_ FT_Bitmap* target ) + { + TScan x, y, cover, x_black; + int varea, harea, hpos; + PCell start, cur, limit; + + cur = ras.cells; + limit = cur + ras.num_cells; + + cover = 0; + ras.span_y = -1; + ras.num_gray_spans = 0; + + cover = 0; + x_black = 32000; + + /* fprintf( stderr, "%2d:", cur->y ); */ + + for (;;) + { + int is_black, icover; + int area, numv; + + start = cur; + y = start->y; + x = start->x; + icover = cover; + varea = cover << PIXEL_BITS; + harea = 0; + hpos = varea; + numv = 0; + + /* accumulate all start cells */ + for (;;) + { + /* XXX : for now, only deal with vertical intersections */ + switch ((cur->dir)&3) + { + case dir_up: + varea += ONE_PIXEL - cur->pos; + if (cur->pos <= 32) + hpos = ONE_PIXEL; + cover++; + numv++; + break; + + case dir_down: + varea -= ONE_PIXEL - cur->pos; + if (cur->pos <= 32) + hpos = 0; + cover--; + numv++; + break; + + case dir_left: + harea += ONE_PIXEL - cur->pos; + break; + + default: + harea -= ONE_PIXEL - cur->pos; + break; + } + + ++cur; + if (cur >= limit || cur->y != start->y || cur->x != start->x) + break; + } + + /* nom compute the "real" area in the pixel */ + if (varea < 0) varea += ONE_PIXEL; + if (harea < 0) harea += ONE_PIXEL; + + if (harea) + area = varea + harea; + else + area = 2*varea; + +#if 1 + if ( varea < ONE_PIXEL && harea == 0 && (icover|cover) == 0 && area < ONE_PIXEL) + area += ONE_HALF; +#endif + + is_black = ( area >= 2*ONE_PIXEL ); + + /* if the start cell isn't black, we may need to draw a black */ + /* segment from a previous cell.. */ + if ( !is_black && start->x > x_black ) + { + /* printf( stderr, " b[%d..%d]", x_black, start->x-1 ); */ + grays_hline( RAS_VAR_ y, x_black, 2*ONE_PIXEL, start->x - x_black ); + } + + /* if the cell is black, then record its position in "x_black" */ + if ( is_black ) + { + if ( x_black > start->x ) + x_black = start->x; + } + + /* if the cell is gray, draw a single gray pixel, then record */ + /* the next cell's position in "x_black" if "cover" is black */ + else + { + x_black = 32000; + if ( area ) + { + /* fprintf( stderr, " [%d:%d]", start->x, varea ); */ + grays_hline( RAS_VAR_ y, start->x, area, 1 ); + if (cover) + x_black = start->x+1; + } + } + + + /* now process scanline changes/end */ + if (cur >= limit || cur->y != start->y) + { + if (cover && x_black < ras.max_ex) + { + /* fprintf( stderr, " f[%d..%d]", x_black, ras.max_ex-1 ); */ + grays_hline( RAS_VAR_ y, x_black, 2*ONE_PIXEL, ras.max_ex-x_black ); + } + + if (cur >= limit) + break; + + /* fprintf( stderr, "\n%2d:", cur->y ); */ + cover = 0; + x_black = 32000; + } + } + if (ras.render_span && ras.num_gray_spans > 0) + ras.render_span( ras.span_y, ras.num_gray_spans, + ras.gray_spans, ras.render_span_closure ); + +#ifdef DEBUG_GRAYS + { + int n; + FT_GraySpan* span; + + fprintf( stderr, "y=%3d ", ras.span_y ); + span = ras.gray_spans; + for (n = 0; n < ras.num_gray_spans; n++, span++) + { + if (span->len > 1) + fprintf( stderr, "[%d..%d]:%02x ", span->x, span->x + span->len-1, span->coverage ); + else + fprintf( stderr, "[%d]:%02x ", span->x, span->coverage ); + } + fprintf( stderr, "\n" ); + } +#endif + } + + static + int Convert_Glyph( RAS_ARG_ FT_Outline* outline ) + { + static + FT_Outline_Funcs interface = + { + (FT_Outline_MoveTo_Func)Move_To2, + (FT_Outline_LineTo_Func)Line_To2, + (FT_Outline_ConicTo_Func)Conic_To2, + (FT_Outline_CubicTo_Func)Cubic_To2 + }; + + /* Set up state in the raster object */ + compute_cbox( RAS_VAR_ outline ); + + if (ras.min_ex < 0) ras.min_ex = 0; + if (ras.min_ey < 0) ras.min_ey = 0; + + if (ras.max_ex > ras.target.width) ras.max_ex = ras.target.width; + if (ras.max_ey > ras.target.rows) ras.max_ey = ras.target.rows; + + ras.min_x = UPSCALE(ras.min_ex << 6); + ras.min_y = UPSCALE(ras.min_ey << 6); + ras.max_x = UPSCALE(ras.max_ex << 6); + ras.max_y = UPSCALE(ras.max_ey << 6); + + ras.num_cells = 0; + ras.contour_cell = 0; + ras.horizontal = 0; + + /* compute vertical intersections */ + if (FT_Outline_Decompose( outline, &interface, &ras )) + return 1; +#if 1 + /* compute horizontal intersections */ + ras.horizontal = 1; + return FT_Outline_Decompose( outline, &interface, &ras ); +#endif + } + + + extern + int grays2_raster_render( TRaster* raster, + FT_Outline* outline, + FT_Bitmap* target_map ) + { + if ( !raster || !raster->cells || !raster->max_cells ) + return -1; + + /* return immediately if the outline is empty */ + if ( outline->n_points == 0 || outline->n_contours <= 0 ) + return 0; + + if ( !outline || !outline->contours || !outline->points ) + return -1; + + if ( outline->n_points != outline->contours[outline->n_contours - 1] + 1 ) + return -1; + + if ( !target_map || !target_map->buffer ) + return -1; + + ras.outline = *outline; + ras.target = *target_map; + ras.num_cells = 0; + ras.cursor = ras.cells; + + if (Convert_Glyph( (PRaster)raster, outline )) + return 1; + + ras.num_cells = ras.cursor - ras.cells; +#ifdef SHELL_SORT + shell_sort( ras.cells, ras.num_cells ); +#else + quick_sort( ras.cells, ras.num_cells ); +#endif + +#ifdef DEBUG_GRAYS + check_sort( ras.cells, ras.num_cells ); + dump_cells( RAS_VAR ); +#endif + +#if 1 + ras.render_span = (FT_GraySpan_Func)grays_render_span; + ras.render_span_closure = &ras; + + grays_sweep( (PRaster)raster, target_map ); + return 0; +#else + return 0; +#endif + } + + + + + + + extern + int grays2_raster_init( FT_Raster raster, + const char* pool_base, + long pool_size ) + { +/* static const char default_palette[5] = { 0, 1, 2, 3, 4 }; */ + + /* check the object address */ + if ( !raster ) + return -1; + + /* check the render pool - we won't go under 4 Kb */ + if ( !pool_base || pool_size < 4096 ) + return -1; + + /* save the pool */ + init_cells( (PRaster)raster, (char*)pool_base, pool_size ); + + return 0; + } + + + + FT_Raster_Interface ft_grays2_raster = + { + sizeof( TRaster ), + ft_glyph_format_outline, + + (FT_Raster_Init_Proc) grays2_raster_init, + (FT_Raster_Set_Mode_Proc) 0, + (FT_Raster_Render_Proc) grays2_raster_render + }; + + diff --git a/demos/src/ftgrays2.h b/demos/src/ftgrays2.h new file mode 100644 index 000000000..e127a8ed7 --- /dev/null +++ b/demos/src/ftgrays2.h @@ -0,0 +1,102 @@ +#ifndef FTGRAYS2_H +#define FTGRAYS2_H + +typedef int TScan; +typedef long TPos; +typedef float TDist; + +#define FT_MAX_GRAY_SPANS 32 + +typedef struct FT_GraySpan_ +{ + short x; + short len; + unsigned char coverage; + +} FT_GraySpan; + +typedef int (*FT_GraySpan_Func)( int y, + int count, + FT_GraySpan* spans, + void* user ); + +typedef enum { + + dir_up = 0, + dir_down = 1, + dir_right = 2, + dir_left = 3, + + dir_horizontal = 2, + dir_reverse = 1, + + dir_unknown = 4 + +} TDir; + +typedef struct TCell_ +{ + unsigned short x; + unsigned short y; + unsigned short pos; + TDir dir; + +} TCell, *PCell; + + + +typedef struct TRaster_ +{ + PCell cells; + PCell cursor; + PCell cell_limit; + int max_cells; + int num_cells; + + TScan min_ex, max_ex; + TScan min_ey, max_ey; + TPos min_x, min_y; + TPos max_x, max_y; + + TScan ex, ey; + TScan cx, cy; + TPos x, y; + + PCell contour_cell; /* first contour cell */ + + char joint; + char horizontal; + TDir dir; + PCell last; + + FT_Vector starter; + FT_Vector* start; + + int error; + + FT_Vector bez_stack[32*3]; + int lev_stack[32]; + + FT_Outline outline; + FT_Bitmap target; + + FT_GraySpan gray_spans[ FT_MAX_GRAY_SPANS ]; + int num_gray_spans; + + FT_GraySpan_Func render_span; + void* render_span_closure; + int span_y; + +} TRaster, *PRaster; + + extern + int grays2_raster_render( TRaster* raster, + FT_Outline* outline, + FT_Bitmap* target_map ); + + extern + int grays2_raster_init( FT_Raster raster, + const char* pool_base, + long pool_size ); + +#endif