gdiplus: Draw custom dashes.
[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 static REAL convert_unit(HDC hdc, GpUnit unit)
76 {
77     switch(unit)
78     {
79         case UnitInch:
80             return (REAL) GetDeviceCaps(hdc, LOGPIXELSX);
81         case UnitPoint:
82             return ((REAL)GetDeviceCaps(hdc, LOGPIXELSX)) / 72.0;
83         case UnitDocument:
84             return ((REAL)GetDeviceCaps(hdc, LOGPIXELSX)) / 300.0;
85         case UnitMillimeter:
86             return ((REAL)GetDeviceCaps(hdc, LOGPIXELSX)) / 25.4;
87         case UnitWorld:
88             ERR("cannot convert UnitWorld\n");
89             return 0.0;
90         case UnitPixel:
91         case UnitDisplay:
92         default:
93             return 1.0;
94     }
95 }
96
97 static INT prepare_dc(GpGraphics *graphics, GpPen *pen)
98 {
99     HPEN gdipen;
100     REAL width;
101     INT save_state = SaveDC(graphics->hdc), i, numdashes;
102     GpPointF pt[2];
103     DWORD dash_array[MAX_DASHLEN];
104
105     EndPath(graphics->hdc);
106
107     /* Get an estimate for the amount the pen width is affected by the world
108      * transform. (This is similar to what some of the wine drivers do.) */
109     pt[0].X = 0.0;
110     pt[0].Y = 0.0;
111     pt[1].X = 1.0;
112     pt[1].Y = 1.0;
113     GdipTransformMatrixPoints(graphics->worldtrans, pt, 2);
114     width = sqrt((pt[1].X - pt[0].X) * (pt[1].X - pt[0].X) +
115                  (pt[1].Y - pt[0].Y) * (pt[1].Y - pt[0].Y)) / sqrt(2.0);
116
117     width *= pen->width * convert_unit(graphics->hdc,
118                           pen->unit == UnitWorld ? graphics->unit : pen->unit);
119
120     if(pen->dash == DashStyleCustom){
121         numdashes = min(pen->numdashes, MAX_DASHLEN);
122
123         TRACE("dashes are: ");
124         for(i = 0; i < numdashes; i++){
125             dash_array[i] = roundr(width * pen->dashes[i]);
126             TRACE("%d, ", dash_array[i]);
127         }
128         TRACE("\n and the pen style is %x\n", pen->style);
129
130         gdipen = ExtCreatePen(pen->style, roundr(width), &pen->brush->lb,
131                               numdashes, dash_array);
132     }
133     else
134         gdipen = ExtCreatePen(pen->style, roundr(width), &pen->brush->lb, 0, NULL);
135
136     SelectObject(graphics->hdc, gdipen);
137
138     return save_state;
139 }
140
141 static void restore_dc(GpGraphics *graphics, INT state)
142 {
143     DeleteObject(SelectObject(graphics->hdc, GetStockObject(NULL_PEN)));
144     RestoreDC(graphics->hdc, state);
145 }
146
147 /* This helper applies all the changes that the points listed in ptf need in
148  * order to be drawn on the device context.  In the end, this should include at
149  * least:
150  *  -scaling by page unit
151  *  -applying world transformation
152  *  -converting from float to int
153  * Native gdiplus uses gdi32 to do all this (via SetMapMode, SetViewportExtEx,
154  * SetWindowExtEx, SetWorldTransform, etc.) but we cannot because we are using
155  * gdi to draw, and these functions would irreparably mess with line widths.
156  */
157 static void transform_and_round_points(GpGraphics *graphics, POINT *pti,
158     GpPointF *ptf, INT count)
159 {
160     REAL unitscale;
161     GpMatrix *matrix;
162     int i;
163
164     unitscale = convert_unit(graphics->hdc, graphics->unit);
165
166     /* apply page scale */
167     if(graphics->unit != UnitDisplay)
168         unitscale *= graphics->scale;
169
170     GdipCloneMatrix(graphics->worldtrans, &matrix);
171     GdipScaleMatrix(matrix, unitscale, unitscale, MatrixOrderAppend);
172     GdipTransformMatrixPoints(matrix, ptf, count);
173     GdipDeleteMatrix(matrix);
174
175     for(i = 0; i < count; i++){
176         pti[i].x = roundr(ptf[i].X);
177         pti[i].y = roundr(ptf[i].Y);
178     }
179 }
180
181 /* GdipDrawPie/GdipFillPie helper function */
182 static void draw_pie(GpGraphics *graphics, REAL x, REAL y, REAL width,
183     REAL height, REAL startAngle, REAL sweepAngle)
184 {
185     GpPointF ptf[4];
186     POINT pti[4];
187
188     ptf[0].X = x;
189     ptf[0].Y = y;
190     ptf[1].X = x + width;
191     ptf[1].Y = y + height;
192
193     deg2xy(startAngle+sweepAngle, x + width / 2.0, y + width / 2.0, &ptf[2].X, &ptf[2].Y);
194     deg2xy(startAngle, x + width / 2.0, y + width / 2.0, &ptf[3].X, &ptf[3].Y);
195
196     transform_and_round_points(graphics, pti, ptf, 4);
197
198     Pie(graphics->hdc, pti[0].x, pti[0].y, pti[1].x, pti[1].y, pti[2].x,
199         pti[2].y, pti[3].x, pti[3].y);
200 }
201
202 /* GdipDrawCurve helper function.
203  * Calculates Bezier points from cardinal spline points. */
204 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
205     REAL *y1, REAL *x2, REAL *y2)
206 {
207     REAL xdiff, ydiff;
208
209     /* calculate tangent */
210     xdiff = pts[2].X - pts[0].X;
211     ydiff = pts[2].Y - pts[0].Y;
212
213     /* apply tangent to get control points */
214     *x1 = pts[1].X - tension * xdiff;
215     *y1 = pts[1].Y - tension * ydiff;
216     *x2 = pts[1].X + tension * xdiff;
217     *y2 = pts[1].Y + tension * ydiff;
218 }
219
220 /* GdipDrawCurve helper function.
221  * Calculates Bezier points from cardinal spline endpoints. */
222 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
223     REAL tension, REAL *x, REAL *y)
224 {
225     /* tangent at endpoints is the line from the endpoint to the adjacent point */
226     *x = roundr(tension * (xadj - xend) + xend);
227     *y = roundr(tension * (yadj - yend) + yend);
228 }
229
230 /* Draws the linecap the specified color and size on the hdc.  The linecap is in
231  * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. Probably
232  * should not be called on an hdc that has a path you care about. */
233 static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL size,
234     const GpCustomLineCap *custom, REAL x1, REAL y1, REAL x2, REAL y2)
235 {
236     HGDIOBJ oldbrush = NULL, oldpen = NULL;
237     GpMatrix *matrix = NULL;
238     HBRUSH brush = NULL;
239     HPEN pen = NULL;
240     PointF ptf[4], *custptf = NULL;
241     POINT pt[4], *custpt = NULL;
242     BYTE *tp = NULL;
243     REAL theta, dsmall, dbig, dx, dy = 0.0;
244     INT i, count;
245     LOGBRUSH lb;
246     BOOL customstroke;
247
248     if((x1 == x2) && (y1 == y2))
249         return;
250
251     theta = gdiplus_atan2(y2 - y1, x2 - x1);
252
253     customstroke = (cap == LineCapCustom) && custom && (!custom->fill);
254     if(!customstroke){
255         brush = CreateSolidBrush(color);
256         lb.lbStyle = BS_SOLID;
257         lb.lbColor = color;
258         lb.lbHatch = 0;
259         pen = ExtCreatePen(PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT |
260                            PS_JOIN_MITER, 1, &lb, 0,
261                            NULL);
262         oldbrush = SelectObject(graphics->hdc, brush);
263         oldpen = SelectObject(graphics->hdc, pen);
264     }
265
266     switch(cap){
267         case LineCapFlat:
268             break;
269         case LineCapSquare:
270         case LineCapSquareAnchor:
271         case LineCapDiamondAnchor:
272             size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
273             if(cap == LineCapDiamondAnchor){
274                 dsmall = cos(theta + M_PI_2) * size;
275                 dbig = sin(theta + M_PI_2) * size;
276             }
277             else{
278                 dsmall = cos(theta + M_PI_4) * size;
279                 dbig = sin(theta + M_PI_4) * size;
280             }
281
282             ptf[0].X = x2 - dsmall;
283             ptf[1].X = x2 + dbig;
284
285             ptf[0].Y = y2 - dbig;
286             ptf[3].Y = y2 + dsmall;
287
288             ptf[1].Y = y2 - dsmall;
289             ptf[2].Y = y2 + dbig;
290
291             ptf[3].X = x2 - dbig;
292             ptf[2].X = x2 + dsmall;
293
294             transform_and_round_points(graphics, pt, ptf, 4);
295             Polygon(graphics->hdc, pt, 4);
296
297             break;
298         case LineCapArrowAnchor:
299             size = size * 4.0 / sqrt(3.0);
300
301             dx = cos(M_PI / 6.0 + theta) * size;
302             dy = sin(M_PI / 6.0 + theta) * size;
303
304             ptf[0].X = x2 - dx;
305             ptf[0].Y = y2 - dy;
306
307             dx = cos(- M_PI / 6.0 + theta) * size;
308             dy = sin(- M_PI / 6.0 + theta) * size;
309
310             ptf[1].X = x2 - dx;
311             ptf[1].Y = y2 - dy;
312
313             ptf[2].X = x2;
314             ptf[2].Y = y2;
315
316             transform_and_round_points(graphics, pt, ptf, 3);
317             Polygon(graphics->hdc, pt, 3);
318
319             break;
320         case LineCapRoundAnchor:
321             dx = dy = ANCHOR_WIDTH * size / 2.0;
322
323             ptf[0].X = x2 - dx;
324             ptf[0].Y = y2 - dy;
325             ptf[1].X = x2 + dx;
326             ptf[1].Y = y2 + dy;
327
328             transform_and_round_points(graphics, pt, ptf, 2);
329             Ellipse(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
330
331             break;
332         case LineCapTriangle:
333             size = size / 2.0;
334             dx = cos(M_PI_2 + theta) * size;
335             dy = sin(M_PI_2 + theta) * size;
336
337             ptf[0].X = x2 - dx;
338             ptf[0].Y = y2 - dy;
339             ptf[1].X = x2 + dx;
340             ptf[1].Y = y2 + dy;
341
342             dx = cos(theta) * size;
343             dy = sin(theta) * size;
344
345             ptf[2].X = x2 + dx;
346             ptf[2].Y = y2 + dy;
347
348             transform_and_round_points(graphics, pt, ptf, 3);
349             Polygon(graphics->hdc, pt, 3);
350
351             break;
352         case LineCapRound:
353             dx = dy = size / 2.0;
354
355             ptf[0].X = x2 - dx;
356             ptf[0].Y = y2 - dy;
357             ptf[1].X = x2 + dx;
358             ptf[1].Y = y2 + dy;
359
360             dx = -cos(M_PI_2 + theta) * size;
361             dy = -sin(M_PI_2 + theta) * size;
362
363             ptf[2].X = x2 - dx;
364             ptf[2].Y = y2 - dy;
365             ptf[3].X = x2 + dx;
366             ptf[3].Y = y2 + dy;
367
368             transform_and_round_points(graphics, pt, ptf, 4);
369             Pie(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y, pt[2].x,
370                 pt[2].y, pt[3].x, pt[3].y);
371
372             break;
373         case LineCapCustom:
374             if(!custom)
375                 break;
376
377             count = custom->pathdata.Count;
378             custptf = GdipAlloc(count * sizeof(PointF));
379             custpt = GdipAlloc(count * sizeof(POINT));
380             tp = GdipAlloc(count);
381
382             if(!custptf || !custpt || !tp || (GdipCreateMatrix(&matrix) != Ok))
383                 goto custend;
384
385             memcpy(custptf, custom->pathdata.Points, count * sizeof(PointF));
386
387             GdipScaleMatrix(matrix, size, size, MatrixOrderAppend);
388             GdipRotateMatrix(matrix, (180.0 / M_PI) * (theta - M_PI_2),
389                              MatrixOrderAppend);
390             GdipTranslateMatrix(matrix, x2, y2, MatrixOrderAppend);
391             GdipTransformMatrixPoints(matrix, custptf, count);
392
393             transform_and_round_points(graphics, custpt, custptf, count);
394
395             for(i = 0; i < count; i++)
396                 tp[i] = convert_path_point_type(custom->pathdata.Types[i]);
397
398             if(custom->fill){
399                 BeginPath(graphics->hdc);
400                 PolyDraw(graphics->hdc, custpt, tp, count);
401                 EndPath(graphics->hdc);
402                 StrokeAndFillPath(graphics->hdc);
403             }
404             else
405                 PolyDraw(graphics->hdc, custpt, tp, count);
406
407 custend:
408             GdipFree(custptf);
409             GdipFree(custpt);
410             GdipFree(tp);
411             GdipDeleteMatrix(matrix);
412             break;
413         default:
414             break;
415     }
416
417     if(!customstroke){
418         SelectObject(graphics->hdc, oldbrush);
419         SelectObject(graphics->hdc, oldpen);
420         DeleteObject(brush);
421         DeleteObject(pen);
422     }
423 }
424
425 /* Shortens the line by the given percent by changing x2, y2.
426  * If percent is > 1.0 then the line will change direction.
427  * If percent is negative it can lengthen the line. */
428 static void shorten_line_percent(REAL x1, REAL  y1, REAL *x2, REAL *y2, REAL percent)
429 {
430     REAL dist, theta, dx, dy;
431
432     if((y1 == *y2) && (x1 == *x2))
433         return;
434
435     dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * -percent;
436     theta = gdiplus_atan2((*y2 - y1), (*x2 - x1));
437     dx = cos(theta) * dist;
438     dy = sin(theta) * dist;
439
440     *x2 = *x2 + dx;
441     *y2 = *y2 + dy;
442 }
443
444 /* Shortens the line by the given amount by changing x2, y2.
445  * If the amount is greater than the distance, the line will become length 0.
446  * If the amount is negative, it can lengthen the line. */
447 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
448 {
449     REAL dx, dy, percent;
450
451     dx = *x2 - x1;
452     dy = *y2 - y1;
453     if(dx == 0 && dy == 0)
454         return;
455
456     percent = amt / sqrt(dx * dx + dy * dy);
457     if(percent >= 1.0){
458         *x2 = x1;
459         *y2 = y1;
460         return;
461     }
462
463     shorten_line_percent(x1, y1, x2, y2, percent);
464 }
465
466 /* Draws lines between the given points, and if caps is true then draws an endcap
467  * at the end of the last line.  FIXME: Startcaps not implemented. */
468 static GpStatus draw_polyline(GpGraphics *graphics, GpPen *pen,
469     GDIPCONST GpPointF * pt, INT count, BOOL caps)
470 {
471     POINT *pti = NULL;
472     GpPointF *ptcopy = NULL;
473     GpStatus status = GenericError;
474
475     if(!count)
476         return Ok;
477
478     pti = GdipAlloc(count * sizeof(POINT));
479     ptcopy = GdipAlloc(count * sizeof(GpPointF));
480
481     if(!pti || !ptcopy){
482         status = OutOfMemory;
483         goto end;
484     }
485
486     memcpy(ptcopy, pt, count * sizeof(GpPointF));
487
488     if(caps){
489         if(pen->endcap == LineCapArrowAnchor)
490             shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
491                              &ptcopy[count-1].X, &ptcopy[count-1].Y, pen->width);
492         else if((pen->endcap == LineCapCustom) && pen->customend)
493             shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
494                              &ptcopy[count-1].X, &ptcopy[count-1].Y,
495                              pen->customend->inset * pen->width);
496
497         if(pen->startcap == LineCapArrowAnchor)
498             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
499                              &ptcopy[0].X, &ptcopy[0].Y, pen->width);
500         else if((pen->startcap == LineCapCustom) && pen->customstart)
501             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
502                              &ptcopy[0].X, &ptcopy[0].Y,
503                              pen->customend->inset * pen->width);
504
505         draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
506                  pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X, pt[count - 1].Y);
507         draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
508                          pt[1].X, pt[1].Y, pt[0].X, pt[0].Y);\
509     }
510
511     transform_and_round_points(graphics, pti, ptcopy, count);
512
513     Polyline(graphics->hdc, pti, count);
514
515 end:
516     GdipFree(pti);
517     GdipFree(ptcopy);
518
519     return status;
520 }
521
522 /* Conducts a linear search to find the bezier points that will back off
523  * the endpoint of the curve by a distance of amt. Linear search works
524  * better than binary in this case because there are multiple solutions,
525  * and binary searches often find a bad one. I don't think this is what
526  * Windows does but short of rendering the bezier without GDI's help it's
527  * the best we can do. If rev then work from the start of the passed points
528  * instead of the end. */
529 static void shorten_bezier_amt(GpPointF * pt, REAL amt, BOOL rev)
530 {
531     GpPointF origpt[4];
532     REAL percent = 0.00, dx, dy, origx, origy, diff = -1.0;
533     INT i, first = 0, second = 1, third = 2, fourth = 3;
534
535     if(rev){
536         first = 3;
537         second = 2;
538         third = 1;
539         fourth = 0;
540     }
541
542     origx = pt[fourth].X;
543     origy = pt[fourth].Y;
544     memcpy(origpt, pt, sizeof(GpPointF) * 4);
545
546     for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
547         /* reset bezier points to original values */
548         memcpy(pt, origpt, sizeof(GpPointF) * 4);
549         /* Perform magic on bezier points. Order is important here.*/
550         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
551         shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
552         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
553         shorten_line_percent(pt[first].X, pt[first].Y, &pt[second].X, &pt[second].Y, percent);
554         shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
555         shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
556
557         dx = pt[fourth].X - origx;
558         dy = pt[fourth].Y - origy;
559
560         diff = sqrt(dx * dx + dy * dy);
561         percent += 0.0005 * amt;
562     }
563 }
564
565 /* Draws bezier curves between given points, and if caps is true then draws an
566  * endcap at the end of the last line.  FIXME: Startcaps not implemented. */
567 static GpStatus draw_polybezier(GpGraphics *graphics, GpPen *pen,
568     GDIPCONST GpPointF * pt, INT count, BOOL caps)
569 {
570     POINT *pti, curpos;
571     GpPointF *ptcopy;
572     REAL x, y;
573     GpStatus status = GenericError;
574
575     if(!count)
576         return Ok;
577
578     pti = GdipAlloc(count * sizeof(POINT));
579     ptcopy = GdipAlloc(count * sizeof(GpPointF));
580
581     if(!pti || !ptcopy){
582         status = OutOfMemory;
583         goto end;
584     }
585
586     memcpy(ptcopy, pt, count * sizeof(GpPointF));
587
588     if(caps){
589         if(pen->endcap == LineCapArrowAnchor)
590             shorten_bezier_amt(&ptcopy[count-4], pen->width, FALSE);
591         /* FIXME The following is seemingly correct only for baseinset < 0 or
592          * baseinset > ~3. With smaller baseinsets, windows actually
593          * lengthens the bezier line instead of shortening it. */
594         else if((pen->endcap == LineCapCustom) && pen->customend){
595             x = pt[count - 1].X;
596             y = pt[count - 1].Y;
597             shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
598                              pen->width * pen->customend->inset);
599             MoveToEx(graphics->hdc, roundr(pt[count - 1].X), roundr(pt[count - 1].Y), &curpos);
600             LineTo(graphics->hdc, roundr(x), roundr(y));
601             MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
602         }
603
604         if(pen->startcap == LineCapArrowAnchor)
605             shorten_bezier_amt(ptcopy, pen->width, TRUE);
606         else if((pen->startcap == LineCapCustom) && pen->customstart){
607             x = ptcopy[0].X;
608             y = ptcopy[0].Y;
609             shorten_line_amt(ptcopy[1].X, ptcopy[1].Y, &x, &y,
610                              pen->width * pen->customend->inset);
611             MoveToEx(graphics->hdc, roundr(pt[0].X), roundr(pt[0].Y), &curpos);
612             LineTo(graphics->hdc, roundr(x), roundr(y));
613             MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
614         }
615
616         /* the direction of the line cap is parallel to the direction at the
617          * end of the bezier (which, if it has been shortened, is not the same
618          * as the direction from pt[count-2] to pt[count-1]) */
619         draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
620             pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
621             pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
622             pt[count - 1].X, pt[count - 1].Y);
623
624         draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
625             pt[0].X - (ptcopy[0].X - ptcopy[1].X),
626             pt[0].Y - (ptcopy[0].Y - ptcopy[1].Y), pt[0].X, pt[0].Y);
627     }
628
629     transform_and_round_points(graphics, pti, ptcopy, count);
630
631     PolyBezier(graphics->hdc, pti, count);
632
633     status = Ok;
634
635 end:
636     GdipFree(pti);
637     GdipFree(ptcopy);
638
639     return status;
640 }
641
642 /* Draws a combination of bezier curves and lines between points. */
643 static GpStatus draw_poly(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF * pt,
644     GDIPCONST BYTE * types, INT count, BOOL caps)
645 {
646     POINT *pti = GdipAlloc(count * sizeof(POINT)), curpos;
647     BYTE *tp = GdipAlloc(count);
648     GpPointF *ptcopy = GdipAlloc(count * sizeof(GpPointF));
649     REAL x = pt[count - 1].X, y = pt[count - 1].Y;
650     INT i, j;
651     GpStatus status = GenericError;
652
653     if(!count){
654         status = Ok;
655         goto end;
656     }
657     if(!pti || !tp || !ptcopy){
658         status = OutOfMemory;
659         goto end;
660     }
661
662     for(i = 1; i < count; i++){
663         if((types[i] & PathPointTypePathTypeMask) == PathPointTypeBezier){
664             if((i + 2 >= count) || !(types[i + 1] & PathPointTypeBezier)
665                 || !(types[i + 1] & PathPointTypeBezier)){
666                 ERR("Bad bezier points\n");
667                 goto end;
668             }
669             i += 2;
670         }
671     }
672
673     memcpy(ptcopy, pt, count * sizeof(GpPointF));
674
675     /* If we are drawing caps, go through the points and adjust them accordingly,
676      * and draw the caps. */
677     if(caps){
678         switch(types[count - 1] & PathPointTypePathTypeMask){
679             case PathPointTypeBezier:
680                 if(pen->endcap == LineCapArrowAnchor)
681                     shorten_bezier_amt(&ptcopy[count - 4], pen->width, FALSE);
682                 else if((pen->endcap == LineCapCustom) && pen->customend){
683                     x = pt[count - 1].X;
684                     y = pt[count - 1].Y;
685                     shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
686                                      pen->width * pen->customend->inset);
687                     MoveToEx(graphics->hdc, roundr(pt[count - 1].X),
688                              roundr(pt[count - 1].Y), &curpos);
689                     LineTo(graphics->hdc, roundr(x), roundr(y));
690                     MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
691                 }
692
693                 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
694                     pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
695                     pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
696                     pt[count - 1].X, pt[count - 1].Y);
697
698                 break;
699             case PathPointTypeLine:
700                 if(pen->endcap == LineCapArrowAnchor)
701                     shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
702                                      &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
703                                      pen->width);
704                 else if((pen->endcap == LineCapCustom) && pen->customend)
705                     shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
706                                      &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
707                                      pen->customend->inset * pen->width);
708
709                 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
710                          pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X,
711                          pt[count - 1].Y);
712
713                 break;
714             default:
715                 ERR("Bad path last point\n");
716                 goto end;
717         }
718
719         /* Find start of points */
720         for(j = 1; j < count && ((types[j] & PathPointTypePathTypeMask)
721             == PathPointTypeStart); j++);
722
723         switch(types[j] & PathPointTypePathTypeMask){
724             case PathPointTypeBezier:
725                 if(pen->startcap == LineCapArrowAnchor)
726                     shorten_bezier_amt(&ptcopy[j - 1], pen->width, TRUE);
727                 else if((pen->startcap == LineCapCustom) && pen->customstart){
728                     x = pt[j - 1].X;
729                     y = pt[j - 1].Y;
730                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y, &x, &y,
731                                      pen->width * pen->customstart->inset);
732                     MoveToEx(graphics->hdc, roundr(pt[j - 1].X), roundr(pt[j - 1].Y), &curpos);
733                     LineTo(graphics->hdc, roundr(x), roundr(y));
734                     MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
735                 }
736
737                 draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
738                     pt[j - 1].X - (ptcopy[j - 1].X - ptcopy[j].X),
739                     pt[j - 1].Y - (ptcopy[j - 1].Y - ptcopy[j].Y),
740                     pt[j - 1].X, pt[j - 1].Y);
741
742                 break;
743             case PathPointTypeLine:
744                 if(pen->startcap == LineCapArrowAnchor)
745                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
746                                      &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
747                                      pen->width);
748                 else if((pen->startcap == LineCapCustom) && pen->customstart)
749                     shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
750                                      &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
751                                      pen->customstart->inset * pen->width);
752
753                 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customstart,
754                          pt[j].X, pt[j].Y, pt[j - 1].X,
755                          pt[j - 1].Y);
756
757                 break;
758             default:
759                 ERR("Bad path points\n");
760                 goto end;
761         }
762     }
763
764     transform_and_round_points(graphics, pti, ptcopy, count);
765
766     for(i = 0; i < count; i++){
767         tp[i] = convert_path_point_type(types[i]);
768     }
769
770     PolyDraw(graphics->hdc, pti, tp, count);
771
772     status = Ok;
773
774 end:
775     GdipFree(pti);
776     GdipFree(ptcopy);
777     GdipFree(tp);
778
779     return status;
780 }
781
782 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
783 {
784     GpStatus retval;
785
786     if(hdc == NULL)
787         return OutOfMemory;
788
789     if(graphics == NULL)
790         return InvalidParameter;
791
792     *graphics = GdipAlloc(sizeof(GpGraphics));
793     if(!*graphics)  return OutOfMemory;
794
795     if((retval = GdipCreateMatrix(&(*graphics)->worldtrans)) != Ok){
796         GdipFree(*graphics);
797         return retval;
798     }
799
800     (*graphics)->hdc = hdc;
801     (*graphics)->hwnd = NULL;
802     (*graphics)->smoothing = SmoothingModeDefault;
803     (*graphics)->compqual = CompositingQualityDefault;
804     (*graphics)->interpolation = InterpolationModeDefault;
805     (*graphics)->pixeloffset = PixelOffsetModeDefault;
806     (*graphics)->unit = UnitDisplay;
807     (*graphics)->scale = 1.0;
808
809     return Ok;
810 }
811
812 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
813 {
814     GpStatus ret;
815
816     if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
817         return ret;
818
819     (*graphics)->hwnd = hwnd;
820
821     return Ok;
822 }
823
824 GpStatus WINGDIPAPI GdipCreateMetafileFromEmf(HENHMETAFILE hemf, BOOL delete,
825     GpMetafile **metafile)
826 {
827     static int calls;
828
829     if(!hemf || !metafile)
830         return InvalidParameter;
831
832     if(!(calls++))
833         FIXME("not implemented\n");
834
835     return NotImplemented;
836 }
837
838 GpStatus WINGDIPAPI GdipCreateMetafileFromWmf(HMETAFILE hwmf, BOOL delete,
839     GDIPCONST WmfPlaceableFileHeader * placeable, GpMetafile **metafile)
840 {
841     static int calls;
842
843     if(!hwmf || !metafile || !placeable)
844         return InvalidParameter;
845
846     if(!(calls++))
847         FIXME("not implemented\n");
848
849     return NotImplemented;
850 }
851
852 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
853 {
854     if(!graphics) return InvalidParameter;
855     if(graphics->hwnd)
856         ReleaseDC(graphics->hwnd, graphics->hdc);
857
858     GdipDeleteMatrix(graphics->worldtrans);
859     HeapFree(GetProcessHeap(), 0, graphics);
860
861     return Ok;
862 }
863
864 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
865     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
866 {
867     INT save_state, num_pts;
868     GpPointF points[MAX_ARC_PTS];
869     GpStatus retval;
870
871     if(!graphics || !pen)
872         return InvalidParameter;
873
874     num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle);
875
876     save_state = prepare_dc(graphics, pen);
877
878     retval = draw_polybezier(graphics, pen, points, num_pts, TRUE);
879
880     restore_dc(graphics, save_state);
881
882     return retval;
883 }
884
885 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
886     REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
887 {
888     INT save_state;
889     GpPointF pt[4];
890     GpStatus retval;
891
892     if(!graphics || !pen)
893         return InvalidParameter;
894
895     pt[0].X = x1;
896     pt[0].Y = y1;
897     pt[1].X = x2;
898     pt[1].Y = y2;
899     pt[2].X = x3;
900     pt[2].Y = y3;
901     pt[3].X = x4;
902     pt[3].Y = y4;
903
904     save_state = prepare_dc(graphics, pen);
905
906     retval = draw_polybezier(graphics, pen, pt, 4, TRUE);
907
908     restore_dc(graphics, save_state);
909
910     return retval;
911 }
912
913 /* Approximates cardinal spline with Bezier curves. */
914 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
915     GDIPCONST GpPointF *points, INT count, REAL tension)
916 {
917     /* PolyBezier expects count*3-2 points. */
918     INT i, len_pt = count*3-2, save_state;
919     GpPointF *pt;
920     REAL x1, x2, y1, y2;
921     GpStatus retval;
922
923     if(!graphics || !pen)
924         return InvalidParameter;
925
926     pt = GdipAlloc(len_pt * sizeof(GpPointF));
927     tension = tension * TENSION_CONST;
928
929     calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
930         tension, &x1, &y1);
931
932     pt[0].X = points[0].X;
933     pt[0].Y = points[0].Y;
934     pt[1].X = x1;
935     pt[1].Y = y1;
936
937     for(i = 0; i < count-2; i++){
938         calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
939
940         pt[3*i+2].X = x1;
941         pt[3*i+2].Y = y1;
942         pt[3*i+3].X = points[i+1].X;
943         pt[3*i+3].Y = points[i+1].Y;
944         pt[3*i+4].X = x2;
945         pt[3*i+4].Y = y2;
946     }
947
948     calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
949         points[count-2].X, points[count-2].Y, tension, &x1, &y1);
950
951     pt[len_pt-2].X = x1;
952     pt[len_pt-2].Y = y1;
953     pt[len_pt-1].X = points[count-1].X;
954     pt[len_pt-1].Y = points[count-1].Y;
955
956     save_state = prepare_dc(graphics, pen);
957
958     retval = draw_polybezier(graphics, pen, pt, len_pt, TRUE);
959
960     GdipFree(pt);
961     restore_dc(graphics, save_state);
962
963     return retval;
964 }
965
966 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
967     INT y1, INT x2, INT y2)
968 {
969     INT save_state;
970     GpPointF pt[2];
971     GpStatus retval;
972
973     if(!pen || !graphics)
974         return InvalidParameter;
975
976     pt[0].X = (REAL)x1;
977     pt[0].Y = (REAL)y1;
978     pt[1].X = (REAL)x2;
979     pt[1].Y = (REAL)y2;
980
981     save_state = prepare_dc(graphics, pen);
982
983     retval = draw_polyline(graphics, pen, pt, 2, TRUE);
984
985     restore_dc(graphics, save_state);
986
987     return retval;
988 }
989
990 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
991     GpPointF *points, INT count)
992 {
993     INT save_state;
994     GpStatus retval;
995
996     if(!pen || !graphics || (count < 2))
997         return InvalidParameter;
998
999     save_state = prepare_dc(graphics, pen);
1000
1001     retval = draw_polyline(graphics, pen, points, count, TRUE);
1002
1003     restore_dc(graphics, save_state);
1004
1005     return retval;
1006 }
1007
1008 GpStatus WINGDIPAPI GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *path)
1009 {
1010     INT save_state;
1011     GpStatus retval;
1012
1013     if(!pen || !graphics)
1014         return InvalidParameter;
1015
1016     save_state = prepare_dc(graphics, pen);
1017
1018     retval = draw_poly(graphics, pen, path->pathdata.Points,
1019                        path->pathdata.Types, path->pathdata.Count, TRUE);
1020
1021     restore_dc(graphics, save_state);
1022
1023     return retval;
1024 }
1025
1026 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
1027     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
1028 {
1029     INT save_state;
1030
1031     if(!graphics || !pen)
1032         return InvalidParameter;
1033
1034     save_state = prepare_dc(graphics, pen);
1035     SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
1036
1037     draw_pie(graphics, x, y, width, height, startAngle, sweepAngle);
1038
1039     restore_dc(graphics, save_state);
1040
1041     return Ok;
1042 }
1043
1044 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
1045     INT y, INT width, INT height)
1046 {
1047     INT save_state;
1048
1049     if(!pen || !graphics)
1050         return InvalidParameter;
1051
1052     save_state = prepare_dc(graphics, pen);
1053     SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
1054
1055     Rectangle(graphics->hdc, x, y, x + width, y + height);
1056
1057     restore_dc(graphics, save_state);
1058
1059     return Ok;
1060 }
1061
1062 GpStatus WINGDIPAPI GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath *path)
1063 {
1064     INT save_state;
1065     GpStatus retval;
1066
1067     if(!brush || !graphics || !path)
1068         return InvalidParameter;
1069
1070     save_state = SaveDC(graphics->hdc);
1071     EndPath(graphics->hdc);
1072     SelectObject(graphics->hdc, brush->gdibrush);
1073     SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE
1074                                                                     : WINDING));
1075
1076     BeginPath(graphics->hdc);
1077     retval = draw_poly(graphics, NULL, path->pathdata.Points,
1078                        path->pathdata.Types, path->pathdata.Count, FALSE);
1079
1080     if(retval != Ok)
1081         goto end;
1082
1083     EndPath(graphics->hdc);
1084     FillPath(graphics->hdc);
1085
1086     retval = Ok;
1087
1088 end:
1089     RestoreDC(graphics->hdc, save_state);
1090
1091     return retval;
1092 }
1093
1094 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
1095     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
1096 {
1097     INT save_state;
1098
1099     if(!graphics || !brush)
1100         return InvalidParameter;
1101
1102     save_state = SaveDC(graphics->hdc);
1103     EndPath(graphics->hdc);
1104     SelectObject(graphics->hdc, brush->gdibrush);
1105     SelectObject(graphics->hdc, GetStockObject(NULL_PEN));
1106
1107     draw_pie(graphics, x, y, width, height, startAngle, sweepAngle);
1108
1109     RestoreDC(graphics->hdc, save_state);
1110
1111     return Ok;
1112 }
1113
1114 GpStatus WINGDIPAPI GdipFillPolygonI(GpGraphics *graphics, GpBrush *brush,
1115     GDIPCONST GpPoint *points, INT count, GpFillMode fillMode)
1116 {
1117     INT save_state, i;
1118     GpPointF *ptf = NULL;
1119     POINT *pti = NULL;
1120     GpStatus retval = Ok;
1121
1122     if(!graphics || !brush || !points || !count)
1123         return InvalidParameter;
1124
1125     ptf = GdipAlloc(count * sizeof(GpPointF));
1126     pti = GdipAlloc(count * sizeof(POINT));
1127     if(!ptf || !pti){
1128         retval = OutOfMemory;
1129         goto end;
1130     }
1131
1132     for(i = 0; i < count; i ++){
1133         ptf[i].X = (REAL) points[i].X;
1134         ptf[i].Y = (REAL) points[i].Y;
1135     }
1136
1137     save_state = SaveDC(graphics->hdc);
1138     EndPath(graphics->hdc);
1139     SelectObject(graphics->hdc, brush->gdibrush);
1140     SelectObject(graphics->hdc, GetStockObject(NULL_PEN));
1141     SetPolyFillMode(graphics->hdc, (fillMode == FillModeAlternate ? ALTERNATE
1142                                                                   : WINDING));
1143
1144     transform_and_round_points(graphics, pti, ptf, count);
1145     Polygon(graphics->hdc, pti, count);
1146
1147     RestoreDC(graphics->hdc, save_state);
1148
1149 end:
1150     GdipFree(ptf);
1151     GdipFree(pti);
1152
1153     return retval;
1154 }
1155
1156 /* FIXME: Compositing quality is not used anywhere except the getter/setter. */
1157 GpStatus WINGDIPAPI GdipGetCompositingQuality(GpGraphics *graphics,
1158     CompositingQuality *quality)
1159 {
1160     if(!graphics || !quality)
1161         return InvalidParameter;
1162
1163     *quality = graphics->compqual;
1164
1165     return Ok;
1166 }
1167
1168 /* FIXME: Interpolation mode is not used anywhere except the getter/setter. */
1169 GpStatus WINGDIPAPI GdipGetInterpolationMode(GpGraphics *graphics,
1170     InterpolationMode *mode)
1171 {
1172     if(!graphics || !mode)
1173         return InvalidParameter;
1174
1175     *mode = graphics->interpolation;
1176
1177     return Ok;
1178 }
1179
1180 GpStatus WINGDIPAPI GdipGetPageScale(GpGraphics *graphics, REAL *scale)
1181 {
1182     if(!graphics || !scale)
1183         return InvalidParameter;
1184
1185     *scale = graphics->scale;
1186
1187     return Ok;
1188 }
1189
1190 GpStatus WINGDIPAPI GdipGetPageUnit(GpGraphics *graphics, GpUnit *unit)
1191 {
1192     if(!graphics || !unit)
1193         return InvalidParameter;
1194
1195     *unit = graphics->unit;
1196
1197     return Ok;
1198 }
1199
1200 /* FIXME: Pixel offset mode is not used anywhere except the getter/setter. */
1201 GpStatus WINGDIPAPI GdipGetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1202     *mode)
1203 {
1204     if(!graphics || !mode)
1205         return InvalidParameter;
1206
1207     *mode = graphics->pixeloffset;
1208
1209     return Ok;
1210 }
1211
1212 /* FIXME: Smoothing mode is not used anywhere except the getter/setter. */
1213 GpStatus WINGDIPAPI GdipGetSmoothingMode(GpGraphics *graphics, SmoothingMode *mode)
1214 {
1215     if(!graphics || !mode)
1216         return InvalidParameter;
1217
1218     *mode = graphics->smoothing;
1219
1220     return Ok;
1221 }
1222
1223 GpStatus WINGDIPAPI GdipGetWorldTransform(GpGraphics *graphics, GpMatrix *matrix)
1224 {
1225     if(!graphics || !matrix)
1226         return InvalidParameter;
1227
1228     memcpy(matrix, graphics->worldtrans, sizeof(GpMatrix));
1229     return Ok;
1230 }
1231
1232 GpStatus WINGDIPAPI GdipRestoreGraphics(GpGraphics *graphics, GraphicsState state)
1233 {
1234     static int calls;
1235
1236     if(!graphics)
1237         return InvalidParameter;
1238
1239     if(!(calls++))
1240         FIXME("graphics state not implemented\n");
1241
1242     return NotImplemented;
1243 }
1244
1245 GpStatus WINGDIPAPI GdipSaveGraphics(GpGraphics *graphics, GraphicsState *state)
1246 {
1247     static int calls;
1248
1249     if(!graphics || !state)
1250         return InvalidParameter;
1251
1252     if(!(calls++))
1253         FIXME("graphics state not implemented\n");
1254
1255     return NotImplemented;
1256 }
1257
1258 GpStatus WINGDIPAPI GdipSetCompositingQuality(GpGraphics *graphics,
1259     CompositingQuality quality)
1260 {
1261     if(!graphics)
1262         return InvalidParameter;
1263
1264     graphics->compqual = quality;
1265
1266     return Ok;
1267 }
1268
1269 GpStatus WINGDIPAPI GdipSetInterpolationMode(GpGraphics *graphics,
1270     InterpolationMode mode)
1271 {
1272     if(!graphics)
1273         return InvalidParameter;
1274
1275     graphics->interpolation = mode;
1276
1277     return Ok;
1278 }
1279
1280 GpStatus WINGDIPAPI GdipSetPageScale(GpGraphics *graphics, REAL scale)
1281 {
1282     if(!graphics || (scale <= 0.0))
1283         return InvalidParameter;
1284
1285     graphics->scale = scale;
1286
1287     return Ok;
1288 }
1289
1290 GpStatus WINGDIPAPI GdipSetPageUnit(GpGraphics *graphics, GpUnit unit)
1291 {
1292     if(!graphics || (unit == UnitWorld))
1293         return InvalidParameter;
1294
1295     graphics->unit = unit;
1296
1297     return Ok;
1298 }
1299
1300 GpStatus WINGDIPAPI GdipSetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1301     mode)
1302 {
1303     if(!graphics)
1304         return InvalidParameter;
1305
1306     graphics->pixeloffset = mode;
1307
1308     return Ok;
1309 }
1310
1311 GpStatus WINGDIPAPI GdipSetSmoothingMode(GpGraphics *graphics, SmoothingMode mode)
1312 {
1313     if(!graphics)
1314         return InvalidParameter;
1315
1316     graphics->smoothing = mode;
1317
1318     return Ok;
1319 }
1320
1321 GpStatus WINGDIPAPI GdipSetWorldTransform(GpGraphics *graphics, GpMatrix *matrix)
1322 {
1323     if(!graphics || !matrix)
1324         return InvalidParameter;
1325
1326     GdipDeleteMatrix(graphics->worldtrans);
1327     return GdipCloneMatrix(matrix, &graphics->worldtrans);
1328 }