gdiplus: Fixed a bug in helper function draw_polybezier.
[wine/gsoc_dplay.git] / dlls / gdiplus / graphics.c
blob3a88295193c6df66cc2ae4b073456d7fe9a7c37b
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 static inline INT roundr(REAL x)
39 return (INT) floor(x+0.5);
42 static inline REAL deg2rad(REAL degrees)
44 return (M_PI*2.0) * degrees / 360.0;
47 /* Converts angle (in degrees) to x/y coordinates */
48 static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y)
50 REAL radAngle, hypotenuse;
52 radAngle = deg2rad(angle);
53 hypotenuse = 50.0; /* arbitrary */
55 *x = x_0 + cos(radAngle) * hypotenuse;
56 *y = y_0 + sin(radAngle) * hypotenuse;
59 /* GdipDrawPie/GdipFillPie helper function */
60 static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen,
61 REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
63 INT save_state;
64 REAL x_0, y_0, x_1, y_1, x_2, y_2;
66 if(!graphics)
67 return InvalidParameter;
69 save_state = SaveDC(graphics->hdc);
70 EndPath(graphics->hdc);
71 SelectObject(graphics->hdc, gdipen);
72 SelectObject(graphics->hdc, gdibrush);
74 x_0 = x + (width/2.0);
75 y_0 = y + (height/2.0);
77 deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
78 deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
80 Pie(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
81 roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
83 RestoreDC(graphics->hdc, save_state);
85 return Ok;
88 /* GdipDrawCurve helper function.
89 * Calculates Bezier points from cardinal spline points. */
90 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
91 REAL *y1, REAL *x2, REAL *y2)
93 REAL xdiff, ydiff;
95 /* calculate tangent */
96 xdiff = pts[2].X - pts[0].X;
97 ydiff = pts[2].Y - pts[0].Y;
99 /* apply tangent to get control points */
100 *x1 = pts[1].X - tension * xdiff;
101 *y1 = pts[1].Y - tension * ydiff;
102 *x2 = pts[1].X + tension * xdiff;
103 *y2 = pts[1].Y + tension * ydiff;
106 /* GdipDrawCurve helper function.
107 * Calculates Bezier points from cardinal spline endpoints. */
108 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
109 REAL tension, REAL *x, REAL *y)
111 /* tangent at endpoints is the line from the endpoint to the adjacent point */
112 *x = roundr(tension * (xadj - xend) + xend);
113 *y = roundr(tension * (yadj - yend) + yend);
116 /* Draws the linecap the specified color and size on the hdc. The linecap is in
117 * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. */
118 static void draw_cap(HDC hdc, COLORREF color, GpLineCap cap, REAL size,
119 REAL x1, REAL y1, REAL x2, REAL y2)
121 HGDIOBJ oldbrush, oldpen;
122 HBRUSH brush;
123 HPEN pen;
124 POINT pt[4];
125 REAL theta, dsmall, dbig, dx, dy, invert;
127 if(x2 != x1)
128 theta = atan((y2 - y1) / (x2 - x1));
129 else if(y2 != y1){
130 theta = M_PI_2 * (y2 > y1 ? 1.0 : -1.0);
132 else
133 return;
135 invert = ((x2 - x1) >= 0.0 ? 1.0 : -1.0);
136 brush = CreateSolidBrush(color);
137 pen = CreatePen(PS_SOLID, 1, color);
138 oldbrush = SelectObject(hdc, brush);
139 oldpen = SelectObject(hdc, pen);
141 switch(cap){
142 case LineCapFlat:
143 break;
144 case LineCapSquare:
145 case LineCapSquareAnchor:
146 case LineCapDiamondAnchor:
147 size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
148 if(cap == LineCapDiamondAnchor){
149 dsmall = cos(theta + M_PI_2) * size;
150 dbig = sin(theta + M_PI_2) * size;
152 else{
153 dsmall = cos(theta + M_PI_4) * size;
154 dbig = sin(theta + M_PI_4) * size;
157 /* calculating the latter points from the earlier points makes them
158 * look a little better because of rounding issues */
159 pt[0].x = roundr(x2 - dsmall);
160 pt[1].x = roundr(((REAL)pt[0].x) + dbig + dsmall);
162 pt[0].y = roundr(y2 - dbig);
163 pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig);
165 pt[1].y = roundr(y2 - dsmall);
166 pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y));
168 pt[3].x = roundr(x2 - dbig);
169 pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig);
171 Polygon(hdc, pt, 4);
173 break;
174 case LineCapArrowAnchor:
175 size = size * 4.0 / sqrt(3.0);
177 dx = cos(M_PI / 6.0 + theta) * size * invert;
178 dy = sin(M_PI / 6.0 + theta) * size * invert;
180 pt[0].x = roundr(x2 - dx);
181 pt[0].y = roundr(y2 - dy);
183 dx = cos(- M_PI / 6.0 + theta) * size * invert;
184 dy = sin(- M_PI / 6.0 + theta) * size * invert;
186 pt[1].x = roundr(x2 - dx);
187 pt[1].y = roundr(y2 - dy);
189 pt[2].x = roundr(x2);
190 pt[2].y = roundr(y2);
192 Polygon(hdc, pt, 3);
194 break;
195 case LineCapRoundAnchor:
196 dx = dy = ANCHOR_WIDTH * size / 2.0;
198 x2 = (REAL) roundr(x2 - dx);
199 y2 = (REAL) roundr(y2 - dy);
201 Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
202 roundr(y2 + 2.0 * dy));
203 break;
204 case LineCapTriangle:
205 size = size / 2.0;
206 dx = cos(M_PI_2 + theta) * size;
207 dy = sin(M_PI_2 + theta) * size;
209 /* Using roundr here can make the triangle float off the end of the
210 * line. */
211 pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx));
212 pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy));
213 pt[1].x = roundr(pt[0].x + 2.0 * dx);
214 pt[1].y = roundr(pt[0].y + 2.0 * dy);
216 dx = cos(theta) * size * invert;
217 dy = sin(theta) * size * invert;
219 pt[2].x = roundr(x2 + dx);
220 pt[2].y = roundr(y2 + dy);
222 Polygon(hdc, pt, 3);
224 break;
225 case LineCapRound:
226 dx = -cos(M_PI_2 + theta) * size * invert;
227 dy = -sin(M_PI_2 + theta) * size * invert;
229 pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx));
230 pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy));
231 pt[1].x = roundr(pt[0].x + 2.0 * dx);
232 pt[1].y = roundr(pt[0].y + 2.0 * dy);
234 dx = dy = size / 2.0;
236 x2 = (REAL) roundr(x2 - dx);
237 y2 = (REAL) roundr(y2 - dy);
239 Pie(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
240 roundr(y2 + 2.0 * dy), pt[0].x, pt[0].y, pt[1].x, pt[1].y);
241 break;
242 case LineCapCustom:
243 FIXME("line cap not implemented\n");
244 default:
245 break;
248 SelectObject(hdc, oldbrush);
249 SelectObject(hdc, oldpen);
250 DeleteObject(brush);
251 DeleteObject(pen);
254 /* Shortens the line by the given percent by changing x2, y2.
255 * If percent is > 1.0 then the line will change direction. */
256 static void shorten_line_percent(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL percent)
258 REAL dist, theta, dx, dy;
260 if((y1 == *y2) && (x1 == *x2))
261 return;
263 dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * percent;
264 theta = (*x2 == x1 ? M_PI_2 : atan((*y2 - y1) / (*x2 - x1)));
265 dx = cos(theta) * dist;
266 dy = sin(theta) * dist;
268 *x2 = *x2 + fabs(dx) * (*x2 > x1 ? -1.0 : 1.0);
269 *y2 = *y2 + fabs(dy) * (*y2 > y1 ? -1.0 : 1.0);
272 /* Shortens the line by the given amount by changing x2, y2.
273 * If the amount is greater than the distance, the line will become length 0. */
274 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
276 REAL dx, dy, percent;
278 dx = *x2 - x1;
279 dy = *y2 - y1;
280 if(dx == 0 && dy == 0)
281 return;
283 percent = amt / sqrt(dx * dx + dy * dy);
284 if(percent >= 1.0){
285 *x2 = x1;
286 *y2 = y1;
287 return;
290 shorten_line_percent(x1, y1, x2, y2, percent);
293 /* Draws lines between the given points, and if caps is true then draws an endcap
294 * at the end of the last line. FIXME: Startcaps not implemented. */
295 static void draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
296 INT count, BOOL caps)
298 POINT *pti = GdipAlloc(count * sizeof(POINT));
299 REAL x = pt[count - 1].X, y = pt[count - 1].Y;
300 INT i;
302 if(caps){
303 if(pen->endcap == LineCapArrowAnchor)
304 shorten_line_amt(pt[count-2].X, pt[count-2].Y, &x, &y, pen->width);
306 draw_cap(hdc, pen->color, pen->endcap, pen->width, pt[count-2].X,
307 pt[count-2].Y, pt[count - 1].X, pt[count - 1].Y);
310 for(i = 0; i < count - 1; i ++){
311 pti[i].x = roundr(pt[i].X);
312 pti[i].y = roundr(pt[i].Y);
315 pti[i].x = roundr(x);
316 pti[i].y = roundr(y);
318 Polyline(hdc, pti, count);
319 GdipFree(pti);
322 /* Conducts a linear search to find the bezier points that will back off
323 * the endpoint of the curve by a distance of amt. Linear search works
324 * better than binary in this case because there are multiple solutions,
325 * and binary searches often find a bad one. I don't think this is what
326 * Windows does but short of rendering the bezier without GDI's help it's
327 * the best we can do. */
328 static void shorten_bezier_amt(GpPointF * pt, REAL amt)
330 GpPointF origpt[4];
331 REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0;
332 INT i;
334 memcpy(origpt, pt, sizeof(GpPointF) * 4);
336 for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
337 /* reset bezier points to original values */
338 memcpy(pt, origpt, sizeof(GpPointF) * 4);
339 /* Perform magic on bezier points. Order is important here.*/
340 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
341 shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
342 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
343 shorten_line_percent(pt[0].X, pt[0].Y, &pt[1].X, &pt[1].Y, percent);
344 shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
345 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
347 dx = pt[3].X - origx;
348 dy = pt[3].Y - origy;
350 diff = sqrt(dx * dx + dy * dy);
351 percent += 0.0005 * amt;
355 /* Draws bezier curves between given points, and if caps is true then draws an
356 * endcap at the end of the last line. FIXME: Startcaps not implemented. */
357 static void draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
358 INT count, BOOL caps)
360 POINT *pti = GdipAlloc(count * sizeof(POINT));
361 GpPointF *ptf = GdipAlloc(4 * sizeof(GpPointF));
362 INT i;
364 memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF));
366 if(caps){
367 if(pen->endcap == LineCapArrowAnchor)
368 shorten_bezier_amt(ptf, pen->width);
370 draw_cap(hdc, pen->color, pen->endcap, pen->width, ptf[3].X,
371 ptf[3].Y, pt[count - 1].X, pt[count - 1].Y);
374 for(i = 0; i < count - 4; i ++){
375 pti[i].x = roundr(pt[i].X);
376 pti[i].y = roundr(pt[i].Y);
378 for(i = 0; i < 4; i ++){
379 pti[i + count - 4].x = roundr(ptf[i].X);
380 pti[i + count - 4].y = roundr(ptf[i].Y);
383 PolyBezier(hdc, pti, count);
384 GdipFree(pti);
385 GdipFree(ptf);
388 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
390 if(hdc == NULL)
391 return OutOfMemory;
393 if(graphics == NULL)
394 return InvalidParameter;
396 *graphics = GdipAlloc(sizeof(GpGraphics));
397 if(!*graphics) return OutOfMemory;
399 (*graphics)->hdc = hdc;
400 (*graphics)->hwnd = NULL;
402 return Ok;
405 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
407 GpStatus ret;
409 if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
410 return ret;
412 (*graphics)->hwnd = hwnd;
414 return Ok;
417 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
419 if(!graphics) return InvalidParameter;
420 if(graphics->hwnd)
421 ReleaseDC(graphics->hwnd, graphics->hdc);
423 HeapFree(GetProcessHeap(), 0, graphics);
425 return Ok;
428 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
429 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
431 HGDIOBJ old_pen;
432 REAL x_0, y_0, x_1, y_1, x_2, y_2;
434 if(!graphics || !pen)
435 return InvalidParameter;
437 old_pen = SelectObject(graphics->hdc, pen->gdipen);
439 x_0 = x + (width/2.0);
440 y_0 = y + (height/2.0);
442 deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
443 deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
445 Arc(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
446 roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
448 SelectObject(graphics->hdc, old_pen);
450 return Ok;
453 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
454 REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
456 INT save_state;
457 GpPointF pt[4];
459 if(!graphics || !pen)
460 return InvalidParameter;
462 pt[0].X = x1;
463 pt[0].Y = y1;
464 pt[1].X = x2;
465 pt[1].Y = y2;
466 pt[2].X = x3;
467 pt[2].Y = y3;
468 pt[3].X = x4;
469 pt[3].Y = y4;
471 save_state = SaveDC(graphics->hdc);
472 EndPath(graphics->hdc);
473 SelectObject(graphics->hdc, pen->gdipen);
475 draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
477 RestoreDC(graphics->hdc, save_state);
479 return Ok;
482 /* Approximates cardinal spline with Bezier curves. */
483 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
484 GDIPCONST GpPointF *points, INT count, REAL tension)
486 /* PolyBezier expects count*3-2 points. */
487 INT i, len_pt = count*3-2, save_state;
488 GpPointF *pt;
489 REAL x1, x2, y1, y2;
491 if(!graphics || !pen)
492 return InvalidParameter;
494 pt = GdipAlloc(len_pt * sizeof(GpPointF));
495 tension = tension * TENSION_CONST;
497 calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
498 tension, &x1, &y1);
500 pt[0].X = points[0].X;
501 pt[0].Y = points[0].Y;
502 pt[1].X = x1;
503 pt[1].Y = y1;
505 for(i = 0; i < count-2; i++){
506 calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
508 pt[3*i+2].X = x1;
509 pt[3*i+2].Y = y1;
510 pt[3*i+3].X = points[i+1].X;
511 pt[3*i+3].Y = points[i+1].Y;
512 pt[3*i+4].X = x2;
513 pt[3*i+4].Y = y2;
516 calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
517 points[count-2].X, points[count-2].Y, tension, &x1, &y1);
519 pt[len_pt-2].X = x1;
520 pt[len_pt-2].Y = y1;
521 pt[len_pt-1].X = points[count-1].X;
522 pt[len_pt-1].Y = points[count-1].Y;
524 save_state = SaveDC(graphics->hdc);
525 EndPath(graphics->hdc);
526 SelectObject(graphics->hdc, pen->gdipen);
528 draw_polybezier(graphics->hdc, pen, pt, len_pt, TRUE);
530 GdipFree(pt);
531 RestoreDC(graphics->hdc, save_state);
533 return Ok;
536 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
537 INT y1, INT x2, INT y2)
539 INT save_state;
540 GpPointF pt[2];
542 if(!pen || !graphics)
543 return InvalidParameter;
545 pt[0].X = (REAL)x1;
546 pt[0].Y = (REAL)y1;
547 pt[1].X = (REAL)x2;
548 pt[1].Y = (REAL)y2;
550 save_state = SaveDC(graphics->hdc);
551 EndPath(graphics->hdc);
552 SelectObject(graphics->hdc, pen->gdipen);
554 draw_polyline(graphics->hdc, pen, pt, 2, TRUE);
556 RestoreDC(graphics->hdc, save_state);
558 return Ok;
561 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
562 GpPointF *points, INT count)
564 HGDIOBJ old_obj;
565 INT i;
567 if(!pen || !graphics || (count < 2))
568 return InvalidParameter;
570 old_obj = SelectObject(graphics->hdc, pen->gdipen);
571 MoveToEx(graphics->hdc, roundr(points[0].X), roundr(points[0].Y), NULL);
573 for(i = 1; i < count; i++){
574 LineTo(graphics->hdc, roundr(points[i].X), roundr(points[i].Y));
577 SelectObject(graphics->hdc, old_obj);
579 return Ok;
582 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
583 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
585 if(!pen)
586 return InvalidParameter;
588 return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
589 width, height, startAngle, sweepAngle);
592 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
593 INT y, INT width, INT height)
595 INT save_state;
597 if(!pen || !graphics)
598 return InvalidParameter;
600 save_state = SaveDC(graphics->hdc);
601 EndPath(graphics->hdc);
602 SelectObject(graphics->hdc, pen->gdipen);
603 SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
605 Rectangle(graphics->hdc, x, y, x + width, y + height);
607 RestoreDC(graphics->hdc, save_state);
609 return Ok;
612 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
613 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
615 if(!brush)
616 return InvalidParameter;
618 return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
619 width, height, startAngle, sweepAngle);