gdiplus: Initial GdipFlattenPath implementation.
This commit is contained in:
parent
78312704d9
commit
72304d8377
|
@ -33,6 +33,135 @@
|
|||
|
||||
WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
|
||||
|
||||
typedef struct path_list_node_t path_list_node_t;
|
||||
struct path_list_node_t {
|
||||
GpPointF pt;
|
||||
BYTE type; /* PathPointTypeStart or PathPointTypeLine */
|
||||
path_list_node_t *next;
|
||||
};
|
||||
|
||||
/* init list */
|
||||
static BOOL init_path_list(path_list_node_t **node, REAL x, REAL y)
|
||||
{
|
||||
*node = GdipAlloc(sizeof(path_list_node_t));
|
||||
if(!*node)
|
||||
return FALSE;
|
||||
|
||||
(*node)->pt.X = x;
|
||||
(*node)->pt.Y = y;
|
||||
(*node)->type = PathPointTypeStart;
|
||||
(*node)->next = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* free all nodes including argument */
|
||||
static void free_path_list(path_list_node_t *node)
|
||||
{
|
||||
path_list_node_t *n = node;
|
||||
|
||||
while(n){
|
||||
n = n->next;
|
||||
GdipFree(node);
|
||||
node = n;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add a node after 'node' */
|
||||
/*
|
||||
* Returns
|
||||
* pointer on success
|
||||
* NULL on allocation problems
|
||||
*/
|
||||
static path_list_node_t* add_path_list_node(path_list_node_t *node, REAL x, REAL y, BOOL type)
|
||||
{
|
||||
path_list_node_t *new;
|
||||
|
||||
new = GdipAlloc(sizeof(path_list_node_t));
|
||||
if(!new)
|
||||
return NULL;
|
||||
|
||||
new->pt.X = x;
|
||||
new->pt.Y = y;
|
||||
new->type = type;
|
||||
new->next = node->next;
|
||||
node->next = new;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
/* returns element count */
|
||||
static INT path_list_count(path_list_node_t *node)
|
||||
{
|
||||
INT count = 1;
|
||||
|
||||
while((node = node->next))
|
||||
++count;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* GdipFlattenPath helper */
|
||||
/*
|
||||
* Used to recursively flatten single Bezier curve
|
||||
* Parameters:
|
||||
* - start : pointer to start point node;
|
||||
* - (x2, y2): first control point;
|
||||
* - (x3, y3): second control point;
|
||||
* - end : pointer to end point node
|
||||
* - flatness: admissible error of linear approximation.
|
||||
*
|
||||
* Return value:
|
||||
* TRUE : success
|
||||
* FALSE: out of memory
|
||||
*
|
||||
* TODO: used quality criteria should be revised to match native as
|
||||
* closer as possible.
|
||||
*/
|
||||
static BOOL flatten_bezier(path_list_node_t *start, REAL x2, REAL y2, REAL x3, REAL y3,
|
||||
path_list_node_t *end, REAL flatness)
|
||||
{
|
||||
/* this 5 middle points with start/end define to half-curves */
|
||||
GpPointF mp[5];
|
||||
GpPointF pt, pt_st;
|
||||
path_list_node_t *node;
|
||||
|
||||
/* calculate bezier curve middle points == new control points */
|
||||
mp[0].X = (start->pt.X + x2) / 2.0;
|
||||
mp[0].Y = (start->pt.Y + y2) / 2.0;
|
||||
/* middle point between control points */
|
||||
pt.X = (x2 + x3) / 2.0;
|
||||
pt.Y = (y2 + y3) / 2.0;
|
||||
mp[1].X = (mp[0].X + pt.X) / 2.0;
|
||||
mp[1].Y = (mp[0].Y + pt.Y) / 2.0;
|
||||
mp[4].X = (end->pt.X + x3) / 2.0;
|
||||
mp[4].Y = (end->pt.Y + y3) / 2.0;
|
||||
mp[3].X = (mp[4].X + pt.X) / 2.0;
|
||||
mp[3].Y = (mp[4].Y + pt.Y) / 2.0;
|
||||
|
||||
mp[2].X = (mp[1].X + mp[3].X) / 2.0;
|
||||
mp[2].Y = (mp[1].Y + mp[3].Y) / 2.0;
|
||||
|
||||
pt = end->pt;
|
||||
pt_st = start->pt;
|
||||
/* check flatness as a half of distance between middle point and a linearized path */
|
||||
if(fabs(((pt.Y - pt_st.Y)*mp[2].X + (pt_st.X - pt.X)*mp[2].Y +
|
||||
(pt_st.Y*pt.X - pt_st.X*pt.Y))) <=
|
||||
(0.5 * flatness*sqrtf((powf(pt.Y - pt_st.Y, 2.0) + powf(pt_st.X - pt.X, 2.0))))){
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
/* add a middle point */
|
||||
if(!(node = add_path_list_node(start, mp[2].X, mp[2].Y, PathPointTypeLine)))
|
||||
return FALSE;
|
||||
|
||||
/* do the same with halfs */
|
||||
flatten_bezier(start, mp[0].X, mp[0].Y, mp[1].X, mp[1].Y, node, flatness);
|
||||
flatten_bezier(node, mp[3].X, mp[3].Y, mp[4].X, mp[4].Y, end, flatness);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipAddPathArc(GpPath *path, REAL x1, REAL y1, REAL x2,
|
||||
REAL y2, REAL startAngle, REAL sweepAngle)
|
||||
{
|
||||
|
@ -818,15 +947,106 @@ GpStatus WINGDIPAPI GdipDeletePath(GpPath *path)
|
|||
|
||||
GpStatus WINGDIPAPI GdipFlattenPath(GpPath *path, GpMatrix* matrix, REAL flatness)
|
||||
{
|
||||
static int calls;
|
||||
path_list_node_t *list, *node;
|
||||
GpPointF pt;
|
||||
INT i = 1;
|
||||
INT startidx = 0;
|
||||
|
||||
TRACE("(%p, %p, %.2f)\n", path, matrix, flatness);
|
||||
|
||||
if(!path)
|
||||
return InvalidParameter;
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(matrix){
|
||||
WARN("transformation not supported yet!\n");
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
return NotImplemented;
|
||||
if(path->pathdata.Count == 0)
|
||||
return Ok;
|
||||
|
||||
pt = path->pathdata.Points[0];
|
||||
if(!init_path_list(&list, pt.X, pt.Y))
|
||||
return OutOfMemory;
|
||||
|
||||
node = list;
|
||||
|
||||
while(i < path->pathdata.Count){
|
||||
|
||||
BYTE type = path->pathdata.Types[i] & PathPointTypePathTypeMask;
|
||||
path_list_node_t *start;
|
||||
|
||||
pt = path->pathdata.Points[i];
|
||||
|
||||
/* save last start point index */
|
||||
if(type == PathPointTypeStart)
|
||||
startidx = i;
|
||||
|
||||
/* always add line points and start points */
|
||||
if((type == PathPointTypeStart) || (type == PathPointTypeLine)){
|
||||
type = (path->pathdata.Types[i] & ~PathPointTypeBezier) | PathPointTypeLine;
|
||||
if(!add_path_list_node(node, pt.X, pt.Y, type))
|
||||
goto memout;
|
||||
|
||||
node = node->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Bezier curve always stored as 4 points */
|
||||
if((path->pathdata.Types[i-1] & PathPointTypePathTypeMask) != PathPointTypeStart){
|
||||
type = (path->pathdata.Types[i] & ~PathPointTypeBezier) | PathPointTypeLine;
|
||||
if(!add_path_list_node(node, pt.X, pt.Y, type))
|
||||
goto memout;
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
/* test for closed figure */
|
||||
if(path->pathdata.Types[i+1] & PathPointTypeCloseSubpath){
|
||||
pt = path->pathdata.Points[startidx];
|
||||
++i;
|
||||
}
|
||||
else
|
||||
{
|
||||
i += 2;
|
||||
pt = path->pathdata.Points[i];
|
||||
};
|
||||
|
||||
start = node;
|
||||
/* add Bezier end point */
|
||||
type = (path->pathdata.Types[i] & ~PathPointTypeBezier) | PathPointTypeLine;
|
||||
if(!add_path_list_node(node, pt.X, pt.Y, type))
|
||||
goto memout;
|
||||
node = node->next;
|
||||
|
||||
/* flatten curve */
|
||||
if(!flatten_bezier(start, path->pathdata.Points[i-2].X, path->pathdata.Points[i-2].Y,
|
||||
path->pathdata.Points[i-1].X, path->pathdata.Points[i-1].Y,
|
||||
node, flatness))
|
||||
goto memout;
|
||||
|
||||
++i;
|
||||
}/* while */
|
||||
|
||||
/* store path data back */
|
||||
i = path_list_count(list);
|
||||
if(!lengthen_path(path, i))
|
||||
goto memout;
|
||||
path->pathdata.Count = i;
|
||||
|
||||
node = list;
|
||||
for(i = 0; i < path->pathdata.Count; i++){
|
||||
path->pathdata.Points[i] = node->pt;
|
||||
path->pathdata.Types[i] = node->type;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
free_path_list(list);
|
||||
return Ok;
|
||||
|
||||
memout:
|
||||
free_path_list(list);
|
||||
return OutOfMemory;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipGetPathData(GpPath *path, GpPathData* pathData)
|
||||
|
|
|
@ -888,10 +888,10 @@ static void test_addpie(void)
|
|||
|
||||
static path_test_t flattenellipse_path[] = {
|
||||
{100.0, 25.0,PathPointTypeStart, 0, 0}, /*0*/
|
||||
{99.0, 30.0, PathPointTypeLine, 0, 1}, /*1*/
|
||||
{96.0, 34.8, PathPointTypeLine, 0, 1}, /*2*/
|
||||
{91.5, 39.0, PathPointTypeLine, 0, 1}, /*3*/
|
||||
{85.5, 42.8, PathPointTypeLine, 0, 1}, /*4*/
|
||||
{99.0, 30.0, PathPointTypeLine, 0, 0}, /*1*/
|
||||
{96.0, 34.8, PathPointTypeLine, 0, 0}, /*2*/
|
||||
{91.5, 39.0, PathPointTypeLine, 0, 0}, /*3*/
|
||||
{85.5, 42.8, PathPointTypeLine, 0, 0}, /*4*/
|
||||
{69.5, 48.0, PathPointTypeLine, 0, 1}, /*5*/
|
||||
{50.0, 50.0, PathPointTypeLine, 0, 1}, /*6*/
|
||||
{30.5, 48.0, PathPointTypeLine, 0, 1}, /*7*/
|
||||
|
@ -916,14 +916,26 @@ static path_test_t flattenellipse_path[] = {
|
|||
|
||||
static path_test_t flattenarc_path[] = {
|
||||
{100.0, 25.0,PathPointTypeStart, 0, 0}, /*0*/
|
||||
{99.0, 30.0, PathPointTypeLine, 0, 1}, /*1*/
|
||||
{96.0, 34.8, PathPointTypeLine, 0, 1}, /*2*/
|
||||
{91.5, 39.0, PathPointTypeLine, 0, 1}, /*3*/
|
||||
{85.5, 42.8, PathPointTypeLine, 0, 1}, /*4*/
|
||||
{99.0, 30.0, PathPointTypeLine, 0, 0}, /*1*/
|
||||
{96.0, 34.8, PathPointTypeLine, 0, 0}, /*2*/
|
||||
{91.5, 39.0, PathPointTypeLine, 0, 0}, /*3*/
|
||||
{85.5, 42.8, PathPointTypeLine, 0, 0}, /*4*/
|
||||
{69.5, 48.0, PathPointTypeLine, 0, 1}, /*5*/
|
||||
{50.0, 50.0, PathPointTypeLine, 0, 1} /*6*/
|
||||
};
|
||||
|
||||
static path_test_t flattenquater_path[] = {
|
||||
{100.0, 50.0,PathPointTypeStart, 0, 0}, /*0*/
|
||||
{99.0, 60.0, PathPointTypeLine, 0, 0}, /*1*/
|
||||
{96.0, 69.5, PathPointTypeLine, 0, 0}, /*2*/
|
||||
{91.5, 78.0, PathPointTypeLine, 0, 0}, /*3*/
|
||||
{85.5, 85.5, PathPointTypeLine, 0, 0}, /*4*/
|
||||
{78.0, 91.5, PathPointTypeLine, 0, 0}, /*5*/
|
||||
{69.5, 96.0, PathPointTypeLine, 0, 0}, /*6*/
|
||||
{60.0, 99.0, PathPointTypeLine, 0, 0}, /*7*/
|
||||
{50.0, 100.0,PathPointTypeLine, 0, 0} /*8*/
|
||||
};
|
||||
|
||||
static void test_flatten(void)
|
||||
{
|
||||
GpStatus status;
|
||||
|
@ -941,11 +953,15 @@ static void test_flatten(void)
|
|||
status = GdipFlattenPath(NULL, m, 0.0);
|
||||
expect(InvalidParameter, status);
|
||||
|
||||
/* flatten empty path */
|
||||
status = GdipFlattenPath(path, NULL, 1.0);
|
||||
expect(Ok, status);
|
||||
|
||||
status = GdipAddPathEllipse(path, 0.0, 0.0, 100.0, 50.0);
|
||||
expect(Ok, status);
|
||||
|
||||
status = GdipFlattenPath(path, NULL, 1.0);
|
||||
todo_wine expect(Ok, status);
|
||||
expect(Ok, status);
|
||||
ok_path(path, flattenellipse_path, sizeof(flattenellipse_path)/sizeof(path_test_t), TRUE);
|
||||
|
||||
status = GdipResetPath(path);
|
||||
|
@ -953,9 +969,18 @@ static void test_flatten(void)
|
|||
status = GdipAddPathArc(path, 0.0, 0.0, 100.0, 50.0, 0.0, 90.0);
|
||||
expect(Ok, status);
|
||||
status = GdipFlattenPath(path, NULL, 1.0);
|
||||
todo_wine expect(Ok, status);
|
||||
expect(Ok, status);
|
||||
ok_path(path, flattenarc_path, sizeof(flattenarc_path)/sizeof(path_test_t), TRUE);
|
||||
|
||||
/* easy case - quater of a full circle */
|
||||
status = GdipResetPath(path);
|
||||
expect(Ok, status);
|
||||
status = GdipAddPathArc(path, 0.0, 0.0, 100.0, 100.0, 0.0, 90.0);
|
||||
expect(Ok, status);
|
||||
status = GdipFlattenPath(path, NULL, 1.0);
|
||||
expect(Ok, status);
|
||||
ok_path(path, flattenquater_path, sizeof(flattenquater_path)/sizeof(path_test_t), FALSE);
|
||||
|
||||
GdipDeleteMatrix(m);
|
||||
GdipDeletePath(path);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue