gdiplus: Implemented GdipSetPenColor.
[wine] / dlls / gdiplus / graphics.c
1 /*
2  * Copyright (C) 2007 Google (Evan Stade)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20 #include <math.h>
21
22 #include "windef.h"
23 #include "winbase.h"
24 #include "winuser.h"
25 #include "wingdi.h"
26 #include "gdiplus.h"
27 #include "gdiplus_private.h"
28 #include "wine/debug.h"
29
30 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
31
32 /* looks-right constants */
33 #define TENSION_CONST (0.3)
34 #define ANCHOR_WIDTH (2.0)
35 #define MAX_ITERS (50)
36
37 /* Converts angle (in degrees) to x/y coordinates */
38 static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y)
39 {
40     REAL radAngle, hypotenuse;
41
42     radAngle = deg2rad(angle);
43     hypotenuse = 50.0; /* arbitrary */
44
45     *x = x_0 + cos(radAngle) * hypotenuse;
46     *y = y_0 + sin(radAngle) * hypotenuse;
47 }
48
49 /* Converts from gdiplus path point type to gdi path point type. */
50 static BYTE convert_path_point_type(BYTE type)
51 {
52     BYTE ret;
53
54     switch(type & PathPointTypePathTypeMask){
55         case PathPointTypeBezier:
56             ret = PT_BEZIERTO;
57             break;
58         case PathPointTypeLine:
59             ret = PT_LINETO;
60             break;
61         case PathPointTypeStart:
62             ret = PT_MOVETO;
63             break;
64         default:
65             ERR("Bad point type\n");
66             return 0;
67     }
68
69     if(type & PathPointTypeCloseSubpath)
70         ret |= PT_CLOSEFIGURE;
71
72     return ret;
73 }
74
75 /* GdipDrawPie/GdipFillPie helper function */
76 static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen,
77     REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
78 {
79     INT save_state;
80     REAL x_0, y_0, x_1, y_1, x_2, y_2;
81
82     if(!graphics)
83         return InvalidParameter;
84
85     save_state = SaveDC(graphics->hdc);
86     EndPath(graphics->hdc);
87     SelectObject(graphics->hdc, gdipen);
88     SelectObject(graphics->hdc, gdibrush);
89
90     x_0 = x + (width/2.0);
91     y_0 = y + (height/2.0);
92
93     deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
94     deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
95
96     Pie(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
97         roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
98
99     RestoreDC(graphics->hdc, save_state);
100
101     return Ok;
102 }
103
104 /* GdipDrawCurve helper function.
105  * Calculates Bezier points from cardinal spline points. */
106 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
107     REAL *y1, REAL *x2, REAL *y2)
108 {
109     REAL xdiff, ydiff;
110
111     /* calculate tangent */
112     xdiff = pts[2].X - pts[0].X;
113     ydiff = pts[2].Y - pts[0].Y;
114
115     /* apply tangent to get control points */
116     *x1 = pts[1].X - tension * xdiff;
117     *y1 = pts[1].Y - tension * ydiff;
118     *x2 = pts[1].X + tension * xdiff;
119     *y2 = pts[1].Y + tension * ydiff;
120 }
121
122 /* GdipDrawCurve helper function.
123  * Calculates Bezier points from cardinal spline endpoints. */
124 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
125     REAL tension, REAL *x, REAL *y)
126 {
127     /* tangent at endpoints is the line from the endpoint to the adjacent point */
128     *x = roundr(tension * (xadj - xend) + xend);
129     *y = roundr(tension * (yadj - yend) + yend);
130 }
131
132 /* Draws the linecap the specified color and size on the hdc.  The linecap is in
133  * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. Probably
134  * should not be called on an hdc that has a path you care about. */
135 static void draw_cap(HDC hdc, COLORREF color, GpLineCap cap, REAL size,
136     const GpCustomLineCap *custom, REAL x1, REAL y1, REAL x2, REAL y2)
137 {
138     HGDIOBJ oldbrush, oldpen;
139     GpMatrix *matrix = NULL;
140     HBRUSH brush;
141     HPEN pen;
142     PointF *custptf = NULL;
143     POINT pt[4], *custpt = NULL;
144     BYTE *tp = NULL;
145     REAL theta, dsmall, dbig, dx, dy;
146     INT i, count;
147     LOGBRUSH lb;
148
149     if((x1 == x2) && (y1 == y2))
150         return;
151
152     theta = gdiplus_atan2(y2 - y1, x2 - x1);
153
154     brush = CreateSolidBrush(color);
155     lb.lbStyle = BS_SOLID;
156     lb.lbColor = color;
157     lb.lbHatch = 0;
158     pen = ExtCreatePen(PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT,
159                ((cap == LineCapCustom) && custom && (custom->fill)) ? size : 1,
160                &lb, 0, NULL);
161     oldbrush = SelectObject(hdc, brush);
162     oldpen = SelectObject(hdc, pen);
163
164     switch(cap){
165         case LineCapFlat:
166             break;
167         case LineCapSquare:
168         case LineCapSquareAnchor:
169         case LineCapDiamondAnchor:
170             size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
171             if(cap == LineCapDiamondAnchor){
172                 dsmall = cos(theta + M_PI_2) * size;
173                 dbig = sin(theta + M_PI_2) * size;
174             }
175             else{
176                 dsmall = cos(theta + M_PI_4) * size;
177                 dbig = sin(theta + M_PI_4) * size;
178             }
179
180             /* calculating the latter points from the earlier points makes them
181              * look a little better because of rounding issues */
182             pt[0].x = roundr(x2 - dsmall);
183             pt[1].x = roundr(((REAL)pt[0].x) + dbig + dsmall);
184
185             pt[0].y = roundr(y2 - dbig);
186             pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig);
187
188             pt[1].y = roundr(y2 - dsmall);
189             pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y));
190
191             pt[3].x = roundr(x2 - dbig);
192             pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig);
193
194             Polygon(hdc, pt, 4);
195
196             break;
197         case LineCapArrowAnchor:
198             size = size * 4.0 / sqrt(3.0);
199
200             dx = cos(M_PI / 6.0 + theta) * size;
201             dy = sin(M_PI / 6.0 + theta) * size;
202
203             pt[0].x = roundr(x2 - dx);
204             pt[0].y = roundr(y2 - dy);
205
206             dx = cos(- M_PI / 6.0 + theta) * size;
207             dy = sin(- M_PI / 6.0 + theta) * size;
208
209             pt[1].x = roundr(x2 - dx);
210             pt[1].y = roundr(y2 - dy);
211
212             pt[2].x = roundr(x2);
213             pt[2].y = roundr(y2);
214
215             Polygon(hdc, pt, 3);
216
217             break;
218         case LineCapRoundAnchor:
219             dx = dy = ANCHOR_WIDTH * size / 2.0;
220
221             x2 = (REAL) roundr(x2 - dx);
222             y2 = (REAL) roundr(y2 - dy);
223
224             Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
225                 roundr(y2 + 2.0 * dy));
226             break;
227         case LineCapTriangle:
228             size = size / 2.0;
229             dx = cos(M_PI_2 + theta) * size;
230             dy = sin(M_PI_2 + theta) * size;
231
232             /* Using roundr here can make the triangle float off the end of the
233              * line. */
234             pt[0].x = ((x2 - x1) >= 0 ? floorf(x2 - dx) : ceilf(x2 - dx));
235             pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(y2 - dy));
236             pt[1].x = roundr(pt[0].x + 2.0 * dx);
237             pt[1].y = roundr(pt[0].y + 2.0 * dy);
238
239             dx = cos(theta) * size;
240             dy = sin(theta) * size;
241
242             pt[2].x = roundr(x2 + dx);
243             pt[2].y = roundr(y2 + dy);
244
245             Polygon(hdc, pt, 3);
246
247             break;
248         case LineCapRound:
249             dx = -cos(M_PI_2 + theta) * size;
250             dy = -sin(M_PI_2 + theta) * size;
251
252             pt[0].x = ((x2 - x1) >= 0 ? floorf(x2 - dx) : ceilf(x2 - dx));
253             pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(y2 - dy));
254             pt[1].x = roundr(pt[0].x + 2.0 * dx);
255             pt[1].y = roundr(pt[0].y + 2.0 * dy);
256
257             dx = dy = size / 2.0;
258
259             x2 = (REAL) roundr(x2 - dx);
260             y2 = (REAL) roundr(y2 - dy);
261
262             Pie(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
263                 roundr(y2 + 2.0 * dy), pt[0].x, pt[0].y, pt[1].x, pt[1].y);
264             break;
265         case LineCapCustom:
266             if(!custom)
267                 break;
268
269             count = custom->pathdata.Count;
270             custptf = GdipAlloc(count * sizeof(PointF));
271             custpt = GdipAlloc(count * sizeof(POINT));
272             tp = GdipAlloc(count);
273
274             if(!custptf || !custpt || !tp || (GdipCreateMatrix(&matrix) != Ok))
275                 goto custend;
276
277             memcpy(custptf, custom->pathdata.Points, count * sizeof(PointF));
278
279             GdipScaleMatrix(matrix, size, size, MatrixOrderAppend);
280             GdipRotateMatrix(matrix, (180.0 / M_PI) * (theta - M_PI_2),
281                              MatrixOrderAppend);
282             GdipTranslateMatrix(matrix, x2, y2, MatrixOrderAppend);
283             GdipTransformMatrixPoints(matrix, custptf, count);
284
285             for(i = 0; i < count; i++){
286                 custpt[i].x = roundr(custptf[i].X);
287                 custpt[i].y = roundr(custptf[i].Y);
288                 tp[i] = convert_path_point_type(custom->pathdata.Types[i]);
289             }
290
291             if(custom->fill){
292                 BeginPath(hdc);
293                 PolyDraw(hdc, custpt, tp, count);
294                 EndPath(hdc);
295                 StrokeAndFillPath(hdc);
296             }
297             else
298                 PolyDraw(hdc, custpt, tp, count);
299
300 custend:
301             GdipFree(custptf);
302             GdipFree(custpt);
303             GdipFree(tp);
304             GdipDeleteMatrix(matrix);
305             break;
306         default:
307             break;
308     }
309
310     SelectObject(hdc, oldbrush);
311     SelectObject(hdc, oldpen);
312     DeleteObject(brush);
313     DeleteObject(pen);
314 }
315
316 /* Shortens the line by the given percent by changing x2, y2.
317  * If percent is > 1.0 then the line will change direction.
318  * If percent is negative it can lengthen the line. */
319 static void shorten_line_percent(REAL x1, REAL  y1, REAL *x2, REAL *y2, REAL percent)
320 {
321     REAL dist, theta, dx, dy;
322
323     if((y1 == *y2) && (x1 == *x2))
324         return;
325
326     dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * -percent;
327     theta = gdiplus_atan2((*y2 - y1), (*x2 - x1));
328     dx = cos(theta) * dist;
329     dy = sin(theta) * dist;
330
331     *x2 = *x2 + dx;
332     *y2 = *y2 + dy;
333 }
334
335 /* Shortens the line by the given amount by changing x2, y2.
336  * If the amount is greater than the distance, the line will become length 0.
337  * If the amount is negative, it can lengthen the line. */
338 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
339 {
340     REAL dx, dy, percent;
341
342     dx = *x2 - x1;
343     dy = *y2 - y1;
344     if(dx == 0 && dy == 0)
345         return;
346
347     percent = amt / sqrt(dx * dx + dy * dy);
348     if(percent >= 1.0){
349         *x2 = x1;
350         *y2 = y1;
351         return;
352     }
353
354     shorten_line_percent(x1, y1, x2, y2, percent);
355 }
356
357 /* Draws lines between the given points, and if caps is true then draws an endcap
358  * at the end of the last line.  FIXME: Startcaps not implemented. */
359 static GpStatus draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
360     INT count, BOOL caps)
361 {
362     POINT *pti = NULL;
363     GpPointF *ptcopy = NULL;
364     INT i;
365     GpStatus status = GenericError;
366
367     if(!count)
368         return Ok;
369
370     pti = GdipAlloc(count * sizeof(POINT));
371     ptcopy = GdipAlloc(count * sizeof(GpPointF));
372
373     if(!pti || !ptcopy){
374         status = OutOfMemory;
375         goto end;
376     }
377
378     memcpy(ptcopy, pt, count * sizeof(GpPointF));
379
380     if(caps){
381         if(pen->endcap == LineCapArrowAnchor)
382             shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
383                              &ptcopy[count-1].X, &ptcopy[count-1].Y, pen->width);
384         else if((pen->endcap == LineCapCustom) && pen->customend)
385             shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
386                              &ptcopy[count-1].X, &ptcopy[count-1].Y,
387                              pen->customend->inset * pen->width);
388
389         if(pen->startcap == LineCapArrowAnchor)
390             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
391                              &ptcopy[0].X, &ptcopy[0].Y, pen->width);
392         else if((pen->startcap == LineCapCustom) && pen->customstart)
393             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
394                              &ptcopy[0].X, &ptcopy[0].Y,
395                              pen->customend->inset * pen->width);
396
397         draw_cap(hdc, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
398                  pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X, pt[count - 1].Y);
399         draw_cap(hdc, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
400                          pt[1].X, pt[1].Y, pt[0].X, pt[0].Y);
401     }
402
403     for(i = 0; i < count; i ++){
404         pti[i].x = roundr(ptcopy[i].X);
405         pti[i].y = roundr(ptcopy[i].Y);
406     }
407
408     Polyline(hdc, pti, count);
409
410 end:
411     GdipFree(pti);
412     GdipFree(ptcopy);
413
414     return status;
415 }
416
417 /* Conducts a linear search to find the bezier points that will back off
418  * the endpoint of the curve by a distance of amt. Linear search works
419  * better than binary in this case because there are multiple solutions,
420  * and binary searches often find a bad one. I don't think this is what
421  * Windows does but short of rendering the bezier without GDI's help it's
422  * the best we can do. If rev then work from the start of the passed points
423  * instead of the end. */
424 static void shorten_bezier_amt(GpPointF * pt, REAL amt, BOOL rev)
425 {
426     GpPointF origpt[4];
427     REAL percent = 0.00, dx, dy, origx, origy, diff = -1.0;
428     INT i, first = 0, second = 1, third = 2, fourth = 3;
429
430     if(rev){
431         first = 3;
432         second = 2;
433         third = 1;
434         fourth = 0;
435     }
436
437     origx = pt[fourth].X;
438     origy = pt[fourth].Y;
439     memcpy(origpt, pt, sizeof(GpPointF) * 4);
440
441     for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
442         /* reset bezier points to original values */
443         memcpy(pt, origpt, sizeof(GpPointF) * 4);
444         /* Perform magic on bezier points. Order is important here.*/
445         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
446         shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
447         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
448         shorten_line_percent(pt[first].X, pt[first].Y, &pt[second].X, &pt[second].Y, percent);
449         shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
450         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
451
452         dx = pt[fourth].X - origx;
453         dy = pt[fourth].Y - origy;
454
455         diff = sqrt(dx * dx + dy * dy);
456         percent += 0.0005 * amt;
457     }
458 }
459
460 /* Draws bezier curves between given points, and if caps is true then draws an
461  * endcap at the end of the last line.  FIXME: Startcaps not implemented. */
462 static GpStatus draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
463     INT count, BOOL caps)
464 {
465     POINT *pti, curpos;
466     GpPointF *ptcopy;
467     INT i;
468     REAL x, y;
469     GpStatus status = GenericError;
470
471     if(!count)
472         return Ok;
473
474     pti = GdipAlloc(count * sizeof(POINT));
475     ptcopy = GdipAlloc(count * sizeof(GpPointF));
476
477     if(!pti || !ptcopy){
478         status = OutOfMemory;
479         goto end;
480     }
481
482     memcpy(ptcopy, pt, count * sizeof(GpPointF));
483
484     if(caps){
485         if(pen->endcap == LineCapArrowAnchor)
486             shorten_bezier_amt(&ptcopy[count-4], pen->width, FALSE);
487         /* FIXME The following is seemingly correct only for baseinset < 0 or
488          * baseinset > ~3. With smaller baseinsets, windows actually
489          * lengthens the bezier line instead of shortening it. */
490         else if((pen->endcap == LineCapCustom) && pen->customend){
491             x = pt[count - 1].X;
492             y = pt[count - 1].Y;
493             shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
494                              pen->width * pen->customend->inset);
495             MoveToEx(hdc, roundr(pt[count - 1].X), roundr(pt[count - 1].Y), &curpos);
496             LineTo(hdc, roundr(x), roundr(y));
497             MoveToEx(hdc, curpos.x, curpos.y, NULL);
498         }
499
500         if(pen->startcap == LineCapArrowAnchor)
501             shorten_bezier_amt(ptcopy, pen->width, TRUE);
502         else if((pen->startcap == LineCapCustom) && pen->customstart){
503             x = ptcopy[0].X;
504             y = ptcopy[0].Y;
505             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y, &x, &y,
506                              pen->width * pen->customend->inset);
507             MoveToEx(hdc, roundr(pt[0].X), roundr(pt[0].Y), &curpos);
508             LineTo(hdc, roundr(x), roundr(y));
509             MoveToEx(hdc, curpos.x, curpos.y, NULL);
510         }
511
512         /* the direction of the line cap is parallel to the direction at the
513          * end of the bezier (which, if it has been shortened, is not the same
514          * as the direction from pt[count-2] to pt[count-1]) */
515         draw_cap(hdc, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
516             pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
517             pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
518             pt[count - 1].X, pt[count - 1].Y);
519
520         draw_cap(hdc, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
521             pt[0].X - (ptcopy[0].X - ptcopy[1].X),
522             pt[0].Y - (ptcopy[0].Y - ptcopy[1].Y), pt[0].X, pt[0].Y);
523     }
524
525     for(i = 0; i < count; i ++){
526         pti[i].x = roundr(ptcopy[i].X);
527         pti[i].y = roundr(ptcopy[i].Y);
528     }
529
530     PolyBezier(hdc, pti, count);
531
532     status = Ok;
533
534 end:
535     GdipFree(pti);
536     GdipFree(ptcopy);
537
538     return status;
539 }
540
541 /* Draws a combination of bezier curves and lines between points. */
542 static GpStatus draw_poly(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
543     GDIPCONST BYTE * types, INT count, BOOL caps)
544 {
545     POINT *pti = GdipAlloc(count * sizeof(POINT)), curpos;
546     BYTE *tp = GdipAlloc(count);
547     GpPointF *ptcopy = GdipAlloc(count * sizeof(GpPointF));
548     REAL x = pt[count - 1].X, y = pt[count - 1].Y;
549     INT i, j;
550     GpStatus status = GenericError;
551
552     if(!count){
553         status = Ok;
554         goto end;
555     }
556     if(!pti || !tp || !ptcopy){
557         status = OutOfMemory;
558         goto end;
559     }
560
561     for(i = 1; i < count; i++){
562         if((types[i] & PathPointTypePathTypeMask) == PathPointTypeBezier){
563             if((i + 2 >= count) || !(types[i + 1] & PathPointTypeBezier)
564                 || !(types[i + 1] & PathPointTypeBezier)){
565                 ERR("Bad bezier points\n");
566                 goto end;
567             }
568             i += 2;
569         }
570     }
571
572     /* If we are drawing caps, go through the points and adjust them accordingly,
573      * and draw the caps. */
574     if(caps){
575         memcpy(ptcopy, pt, count * sizeof(GpPointF));
576
577         switch(types[count - 1] & PathPointTypePathTypeMask){
578             case PathPointTypeBezier:
579                 if(pen->endcap == LineCapArrowAnchor)
580                     shorten_bezier_amt(&ptcopy[count - 4], pen->width, FALSE);
581                 else if((pen->endcap == LineCapCustom) && pen->customend){
582                     x = pt[count - 1].X;
583                     y = pt[count - 1].Y;
584                     shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
585                                      pen->width * pen->customend->inset);
586                     MoveToEx(hdc, roundr(pt[count - 1].X), roundr(pt[count - 1].Y), &curpos);
587                     LineTo(hdc, roundr(x), roundr(y));
588                     MoveToEx(hdc, curpos.x, curpos.y, NULL);
589                 }
590
591                 draw_cap(hdc, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
592                     pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
593                     pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
594                     pt[count - 1].X, pt[count - 1].Y);
595
596                 break;
597             case PathPointTypeLine:
598                 if(pen->endcap == LineCapArrowAnchor)
599                     shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
600                                      &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
601                                      pen->width);
602                 else if((pen->endcap == LineCapCustom) && pen->customend)
603                     shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
604                                      &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
605                                      pen->customend->inset * pen->width);
606
607                 draw_cap(hdc, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
608                          pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X,
609                          pt[count - 1].Y);
610
611                 break;
612             default:
613                 ERR("Bad path last point\n");
614                 goto end;
615         }
616
617         /* Find start of points */
618         for(j = 1; j < count && ((types[j] & PathPointTypePathTypeMask)
619             == PathPointTypeStart); j++);
620
621         switch(types[j] & PathPointTypePathTypeMask){
622             case PathPointTypeBezier:
623                 if(pen->startcap == LineCapArrowAnchor)
624                     shorten_bezier_amt(&ptcopy[j - 1], pen->width, TRUE);
625                 else if((pen->startcap == LineCapCustom) && pen->customstart){
626                     x = pt[j - 1].X;
627                     y = pt[j - 1].Y;
628                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y, &x, &y,
629                                      pen->width * pen->customstart->inset);
630                     MoveToEx(hdc, roundr(pt[j - 1].X), roundr(pt[j - 1].Y), &curpos);
631                     LineTo(hdc, roundr(x), roundr(y));
632                     MoveToEx(hdc, curpos.x, curpos.y, NULL);
633                 }
634
635                 draw_cap(hdc, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
636                     pt[j - 1].X - (ptcopy[j - 1].X - ptcopy[j].X),
637                     pt[j - 1].Y - (ptcopy[j - 1].Y - ptcopy[j].Y),
638                     pt[j - 1].X, pt[j - 1].Y);
639
640                 break;
641             case PathPointTypeLine:
642                 if(pen->startcap == LineCapArrowAnchor)
643                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
644                                      &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
645                                      pen->width);
646                 else if((pen->startcap == LineCapCustom) && pen->customstart)
647                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
648                                      &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
649                                      pen->customstart->inset * pen->width);
650
651                 draw_cap(hdc, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customstart,
652                          pt[j].X, pt[j].Y, pt[j - 1].X,
653                          pt[j - 1].Y);
654
655                 break;
656             default:
657                 ERR("Bad path points\n");
658                 goto end;
659         }
660         for(i = 0; i < count; i++){
661             pti[i].x = roundr(ptcopy[i].X);
662             pti[i].y = roundr(ptcopy[i].Y);
663         }
664     }
665     else
666         for(i = 0; i < count; i++){
667             pti[i].x = roundr(pt[i].X);
668             pti[i].y = roundr(pt[i].Y);
669         }
670
671     for(i = 0; i < count; i++){
672         tp[i] = convert_path_point_type(types[i]);
673     }
674
675     PolyDraw(hdc, pti, tp, count);
676
677     status = Ok;
678
679 end:
680     GdipFree(pti);
681     GdipFree(ptcopy);
682     GdipFree(tp);
683
684     return status;
685 }
686
687 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
688 {
689     if(hdc == NULL)
690         return OutOfMemory;
691
692     if(graphics == NULL)
693         return InvalidParameter;
694
695     *graphics = GdipAlloc(sizeof(GpGraphics));
696     if(!*graphics)  return OutOfMemory;
697
698     (*graphics)->hdc = hdc;
699     (*graphics)->hwnd = NULL;
700     (*graphics)->smoothing = SmoothingModeDefault;
701     (*graphics)->compqual = CompositingQualityDefault;
702     (*graphics)->interpolation = InterpolationModeDefault;
703     (*graphics)->pixeloffset = PixelOffsetModeDefault;
704
705     return Ok;
706 }
707
708 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
709 {
710     GpStatus ret;
711
712     if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
713         return ret;
714
715     (*graphics)->hwnd = hwnd;
716
717     return Ok;
718 }
719
720 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
721 {
722     if(!graphics) return InvalidParameter;
723     if(graphics->hwnd)
724         ReleaseDC(graphics->hwnd, graphics->hdc);
725
726     HeapFree(GetProcessHeap(), 0, graphics);
727
728     return Ok;
729 }
730
731 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
732     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
733 {
734     INT save_state, num_pts;
735     GpPointF points[MAX_ARC_PTS];
736     GpStatus retval;
737
738     if(!graphics || !pen)
739         return InvalidParameter;
740
741     num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle);
742
743     save_state = SaveDC(graphics->hdc);
744     EndPath(graphics->hdc);
745     SelectObject(graphics->hdc, pen->gdipen);
746
747     retval = draw_polybezier(graphics->hdc, pen, points, num_pts, TRUE);
748
749     RestoreDC(graphics->hdc, save_state);
750
751     return retval;
752 }
753
754 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
755     REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
756 {
757     INT save_state;
758     GpPointF pt[4];
759     GpStatus retval;
760
761     if(!graphics || !pen)
762         return InvalidParameter;
763
764     pt[0].X = x1;
765     pt[0].Y = y1;
766     pt[1].X = x2;
767     pt[1].Y = y2;
768     pt[2].X = x3;
769     pt[2].Y = y3;
770     pt[3].X = x4;
771     pt[3].Y = y4;
772
773     save_state = SaveDC(graphics->hdc);
774     EndPath(graphics->hdc);
775     SelectObject(graphics->hdc, pen->gdipen);
776
777     retval = draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
778
779     RestoreDC(graphics->hdc, save_state);
780
781     return retval;
782 }
783
784 /* Approximates cardinal spline with Bezier curves. */
785 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
786     GDIPCONST GpPointF *points, INT count, REAL tension)
787 {
788     /* PolyBezier expects count*3-2 points. */
789     INT i, len_pt = count*3-2, save_state;
790     GpPointF *pt;
791     REAL x1, x2, y1, y2;
792     GpStatus retval;
793
794     if(!graphics || !pen)
795         return InvalidParameter;
796
797     pt = GdipAlloc(len_pt * sizeof(GpPointF));
798     tension = tension * TENSION_CONST;
799
800     calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
801         tension, &x1, &y1);
802
803     pt[0].X = points[0].X;
804     pt[0].Y = points[0].Y;
805     pt[1].X = x1;
806     pt[1].Y = y1;
807
808     for(i = 0; i < count-2; i++){
809         calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
810
811         pt[3*i+2].X = x1;
812         pt[3*i+2].Y = y1;
813         pt[3*i+3].X = points[i+1].X;
814         pt[3*i+3].Y = points[i+1].Y;
815         pt[3*i+4].X = x2;
816         pt[3*i+4].Y = y2;
817     }
818
819     calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
820         points[count-2].X, points[count-2].Y, tension, &x1, &y1);
821
822     pt[len_pt-2].X = x1;
823     pt[len_pt-2].Y = y1;
824     pt[len_pt-1].X = points[count-1].X;
825     pt[len_pt-1].Y = points[count-1].Y;
826
827     save_state = SaveDC(graphics->hdc);
828     EndPath(graphics->hdc);
829     SelectObject(graphics->hdc, pen->gdipen);
830
831     retval = draw_polybezier(graphics->hdc, pen, pt, len_pt, TRUE);
832
833     GdipFree(pt);
834     RestoreDC(graphics->hdc, save_state);
835
836     return retval;
837 }
838
839 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
840     INT y1, INT x2, INT y2)
841 {
842     INT save_state;
843     GpPointF pt[2];
844     GpStatus retval;
845
846     if(!pen || !graphics)
847         return InvalidParameter;
848
849     pt[0].X = (REAL)x1;
850     pt[0].Y = (REAL)y1;
851     pt[1].X = (REAL)x2;
852     pt[1].Y = (REAL)y2;
853
854     save_state = SaveDC(graphics->hdc);
855     EndPath(graphics->hdc);
856     SelectObject(graphics->hdc, pen->gdipen);
857
858     retval = draw_polyline(graphics->hdc, pen, pt, 2, TRUE);
859
860     RestoreDC(graphics->hdc, save_state);
861
862     return retval;
863 }
864
865 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
866     GpPointF *points, INT count)
867 {
868     INT save_state;
869     GpStatus retval;
870
871     if(!pen || !graphics || (count < 2))
872         return InvalidParameter;
873
874     save_state = SaveDC(graphics->hdc);
875     EndPath(graphics->hdc);
876     SelectObject(graphics->hdc, pen->gdipen);
877
878     retval = draw_polyline(graphics->hdc, pen, points, count, TRUE);
879
880     RestoreDC(graphics->hdc, save_state);
881
882     return retval;
883 }
884
885 GpStatus WINGDIPAPI GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *path)
886 {
887     INT save_state;
888     GpStatus retval;
889
890     if(!pen || !graphics)
891         return InvalidParameter;
892
893     save_state = SaveDC(graphics->hdc);
894     EndPath(graphics->hdc);
895     SelectObject(graphics->hdc, pen->gdipen);
896
897     retval = draw_poly(graphics->hdc, pen, path->pathdata.Points,
898                        path->pathdata.Types, path->pathdata.Count, TRUE);
899
900     RestoreDC(graphics->hdc, save_state);
901
902     return retval;
903 }
904
905 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
906     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
907 {
908     if(!pen)
909         return InvalidParameter;
910
911     return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
912         width, height, startAngle, sweepAngle);
913 }
914
915 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
916     INT y, INT width, INT height)
917 {
918     INT save_state;
919
920     if(!pen || !graphics)
921         return InvalidParameter;
922
923     save_state = SaveDC(graphics->hdc);
924     EndPath(graphics->hdc);
925     SelectObject(graphics->hdc, pen->gdipen);
926     SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
927
928     Rectangle(graphics->hdc, x, y, x + width, y + height);
929
930     RestoreDC(graphics->hdc, save_state);
931
932     return Ok;
933 }
934
935 GpStatus WINGDIPAPI GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath *path)
936 {
937     INT save_state;
938     GpStatus retval;
939
940     if(!brush || !graphics || !path)
941         return InvalidParameter;
942
943     save_state = SaveDC(graphics->hdc);
944     EndPath(graphics->hdc);
945     SelectObject(graphics->hdc, brush->gdibrush);
946     SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE
947                                                                     : WINDING));
948
949     BeginPath(graphics->hdc);
950     retval = draw_poly(graphics->hdc, NULL, path->pathdata.Points,
951                        path->pathdata.Types, path->pathdata.Count, FALSE);
952
953     if(retval != Ok)
954         goto end;
955
956     EndPath(graphics->hdc);
957     FillPath(graphics->hdc);
958
959     retval = Ok;
960
961 end:
962     RestoreDC(graphics->hdc, save_state);
963
964     return retval;
965 }
966
967 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
968     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
969 {
970     if(!brush)
971         return InvalidParameter;
972
973     return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
974         width, height, startAngle, sweepAngle);
975 }
976
977 /* FIXME: Compositing quality is not used anywhere except the getter/setter. */
978 GpStatus WINGDIPAPI GdipGetCompositingQuality(GpGraphics *graphics,
979     CompositingQuality *quality)
980 {
981     if(!graphics || !quality)
982         return InvalidParameter;
983
984     *quality = graphics->compqual;
985
986     return Ok;
987 }
988
989 /* FIXME: Interpolation mode is not used anywhere except the getter/setter. */
990 GpStatus WINGDIPAPI GdipGetInterpolationMode(GpGraphics *graphics,
991     InterpolationMode *mode)
992 {
993     if(!graphics || !mode)
994         return InvalidParameter;
995
996     *mode = graphics->interpolation;
997
998     return Ok;
999 }
1000
1001 /* FIXME: Pixel offset mode is not used anywhere except the getter/setter. */
1002 GpStatus WINGDIPAPI GdipGetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1003     *mode)
1004 {
1005     if(!graphics || !mode)
1006         return InvalidParameter;
1007
1008     *mode = graphics->pixeloffset;
1009
1010     return Ok;
1011 }
1012
1013 /* FIXME: Smoothing mode is not used anywhere except the getter/setter. */
1014 GpStatus WINGDIPAPI GdipGetSmoothingMode(GpGraphics *graphics, SmoothingMode *mode)
1015 {
1016     if(!graphics || !mode)
1017         return InvalidParameter;
1018
1019     *mode = graphics->smoothing;
1020
1021     return Ok;
1022 }
1023
1024 GpStatus WINGDIPAPI GdipRestoreGraphics(GpGraphics *graphics, GraphicsState state)
1025 {
1026     if(!graphics)
1027         return InvalidParameter;
1028
1029     FIXME("graphics state not implemented\n");
1030
1031     return NotImplemented;
1032 }
1033
1034 GpStatus WINGDIPAPI GdipSaveGraphics(GpGraphics *graphics, GraphicsState *state)
1035 {
1036     if(!graphics || !state)
1037         return InvalidParameter;
1038
1039     FIXME("graphics state not implemented\n");
1040
1041     return NotImplemented;
1042 }
1043
1044 GpStatus WINGDIPAPI GdipSetCompositingQuality(GpGraphics *graphics,
1045     CompositingQuality quality)
1046 {
1047     if(!graphics)
1048         return InvalidParameter;
1049
1050     graphics->compqual = quality;
1051
1052     return Ok;
1053 }
1054
1055 GpStatus WINGDIPAPI GdipSetInterpolationMode(GpGraphics *graphics,
1056     InterpolationMode mode)
1057 {
1058     if(!graphics)
1059         return InvalidParameter;
1060
1061     graphics->interpolation = mode;
1062
1063     return Ok;
1064 }
1065
1066 GpStatus WINGDIPAPI GdipSetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1067     mode)
1068 {
1069     if(!graphics)
1070         return InvalidParameter;
1071
1072     graphics->pixeloffset = mode;
1073
1074     return Ok;
1075 }
1076
1077 GpStatus WINGDIPAPI GdipSetSmoothingMode(GpGraphics *graphics, SmoothingMode mode)
1078 {
1079     if(!graphics)
1080         return InvalidParameter;
1081
1082     graphics->smoothing = mode;
1083
1084     return Ok;
1085 }