gdiplus: Added support for more page units.
[wine/multimedia.git] / dlls / gdiplus / graphics.c
blobdf481cde05816c0b6b7e8c3084b125f2ef7e8f58
1 /*
2 * Copyright (C) 2007 Google (Evan Stade)
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.
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.
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
19 #include <stdarg.h>
20 #include <math.h>
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"
30 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
32 /* looks-right constants */
33 #define TENSION_CONST (0.3)
34 #define ANCHOR_WIDTH (2.0)
35 #define MAX_ITERS (50)
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)
40 REAL radAngle, hypotenuse;
42 radAngle = deg2rad(angle);
43 hypotenuse = 50.0; /* arbitrary */
45 *x = x_0 + cos(radAngle) * hypotenuse;
46 *y = y_0 + sin(radAngle) * hypotenuse;
49 /* Converts from gdiplus path point type to gdi path point type. */
50 static BYTE convert_path_point_type(BYTE type)
52 BYTE ret;
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;
69 if(type & PathPointTypeCloseSubpath)
70 ret |= PT_CLOSEFIGURE;
72 return ret;
75 /* This helper applies all the changes that the points listed in ptf need in
76 * order to be drawn on the device context. In the end, this should include at
77 * least:
78 * -scaling by page unit
79 * -applying world transformation
80 * -converting from float to int
81 * Native gdiplus uses gdi32 to do all this (via SetMapMode, SetViewportExtEx,
82 * SetWindowExtEx, SetWorldTransform, etc.) but we cannot because we are using
83 * gdi to draw, and these functions would irreparably mess with line widths.
85 static void transform_and_round_points(GpGraphics *graphics, POINT *pti,
86 GDIPCONST GpPointF *ptf, INT count)
88 REAL unitscale;
89 int i;
91 switch(graphics->unit)
93 case UnitInch:
94 unitscale = GetDeviceCaps(graphics->hdc, LOGPIXELSX);
95 break;
96 case UnitPoint:
97 unitscale = ((REAL)GetDeviceCaps(graphics->hdc, LOGPIXELSX)) / 72.0;
98 break;
99 case UnitDocument:
100 unitscale = ((REAL)GetDeviceCaps(graphics->hdc, LOGPIXELSX)) / 300.0;
101 break;
102 case UnitMillimeter:
103 unitscale = ((REAL)GetDeviceCaps(graphics->hdc, LOGPIXELSX)) / 25.4;
104 break;
105 case UnitPixel:
106 case UnitDisplay:
107 default:
108 unitscale = 1.0;
109 break;
112 for(i = 0; i < count; i++){
113 pti[i].x = roundr(unitscale * ptf[i].X);
114 pti[i].y = roundr(unitscale * ptf[i].Y);
118 /* GdipDrawPie/GdipFillPie helper function */
119 static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen,
120 REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
122 INT save_state;
123 GpPointF ptf[4];
124 POINT pti[4];
126 if(!graphics)
127 return InvalidParameter;
129 save_state = SaveDC(graphics->hdc);
130 EndPath(graphics->hdc);
131 SelectObject(graphics->hdc, gdipen);
132 SelectObject(graphics->hdc, gdibrush);
134 ptf[0].X = x;
135 ptf[0].Y = y;
136 ptf[1].X = x + width;
137 ptf[1].Y = y + height;
139 deg2xy(startAngle+sweepAngle, x + width / 2.0, y + width / 2.0, &ptf[2].X, &ptf[2].Y);
140 deg2xy(startAngle, x + width / 2.0, y + width / 2.0, &ptf[3].X, &ptf[3].Y);
142 transform_and_round_points(graphics, pti, ptf, 4);
144 Pie(graphics->hdc, pti[0].x, pti[0].y, pti[1].x, pti[1].y, pti[2].x,
145 pti[2].y, pti[3].x, pti[3].y);
147 RestoreDC(graphics->hdc, save_state);
149 return Ok;
152 /* GdipDrawCurve helper function.
153 * Calculates Bezier points from cardinal spline points. */
154 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
155 REAL *y1, REAL *x2, REAL *y2)
157 REAL xdiff, ydiff;
159 /* calculate tangent */
160 xdiff = pts[2].X - pts[0].X;
161 ydiff = pts[2].Y - pts[0].Y;
163 /* apply tangent to get control points */
164 *x1 = pts[1].X - tension * xdiff;
165 *y1 = pts[1].Y - tension * ydiff;
166 *x2 = pts[1].X + tension * xdiff;
167 *y2 = pts[1].Y + tension * ydiff;
170 /* GdipDrawCurve helper function.
171 * Calculates Bezier points from cardinal spline endpoints. */
172 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
173 REAL tension, REAL *x, REAL *y)
175 /* tangent at endpoints is the line from the endpoint to the adjacent point */
176 *x = roundr(tension * (xadj - xend) + xend);
177 *y = roundr(tension * (yadj - yend) + yend);
180 /* Draws the linecap the specified color and size on the hdc. The linecap is in
181 * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. Probably
182 * should not be called on an hdc that has a path you care about. */
183 static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL size,
184 const GpCustomLineCap *custom, REAL x1, REAL y1, REAL x2, REAL y2)
186 HGDIOBJ oldbrush, oldpen;
187 GpMatrix *matrix = NULL;
188 HBRUSH brush;
189 HPEN pen;
190 PointF ptf[4], *custptf = NULL;
191 POINT pt[4], *custpt = NULL;
192 BYTE *tp = NULL;
193 REAL theta, dsmall, dbig, dx, dy = 0.0;
194 INT i, count;
195 LOGBRUSH lb;
197 if((x1 == x2) && (y1 == y2))
198 return;
200 theta = gdiplus_atan2(y2 - y1, x2 - x1);
202 brush = CreateSolidBrush(color);
203 lb.lbStyle = BS_SOLID;
204 lb.lbColor = color;
205 lb.lbHatch = 0;
206 pen = ExtCreatePen(PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT,
207 ((cap == LineCapCustom) && custom && (custom->fill)) ? size : 1,
208 &lb, 0, NULL);
209 oldbrush = SelectObject(graphics->hdc, brush);
210 oldpen = SelectObject(graphics->hdc, pen);
212 switch(cap){
213 case LineCapFlat:
214 break;
215 case LineCapSquare:
216 case LineCapSquareAnchor:
217 case LineCapDiamondAnchor:
218 size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
219 if(cap == LineCapDiamondAnchor){
220 dsmall = cos(theta + M_PI_2) * size;
221 dbig = sin(theta + M_PI_2) * size;
223 else{
224 dsmall = cos(theta + M_PI_4) * size;
225 dbig = sin(theta + M_PI_4) * size;
228 ptf[0].X = x2 - dsmall;
229 ptf[1].X = x2 + dbig;
231 ptf[0].Y = y2 - dbig;
232 ptf[3].Y = y2 + dsmall;
234 ptf[1].Y = y2 - dsmall;
235 ptf[2].Y = y2 + dbig;
237 ptf[3].X = x2 - dbig;
238 ptf[2].X = x2 + dsmall;
240 transform_and_round_points(graphics, pt, ptf, 4);
241 Polygon(graphics->hdc, pt, 4);
243 break;
244 case LineCapArrowAnchor:
245 size = size * 4.0 / sqrt(3.0);
247 dx = cos(M_PI / 6.0 + theta) * size;
248 dy = sin(M_PI / 6.0 + theta) * size;
250 ptf[0].X = x2 - dx;
251 ptf[0].Y = y2 - dy;
253 dx = cos(- M_PI / 6.0 + theta) * size;
254 dy = sin(- M_PI / 6.0 + theta) * size;
256 ptf[1].X = x2 - dx;
257 ptf[1].Y = y2 - dy;
259 ptf[2].X = x2;
260 ptf[2].Y = y2;
262 transform_and_round_points(graphics, pt, ptf, 3);
263 Polygon(graphics->hdc, pt, 3);
265 break;
266 case LineCapRoundAnchor:
267 dx = dy = ANCHOR_WIDTH * size / 2.0;
269 ptf[0].X = x2 - dx;
270 ptf[0].Y = y2 - dy;
271 ptf[1].X = x2 + dx;
272 ptf[1].Y = y2 + dy;
274 transform_and_round_points(graphics, pt, ptf, 2);
275 Ellipse(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
277 break;
278 case LineCapTriangle:
279 size = size / 2.0;
280 dx = cos(M_PI_2 + theta) * size;
281 dy = sin(M_PI_2 + theta) * size;
283 ptf[0].X = x2 - dx;
284 ptf[0].Y = y2 - dy;
285 ptf[1].X = x2 + dx;
286 ptf[1].Y = y2 + dy;
288 dx = cos(theta) * size;
289 dy = sin(theta) * size;
291 ptf[2].X = x2 + dx;
292 ptf[2].Y = y2 + dy;
294 transform_and_round_points(graphics, pt, ptf, 3);
295 Polygon(graphics->hdc, pt, 3);
297 break;
298 case LineCapRound:
299 dx = dy = size / 2.0;
301 ptf[0].X = x2 - dx;
302 ptf[0].Y = y2 - dy;
303 ptf[1].X = x2 + dx;
304 ptf[1].Y = y2 + dy;
306 dx = -cos(M_PI_2 + theta) * size;
307 dy = -sin(M_PI_2 + theta) * size;
309 ptf[2].X = x2 - dx;
310 ptf[2].Y = y2 - dy;
311 ptf[3].X = x2 + dx;
312 ptf[3].Y = y2 + dy;
314 transform_and_round_points(graphics, pt, ptf, 4);
315 Pie(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y, pt[2].x,
316 pt[2].y, pt[3].x, pt[3].y);
318 break;
319 case LineCapCustom:
320 if(!custom)
321 break;
323 count = custom->pathdata.Count;
324 custptf = GdipAlloc(count * sizeof(PointF));
325 custpt = GdipAlloc(count * sizeof(POINT));
326 tp = GdipAlloc(count);
328 if(!custptf || !custpt || !tp || (GdipCreateMatrix(&matrix) != Ok))
329 goto custend;
331 memcpy(custptf, custom->pathdata.Points, count * sizeof(PointF));
333 GdipScaleMatrix(matrix, size, size, MatrixOrderAppend);
334 GdipRotateMatrix(matrix, (180.0 / M_PI) * (theta - M_PI_2),
335 MatrixOrderAppend);
336 GdipTranslateMatrix(matrix, x2, y2, MatrixOrderAppend);
337 GdipTransformMatrixPoints(matrix, custptf, count);
339 transform_and_round_points(graphics, custpt, custptf, count);
341 for(i = 0; i < count; i++)
342 tp[i] = convert_path_point_type(custom->pathdata.Types[i]);
344 if(custom->fill){
345 BeginPath(graphics->hdc);
346 PolyDraw(graphics->hdc, custpt, tp, count);
347 EndPath(graphics->hdc);
348 StrokeAndFillPath(graphics->hdc);
350 else
351 PolyDraw(graphics->hdc, custpt, tp, count);
353 custend:
354 GdipFree(custptf);
355 GdipFree(custpt);
356 GdipFree(tp);
357 GdipDeleteMatrix(matrix);
358 break;
359 default:
360 break;
363 SelectObject(graphics->hdc, oldbrush);
364 SelectObject(graphics->hdc, oldpen);
365 DeleteObject(brush);
366 DeleteObject(pen);
369 /* Shortens the line by the given percent by changing x2, y2.
370 * If percent is > 1.0 then the line will change direction.
371 * If percent is negative it can lengthen the line. */
372 static void shorten_line_percent(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL percent)
374 REAL dist, theta, dx, dy;
376 if((y1 == *y2) && (x1 == *x2))
377 return;
379 dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * -percent;
380 theta = gdiplus_atan2((*y2 - y1), (*x2 - x1));
381 dx = cos(theta) * dist;
382 dy = sin(theta) * dist;
384 *x2 = *x2 + dx;
385 *y2 = *y2 + dy;
388 /* Shortens the line by the given amount by changing x2, y2.
389 * If the amount is greater than the distance, the line will become length 0.
390 * If the amount is negative, it can lengthen the line. */
391 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
393 REAL dx, dy, percent;
395 dx = *x2 - x1;
396 dy = *y2 - y1;
397 if(dx == 0 && dy == 0)
398 return;
400 percent = amt / sqrt(dx * dx + dy * dy);
401 if(percent >= 1.0){
402 *x2 = x1;
403 *y2 = y1;
404 return;
407 shorten_line_percent(x1, y1, x2, y2, percent);
410 /* Draws lines between the given points, and if caps is true then draws an endcap
411 * at the end of the last line. FIXME: Startcaps not implemented. */
412 static GpStatus draw_polyline(GpGraphics *graphics, GpPen *pen,
413 GDIPCONST GpPointF * pt, INT count, BOOL caps)
415 POINT *pti = NULL;
416 GpPointF *ptcopy = NULL;
417 GpStatus status = GenericError;
419 if(!count)
420 return Ok;
422 pti = GdipAlloc(count * sizeof(POINT));
423 ptcopy = GdipAlloc(count * sizeof(GpPointF));
425 if(!pti || !ptcopy){
426 status = OutOfMemory;
427 goto end;
430 if(caps){
431 memcpy(ptcopy, pt, count * sizeof(GpPointF));
433 if(pen->endcap == LineCapArrowAnchor)
434 shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
435 &ptcopy[count-1].X, &ptcopy[count-1].Y, pen->width);
436 else if((pen->endcap == LineCapCustom) && pen->customend)
437 shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y,
438 &ptcopy[count-1].X, &ptcopy[count-1].Y,
439 pen->customend->inset * pen->width);
441 if(pen->startcap == LineCapArrowAnchor)
442 shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
443 &ptcopy[0].X, &ptcopy[0].Y, pen->width);
444 else if((pen->startcap == LineCapCustom) && pen->customstart)
445 shorten_line_amt(ptcopy[1].X, ptcopy[1].Y,
446 &ptcopy[0].X, &ptcopy[0].Y,
447 pen->customend->inset * pen->width);
449 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
450 pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X, pt[count - 1].Y);
451 draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
452 pt[1].X, pt[1].Y, pt[0].X, pt[0].Y);\
454 transform_and_round_points(graphics, pti, ptcopy, count);
456 else
457 transform_and_round_points(graphics, pti, pt, count);
459 Polyline(graphics->hdc, pti, count);
461 end:
462 GdipFree(pti);
463 GdipFree(ptcopy);
465 return status;
468 /* Conducts a linear search to find the bezier points that will back off
469 * the endpoint of the curve by a distance of amt. Linear search works
470 * better than binary in this case because there are multiple solutions,
471 * and binary searches often find a bad one. I don't think this is what
472 * Windows does but short of rendering the bezier without GDI's help it's
473 * the best we can do. If rev then work from the start of the passed points
474 * instead of the end. */
475 static void shorten_bezier_amt(GpPointF * pt, REAL amt, BOOL rev)
477 GpPointF origpt[4];
478 REAL percent = 0.00, dx, dy, origx, origy, diff = -1.0;
479 INT i, first = 0, second = 1, third = 2, fourth = 3;
481 if(rev){
482 first = 3;
483 second = 2;
484 third = 1;
485 fourth = 0;
488 origx = pt[fourth].X;
489 origy = pt[fourth].Y;
490 memcpy(origpt, pt, sizeof(GpPointF) * 4);
492 for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
493 /* reset bezier points to original values */
494 memcpy(pt, origpt, sizeof(GpPointF) * 4);
495 /* Perform magic on bezier points. Order is important here.*/
496 shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
497 shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
498 shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
499 shorten_line_percent(pt[first].X, pt[first].Y, &pt[second].X, &pt[second].Y, percent);
500 shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent);
501 shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent);
503 dx = pt[fourth].X - origx;
504 dy = pt[fourth].Y - origy;
506 diff = sqrt(dx * dx + dy * dy);
507 percent += 0.0005 * amt;
511 /* Draws bezier curves between given points, and if caps is true then draws an
512 * endcap at the end of the last line. FIXME: Startcaps not implemented. */
513 static GpStatus draw_polybezier(GpGraphics *graphics, GpPen *pen,
514 GDIPCONST GpPointF * pt, INT count, BOOL caps)
516 POINT *pti, curpos;
517 GpPointF *ptcopy;
518 REAL x, y;
519 GpStatus status = GenericError;
521 if(!count)
522 return Ok;
524 pti = GdipAlloc(count * sizeof(POINT));
525 ptcopy = GdipAlloc(count * sizeof(GpPointF));
527 if(!pti || !ptcopy){
528 status = OutOfMemory;
529 goto end;
532 if(caps){
533 memcpy(ptcopy, pt, count * sizeof(GpPointF));
535 if(pen->endcap == LineCapArrowAnchor)
536 shorten_bezier_amt(&ptcopy[count-4], pen->width, FALSE);
537 /* FIXME The following is seemingly correct only for baseinset < 0 or
538 * baseinset > ~3. With smaller baseinsets, windows actually
539 * lengthens the bezier line instead of shortening it. */
540 else if((pen->endcap == LineCapCustom) && pen->customend){
541 x = pt[count - 1].X;
542 y = pt[count - 1].Y;
543 shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
544 pen->width * pen->customend->inset);
545 MoveToEx(graphics->hdc, roundr(pt[count - 1].X), roundr(pt[count - 1].Y), &curpos);
546 LineTo(graphics->hdc, roundr(x), roundr(y));
547 MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
550 if(pen->startcap == LineCapArrowAnchor)
551 shorten_bezier_amt(ptcopy, pen->width, TRUE);
552 else if((pen->startcap == LineCapCustom) && pen->customstart){
553 x = ptcopy[0].X;
554 y = ptcopy[0].Y;
555 shorten_line_amt(ptcopy[1].X, ptcopy[1].Y, &x, &y,
556 pen->width * pen->customend->inset);
557 MoveToEx(graphics->hdc, roundr(pt[0].X), roundr(pt[0].Y), &curpos);
558 LineTo(graphics->hdc, roundr(x), roundr(y));
559 MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
562 /* the direction of the line cap is parallel to the direction at the
563 * end of the bezier (which, if it has been shortened, is not the same
564 * as the direction from pt[count-2] to pt[count-1]) */
565 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
566 pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
567 pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
568 pt[count - 1].X, pt[count - 1].Y);
570 draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
571 pt[0].X - (ptcopy[0].X - ptcopy[1].X),
572 pt[0].Y - (ptcopy[0].Y - ptcopy[1].Y), pt[0].X, pt[0].Y);
574 transform_and_round_points(graphics, pti, ptcopy, count);
576 else
577 transform_and_round_points(graphics, pti, pt, count);
579 PolyBezier(graphics->hdc, pti, count);
581 status = Ok;
583 end:
584 GdipFree(pti);
585 GdipFree(ptcopy);
587 return status;
590 /* Draws a combination of bezier curves and lines between points. */
591 static GpStatus draw_poly(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF * pt,
592 GDIPCONST BYTE * types, INT count, BOOL caps)
594 POINT *pti = GdipAlloc(count * sizeof(POINT)), curpos;
595 BYTE *tp = GdipAlloc(count);
596 GpPointF *ptcopy = GdipAlloc(count * sizeof(GpPointF));
597 REAL x = pt[count - 1].X, y = pt[count - 1].Y;
598 INT i, j;
599 GpStatus status = GenericError;
601 if(!count){
602 status = Ok;
603 goto end;
605 if(!pti || !tp || !ptcopy){
606 status = OutOfMemory;
607 goto end;
610 for(i = 1; i < count; i++){
611 if((types[i] & PathPointTypePathTypeMask) == PathPointTypeBezier){
612 if((i + 2 >= count) || !(types[i + 1] & PathPointTypeBezier)
613 || !(types[i + 1] & PathPointTypeBezier)){
614 ERR("Bad bezier points\n");
615 goto end;
617 i += 2;
621 /* If we are drawing caps, go through the points and adjust them accordingly,
622 * and draw the caps. */
623 if(caps){
624 memcpy(ptcopy, pt, count * sizeof(GpPointF));
626 switch(types[count - 1] & PathPointTypePathTypeMask){
627 case PathPointTypeBezier:
628 if(pen->endcap == LineCapArrowAnchor)
629 shorten_bezier_amt(&ptcopy[count - 4], pen->width, FALSE);
630 else if((pen->endcap == LineCapCustom) && pen->customend){
631 x = pt[count - 1].X;
632 y = pt[count - 1].Y;
633 shorten_line_amt(pt[count - 2].X, pt[count - 2].Y, &x, &y,
634 pen->width * pen->customend->inset);
635 MoveToEx(graphics->hdc, roundr(pt[count - 1].X),
636 roundr(pt[count - 1].Y), &curpos);
637 LineTo(graphics->hdc, roundr(x), roundr(y));
638 MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
641 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
642 pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X),
643 pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y),
644 pt[count - 1].X, pt[count - 1].Y);
646 break;
647 case PathPointTypeLine:
648 if(pen->endcap == LineCapArrowAnchor)
649 shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
650 &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
651 pen->width);
652 else if((pen->endcap == LineCapCustom) && pen->customend)
653 shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y,
654 &ptcopy[count - 1].X, &ptcopy[count - 1].Y,
655 pen->customend->inset * pen->width);
657 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend,
658 pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X,
659 pt[count - 1].Y);
661 break;
662 default:
663 ERR("Bad path last point\n");
664 goto end;
667 /* Find start of points */
668 for(j = 1; j < count && ((types[j] & PathPointTypePathTypeMask)
669 == PathPointTypeStart); j++);
671 switch(types[j] & PathPointTypePathTypeMask){
672 case PathPointTypeBezier:
673 if(pen->startcap == LineCapArrowAnchor)
674 shorten_bezier_amt(&ptcopy[j - 1], pen->width, TRUE);
675 else if((pen->startcap == LineCapCustom) && pen->customstart){
676 x = pt[j - 1].X;
677 y = pt[j - 1].Y;
678 shorten_line_amt(ptcopy[j].X, ptcopy[j].Y, &x, &y,
679 pen->width * pen->customstart->inset);
680 MoveToEx(graphics->hdc, roundr(pt[j - 1].X), roundr(pt[j - 1].Y), &curpos);
681 LineTo(graphics->hdc, roundr(x), roundr(y));
682 MoveToEx(graphics->hdc, curpos.x, curpos.y, NULL);
685 draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart,
686 pt[j - 1].X - (ptcopy[j - 1].X - ptcopy[j].X),
687 pt[j - 1].Y - (ptcopy[j - 1].Y - ptcopy[j].Y),
688 pt[j - 1].X, pt[j - 1].Y);
690 break;
691 case PathPointTypeLine:
692 if(pen->startcap == LineCapArrowAnchor)
693 shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
694 &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
695 pen->width);
696 else if((pen->startcap == LineCapCustom) && pen->customstart)
697 shorten_line_amt(ptcopy[j].X, ptcopy[j].Y,
698 &ptcopy[j - 1].X, &ptcopy[j - 1].Y,
699 pen->customstart->inset * pen->width);
701 draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customstart,
702 pt[j].X, pt[j].Y, pt[j - 1].X,
703 pt[j - 1].Y);
705 break;
706 default:
707 ERR("Bad path points\n");
708 goto end;
710 transform_and_round_points(graphics, pti, ptcopy, count);
712 else
713 transform_and_round_points(graphics, pti, pt, count);
715 for(i = 0; i < count; i++){
716 tp[i] = convert_path_point_type(types[i]);
719 PolyDraw(graphics->hdc, pti, tp, count);
721 status = Ok;
723 end:
724 GdipFree(pti);
725 GdipFree(ptcopy);
726 GdipFree(tp);
728 return status;
731 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
733 if(hdc == NULL)
734 return OutOfMemory;
736 if(graphics == NULL)
737 return InvalidParameter;
739 *graphics = GdipAlloc(sizeof(GpGraphics));
740 if(!*graphics) return OutOfMemory;
742 (*graphics)->hdc = hdc;
743 (*graphics)->hwnd = NULL;
744 (*graphics)->smoothing = SmoothingModeDefault;
745 (*graphics)->compqual = CompositingQualityDefault;
746 (*graphics)->interpolation = InterpolationModeDefault;
747 (*graphics)->pixeloffset = PixelOffsetModeDefault;
748 (*graphics)->unit = UnitDisplay;
750 return Ok;
753 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
755 GpStatus ret;
757 if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
758 return ret;
760 (*graphics)->hwnd = hwnd;
762 return Ok;
765 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
767 if(!graphics) return InvalidParameter;
768 if(graphics->hwnd)
769 ReleaseDC(graphics->hwnd, graphics->hdc);
771 HeapFree(GetProcessHeap(), 0, graphics);
773 return Ok;
776 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
777 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
779 INT save_state, num_pts;
780 GpPointF points[MAX_ARC_PTS];
781 GpStatus retval;
783 if(!graphics || !pen)
784 return InvalidParameter;
786 num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle);
788 save_state = SaveDC(graphics->hdc);
789 EndPath(graphics->hdc);
790 SelectObject(graphics->hdc, pen->gdipen);
792 retval = draw_polybezier(graphics, pen, points, num_pts, TRUE);
794 RestoreDC(graphics->hdc, save_state);
796 return retval;
799 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
800 REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
802 INT save_state;
803 GpPointF pt[4];
804 GpStatus retval;
806 if(!graphics || !pen)
807 return InvalidParameter;
809 pt[0].X = x1;
810 pt[0].Y = y1;
811 pt[1].X = x2;
812 pt[1].Y = y2;
813 pt[2].X = x3;
814 pt[2].Y = y3;
815 pt[3].X = x4;
816 pt[3].Y = y4;
818 save_state = SaveDC(graphics->hdc);
819 EndPath(graphics->hdc);
820 SelectObject(graphics->hdc, pen->gdipen);
822 retval = draw_polybezier(graphics, pen, pt, 4, TRUE);
824 RestoreDC(graphics->hdc, save_state);
826 return retval;
829 /* Approximates cardinal spline with Bezier curves. */
830 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
831 GDIPCONST GpPointF *points, INT count, REAL tension)
833 /* PolyBezier expects count*3-2 points. */
834 INT i, len_pt = count*3-2, save_state;
835 GpPointF *pt;
836 REAL x1, x2, y1, y2;
837 GpStatus retval;
839 if(!graphics || !pen)
840 return InvalidParameter;
842 pt = GdipAlloc(len_pt * sizeof(GpPointF));
843 tension = tension * TENSION_CONST;
845 calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
846 tension, &x1, &y1);
848 pt[0].X = points[0].X;
849 pt[0].Y = points[0].Y;
850 pt[1].X = x1;
851 pt[1].Y = y1;
853 for(i = 0; i < count-2; i++){
854 calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
856 pt[3*i+2].X = x1;
857 pt[3*i+2].Y = y1;
858 pt[3*i+3].X = points[i+1].X;
859 pt[3*i+3].Y = points[i+1].Y;
860 pt[3*i+4].X = x2;
861 pt[3*i+4].Y = y2;
864 calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
865 points[count-2].X, points[count-2].Y, tension, &x1, &y1);
867 pt[len_pt-2].X = x1;
868 pt[len_pt-2].Y = y1;
869 pt[len_pt-1].X = points[count-1].X;
870 pt[len_pt-1].Y = points[count-1].Y;
872 save_state = SaveDC(graphics->hdc);
873 EndPath(graphics->hdc);
874 SelectObject(graphics->hdc, pen->gdipen);
876 retval = draw_polybezier(graphics, pen, pt, len_pt, TRUE);
878 GdipFree(pt);
879 RestoreDC(graphics->hdc, save_state);
881 return retval;
884 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
885 INT y1, INT x2, INT y2)
887 INT save_state;
888 GpPointF pt[2];
889 GpStatus retval;
891 if(!pen || !graphics)
892 return InvalidParameter;
894 pt[0].X = (REAL)x1;
895 pt[0].Y = (REAL)y1;
896 pt[1].X = (REAL)x2;
897 pt[1].Y = (REAL)y2;
899 save_state = SaveDC(graphics->hdc);
900 EndPath(graphics->hdc);
901 SelectObject(graphics->hdc, pen->gdipen);
903 retval = draw_polyline(graphics, pen, pt, 2, TRUE);
905 RestoreDC(graphics->hdc, save_state);
907 return retval;
910 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
911 GpPointF *points, INT count)
913 INT save_state;
914 GpStatus retval;
916 if(!pen || !graphics || (count < 2))
917 return InvalidParameter;
919 save_state = SaveDC(graphics->hdc);
920 EndPath(graphics->hdc);
921 SelectObject(graphics->hdc, pen->gdipen);
923 retval = draw_polyline(graphics, pen, points, count, TRUE);
925 RestoreDC(graphics->hdc, save_state);
927 return retval;
930 GpStatus WINGDIPAPI GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *path)
932 INT save_state;
933 GpStatus retval;
935 if(!pen || !graphics)
936 return InvalidParameter;
938 save_state = SaveDC(graphics->hdc);
939 EndPath(graphics->hdc);
940 SelectObject(graphics->hdc, pen->gdipen);
942 retval = draw_poly(graphics, pen, path->pathdata.Points,
943 path->pathdata.Types, path->pathdata.Count, TRUE);
945 RestoreDC(graphics->hdc, save_state);
947 return retval;
950 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
951 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
953 if(!pen)
954 return InvalidParameter;
956 return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
957 width, height, startAngle, sweepAngle);
960 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
961 INT y, INT width, INT height)
963 INT save_state;
965 if(!pen || !graphics)
966 return InvalidParameter;
968 save_state = SaveDC(graphics->hdc);
969 EndPath(graphics->hdc);
970 SelectObject(graphics->hdc, pen->gdipen);
971 SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
973 Rectangle(graphics->hdc, x, y, x + width, y + height);
975 RestoreDC(graphics->hdc, save_state);
977 return Ok;
980 GpStatus WINGDIPAPI GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath *path)
982 INT save_state;
983 GpStatus retval;
985 if(!brush || !graphics || !path)
986 return InvalidParameter;
988 save_state = SaveDC(graphics->hdc);
989 EndPath(graphics->hdc);
990 SelectObject(graphics->hdc, brush->gdibrush);
991 SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE
992 : WINDING));
994 BeginPath(graphics->hdc);
995 retval = draw_poly(graphics, NULL, path->pathdata.Points,
996 path->pathdata.Types, path->pathdata.Count, FALSE);
998 if(retval != Ok)
999 goto end;
1001 EndPath(graphics->hdc);
1002 FillPath(graphics->hdc);
1004 retval = Ok;
1006 end:
1007 RestoreDC(graphics->hdc, save_state);
1009 return retval;
1012 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
1013 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
1015 if(!brush)
1016 return InvalidParameter;
1018 return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
1019 width, height, startAngle, sweepAngle);
1022 GpStatus WINGDIPAPI GdipFillPolygonI(GpGraphics *graphics, GpBrush *brush,
1023 GDIPCONST GpPoint *points, INT count, GpFillMode fillMode)
1025 INT save_state, i;
1026 GpPointF *ptf = NULL;
1027 POINT *pti = NULL;
1028 GpStatus retval = Ok;
1030 if(!graphics || !brush || !points || !count)
1031 return InvalidParameter;
1033 ptf = GdipAlloc(count * sizeof(GpPointF));
1034 pti = GdipAlloc(count * sizeof(POINT));
1035 if(!ptf || !pti){
1036 retval = OutOfMemory;
1037 goto end;
1040 for(i = 0; i < count; i ++){
1041 ptf[i].X = (REAL) points[i].X;
1042 ptf[i].Y = (REAL) points[i].Y;
1045 save_state = SaveDC(graphics->hdc);
1046 EndPath(graphics->hdc);
1047 SelectObject(graphics->hdc, brush->gdibrush);
1048 SelectObject(graphics->hdc, GetStockObject(NULL_PEN));
1049 SetPolyFillMode(graphics->hdc, (fillMode == FillModeAlternate ? ALTERNATE
1050 : WINDING));
1052 transform_and_round_points(graphics, pti, ptf, count);
1053 Polygon(graphics->hdc, pti, count);
1055 RestoreDC(graphics->hdc, save_state);
1057 end:
1058 GdipFree(ptf);
1059 GdipFree(pti);
1061 return retval;
1064 /* FIXME: Compositing quality is not used anywhere except the getter/setter. */
1065 GpStatus WINGDIPAPI GdipGetCompositingQuality(GpGraphics *graphics,
1066 CompositingQuality *quality)
1068 if(!graphics || !quality)
1069 return InvalidParameter;
1071 *quality = graphics->compqual;
1073 return Ok;
1076 /* FIXME: Interpolation mode is not used anywhere except the getter/setter. */
1077 GpStatus WINGDIPAPI GdipGetInterpolationMode(GpGraphics *graphics,
1078 InterpolationMode *mode)
1080 if(!graphics || !mode)
1081 return InvalidParameter;
1083 *mode = graphics->interpolation;
1085 return Ok;
1088 GpStatus WINGDIPAPI GdipGetPageUnit(GpGraphics *graphics, GpUnit *unit)
1090 if(!graphics || !unit)
1091 return InvalidParameter;
1093 *unit = graphics->unit;
1095 return Ok;
1098 /* FIXME: Pixel offset mode is not used anywhere except the getter/setter. */
1099 GpStatus WINGDIPAPI GdipGetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1100 *mode)
1102 if(!graphics || !mode)
1103 return InvalidParameter;
1105 *mode = graphics->pixeloffset;
1107 return Ok;
1110 /* FIXME: Smoothing mode is not used anywhere except the getter/setter. */
1111 GpStatus WINGDIPAPI GdipGetSmoothingMode(GpGraphics *graphics, SmoothingMode *mode)
1113 if(!graphics || !mode)
1114 return InvalidParameter;
1116 *mode = graphics->smoothing;
1118 return Ok;
1121 GpStatus WINGDIPAPI GdipRestoreGraphics(GpGraphics *graphics, GraphicsState state)
1123 if(!graphics)
1124 return InvalidParameter;
1126 FIXME("graphics state not implemented\n");
1128 return NotImplemented;
1131 GpStatus WINGDIPAPI GdipSaveGraphics(GpGraphics *graphics, GraphicsState *state)
1133 if(!graphics || !state)
1134 return InvalidParameter;
1136 FIXME("graphics state not implemented\n");
1138 return NotImplemented;
1141 GpStatus WINGDIPAPI GdipSetCompositingQuality(GpGraphics *graphics,
1142 CompositingQuality quality)
1144 if(!graphics)
1145 return InvalidParameter;
1147 graphics->compqual = quality;
1149 return Ok;
1152 GpStatus WINGDIPAPI GdipSetInterpolationMode(GpGraphics *graphics,
1153 InterpolationMode mode)
1155 if(!graphics)
1156 return InvalidParameter;
1158 graphics->interpolation = mode;
1160 return Ok;
1163 GpStatus WINGDIPAPI GdipSetPageUnit(GpGraphics *graphics, GpUnit unit)
1165 if(!graphics || (unit == UnitWorld))
1166 return InvalidParameter;
1168 graphics->unit = unit;
1170 return Ok;
1173 GpStatus WINGDIPAPI GdipSetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
1174 mode)
1176 if(!graphics)
1177 return InvalidParameter;
1179 graphics->pixeloffset = mode;
1181 return Ok;
1184 GpStatus WINGDIPAPI GdipSetSmoothingMode(GpGraphics *graphics, SmoothingMode mode)
1186 if(!graphics)
1187 return InvalidParameter;
1189 graphics->smoothing = mode;
1191 return Ok;